Skip to content

Commit fd31f2d

Browse files
authored
Merge pull request #141 from mbel-tech/fatina-del-codice/beautiful-hypatia-wlxfol
fix(views): ID correspondence review fixes (pair ownership, sidebar status, rebuilds)
2 parents 8549b09 + 2214305 commit fd31f2d

79 files changed

Lines changed: 8578 additions & 144 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎CHANGELOG.md‎

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

1010
### Added
1111

12-
- **ID correspondence between views (3-D projects).** A new **Views** page (page 10, under the Sessions row of the sidebar, reached by Next/Back between Sessions and Calibration; 3-D projects only) records which session is the top view and which the side view, pairs them by two session-name patterns with a `key` group (with an ⓘ help and a live "catches N sessions" line) or by hand, and maps each top-view fish to a side-view fish: by a "Same IDs" tick, or in a table with two highlighted track plots. The project file gains `SessionRef.view_role`, `view_pairs` and `mode.pairing`; `mode.id_map` is gone (projects that contain it still load). It never blocks Next, and 3-D projects still cannot be run.
12+
- **Fusion of top and side views (3-D projects).** The Views page gains a **Fusion** section for the selected pair with a status line and **Set up fusion…**. The dialog shows the side view with two draggable lines for the water surface and the tank floor, fields for both rows, the tank height (default 20 cm), the shared horizontal axis (top-view x or y) with Flip, and the frame offset (hidden and fixed at 0 for "One video, two panels"), a live summary (shared frames, fused fish, positions outside the water column, agreement) and **Suggest offset**, which scans ±5 s around 0 (about 3 s on a 1-hour, 5-fish recording; needs a calibrated top view, otherwise it is disabled with a hint). The fusion keeps the fish matched in the pair on the frames both views have and adds a depth (0 = surface, 1 = floor; positions outside the column become empty and are counted). The cameras are assumed to be at right angles; the two views' horizontal positions are compared in cm and a warning shows when they disagree by more than 10% of the top-view range (needs a calibrated top view). Frame rates must match. New `FusionSettings` on each pair (`ViewPair.fusion`), `PreprocessedSession.depth`, `Engine.fuse_pair`, `fuse_all` and `suggest_offset`. The Views status warns "Fusion setup needed for …" and never blocks. 3-D projects still cannot be run: the 3-D metrics step is not built.
13+
- **Note on provenance hashes:** `project_hash` now includes each session's (empty) `panel` field, so the provenance hash of an existing project changes once after this update. Results are not affected.
14+
- **Panels: one video, two views (3-D projects, "One video, two panels" layout).** A session can now have a *panel*, the rectangle of the video it covers (`SessionRef.panel`; projects without it load unchanged). Reading a session with a panel keeps the animals that have at least 50% of their positions inside it, sets positions outside to NaN and reports coordinates relative to the panel (its top-left is (0, 0), the video size becomes the panel size), so previews, runs and the CLI all see the panel. The same folder with a different panel is a different session, and the panel is part of the preprocessing cache key. The Views page gains a **Panels** section (that layout only) with **Split into panels…** (one tracker run on the whole frame becomes `<id>__top` and `<id>__side` plus a hand-made pair), **Set panel…** (two tracker runs on one video, each limited to one panel) and **Clear panel**. The panel editor has the presets Left | Right and Top | Bottom with a split slider, exact x, y, width and height fields, a preview over the tracker's background image (or the first video frame, or the tracks), and a per-fish table that flags fish under 90% inside as *low* and under 50% as *left out*. Changing or clearing a panel resets the fish map of the pair that holds the session. The zones backdrop, calibration and preview show the cropped panel. Zones are still one set per project, and per-view pixel-scale calibration is not part of this change. 3-D projects still cannot be run.
15+
- **ID correspondence between views (3-D projects).** A new **Views** page (page 10, under the Sessions row of the sidebar, reached by Next/Back between Sessions and Calibration; 3-D projects only) records which session is the top view and which the side view, pairs them by two session-name patterns with a `key` group (with an ⓘ help and a live "catches N sessions" line) or by hand, and maps each top-view fish to a side-view fish: by a "Same IDs" tick, or in a table with two highlighted track plots. The project file gains `SessionRef.view_role`, `view_pairs` and `mode.pairing`; `mode.id_map` is gone (projects that contain it still load). The fish map is what counts: a pair is matched when its map is not empty. **Pair by pattern** keeps the pairs it made earlier that the patterns still produce, with their matching, removes the ones they no longer produce, and skips pairs you made or changed by hand (ticking Same IDs or changing a fish makes a pair yours). In 3-D projects the Sessions row of the sidebar shows the worse of the Sessions and Views statuses, with the Views message in its tooltip. It never blocks Next, and 3-D projects still cannot be run.
1316
- **Session calibration works beyond idtracker.ai.** The Calibration screen describes the mode per tracker, with an ⓘ note linking to each tracker's documentation, and disables it when no session can supply a scale. DeepLabCut, SLEAP and Ctrax raw files take an optional **Scale (pixels per cm)** import option.
1417
- **`trx.mat` reader (Ctrax / FlyTracker / JAABA).** Built from the documented layout, unverified against a real sample. `pxpermm` becomes the session scale.
1518
- **Average or median of several idtracker.ai length calibrations.** The average (idtracker.ai's own `length_unit`) stays the default.

‎app/main_window.py‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,7 @@ def __init__(self, parent: QWidget | None = None) -> None:
214214
self._store.exportChanged,
215215
self._store.runResultsChanged,
216216
self._store.modeChanged,
217+
self._store.viewsChanged,
217218
):
218219
sig.connect(self._refresh_stage_status)
219220
self._store.runLogAppended.connect(self._run_log.append)
@@ -507,7 +508,7 @@ def _on_project_opened(self) -> None:
507508

