Skip to content

Commit a78e2bc

Browse files
authored
Merge pull request #165 from mbel-tech/docs/sp6-metric-docs
2 parents b1470d2 + 3178ac6 commit a78e2bc

4 files changed

Lines changed: 44 additions & 5 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
99

1010
### Added
1111

12+
- **Metric documentation for the 3-D fixes.** IL-15, IL-16 and IL-17 (METRICS_SPEC and the in-app text) now say that a native file's z is cleaned like x and y and bounded by the water column, and that IL-15 on a fused session counts the side view's own samples; `docs/dev/DECISIONS.md` gains D-044 recording the decisions of the 3-D review.
1213
- **Wiki: 3-D known issues and what to diff after the 3-D fixes.** *Known issues* gains a 3-D section (one camera pair and no optics correction, flagged fused depth, GL-1 vs GL-16 frames, the shared native water column, `flip`), and *Reanalysing after a metric fix* lists the 3-D numbers that changed since v0.1.0.
1314
- **User guide: the 3-D data path after the review fixes.** The guide describes the Fusion dialog's upside-down box and helper line, the depth provenance flags, `z_cm`, the water column's effect on IL-16/IL-17, the README count of samples outside it, and GL-16's `frac_frames_used_3d`; the PDF is rebuilt. The guide has no screenshot of the Fusion dialog or the native Calibration page, so none changed.
1415
- **Clearer wording where the 3-D settings are made.** The Fusion dialog says under the shared horizontal axis and flip that they are used only to check that the two views agree and do not change the fused positions (the two never moved the fused data, but sat beside the surface and floor rows as if they did). The Calibration page's water-column help says one surface and floor apply to every session, so the files must share a vertical axis and an origin (the origin cannot be checked from the files), and that samples outside the column are left out of the vertical-position metrics and of the 3-D path length and speed. The run `README.md` of a native 3-D session with a water column gains a row, **Samples outside the water column**, with the count (0 when nothing is outside), so the exclusion is visible next to the numbers it affects.

