.. DO NOT EDIT. .. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY. .. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE: .. "examples/scale.py" .. LINE NUMBERS ARE GIVEN BELOW. .. only:: html .. note:: :class: sphx-glr-download-link-note :ref:`Go to the end ` to download the full example code or to run this example in your browser via Binder. .. rst-class:: sphx-glr-example-title .. _sphx_glr_examples_scale.py: Scale pose tracks to real-world units ======================================== Convert pixel coordinates to physical units using a known reference distance. .. GENERATED FROM PYTHON SOURCE LINES 7-9 Imports ------- .. GENERATED FROM PYTHON SOURCE LINES 9-23 .. code-block:: Python import numpy as np from matplotlib import pyplot as plt from scipy.signal import find_peaks from movement import sample_data from movement.filtering import ( filter_by_confidence, interpolate_over_time, rolling_filter, ) from movement.kinematics import compute_pairwise_distances from movement.transforms import scale .. GENERATED FROM PYTHON SOURCE LINES 24-34 Load sample dataset ------------------- In this example, we will use the ``DLC_single-mouse_DBTravelator_2D`` sample dataset, which contains DeepLabCut predictions of a single mouse running across a dual-belt travelator, where the second belt runs faster than the first. The back wall of the travelator has a visible 1 cm grid, which we can use as a scaling reference to convert pixel coordinates into real-world units. .. GENERATED FROM PYTHON SOURCE LINES 34-40 .. code-block:: Python ds = sample_data.fetch_dataset( "DLC_single-mouse_DBTravelator_2D.predictions.h5", with_video=True ) print(ds) .. rst-class:: sphx-glr-script-out .. code-block:: none Size: 508kB Dimensions: (time: 418, space: 2, keypoints: 50, individuals: 1) Coordinates: * time (time) float64 3kB 0.0 0.004049 0.008097 ... 1.68 1.684 1.688 * space (space) Size: 437kB Dimensions: (time: 418, space: 2, keypoints: 43) Coordinates: * time (time) float64 3kB 0.0 0.004049 0.008097 ... 1.68 1.684 1.688 * space (space) `. First, open the video file in napari: .. code-block:: python import napari viewer = napari.Viewer() viewer.open(ds_mouse.video_path) .. note:: You can also load a single frame image instead of the full video. Next, measure a known distance in the napari viewer: 1. Add a new shapes layer by clicking 'New shapes layer' in the layer list. 2. Select the 'Add lines' tool (shortcut: L) from the layer controls. 3. Draw a line across a feature of known length. In this case, the grid squares are 1x1 cm, so we can draw a line along one side of a grid square. .. image:: /_static/napari_scale_draw.png :width: 600 4. To read the line length in pixels, go to Layers → Measure → Toggle shape dimensions measurement (napari builtins). This displays P (perimeter) and A (area). In the case of a single line, the perimeter value corresponds to the line length in pixels, and the area will always be 0. .. image:: /_static/napari_scale_measure.png :width: 600 5. Close the napari viewer. .. GENERATED FROM PYTHON SOURCE LINES 246-248 We can then retrieve the measured distance and line coordinates from the shapes layer. .. GENERATED FROM PYTHON SOURCE LINES 248-260 .. code-block:: Python shapes_layer = viewer.layers["Shapes"] measurements_px = shapes_layer.features shape_coords = shapes_layer.data print(f"Measurements:\n{measurements_px}\n") print(f"Coordinates:\n{shape_coords}") # Extract the line length in pixels from the perimeter (P) column distance_px = measurements_px["_perimeter"].values.squeeze() .. rst-class:: sphx-glr-script-out .. code-block:: none Measurements: _perimeter _area 0 29.063293 0.0 Coordinates: [array([[275. , 48.455124, 930.55444 ], [275. , 48.452595, 959.61774 ]], dtype=float32)] .. GENERATED FROM PYTHON SOURCE LINES 297-307 Transform poses to real units ----------------------------- We now transform the pose coordinates to real-world units in two steps: scaling from pixels to centimetres, and flipping the y-axis so that positive y corresponds to upward in real-world space (image coordinates have the y-axis pointing downward). First, we calculate the scaling factor. The measured line spans one grid square, which we know to be 1 cm. Dividing this known length by the distance in pixels gives us the scaling factor. .. GENERATED FROM PYTHON SOURCE LINES 307-311 .. code-block:: Python scaling_factor = 1 / distance_px print(f"Scaling factor: {scaling_factor:.6f} cm/pixel") .. rst-class:: sphx-glr-script-out .. code-block:: none Scaling factor: 0.034408 cm/pixel .. GENERATED FROM PYTHON SOURCE LINES 312-323 .. note:: This scaling factor assumes a constant relationship between pixels and real-world units across the entire image, based on a single reference measurement taken at the far side of the belt. In practice, several factors can violate this assumption. For example, in 2D imaging, perspective effects mean that the apparent size of objects varies with their distance from the camera. Distances measured in the plane of the reference grid will therefore be close to accurate, but can deviate as the mouse moves away from this plane. Calibrated multi-camera setups avoid these assumptions by mapping image coordinates directly to real-world 3D space. .. GENERATED FROM PYTHON SOURCE LINES 325-326 Let's inspect the position values before scaling. .. GENERATED FROM PYTHON SOURCE LINES 326-331 .. code-block:: Python # Select a frame range in which the mouse is visible sample_range = np.arange(300, 301) print(ds_mouse.position.isel(time=sample_range).values) .. rst-class:: sphx-glr-script-out .. code-block:: none [[[1398.2734375 1321.61193848 1330.91986084 1324.80633545 1306.98193359 1288.26031494 1270.04632568 1252.95697021 1235.85125732 1217.36029053 1199.72625732 1181.1987915 1163.58392334 1146.29034424 1126.3626709 1126.17126465 1103.95812988 1081.17657471 1057.53509521 1035.8026123 1012.95870972 988.29507446 968.99377441 946.29476929 925.46606445 903.1199646 882.91726685 1299.08605957 1289.45465088 1281.8973999 1283.18951416 1340.15515137 1337.95861816 1327.96911621 1313.1293335 1192.83721924 1185.10662842 1160.95397949 1195.93652344 1151.7109375 1136.6963501 1134.84191895 1168.97979736] [ 179.98707581 114.40616608 126.98419189 124.15420151 118.58591843 116.37707901 113.20304489 108.78902054 104.51488113 100.12917328 97.42570114 98.87822342 104.46193314 111.52003479 118.15843201 124.30410385 119.9714241 116.60536957 115.4688797 114.47219467 115.20085907 118.57863998 118.48215866 121.45074081 121.86370087 123.55347824 125.70636749 200.3639679 199.53270721 196.58586884 173.59635162 192.48999023 185.47390747 180.66627502 170.02613068 192.91704559 179.59600067 163.21473694 142.73191833 200.17955017 194.60357666 166.07353973 161.26473236]]] .. GENERATED FROM PYTHON SOURCE LINES 332-335 Now we apply :func:`movement.transforms.scale` to convert from pixels to centimetres. We can assign our space unit 'cm' here, to be stored as an attribute in ``xarray.DataArray.attrs['space_unit']``. .. GENERATED FROM PYTHON SOURCE LINES 335-340 .. code-block:: Python ds_mouse["position"] = scale( ds_mouse["position"], factor=scaling_factor, space_unit="cm" ) .. GENERATED FROM PYTHON SOURCE LINES 341-343 Now we inspect our sample of values again. We can see the values have been adjusted. .. GENERATED FROM PYTHON SOURCE LINES 343-346 .. code-block:: Python print(ds_mouse.position.isel(time=sample_range).values) .. rst-class:: sphx-glr-script-out .. code-block:: none [[[48.11132164 45.4735786 45.79384245 45.58349033 44.97019431 44.32602716 43.69932635 43.11132156 42.52275395 41.88652299 41.27977712 40.64229031 40.03620386 39.44117221 38.75550754 38.74892169 37.98461963 37.20075955 36.38731149 35.63954753 34.85354222 34.00492417 33.34081153 32.55979181 31.84312474 31.07424766 30.37912004 44.69851574 44.36712147 44.10709412 44.15155276 46.11160722 46.03602965 45.69231423 45.18171198 41.04274141 40.77674985 39.94571364 41.14938123 39.62768216 39.11106529 39.04725865 40.22186327] [ 6.1929347 3.93644884 4.36922932 4.27185596 4.08026435 4.00426335 3.89505225 3.74317599 3.59611284 3.44521088 3.35219072 3.40216862 3.59429102 3.83714381 4.06555554 4.27701375 4.12793637 4.0121183 3.97301433 3.93872073 3.96379237 4.08001392 4.07669422 4.1788362 4.19304519 4.25118648 4.32526237 6.89405595 6.86545421 6.76406038 5.97304482 6.62313077 6.38172376 6.21630436 5.85020186 6.63782475 6.17947872 5.61583771 4.91107179 6.88771056 6.69585434 5.71420244 5.54874261]]] .. GENERATED FROM PYTHON SOURCE LINES 347-350 The scaled data array's attributes now contain the ``space_unit`` and a ``log`` entry recording the operation and its parameters, alongside the operations applied in earlier steps. .. GENERATED FROM PYTHON SOURCE LINES 350-354 .. code-block:: Python print(f"Unit:\n{ds_mouse['position'].space_unit}\n") print(f"Log:\n{ds_mouse['position'].log}") .. rst-class:: sphx-glr-script-out .. code-block:: none Unit: cm Log: [ { "operation": "filter_by_confidence", "datetime": "2026-03-09 12:14:28.913349", "confidence": " Size: 144kB\n0.02365 0.01434 0.01821 0.02692 0.05129 ... 0.004628 0.007376 0.003921 0.005941\nCoordinates:\n * time (time) float64 3kB 0.0 0.004049 0.008097 ... 1.68 1.684 1.688\n * keypoints (keypoints) ` .. container:: sphx-glr-download sphx-glr-download-python :download:`Download Python source code: scale.py ` .. container:: sphx-glr-download sphx-glr-download-zip :download:`Download zipped: scale.zip ` .. only:: html .. rst-class:: sphx-glr-signature `Gallery generated by Sphinx-Gallery `_