508509
def _refresh_stage_status(self) -> None:
509510
from app.navigation import STAGES
510-
from ui.store.stage_status import compute_stage_statuses, stage_summaries
511+
from ui.store.stage_status import compute_stage_statuses, sessions_row, stage_summaries
511512

512513
self._page_statuses = compute_stage_statuses(
513514
self._store.manifest, has_run_results=self._store.run_results is not None
@@ -528,6 +529,8 @@ def _refresh_stage_status(self) -> None:
528529
summaries[7] = summaries[8] = f"{len(results) - failed} succeeded · {failed} failed"
529530
for stage_index, (_label, first_page) in enumerate(STAGES):
530531
info = self._page_statuses[first_page]
532+
if stage_index == 1:
533+
info = sessions_row(self._page_statuses, manifest)
531534
self._sidebar.set_status(
532535
stage_index, info.status, info.message, summary=summaries[stage_index]
533536
)
@@ -555,7 +558,7 @@ def _update_next_action(self) -> None:
555558
self._refresh_footer_labels(page)
556559

557560
def _refresh_footer_labels(self, page: int) -> None:
558-
from app.navigation import PAGE_TO_STAGE, STAGES
561+
from app.navigation import PAGE_TO_STAGE, STAGES, page_label
559562

560563
mode = self._mode()
561564
prev_p, next_p = prev_page(mode, page), next_page(mode, page)
@@ -569,7 +572,7 @@ def _refresh_footer_labels(self, page: int) -> None:
569572
elif next_p is None:
570573
text, role = "Done", "primary"
571574
else:
572-
text = f"Next: {STAGES[PAGE_TO_STAGE[next_p]][0]} →".replace("&", "&&")
575+
text = f"Next: {page_label(next_p)} →".replace("&", "&&")
573576
role = "primary"
574577
self._btn_next.setText(text)
575578
if self._btn_next.property("role") != role:

‎app/navigation.py‎

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
)
1313

1414
from app.theme import theme
15+
from ui.store.screen_flow import VIEWS_PAGE
16+
from ui.store.stage_status import VIEWS_PAGE_NAME
1517

