Skip to content

Commit 55dcb2e

Browse files
committed
Extend napari dimension slider tests to bboxes data
1 parent 8d49d6a commit 55dcb2e

File tree

2 files changed

+208
-1
lines changed

2 files changed

+208
-1
lines changed

tests/fixtures/napari.py

Lines changed: 100 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import pandas as pd
55
import pytest
66

7-
from movement.io import save_poses
7+
from movement.io import save_bboxes, save_poses
88

99

1010
@pytest.fixture
@@ -129,6 +129,105 @@ def valid_poses_path_and_ds_nan_end(
129129
return (out_path, ds)
130130

131131

132+
@pytest.fixture
133+
def valid_bboxes_path_and_ds(valid_bboxes_dataset, tmp_path):
134+
"""Return a (path, dataset) pair representing a bboxes dataset
135+
with data for 10 frames.
136+
137+
The fixture is derived from the ``valid_bboxes_dataset`` fixture.
138+
"""
139+
out_path = tmp_path / "ds_bboxes.csv"
140+
save_bboxes.to_via_tracks_file(valid_bboxes_dataset, out_path)
141+
return (out_path, valid_bboxes_dataset)
142+
143+
144+
@pytest.fixture
145+
def valid_bboxes_path_and_ds_short(valid_bboxes_dataset, tmp_path):
146+
"""Return a (path, dataset) pair representing a bboxes dataset
147+
with data for 5 frames.
148+
149+
The fixture is derived from the ``valid_bboxes_dataset`` fixture.
150+
"""
151+
valid_bboxes_dataset = valid_bboxes_dataset.sel(time=slice(0, 5))
152+
out_path = tmp_path / "ds_bboxes_short.csv"
153+
save_bboxes.to_via_tracks_file(valid_bboxes_dataset, out_path)
154+
return (out_path, valid_bboxes_dataset)
155+
156+
157+
@pytest.fixture
158+
def valid_bboxes_path_and_ds_with_localised_nans(
159+
valid_bboxes_dataset, tmp_path
160+
):
161+
"""Return a factory of (path, dataset) pairs representing
162+
valid bboxes datasets with NaN values at specific locations.
163+
"""
164+
ds = valid_bboxes_dataset.copy(deep=True)
165+
166+
def _valid_bboxes_path_and_ds_with_localised_nans(
167+
nan_location, filename="ds_bboxes_with_nans.csv"
168+
):
169+
"""Return a valid bboxes dataset and corresponding file with NaN
170+
values at specific locations.
171+
172+
The ``nan_location`` parameter is a dictionary with keys
173+
``"time"`` and ``"individuals"`` specifying which coordinates
174+
to set to NaN.
175+
"""
176+
if nan_location["time"] == "start":
177+
time_point = 0
178+
elif nan_location["time"] == "middle":
179+
time_point = ds.coords["time"][
180+
ds.coords["time"].shape[0] // 2
181+
]
182+
elif nan_location["time"] == "end":
183+
time_point = ds.coords["time"][-1]
184+
185+
ds.position.loc[
186+
{
187+
"individuals": nan_location["individuals"],
188+
"time": time_point,
189+
}
190+
] = np.nan
191+
192+
out_path = tmp_path / filename
193+
save_bboxes.to_via_tracks_file(ds, out_path)
194+
return (out_path, ds)
195+
196+
return _valid_bboxes_path_and_ds_with_localised_nans
197+
198+
199+
@pytest.fixture
200+
def valid_bboxes_path_and_ds_nan_start(
201+
valid_bboxes_path_and_ds_with_localised_nans,
202+
):
203+
"""Return a (path, dataset) pair for a bboxes dataset with all NaN
204+
values for the first frame.
205+
"""
206+
return valid_bboxes_path_and_ds_with_localised_nans(
207+
{
208+
"time": "start",
209+
"individuals": ["id_0", "id_1"],
210+
},
211+
filename="ds_bboxes_with_nan_start.csv",
212+
)
213+
214+
215+
@pytest.fixture
216+
def valid_bboxes_path_and_ds_nan_end(
217+
valid_bboxes_path_and_ds_with_localised_nans,
218+
):
219+
"""Return a (path, dataset) pair for a bboxes dataset with all NaN
220+
values for the last frame.
221+
"""
222+
return valid_bboxes_path_and_ds_with_localised_nans(
223+
{
224+
"time": "end",
225+
"individuals": ["id_0", "id_1"],
226+
},
227+
filename="ds_bboxes_with_nan_end.csv",
228+
)
229+
230+
132231
@pytest.fixture
133232
def sample_layer_data(rng):
134233
"""Return a dictionary of sample data for each napari layer type."""

tests/test_unit/test_napari_plugin/test_data_loader_widget.py

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -567,6 +567,67 @@ def test_dimension_slider_with_nans(
567567
)
568568

569569

570+
@pytest.mark.parametrize(
571+
"nan_time_location",
572+
["start", "middle", "end"],
573+
)
574+
@pytest.mark.parametrize(
575+
"nan_individuals",
576+
[["id_0"], ["id_0", "id_1"]],
577+
ids=["one_individual", "all_individuals"],
578+
)
579+
def test_dimension_slider_with_nans_bboxes(
580+
valid_bboxes_path_and_ds_with_localised_nans,
581+
nan_time_location,
582+
nan_individuals,
583+
make_napari_viewer_proxy,
584+
):
585+
"""Test that the dimension slider is set to the total number of frames
586+
when bboxes data layers with NaNs are loaded.
587+
"""
588+
nan_location = {
589+
"time": nan_time_location,
590+
"individuals": nan_individuals,
591+
}
592+
file_path, ds = valid_bboxes_path_and_ds_with_localised_nans(
593+
nan_location
594+
)
595+
596+
# Define the expected frame index with the NaN value
597+
if nan_location["time"] == "start":
598+
expected_frame = ds.coords["time"][0]
599+
elif nan_location["time"] == "middle":
600+
expected_frame = ds.coords["time"][ds.coords["time"].shape[0] // 2]
601+
elif nan_location["time"] == "end":
602+
expected_frame = ds.coords["time"][-1]
603+
604+
# Load the data loader widget
605+
viewer = make_napari_viewer_proxy()
606+
data_loader_widget = DataLoader(viewer)
607+
608+
# Read sample data with a NaN at the specified location
609+
data_loader_widget.file_path_edit.setText(file_path.as_posix())
610+
data_loader_widget.source_software_combo.setCurrentText("VIA-tracks")
611+
612+
# Check the data contains nans where expected
613+
assert (
614+
ds.position.sel(
615+
individuals=nan_location["individuals"],
616+
time=expected_frame,
617+
)
618+
.isnull()
619+
.all()
620+
)
621+
622+
# Call the _on_load_clicked method
623+
data_loader_widget._on_load_clicked()
624+
625+
# Check the frame slider is set to the full range of frames
626+
assert viewer.dims.range[0] == RangeTuple(
627+
start=0.0, stop=ds.position.shape[0] - 1, step=1.0
628+
)
629+
630+
570631
@pytest.mark.parametrize(
571632
"list_input_data_files",
572633
[
@@ -614,6 +675,53 @@ def test_dimension_slider_multiple_files(
614675
assert max_frames == ds_long.sizes["time"]
615676

616677

678+
@pytest.mark.parametrize(
679+
"list_input_data_files",
680+
[
681+
["valid_bboxes_path_and_ds", "valid_bboxes_path_and_ds_short"],
682+
["valid_bboxes_path_and_ds_short", "valid_bboxes_path_and_ds"],
683+
],
684+
ids=["long_first", "short_first"],
685+
)
686+
def test_dimension_slider_multiple_files_bboxes(
687+
list_input_data_files, make_napari_viewer_proxy, request
688+
):
689+
"""Test that the dimension slider is set to the maximum number of frames
690+
when multiple bboxes data layers are loaded.
691+
"""
692+
# Get the datasets to load (paths and ds)
693+
list_paths, list_datasets = [
694+
[
695+
request.getfixturevalue(file_name)[j]
696+
for file_name in list_input_data_files
697+
]
698+
for j in range(len(list_input_data_files))
699+
]
700+
701+
# Get the maximum number of frames from all datasets
702+
max_frames = max(ds.sizes["time"] for ds in list_datasets)
703+
704+
# Load the data loader widget
705+
viewer = make_napari_viewer_proxy()
706+
data_loader_widget = DataLoader(viewer)
707+
708+
# Load each dataset in order
709+
for file_path in list_paths:
710+
data_loader_widget.file_path_edit.setText(file_path.as_posix())
711+
data_loader_widget.source_software_combo.setCurrentText("VIA-tracks")
712+
data_loader_widget._on_load_clicked()
713+
714+
# Check the frame slider is as expected
715+
assert viewer.dims.range[0] == RangeTuple(
716+
start=0.0, stop=max_frames - 1, step=1.0
717+
)
718+
719+
# Check the maximum number of frames is the number of frames
720+
# in the longest dataset
721+
_, ds_long = request.getfixturevalue("valid_bboxes_path_and_ds")
722+
assert max_frames == ds_long.sizes["time"]
723+
724+
617725
@pytest.mark.parametrize(
618726
"list_input_data_files",
619727
[

0 commit comments

Comments
 (0)