diff --git a/brainreg_segment/layout/gui_constants.py b/brainreg_segment/layout/gui_constants.py index c4b22cf..f82ac8a 100644 --- a/brainreg_segment/layout/gui_constants.py +++ b/brainreg_segment/layout/gui_constants.py @@ -14,6 +14,8 @@ "left" # Alignment of text in pushbuttons in methods chooser panel ) +SAVE_DEFAULT = False + POINT_SIZE = 100 SPLINE_SIZE = 50 NUM_COLORS = 10 diff --git a/brainreg_segment/segment.py b/brainreg_segment/segment.py index ba786aa..49fc95b 100644 --- a/brainreg_segment/segment.py +++ b/brainreg_segment/segment.py @@ -451,10 +451,9 @@ def initialise_loaded_data(self): ] self.hemispheres_data = self.hemispheres_layer.data - self.prevent_layer_edit() - self.initialise_segmentation_interface() self.status_label.setText("Ready") + self.prevent_layer_edit() def collate_widget_layers(self): """ @@ -486,6 +485,7 @@ def collate_widget_layers(self): ] def prevent_layer_edit(self): + print("Preventing layer edit") self.collate_widget_layers() for layer in self.non_editable_widget_layers: layer.editable = False @@ -568,18 +568,21 @@ def save(self, override=True): else: choice = True # for debugging if choice: - print("Saving") - worker = save_all( - self.paths.regions_directory, - self.paths.tracks_directory, - self.label_layers, - self.track_layers, - track_file_extension=TRACK_FILE_EXT, - ) - worker.start() + self.run_save() else: print('Not saving because user chose "Cancel" \n') + def run_save(self): + print("Saving") + worker = save_all( + self.paths.regions_directory, + self.paths.tracks_directory, + self.label_layers, + self.track_layers, + track_file_extension=TRACK_FILE_EXT, + ) + worker.start() + def export_to_brainrender(self, override=False): if not override: choice = display_warning( diff --git a/brainreg_segment/segmentation_panels/regions.py b/brainreg_segment/segmentation_panels/regions.py index 11aea94..d92af15 100644 --- a/brainreg_segment/segmentation_panels/regions.py +++ b/brainreg_segment/segmentation_panels/regions.py @@ -7,6 +7,7 @@ COLUMN_WIDTH, IMAGE_FILE_EXT, NUM_COLORS, + SAVE_DEFAULT, SEGM_METHODS_PANEL_ALIGN, SUMMARIZE_VOLUMES_DEFAULT, ) @@ -34,6 +35,7 @@ def __init__( parent, calculate_volumes_default=CALCULATE_VOLUMES_DEFAULT, summarise_volumes_default=SUMMARIZE_VOLUMES_DEFAULT, + save_default=SAVE_DEFAULT, brush_size=BRUSH_SIZE, image_file_extension=IMAGE_FILE_EXT, num_colors=NUM_COLORS, @@ -43,6 +45,7 @@ def __init__( self.calculate_volumes_default = calculate_volumes_default self.summarise_volumes_default = summarise_volumes_default + self.save_default = save_default # Brushes / ... self.brush_size_default = BRUSH_SIZE # Keep track of default @@ -60,7 +63,7 @@ def add_region_panel(self, row): "Add new region", region_layout, self.add_new_region, - row=2, + row=3, column=0, tooltip="Create a new empty segmentation layer " "to manually segment a new region.", @@ -70,7 +73,7 @@ def add_region_panel(self, row): "Analyse regions", region_layout, self.run_region_analysis, - row=2, + row=3, column=1, tooltip="Analyse the spatial distribution of the " "segmented regions.", @@ -79,7 +82,7 @@ def add_region_panel(self, row): "Add region from selected layer", region_layout, self.add_region_from_existing_layer, - row=3, + row=4, column=0, tooltip="Adds a region from a selected labels layer (e.g. " "from another plugin). Make sure this region " @@ -105,6 +108,13 @@ def add_region_panel(self, row): tooltip="Summarise each segmented region " "(e.g. center, volume etc.).", ) + self.save_checkbox = add_checkbox( + region_layout, + self.save_default, + "Save segmentation", + row=2, + tooltip="Save the segmentation layers during analysis.", + ) region_layout.setColumnMinimumWidth(1, COLUMN_WIDTH) self.region_panel.setLayout(region_layout) @@ -204,6 +214,9 @@ def run_region_analysis(self, override=False): choice = True # for debugging if choice: + if self.save_checkbox.isChecked(): + self.parent.run_save() + print("Running region analysis") if check_segmentation_in_correct_space( diff --git a/brainreg_segment/segmentation_panels/tracks.py b/brainreg_segment/segmentation_panels/tracks.py index 3213e52..ba1b9ae 100644 --- a/brainreg_segment/segmentation_panels/tracks.py +++ b/brainreg_segment/segmentation_panels/tracks.py @@ -9,6 +9,7 @@ COLUMN_WIDTH, FIT_DEGREE_DEFAULT, POINT_SIZE, + SAVE_DEFAULT, SEGM_METHODS_PANEL_ALIGN, SPLINE_POINTS_DEFAULT, SPLINE_SIZE, @@ -47,6 +48,7 @@ def __init__( spline_smoothing_default=SPLINE_SMOOTHING_DEFAULT, fit_degree_default=FIT_DEGREE_DEFAULT, summarise_track_default=SUMMARISE_TRACK_DEFAULT, + save_default=SAVE_DEFAULT, ): super(TrackSeg, self).__init__() self.parent = parent @@ -62,6 +64,7 @@ def __init__( self.spline_size = spline_size # Initialise self.spline_smoothing_default = spline_smoothing_default self.fit_degree_default = fit_degree_default + self.save_default = save_default # File formats self.track_file_extension = track_file_extension @@ -78,7 +81,7 @@ def add_track_panel(self, row): "Add track", track_layout, self.add_track, - row=5, + row=6, column=0, tooltip="Create a new empty segmentation layer " "to manually annotate a new track.", @@ -88,7 +91,7 @@ def add_track_panel(self, row): "Trace tracks", track_layout, self.run_track_analysis, - row=5, + row=6, column=1, tooltip="Join up the points using a spline fit " "and save the distribution of the track in " @@ -99,7 +102,7 @@ def add_track_panel(self, row): "Add track from selected layer", track_layout, self.add_track_from_existing_layer, - row=6, + row=7, column=0, tooltip="Adds a track from a selected points layer (e.g. " "from another plugin). Make sure this track " @@ -111,7 +114,7 @@ def add_track_panel(self, row): "Add surface points", track_layout, self.add_surface_points, - row=6, + row=7, column=1, tooltip="Add an additional first point at the surface of the " "brain. Selecting this option will add an additional " @@ -128,14 +131,21 @@ def add_track_panel(self, row): "each part of the interpolated track " "(determined by the number of spline points). ", ) + self.save_checkbox = add_checkbox( + track_layout, + self.save_default, + "Save tracing", + row=1, + tooltip="Save the traced layers during analysis.", + ) self.fit_degree = add_int_box( track_layout, self.fit_degree_default, - 1, + 2, 5, "Fit degree", - row=1, + row=2, tooltip="Degree of polynomial to fit to the track.", ) @@ -146,7 +156,7 @@ def add_track_panel(self, row): 1, "Spline smoothing", 0.1, - row=2, + row=3, tooltip="How closely or not to fit the points " "(lower numbers fit more closely, for " "a less smooth interpolation).", @@ -158,7 +168,7 @@ def add_track_panel(self, row): 1, 10000, "Spline points", - row=3, + row=4, tooltip="How many points are sampled from the " "interpolation (used for the summary).", ) @@ -294,6 +304,9 @@ def run_track_analysis(self, override=False): choice = True # for debugging if choice: + if self.save_checkbox.isChecked(): + self.parent.run_save() + print("Running track analysis") self.splines, self.spline_names = track_analysis( self.parent.viewer, diff --git a/tests/conftest.py b/tests/conftest.py index 136c86e..be98ad2 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -27,8 +27,15 @@ def segmentation_widget(make_napari_viewer): return widget -@pytest.fixture +@pytest.fixture(scope="function") def segmentation_widget_with_data_atlas_space(tmp_path, segmentation_widget): + """ + Fixture to load a brainreg directory into the segmentation widget. + Data is copied to tmpdir so that when it's loaded, so all the paths + are set correctly. + The manual segmentation data is then deleted so that saving/export + can be properly tested + """ tmp_input_dir = tmp_path / "brainreg_output" shutil.copytree(brainreg_dir, tmp_input_dir) segmentation_widget.standard_space = True @@ -37,4 +44,6 @@ def segmentation_widget_with_data_atlas_space(tmp_path, segmentation_widget): ) segmentation_widget.directory = Path(tmp_input_dir) segmentation_widget.load_brainreg_directory() + # delete manual segmentation data to ensure it's saved correctly in tests + shutil.rmtree(segmentation_widget.paths.main_directory) return segmentation_widget diff --git a/tests/data/brainreg_output/manual_segmentation/standard_space/regions/summary.csv b/tests/data/brainreg_output/manual_segmentation/standard_space/regions/summary.csv index 3eba61f..0ff5030 100644 --- a/tests/data/brainreg_output/manual_segmentation/standard_space/regions/summary.csv +++ b/tests/data/brainreg_output/manual_segmentation/standard_space/regions/summary.csv @@ -1,2 +1,2 @@ region,volume_mm3,axis_0_min_um,axis_1_min_um,axis_2_min_um,axis_0_max_um,axis_1_max_um,axis_2_max_um,axis_0_center_um,axis_1_center_um,axis_2_center_um -test_region,1.95,123,29,46,142,65,80,132,46,62 +test_region,1.95,6150.0,1450.0,2300.0,7100.0,3250.0,4000.0,6600.0,2329.8173076923076,3141.2339743589746 diff --git a/tests/data/brainreg_output/manual_segmentation/standard_space/tracks/test_track.h5 b/tests/data/brainreg_output/manual_segmentation/standard_space/tracks/test_track.h5 deleted file mode 100644 index 2d51cd2..0000000 Binary files a/tests/data/brainreg_output/manual_segmentation/standard_space/tracks/test_track.h5 and /dev/null differ diff --git a/tests/data/brainreg_output/manual_segmentation/standard_space/tracks/test_track.npy b/tests/data/brainreg_output/manual_segmentation/standard_space/tracks/test_track.npy new file mode 100644 index 0000000..887f325 Binary files /dev/null and b/tests/data/brainreg_output/manual_segmentation/standard_space/tracks/test_track.npy differ diff --git a/tests/tests/test_integration/test_gui/test_regions.py b/tests/tests/test_integration/test_gui/test_region_ui_functionality.py similarity index 58% rename from tests/tests/test_integration/test_gui/test_regions.py rename to tests/tests/test_integration/test_gui/test_region_ui_functionality.py index 18e1334..abf05b6 100644 --- a/tests/tests/test_integration/test_gui/test_regions.py +++ b/tests/tests/test_integration/test_gui/test_region_ui_functionality.py @@ -1,8 +1,11 @@ from filecmp import cmp from pathlib import Path +from time import sleep +import numpy as np import pandas as pd import pytest +from tifffile import imread brainreg_dir = Path.cwd() / "tests" / "data" / "brainreg_output" validate_regions_dir = ( @@ -11,8 +14,8 @@ @pytest.fixture -def test_regions_dir(tmpdir): - tmp_input_dir = tmpdir / "brainreg_output" +def test_regions_dir(tmp_path): + tmp_input_dir = tmp_path / "brainreg_output" test_regions_dir = ( tmp_input_dir / "manual_segmentation" / "standard_space" / "regions" ) @@ -59,23 +62,57 @@ def test_add_existing_region( assert len(segmentation_widget_with_data_atlas_space.label_layers) == 2 -def test_region_analysis( +def test_region_analysis_without_save( segmentation_widget_with_data_atlas_space, test_regions_dir ): + segmentation_widget_with_data_atlas_space.region_seg.calculate_volumes_checkbox.setChecked( + True + ) + segmentation_widget_with_data_atlas_space.region_seg.summarise_volumes_checkbox.setChecked( + True + ) segmentation_widget_with_data_atlas_space.region_seg.run_region_analysis( override=True ) - region_csv_validate = pd.read_csv(validate_regions_dir / "test_region.csv") - region_csv_test = pd.read_csv(test_regions_dir / "test_region.csv") - pd.testing.assert_frame_equal(region_csv_test, region_csv_validate) - summary_csv_validate = pd.read_csv(validate_regions_dir / "summary.csv") - summary_csv_test = pd.read_csv(test_regions_dir / "summary.csv") - pd.testing.assert_frame_equal(summary_csv_test, summary_csv_validate) + # ensure data is saved before it is loaded again + sleep(8) + check_analysis(test_regions_dir, validate_regions_dir) + + # check saving didn't happen (default) + test_saved_region = Path(test_regions_dir / "test_region.tiff") + assert test_saved_region.exists() is False + + +def test_region_analysis_with_save( + segmentation_widget_with_data_atlas_space, test_regions_dir +): + segmentation_widget_with_data_atlas_space.region_seg.calculate_volumes_checkbox.setChecked( + True + ) + segmentation_widget_with_data_atlas_space.region_seg.summarise_volumes_checkbox.setChecked( + True + ) + segmentation_widget_with_data_atlas_space.region_seg.save_checkbox.setChecked( + True + ) + segmentation_widget_with_data_atlas_space.region_seg.run_region_analysis( + override=True + ) + + # ensure data is saved before it is loaded again + sleep(8) + check_analysis(test_regions_dir, validate_regions_dir) + check_saving(test_regions_dir, validate_regions_dir) -def test_region_save(segmentation_widget_with_data_atlas_space): +def test_region_save( + segmentation_widget_with_data_atlas_space, test_regions_dir +): segmentation_widget_with_data_atlas_space.save(override=True) + # ensure data is saved before it is loaded again + sleep(8) + check_saving(test_regions_dir, validate_regions_dir) def test_region_export( @@ -84,7 +121,26 @@ def test_region_export( segmentation_widget_with_data_atlas_space.export_to_brainrender( override=True ) + + # ensure data is saved before it is loaded again + sleep(8) cmp( validate_regions_dir / "test_region.obj", test_regions_dir / "test_region.obj", ) + + +def check_analysis(test_regions_dir, validate_regions_dir): + region_csv_validate = pd.read_csv(validate_regions_dir / "test_region.csv") + region_csv_test = pd.read_csv(test_regions_dir / "test_region.csv") + pd.testing.assert_frame_equal(region_csv_test, region_csv_validate) + + summary_csv_validate = pd.read_csv(validate_regions_dir / "summary.csv") + summary_csv_test = pd.read_csv(test_regions_dir / "summary.csv") + pd.testing.assert_frame_equal(summary_csv_test, summary_csv_validate) + + +def check_saving(test_regions_dir, validate_regions_dir): + image_validate = imread(validate_regions_dir / "test_region.tiff") + image_test = imread(test_regions_dir / "test_region.tiff") + np.testing.assert_array_equal(image_test, image_validate) diff --git a/tests/tests/test_integration/test_gui/test_tracks.py b/tests/tests/test_integration/test_gui/test_track_ui_functionality.py similarity index 69% rename from tests/tests/test_integration/test_gui/test_tracks.py rename to tests/tests/test_integration/test_gui/test_track_ui_functionality.py index b9f25e1..0534f6c 100644 --- a/tests/tests/test_integration/test_gui/test_tracks.py +++ b/tests/tests/test_integration/test_gui/test_track_ui_functionality.py @@ -1,4 +1,5 @@ from pathlib import Path +from time import sleep import numpy as np import pandas as pd @@ -11,8 +12,8 @@ @pytest.fixture -def test_tracks_dir(tmpdir): - tmp_input_dir = tmpdir / "brainreg_output" +def test_tracks_dir(tmp_path): + tmp_input_dir = tmp_path / "brainreg_output" test_tracks_dir = ( tmp_input_dir / "manual_segmentation" / "standard_space" / "tracks" ) @@ -72,32 +73,67 @@ def test_add_surface_point( ) -def test_track_analysis( +def test_track_analysis_without_save( segmentation_widget_with_data_atlas_space, test_tracks_dir ): segmentation_widget_with_data_atlas_space.track_seg.run_track_analysis( override=True ) - regions_validate = pd.read_csv(validate_tracks_dir / "test_track.csv") - regions_test = pd.read_csv(test_tracks_dir / "test_track.csv") - pd.testing.assert_frame_equal(regions_validate, regions_test) + # check saving didn't happen (default) + test_saved_track = Path(test_tracks_dir / "test_track.points") + assert test_saved_track.exists() is False + + check_analysis(test_tracks_dir, validate_tracks_dir) + + +def test_track_analysis_with_save( + segmentation_widget_with_data_atlas_space, test_tracks_dir, rtol=1e-10 +): + segmentation_widget_with_data_atlas_space.track_seg.save_checkbox.setChecked( + True + ) + segmentation_widget_with_data_atlas_space.track_seg.run_track_analysis( + override=True + ) + + check_analysis(test_tracks_dir, validate_tracks_dir) + check_saving(test_tracks_dir, validate_tracks_dir, rtol) def test_track_save( segmentation_widget_with_data_atlas_space, test_tracks_dir, rtol=1e-10 ): segmentation_widget_with_data_atlas_space.save(override=True) - points_validate = pd.read_hdf(validate_tracks_dir / "test_track.points") - points_test = pd.read_hdf(test_tracks_dir / "test_track.points") - np.testing.assert_allclose(points_validate, points_test, rtol=rtol) + + # ensure data is saved before it is loaded again + sleep(8) + check_saving(test_tracks_dir, validate_tracks_dir, rtol) def test_track_export( segmentation_widget_with_data_atlas_space, test_tracks_dir ): + segmentation_widget_with_data_atlas_space.track_seg.run_track_analysis( + override=True + ) segmentation_widget_with_data_atlas_space.export_to_brainrender( override=True ) - spline_validate = pd.read_hdf(validate_tracks_dir / "test_track.h5") - spline_test = pd.read_hdf(test_tracks_dir / "test_track.h5") - pd.testing.assert_frame_equal(spline_test, spline_validate) + + # ensure data is saved before it is loaded again + sleep(8) + spline_validate = np.load(validate_tracks_dir / "test_track.npy") + spline_test = np.load(test_tracks_dir / "test_track.npy") + np.testing.assert_equal(spline_validate, spline_test) + + +def check_analysis(test_tracks_dir, validate_tracks_dir): + regions_validate = pd.read_csv(validate_tracks_dir / "test_track.csv") + regions_test = pd.read_csv(test_tracks_dir / "test_track.csv") + pd.testing.assert_frame_equal(regions_validate, regions_test) + + +def check_saving(test_tracks_dir, validate_tracks_dir, rtol): + points_validate = pd.read_hdf(validate_tracks_dir / "test_track.points") + points_test = pd.read_hdf(test_tracks_dir / "test_track.points") + np.testing.assert_allclose(points_validate, points_test, rtol=rtol)