1618
# 9 wizard stages, one sidebar row per page (Preview and Export share the
1719
# last row). The number lives in the painted dot, not the label.
@@ -28,14 +30,19 @@
2830
("Preview & Export", 8),
2931
]
3032

31-
# The 3-D-only Views page: appended after the original 10, so their indices
32-
# are unchanged. It is routed between Sessions and Calibration (screen_flow).
33-
VIEWS_PAGE = 10
33+
# The 3-D-only Views page (VIEWS_PAGE, defined in ui.store.screen_flow) is
34+
# appended after the original 10, so their indices are unchanged. It is routed
35+
# between Sessions and Calibration (screen_flow).
3436

3537
# Maps each page index → its parent stage index (9 stages, 11 pages; Views
3638
# sits under Sessions).
3739
PAGE_TO_STAGE: list[int] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 8, 1]
3840

41+
42+
def page_label(page: int) -> str:
43+
"""The name the footer uses for *page*: its stage, or "Views" for the Views page."""
44+
return VIEWS_PAGE_NAME if page == VIEWS_PAGE else STAGES[PAGE_TO_STAGE[page]][0]
45+
3946
STATUS_ROLE = Qt.ItemDataRole.UserRole
4047
SUMMARY_ROLE = Qt.ItemDataRole.UserRole + 1
4148
LOCKED_ROLE = Qt.ItemDataRole.UserRole + 2

‎docs/3d-movement/2026-10-08-3d-roadmap.md‎

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# 3-D movement: roadmap
22

3-
**Status:** approved 2026-10-08. Sub-project C is in progress; E (mode switch) is implemented; A, B and D are not started.
3+
**Status:** approved 2026-10-08. Sub-project C is in progress; E (mode switch), G, F and D (fusion) are implemented; A and B are not started.
44
**Relates to:** `docs/dev/DECISIONS.md` D-031, `docs/dev/ROADMAP.md` (Z-10),
55
`docs/tracker-formats/2026-10-07-tracker-import-design.md` (Tier 3)
66

@@ -11,14 +11,15 @@ build cycle.
1111
|---|---|---|
1212
| **C** | **Side-view depth**: a camera-view setting and a continuous vertical-position metric from ordinary 2-D side recordings | nothing unmerged, no 3-D data |
1313
| **E** | **2D/3D project mode**: `ProjectManifest.mode`, Project-screen choice, lock, 3-D compute blocked. Done; see [the spec](2026-10-09-mode-switch-design.md) | nothing |
14+
| F | **Panel split**: one video with the top and side views in two panels becomes two sessions (a panel rectangle per session, panel-relative coordinates). Done; see [the spec](2026-10-09-panel-split-design.md) | E |
1415
| G | **ID correspondence**: view roles, pairing top and side sessions (regex or by hand), fish mapping between views, a Views step. Done; see [the spec](2026-10-09-id-correspondence-design.md) | E |
1516
| A | Read 3-D tracker files (Anipose pose-3d CSV, DLC-3D). Metrics run on a chosen plane and z is stored (D-031 as written) | `feat/native-units` merged |
1617
| B | Metrics that use z: 3-D speed and path length, vertical position, 3-D neighbour distance. **Amends D-031** | A for data, C for the vertical-axis setting |
17-
| D | Fuse top and side recordings into one 3-D session | B's data model, frame sync, identity matching across views |
18+
| D | Fuse top and side recordings into one 3-D session. Done; see [the spec](2026-10-09-fusion-design.md) | F and G (done); compute stays blocked until B |
1819

19-
**Order: C, A, B, D.** A moves first if real 3-D files turn up. C goes first because no data
20-
of any kind exists yet, it is testable on synthetic tracks, and it creates the project-level
21-
camera-view setting that B reuses.
20+
**Order: C, F/G/E and D are done; B then A remain.** A moves first if real 3-D files turn up. C went
21+
first because no data of any kind existed, it is testable on synthetic tracks, and it created the
22+
project-level camera-view setting that B reuses.
2223

2324
## Hand-offs between the sub-projects
2425

0 commit comments

Comments
 (0)