‎docs/METRICS_SPEC.md‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -409,7 +409,7 @@ info-button modal (§6).
409409
| **Formula** | `T`, `B` = top and bottom image rows of the main zones (`T < B`); `d[t,k] = (y[t,k] − T) / (B − T)` for frames with `T ≤ y[t,k] ≤ B`; `mean_depth_fraction`, `median_depth_fraction` and `sd_depth_fraction` (n − 1) over those frames; `mean_depth_cm = mean(y − T) / px_per_cm`; `frac_outside_extent` = share of non-NaN frames with `y < T` or `y > B` |
410410
| **Output columns** | `individual_id`, `mean_depth_fraction`, `median_depth_fraction`, `sd_depth_fraction`, `mean_depth_cm`, `frac_outside_extent`, `depth_extent_source` |
411411
| **Units** | fraction (0 = water surface, 1 = tank floor); cm; fraction; categorical |
412-
| **Assumptions** | The project declares a side camera view, and the top edge of the main zones is the water surface and the bottom edge the tank floor. The camera is upright: image rows grow downward, so a larger y is deeper. Every main-level additive ("+") zone is pooled into one column, so tanks side by side share it; tanks stacked on top of each other would pool into one tall column. Subtractive ("−") zones and secondary-level zones do not move the extent. Frames outside the column are dropped and counted, never clipped to the surface or floor. Interpolated frames are included. |
412+
| **Assumptions** | The project declares a side camera view, and the top edge of the main zones is the water surface and the bottom edge the tank floor. The camera is upright: image rows grow downward, so a larger y is deeper. Every main-level additive ("+") zone is pooled into one column, so tanks side by side share it; tanks stacked on top of each other would pool into one tall column. Subtractive ("−") zones and secondary-level zones do not move the extent. Frames outside the column are dropped and counted, never clipped to the surface or floor. Interpolated frames are included. On a fused 3-D session `frac_outside_extent` is the share of the side view's own samples outside the column (top-view gaps do not change it). On a native 3-D file the entered column applies to every session, and without it `depth_extent_source` is `none:no_water_column`. |
413413
| **Warnings** | No refraction or parallax correction: a fish near the front glass looks larger and sits at a different apparent depth than one at the back, and depth in cm uses a single scale for the whole picture. A column taken from zones that do not match the video, or a tracker following reflections, shows up as a high `frac_outside_extent` (a warning is logged above 5 %). Without a main zone there is no depth: the values are NaN and `depth_extent_source` says why (`none:no_main_zone`, `none:degenerate_zone`, `none:frame_size_mismatch` for zones drawn at another recorded frame size, `none:not_derived`); the video frame is never used instead. Depth is measured from the surface; height above the floor is `1 − depth`. For band occupancy, latency, visits and dwell on a side view, draw stacked secondary-level zones and use Z-1 to Z-9. |
414414
| **Parameters** | `water_column` is `derived=True` and cannot be overridden. |
415415
| **Reference** | Standard descriptive statistic of vertical position in the water column; no single originating work defines this mean-depth fraction. The construct, vertical position as a behavioural measure in novel-tank assays, is in the supporting references. |
@@ -427,7 +427,7 @@ info-button modal (§6).
427427
| **Formula** | `Σ_t ‖P[t+1, k] − P[t, k]‖` over frame pairs where both positions are finite, with `P = (x_px / px_per_cm, y_px / px_per_cm, depth · tank_height_cm)` |
428428
| **Output columns** | `individual_id`, `path_length_3d_cm`, `path_length_3d_bl`, `n_valid_steps` |
429429
| **Units** | cm / BL / count |
430-
| **Assumptions** | The depth comes from a side camera at right angles to the top camera. A step needs three finite coordinates (x, y, depth) at both of its frames; an animal with no such step has **no measured distance**, reported as NaN (cm and BL) with `n_valid_steps` 0, never as 0. Interpolated frames contribute a straight line |
430+
| **Assumptions** | The depth comes from a side camera at right angles to the top camera. A step needs three finite coordinates (x, y, depth) at both of its frames; an animal with no such step has **no measured distance**, reported as NaN (cm and BL) with `n_valid_steps` 0, never as 0. Interpolated frames contribute a straight line. A native 3-D file's z is gap-filled and smoothed with the same settings as x and y, and a sample outside the water column (when one is set) is left out, so the step touching it is dropped, never clipped |
431431
| **Warnings** | Under-smoothed data inflates the length, as for IL-1 (and depth noise adds to it). No refraction or parallax correction. A wrong tank height scales every vertical distance. Over the steps where depth is also finite (see `n_valid_steps`) it is at least IL-1. Without depth or a cm scale all columns are NaN |
432432
| **Reference** | Standard kinematics |
433433
| **Supporting references** | Martin & Bateson 2007, Measuring Behaviour: An Introductory Guide, 3rd ed. (Cambridge University Press) (DOI: 10.1017/CBO9780511810893) |
@@ -441,7 +441,7 @@ info-button modal (§6).
441441
| **Priority** | Optional |
442442
| **Inputs** | `PreprocessedSession.kinematics.speed_px_s`, `PreprocessedSession.depth`, `px_per_cm` and `depth_height_cm` of a fused 3-D session (for a native 3-D file, the absolute `PreprocessedSession.z` in place of `depth · tank_height_cm`); the pipeline's `KinematicsCfg` |
443443
| **Required preprocessing** | A fused 3-D session (top view plus side view) with a cm scale for the top view (scalar or session calibration, not body-length calibration). A project without these skips the metric and the run records why |
444-
| **Formula** | `v3[t, k] = sqrt(vh² + vz²)` with `vh = speed_px_s[t, k] / px_per_cm` (identical to the horizontal part of IL-2 in cm/s) and `vz` the vertical speed in cm/s of `Z = depth · tank_height_cm`, from the same estimator and window as the pipeline's kinematics (Savitzky-Golay by default, forward difference when the project selects it); mean/median/max over frames where both `vh` and `vz` are finite |
444+
| **Formula** | `v3[t, k] = sqrt(vh² + vz²)` with `vh = speed_px_s[t, k] / px_per_cm` (identical to the horizontal part of IL-2 in cm/s) and `vz` the vertical speed in cm/s of `Z = depth · tank_height_cm` (for a native file, its z after the same gap filling and smoothing as x and y, with samples outside the water column left out), from the same estimator and window as the pipeline's kinematics (Savitzky-Golay by default, forward difference when the project selects it); mean/median/max over frames where both `vh` and `vz` are finite |
445445
| **Output columns** | `individual_id`, `mean_speed_3d_cm_s`, `median_speed_3d_cm_s`, `max_speed_3d_cm_s`, `mean_speed_3d_bl_s` |
446446
| **Units** | cm/s / BL/s |
447447
| **Assumptions** | The depth comes from a side camera at right angles to the top camera. A frame needs both speeds finite: where the estimator gives no vertical speed (the last frame of the forward-difference estimator, a depth segment shorter than the window needs) the frame is left out of the 3-D speed, never counted as 0. An animal with no such frame is NaN. In a binned run `vz` is estimated inside each bin, so the estimator's edge frames differ slightly from the whole-session values |

‎docs/dev/DECISIONS.md‎

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1139,3 +1139,33 @@ an error. Missing-as-all-zero keeps a real zero coordinate that a per-axis rule
11391139
pixel sessions; the board unit must be confirmed by the user; no refraction correction; the water
11401140
column is entered, not estimated; no z column in the per-frame table; formats are read from synthetic
11411141
files only.
1142+
1143+
### D-044 · The 3-D review fixes: water column bounds z, depth flags, GL-16 keeps complete-case, right-handed plane
1144+
1145+
**Status:** accepted; implemented (PRs #147, #150, #152, #155-#161). It amends D-043 and D-041 in the
1146+
places named below; everything else in them stands.
1147+
1148+
**Decision:** *Native z is preprocessed like x and y* (`preprocess/z_axis.py`): bridged across tracking
1149+
intervals, gap-filled with the xy settings, smoothed with the xy window, and re-filled where xy was
1150+
jump-replaced. D-043's "z not smoothed, interpolated" is replaced; unsmoothed z had inflated the vertical
1151+
part of IL-16 and no setting could reduce it. *The water column bounds z* for IL-15, IL-16, IL-17 and
1152+
GL-16 (`geometry3d.vertical_cm`): a sample outside is NaN and the step touching it is dropped, never
1153+
clipped. This amends D-043's reading that z is absolute so outside frames keep their speed. The exported
1154+
`z_cm` stays the file's own coordinate, and the run README counts the samples left out. *z is exported:*
1155+
`z_cm` in the per-frame table (D-043 said it was not), and the wide CSV also writes the per-frame table when
1156+
it has vertical columns. *Fused depth provenance is flagged, not blanked:* `depth_interpolated` and
1157+
`depth_jump_replaced` come from the side view's own flags and are True only where the depth is a number;
1158+
the depth values are unchanged. IL-15's `frac_outside_extent` uses the side view's samples as denominator
1159+
(`depth_side_valid`). *GL-16 keeps its complete-case policy and discloses it:* GL-1 also skips frames with
1160+
a missing animal, so the earlier idea of matching GL-1's per-frame policy rested on a wrong premise; the
1161+
difference is only the extra frames lost to missing depth, reported as `frac_frames_used_3d`, with a warning
1162+
above a 5 % shortfall. *Geometry:* an upside-down side view is valid (`surface_row` and `floor_row` need only
1163+
differ); native sessions of one project must share a `vertical_axis` (`NATIVE_3D_AXIS_MISMATCH`); the
1164+
horizontal plane of a native file is right-handed (`z -> (x, y)`, `x -> (y, z)`, `y -> (z, x)`), which
1165+
mirrors IL-11's heading and turn-bias sign for `vertical_axis` x or y.
1166+
1167+
**Not changed, on purpose:** `VideoInfo.n_frames` stays the row count (the reader contract pins it, and the
1168+
Sessions screen is the only consumer); the water column stays in the preprocess cache key, because the cached
1169+
object already includes `apply_calibration_and_zones`. *Limits:* `flip` reverses the vertical axis only, which
1170+
also mirrors the view from above; one origin for the water column cannot be checked from the files; no
1171+
refraction or parallax correction.

‎track2data/metrics/individual.py‎

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1700,7 +1700,10 @@ class VerticalPosition(Metric):
17001700
"Interpolated frames are included",
17011701
"A native 3-D file (Anipose, DeepLabCut 3-D) takes its depth from the absolute z "
17021702
"and the surface and floor z entered by the user (depth_extent_source = "
1703-
"water_column); positions outside that column are dropped and counted",
1703+
"water_column); positions outside that column are dropped and counted. Without the "
1704+
"water column there is no depth and depth_extent_source is none:no_water_column",
1705+
"On a fused 3-D session frac_outside_extent is the share of the side view's own "
1706+
"samples that fell outside the column, so top-view gaps do not change it",
17041707
],
17051708
warnings=[
17061709
"No refraction or parallax correction: a fish near the front glass looks "
@@ -1898,6 +1901,9 @@ class PathLength3D(Metric):
18981901
"n_valid_steps says how many steps the length rests on",
18991902
"Interpolated frames contribute a straight line, which understates the real path "
19001903
"across a gap -- see D-11's frac_interpolated",
1904+
"A native 3-D file's z goes through the same gap filling and smoothing as x and y, "
1905+
"and a sample outside the water column (when one is set) is left out, so a step "
1906+
"touching it is dropped, never clipped",
19011907
],
19021908
warnings=[
19031909
"Under-smoothed data inflates the length, exactly as for IL-1: summing step "
@@ -1993,7 +1999,9 @@ class Speed3D(Metric):
19931999
"forward-difference estimator, a depth segment too short for the window) the "
19942000
"frame is left out, never counted as zero",
19952001
"The depth comes from a side camera at right angles to the top camera; a native "
1996-
"3-D file's vertical series is its absolute z (in the project unit)",
2002+
"3-D file's vertical series is its absolute z (in the project unit), gap-filled and "
2003+
"smoothed like x and y, with samples outside the water column (when one is set) "
2004+
"left out",
19972005
],
19982006
warnings=[
19992007
"Max speed is sensitive to remaining jump artefacts, and to depth noise, which "

0 commit comments

Comments
 (0)