From f378a3b15a6982ef29c0849dafe72d2cfd566052 Mon Sep 17 00:00:00 2001 From: Lachlan Whitehead Date: Thu, 29 Aug 2024 10:53:23 +1000 Subject: [PATCH 01/12] unify command line args with config file args --- core/lls_core/cmds/__main__.py | 40 +++++++++++---------------- core/lls_core/models/deconvolution.py | 2 +- core/lls_core/models/lattice_data.py | 6 ++-- 3 files changed, 20 insertions(+), 28 deletions(-) diff --git a/core/lls_core/cmds/__main__.py b/core/lls_core/cmds/__main__.py index 4b18f74..305a413 100644 --- a/core/lls_core/cmds/__main__.py +++ b/core/lls_core/cmds/__main__.py @@ -36,20 +36,17 @@ class CliDeskewDirection(StrEnum): "input_image": ["input_image"], "angle": ["angle"], "skew": ["skew"], - "pixel_sizes": ["physical_pixel_sizes"], - "rois": ["crop", "roi_list"], - "roi_indices": ["crop", "roi_subset"], - "z_start": ["crop", "z_range", 0], - "z_end": ["crop", "z_range", 1], + "physical_pixel_sizes": ["physical_pixel_sizes"], + "roi_list": ["crop", "roi_list"], + "roi_subset": ["crop", "roi_subset"], + "z_range": ["crop", "z_range"], "decon_processing": ["deconvolution", "decon_processing"], "psf": ["deconvolution", "psf"], - "psf_num_iter": ["deconvolution", "psf_num_iter"], + "decon_num_iter": ["deconvolution", "decon_num_iter"], "background": ["deconvolution", "background"], "workflow": ["workflow"], - "time_start": ["time_range", 0], - "time_end": ["time_range", 1], - "channel_start": ["channel_range", 0], - "channel_end": ["channel_range", 1], + "time_range": ["time_range"], + "channel_range": ["channel_range"], "save_dir": ["save_dir"], "save_name": ["save_name"], "save_type": ["save_type"], @@ -145,30 +142,25 @@ def process( input_image: Path = Argument(None, help="Path to the image file to read, in a format readable by AICSImageIO, for example .tiff or .czi", show_default=False), skew: CliDeskewDirection = field_from_model(DeskewParams, "skew"),# DeskewParams.make_typer_field("skew"), angle: float = field_from_model(DeskewParams, "angle") , - pixel_sizes: Tuple[float, float, float] = field_from_model(DeskewParams, "physical_pixel_sizes", extra_description="This takes three arguments, corresponding to the Z, Y and X pixel dimensions respectively", default=( + physical_pixel_sizes: Tuple[float, float, float] = field_from_model(DeskewParams, "physical_pixel_sizes", extra_description="This takes three arguments, corresponding to the Z, Y and X pixel dimensions respectively", default=( DefinedPixelSizes.get_default("Z"), DefinedPixelSizes.get_default("Y"), DefinedPixelSizes.get_default("X") )), - rois: List[Path] = field_from_model(CropParams, "roi_list", description="A list of paths pointing to regions of interest to crop to, in ImageJ format."), #Option([], help="A list of paths pointing to regions of interest to crop to, in ImageJ format."), - roi_indices: List[int] = field_from_model(CropParams, "roi_subset"), - # Ideally this and other range values would be defined as Tuples, but these seem to be broken: https://github.com/tiangolo/typer/discussions/667 - z_start: Optional[int] = Option(0, help="The index of the first Z slice to use. All prior Z slices will be discarded.", show_default=False), - z_end: Optional[int] = Option(None, help="The index of the last Z slice to use. The selected index and all subsequent Z slices will be discarded. Defaults to the last z index of the image.", show_default=False), - + roi_list: List[Path] = field_from_model(CropParams, "roi_list", description="A list of paths pointing to regions of interest to crop to, in ImageJ format."), #Option([], help="A list of paths pointing to regions of interest to crop to, in ImageJ format."), + roi_subset: List[int] = field_from_model(CropParams, "roi_subset"), + z_range: Optional[Tuple[int,int]] = Option(None, help="The index of the first Z slice to use. All prior Z slices will be discarded.", show_default=False), + enable_deconvolution: bool = Option(False, "--deconvolution/--disable-deconvolution", rich_help_panel="Deconvolution"), decon_processing: DeconvolutionChoice = field_from_model(DeconvolutionParams, "decon_processing", rich_help_panel="Deconvolution"), psf: List[Path] = field_from_model(DeconvolutionParams, "psf", description="One or more paths pointing to point spread functions to use for deconvolution. Each file should in a standard image format (.czi, .tiff etc), containing a 3D image array. This option can be used multiple times to provide multiple PSF files.", rich_help_panel="Deconvolution"), - psf_num_iter: int = field_from_model(DeconvolutionParams, "psf_num_iter", rich_help_panel="Deconvolution"), + decon_num_iter: int = field_from_model(DeconvolutionParams, "decon_num_iter", rich_help_panel="Deconvolution"), background: str = field_from_model(DeconvolutionParams, "background", rich_help_panel="Deconvolution"), - time_start: Optional[int] = Option(0, help="Index of the first time slice to use (inclusive). Defaults to the first time index of the image.", rich_help_panel="Output"), - time_end: Optional[int] = Option(None, help="Index of the first time slice to use (exclusive). Defaults to the last time index of the image.", show_default=False, rich_help_panel="Output"), - - channel_start: Optional[int] = Option(0, help="Index of the first channel slice to use (inclusive). Defaults to the first channel index of the image.", rich_help_panel="Output"), - channel_end: Optional[int] = Option(None, help="Index of the first channel slice to use (exclusive). Defaults to the last channel index of the image.", show_default=False, rich_help_panel="Output"), - + time_range: Optional[Tuple[int,int]] = Option(None, help="Start frame and stop frame for time cropping - Defaults to full range.", rich_help_panel="Output"), + channel_range: Optional[Tuple[int,int]] = Option(None,help="Index of the first channel (inclusive) and last slice (exclusive) to use. Defaults to full range.", rich_help_panel="Output"), + save_dir: Path = field_from_model(OutputParams, "save_dir", rich_help_panel="Output"), save_name: Optional[str] = field_from_model(OutputParams, "save_name", rich_help_panel="Output"), save_type: SaveFileType = field_from_model(OutputParams, "save_type", rich_help_panel="Output"), diff --git a/core/lls_core/models/deconvolution.py b/core/lls_core/models/deconvolution.py index c628afe..6bfc32a 100644 --- a/core/lls_core/models/deconvolution.py +++ b/core/lls_core/models/deconvolution.py @@ -22,7 +22,7 @@ class DeconvolutionParams(FieldAccessModel): default=[], description="List of Point Spread Functions to use for deconvolution. Each of which should be a 3D array. Each PSF can also be provided as a `str` path, in which case they will be loaded from disk as images." ) - psf_num_iter: NonNegativeInt = Field( + decon_num_iter: NonNegativeInt = Field( default=10, description="Number of iterations to perform in deconvolution" ) diff --git a/core/lls_core/models/lattice_data.py b/core/lls_core/models/lattice_data.py index 185cf73..bbb2e35 100644 --- a/core/lls_core/models/lattice_data.py +++ b/core/lls_core/models/lattice_data.py @@ -345,7 +345,7 @@ def _process_crop(self) -> Iterable[ImageSlice]: deconv_args: dict[Any, Any] = {} if self.deconvolution is not None: deconv_args = dict( - num_iter = self.deconvolution.psf_num_iter, + num_iter = self.deconvolution.decon_num_iter, psf = self.deconvolution.psf[slice.channel].to_numpy(), decon_processing=self.deconvolution.decon_processing ) @@ -390,13 +390,13 @@ def _process_non_crop(self) -> Iterable[ImageSlice]: dxdata=self.dx, dzpsf=self.dz, dxpsf=self.dx, - num_iter=self.deconvolution.psf_num_iter + num_iter=self.deconvolution.decon_num_iter ) else: data = skimage_decon( vol_zyx=data, psf=self.deconvolution.psf[slice.channel].to_numpy(), - num_iter=self.deconvolution.psf_num_iter, + num_iter=self.deconvolution.decon_num_iter, clip=False, filter_epsilon=0, boundary='nearest' From 9f2cea74794618e87872fbcfa066406b6fd31f0d Mon Sep 17 00:00:00 2001 From: Lachlan Whitehead Date: Thu, 29 Aug 2024 13:18:42 +1000 Subject: [PATCH 02/12] update test --- core/tests/test_arg_parser.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/tests/test_arg_parser.py b/core/tests/test_arg_parser.py index 26bb1e2..a037f7f 100644 --- a/core/tests/test_arg_parser.py +++ b/core/tests/test_arg_parser.py @@ -10,9 +10,9 @@ def test_voxel_parsing(): parser = command.make_parser(ctx) args, _, _ = parser.parse_args(args=[ "process", - "input", + "input-image", "--save-name", "output", "--save-type", "tiff", - "--pixel-sizes", "1", "1", "1" + "--physical_pixel_sizes", "1", "1", "1" ]) - assert args["pixel_sizes"] == ("1", "1", "1") + assert args["physical_pixel_sizes"] == ("1", "1", "1") From 0d93f9ea19862f827a24864aac8167bc89731de4 Mon Sep 17 00:00:00 2001 From: Lachlan Whitehead Date: Thu, 29 Aug 2024 14:19:57 +1000 Subject: [PATCH 03/12] update test_arg_parser --- core/tests/test_arg_parser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/tests/test_arg_parser.py b/core/tests/test_arg_parser.py index a037f7f..1505be0 100644 --- a/core/tests/test_arg_parser.py +++ b/core/tests/test_arg_parser.py @@ -13,6 +13,6 @@ def test_voxel_parsing(): "input-image", "--save-name", "output", "--save-type", "tiff", - "--physical_pixel_sizes", "1", "1", "1" + "--physical-pixel-sizes", "1", "1", "1" ]) assert args["physical_pixel_sizes"] == ("1", "1", "1") From 629cadce4f831ce726e4c71caf2495f5af5db55a Mon Sep 17 00:00:00 2001 From: Lachlan Whitehead Date: Thu, 29 Aug 2024 16:03:53 +1000 Subject: [PATCH 04/12] update test_batch_deskew using time-range --- core/tests/test_cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/tests/test_cli.py b/core/tests/test_cli.py index 7d96b33..38867ab 100644 --- a/core/tests/test_cli.py +++ b/core/tests/test_cli.py @@ -62,7 +62,7 @@ def assert_h5(output_dir: Path): [ [["--save-type", "h5"], assert_h5], [["--save-type", "tiff"], assert_tiff], - [["--save-type", "tiff", "--time-start", "0", "--time-end", "1"], assert_tiff], + [["--save-type", "tiff", "--time-range", "0", "1"], assert_tiff], ] ) def test_batch_deskew(flags: List[str], check_fn: Callable[[Path], None]): From 34ffc200efde35e709ba222fdd3029969891552a Mon Sep 17 00:00:00 2001 From: Lachlan Whitehead Date: Mon, 2 Sep 2024 14:45:01 +1000 Subject: [PATCH 05/12] fix help strings --- core/lls_core/cmds/__main__.py | 12 ++++++------ core/lls_core/models/lattice_data.py | 1 + 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/core/lls_core/cmds/__main__.py b/core/lls_core/cmds/__main__.py index 098f7f6..d567fb7 100644 --- a/core/lls_core/cmds/__main__.py +++ b/core/lls_core/cmds/__main__.py @@ -147,17 +147,17 @@ def process( DefinedPixelSizes.get_default("X") )), - roi_list: List[Path] = field_from_model(CropParams, "roi_list", description="A list of paths pointing to regions of interest to crop to, in ImageJ format."), #Option([], help="A list of paths pointing to regions of interest to crop to, in ImageJ format."), - roi_subset: List[int] = field_from_model(CropParams, "roi_subset"), - z_range: Optional[Tuple[int,int]] = Option(None, help="The index of the first Z slice to use. All prior Z slices will be discarded.", show_default=False), + roi_list: List[Path] = field_from_model(CropParams, "roi_list", description="Path pointing to regions of interest to crop to, in ImageJ ROI Manager format."), #Option([], help="A list of paths pointing to regions of interest to crop to, in ImageJ format."), + roi_subset: List[int] = field_from_model(CropParams, "roi_subset", description="List of integers describing a subset of ROIs to process. Defaults to processing all ROIs"), + z_range: Optional[Tuple[int,int]] = Option(None, help="The index of the first Z slice (inclusive) and the last z slice (exclusive) to use. All other Z slices will be discarded. Defaults to all slices.", show_default=False), enable_deconvolution: bool = Option(False, "--deconvolution/--disable-deconvolution", rich_help_panel="Deconvolution"), decon_processing: DeconvolutionChoice = field_from_model(DeconvolutionParams, "decon_processing", rich_help_panel="Deconvolution"), psf: List[Path] = field_from_model(DeconvolutionParams, "psf", description="One or more paths pointing to point spread functions to use for deconvolution. Each file should in a standard image format (.czi, .tiff etc), containing a 3D image array. This option can be used multiple times to provide multiple PSF files.", rich_help_panel="Deconvolution"), - decon_num_iter: int = field_from_model(DeconvolutionParams, "decon_num_iter", rich_help_panel="Deconvolution"), - background: str = field_from_model(DeconvolutionParams, "background", rich_help_panel="Deconvolution"), + decon_num_iter: int = field_from_model(DeconvolutionParams, "decon_num_iter", description="Number of iterations of deconvolution to perform", rich_help_panel="Deconvolution"), + background: str = field_from_model(DeconvolutionParams, "background", description="Mean background value for deconvolution background subtraction, can also be 'auto' for automatic bg detection. Defaults to 0.", rich_help_panel="Deconvolution"), - time_range: Optional[Tuple[int,int]] = Option(None, help="Start frame and stop frame for time cropping - Defaults to full range.", rich_help_panel="Output"), + time_range: Optional[Tuple[int,int]] = Option(None, help="Start frame (inclusive) and stop frame (exclusive) for time cropping - Defaults to full range.", rich_help_panel="Output"), channel_range: Optional[Tuple[int,int]] = Option(None,help="Index of the first channel (inclusive) and last slice (exclusive) to use. Defaults to full range.", rich_help_panel="Output"), save_dir: Path = field_from_model(OutputParams, "save_dir", rich_help_panel="Output"), diff --git a/core/lls_core/models/lattice_data.py b/core/lls_core/models/lattice_data.py index bbb2e35..2512d8a 100644 --- a/core/lls_core/models/lattice_data.py +++ b/core/lls_core/models/lattice_data.py @@ -59,6 +59,7 @@ def read_image(cls, values: dict): from lls_core.types import is_pathlike from pathlib import Path input_image = values.get("input_image") + logger.info(f"Processing File {input_image}") # this is handy for debugging if is_pathlike(input_image): if values.get("save_name") is None: values["save_name"] = Path(values["input_image"]).stem From 317eb5a91ac30cf21b09a40d3e94bae5416b3c12 Mon Sep 17 00:00:00 2001 From: Lachlan Whitehead Date: Tue, 3 Sep 2024 10:36:09 +1000 Subject: [PATCH 06/12] add check/fix for incomplete final frame --- core/lls_core/models/deskew.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/core/lls_core/models/deskew.py b/core/lls_core/models/deskew.py index d432443..b3f9e84 100644 --- a/core/lls_core/models/deskew.py +++ b/core/lls_core/models/deskew.py @@ -192,6 +192,15 @@ def read_image(cls, values: dict): # If the image was convertible to AICSImage, we should use the metadata from there if aics: values["input_image"] = aics.xarray_dask_data + + #check if final frame is complte. If not, get rid of it + final_frame = values["input_image"][-1] + try: + final_frame.compute() + except ValueError as e: + print("Final frame is borked. Acquisition probably stopped prematurely. Removing final frame.") + values["input_image"] = values["input_image"][0:-1] + # Take pixel sizes from the image metadata, but only if they're defined # and only if we don't already have them if all(size is not None for size in aics.physical_pixel_sizes) and values.get("physical_pixel_sizes") is None: From 78dadb8e5e71fc5f4b43140676c1dcc9d1e02f89 Mon Sep 17 00:00:00 2001 From: Lachlan Whitehead Date: Fri, 6 Sep 2024 10:20:31 +1000 Subject: [PATCH 07/12] move final frame fix to model validator --- core/lls_core/cropping.py | 2 +- core/lls_core/models/deskew.py | 11 +---------- core/lls_core/models/lattice_data.py | 15 +++++++++++++++ 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/core/lls_core/cropping.py b/core/lls_core/cropping.py index 2f0c35a..f57f289 100644 --- a/core/lls_core/cropping.py +++ b/core/lls_core/cropping.py @@ -43,7 +43,7 @@ def read_imagej_roi(roi_path: PathLike) -> List[Roi]: if roi_path.suffix == ".zip": ij_roi = read_roi_zip(roi_path) elif roi_path.suffix == ".roi": - ij_roi = read_roi_file(roi_path) + ij_roi = read_roi_file(str(roi_path)) else: raise Exception("ImageJ ROI file needs to be a zip/roi file") diff --git a/core/lls_core/models/deskew.py b/core/lls_core/models/deskew.py index b3f9e84..de741b4 100644 --- a/core/lls_core/models/deskew.py +++ b/core/lls_core/models/deskew.py @@ -191,16 +191,7 @@ def read_image(cls, values: dict): # If the image was convertible to AICSImage, we should use the metadata from there if aics: - values["input_image"] = aics.xarray_dask_data - - #check if final frame is complte. If not, get rid of it - final_frame = values["input_image"][-1] - try: - final_frame.compute() - except ValueError as e: - print("Final frame is borked. Acquisition probably stopped prematurely. Removing final frame.") - values["input_image"] = values["input_image"][0:-1] - + values["input_image"] = aics.xarray_dask_data # Take pixel sizes from the image metadata, but only if they're defined # and only if we don't already have them if all(size is not None for size in aics.physical_pixel_sizes) and values.get("physical_pixel_sizes") is None: diff --git a/core/lls_core/models/lattice_data.py b/core/lls_core/models/lattice_data.py index 2512d8a..54a6009 100644 --- a/core/lls_core/models/lattice_data.py +++ b/core/lls_core/models/lattice_data.py @@ -75,6 +75,21 @@ def read_image(cls, values: dict): # Use the Deskew version of this validator, to do the actual image loading return super().read_image(values) + @validator("input_image", pre=True, always=True) + def incomplete_final_frame(cls, v: DataArray) -> Any: + """ + Check final frame, if acquisition is stopped halfway through it causes failures + This validator will remove a bad final frame + """ + final_frame = v[-1] + try: + final_frame.compute() + except ValueError as e: + logger.warn("Final frame is borked. Acquisition probably stopped prematurely. Removing final frame.") + v = v[0:-1] + return v + + @validator("workflow", pre=True) def parse_workflow(cls, v: Any): # Load the workflow from disk if it was provided as a path From 02dc13f79829de26e9f94316da89b04476e0feb1 Mon Sep 17 00:00:00 2001 From: Lachlan Whitehead Date: Fri, 6 Sep 2024 13:46:44 +1000 Subject: [PATCH 08/12] move help to models --- core/lls_core/cmds/__main__.py | 18 +++++++++--------- core/lls_core/models/lattice_data.py | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/core/lls_core/cmds/__main__.py b/core/lls_core/cmds/__main__.py index d567fb7..89dc766 100644 --- a/core/lls_core/cmds/__main__.py +++ b/core/lls_core/cmds/__main__.py @@ -147,24 +147,24 @@ def process( DefinedPixelSizes.get_default("X") )), - roi_list: List[Path] = field_from_model(CropParams, "roi_list", description="Path pointing to regions of interest to crop to, in ImageJ ROI Manager format."), #Option([], help="A list of paths pointing to regions of interest to crop to, in ImageJ format."), - roi_subset: List[int] = field_from_model(CropParams, "roi_subset", description="List of integers describing a subset of ROIs to process. Defaults to processing all ROIs"), - z_range: Optional[Tuple[int,int]] = Option(None, help="The index of the first Z slice (inclusive) and the last z slice (exclusive) to use. All other Z slices will be discarded. Defaults to all slices.", show_default=False), + roi_list: List[Path] = field_from_model(CropParams, "roi_list"), + roi_subset: List[int] = field_from_model(CropParams, "roi_subset"), + z_range: Optional[Tuple[int,int]] = field_from_model(CropParams, "z_range", show_default=False), enable_deconvolution: bool = Option(False, "--deconvolution/--disable-deconvolution", rich_help_panel="Deconvolution"), decon_processing: DeconvolutionChoice = field_from_model(DeconvolutionParams, "decon_processing", rich_help_panel="Deconvolution"), - psf: List[Path] = field_from_model(DeconvolutionParams, "psf", description="One or more paths pointing to point spread functions to use for deconvolution. Each file should in a standard image format (.czi, .tiff etc), containing a 3D image array. This option can be used multiple times to provide multiple PSF files.", rich_help_panel="Deconvolution"), - decon_num_iter: int = field_from_model(DeconvolutionParams, "decon_num_iter", description="Number of iterations of deconvolution to perform", rich_help_panel="Deconvolution"), - background: str = field_from_model(DeconvolutionParams, "background", description="Mean background value for deconvolution background subtraction, can also be 'auto' for automatic bg detection. Defaults to 0.", rich_help_panel="Deconvolution"), + psf: List[Path] = field_from_model(DeconvolutionParams, "psf", rich_help_panel="Deconvolution"), + decon_num_iter: int = field_from_model(DeconvolutionParams, "decon_num_iter", rich_help_panel="Deconvolution"), + background: str = field_from_model(DeconvolutionParams, "background", rich_help_panel="Deconvolution"), - time_range: Optional[Tuple[int,int]] = Option(None, help="Start frame (inclusive) and stop frame (exclusive) for time cropping - Defaults to full range.", rich_help_panel="Output"), - channel_range: Optional[Tuple[int,int]] = Option(None,help="Index of the first channel (inclusive) and last slice (exclusive) to use. Defaults to full range.", rich_help_panel="Output"), + time_range: Optional[Tuple[int,int]] = field_from_model(OutputParams, "time_range", rich_help_panel="Output"), + channel_range: Optional[Tuple[int,int]] = field_from_model(OutputParams,"channel_range", rich_help_panel="Output"), save_dir: Path = field_from_model(OutputParams, "save_dir", rich_help_panel="Output"), save_name: Optional[str] = field_from_model(OutputParams, "save_name", rich_help_panel="Output"), save_type: SaveFileType = field_from_model(OutputParams, "save_type", rich_help_panel="Output"), - workflow: Optional[Path] = Option(None, help="Path to a Napari Workflow file, in YAML format. If provided, the configured desekewing processing will be added to the chosen workflow.", show_default=False), + workflow: Optional[Path] = field_from_model(LatticeData, "workflow", show_default=False), json_config: Optional[Path] = Option(None, show_default=False, help="Path to a JSON file from which parameters will be read."), yaml_config: Optional[Path] = Option(None, show_default=False, help="Path to a YAML file from which parameters will be read."), diff --git a/core/lls_core/models/lattice_data.py b/core/lls_core/models/lattice_data.py index 54a6009..4eecefa 100644 --- a/core/lls_core/models/lattice_data.py +++ b/core/lls_core/models/lattice_data.py @@ -81,7 +81,7 @@ def incomplete_final_frame(cls, v: DataArray) -> Any: Check final frame, if acquisition is stopped halfway through it causes failures This validator will remove a bad final frame """ - final_frame = v[-1] + final_frame = v.isel(T=-1,C=-1) try: final_frame.compute() except ValueError as e: From b014a00ec53cd2e7446c586468b9d2f946c7a278 Mon Sep 17 00:00:00 2001 From: Michael Milton Date: Fri, 27 Sep 2024 10:24:08 +1000 Subject: [PATCH 09/12] Use warning rather than warn; use drop_isel to drop incomplete frames --- core/lls_core/models/lattice_data.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/lls_core/models/lattice_data.py b/core/lls_core/models/lattice_data.py index 4eecefa..dbbbd7b 100644 --- a/core/lls_core/models/lattice_data.py +++ b/core/lls_core/models/lattice_data.py @@ -81,12 +81,12 @@ def incomplete_final_frame(cls, v: DataArray) -> Any: Check final frame, if acquisition is stopped halfway through it causes failures This validator will remove a bad final frame """ - final_frame = v.isel(T=-1,C=-1) + final_frame = v.isel(T=-1,C=-1, drop=True) try: final_frame.compute() - except ValueError as e: - logger.warn("Final frame is borked. Acquisition probably stopped prematurely. Removing final frame.") - v = v[0:-1] + except ValueError: + logger.warning("Final frame is borked. Acquisition probably stopped prematurely. Removing final frame.") + v = v.drop_isel(T=-1, C=-1) return v From 74106c7b64fb16e5eef81846aa30551664fa65ab Mon Sep 17 00:00:00 2001 From: Michael Milton Date: Fri, 27 Sep 2024 10:24:57 +1000 Subject: [PATCH 10/12] Remove old incomplete acquisition code --- core/lls_core/models/lattice_data.py | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/core/lls_core/models/lattice_data.py b/core/lls_core/models/lattice_data.py index dbbbd7b..9edbcda 100644 --- a/core/lls_core/models/lattice_data.py +++ b/core/lls_core/models/lattice_data.py @@ -321,24 +321,6 @@ def generate_workflows( # The user can use any of these arguments as inputs to their tasks yield lattice_slice.copy_with_data(user_workflow) - def check_incomplete_acquisition(self, volume: ArrayLike, time_point: int, channel: int): - """ - Checks for a slice with incomplete data, caused by incomplete acquisition - """ - import numpy as np - if not isinstance(volume, DaskArray): - return volume - orig_shape = volume.shape - raw_vol = volume.compute() - if raw_vol.shape != orig_shape: - logger.warn(f"Time {time_point}, channel {channel} is incomplete. Actual shape {orig_shape}, got {raw_vol.shape}") - z_diff, y_diff, x_diff = np.subtract(orig_shape, raw_vol.shape) - logger.info(f"Padding with{z_diff,y_diff,x_diff}") - raw_vol = np.pad(raw_vol, ((0, z_diff), (0, y_diff), (0, x_diff))) - if raw_vol.shape != orig_shape: - raise Exception(f"Shape of last timepoint still doesn't match. Got {raw_vol.shape}") - return raw_vol - @property def deskewed_volume(self) -> DaskArray: from dask.array import zeros From 951105b16b740de4298a93f51a202c98b3222a7d Mon Sep 17 00:00:00 2001 From: Lachlan Whitehead Date: Tue, 15 Oct 2024 09:52:30 +1100 Subject: [PATCH 11/12] fix to not remove channel when final frame is incomplete --- core/lls_core/models/lattice_data.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/lls_core/models/lattice_data.py b/core/lls_core/models/lattice_data.py index 9edbcda..8a9fd2c 100644 --- a/core/lls_core/models/lattice_data.py +++ b/core/lls_core/models/lattice_data.py @@ -86,7 +86,7 @@ def incomplete_final_frame(cls, v: DataArray) -> Any: final_frame.compute() except ValueError: logger.warning("Final frame is borked. Acquisition probably stopped prematurely. Removing final frame.") - v = v.drop_isel(T=-1, C=-1) + v = v.drop_isel(T=-1) return v From e3da61248a84368cfaac06fc8485a47615b7a546 Mon Sep 17 00:00:00 2001 From: Lachlan Whitehead Date: Tue, 15 Oct 2024 11:12:43 +1100 Subject: [PATCH 12/12] update psf_num_iter to decon_num_iter --- core/lls_core/models/lattice_data.py | 2 +- plugin/napari_lattice/fields.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/core/lls_core/models/lattice_data.py b/core/lls_core/models/lattice_data.py index 53ded68..d7e2c98 100644 --- a/core/lls_core/models/lattice_data.py +++ b/core/lls_core/models/lattice_data.py @@ -370,7 +370,7 @@ def _process_crop(self) -> Iterable[ImageSlice]: deconv_args: dict[Any, Any] = {} if self.deconvolution is not None: deconv_args = dict( - num_iter = self.deconvolution.psf_num_iter, + num_iter = self.deconvolution.decon_num_iter, psf = self.deconvolution.psf[slice.channel].to_numpy(), decon_processing=self.deconvolution.decon_processing ) diff --git a/plugin/napari_lattice/fields.py b/plugin/napari_lattice/fields.py index 2360c69..c46e1b5 100644 --- a/plugin/napari_lattice/fields.py +++ b/plugin/napari_lattice/fields.py @@ -375,7 +375,7 @@ class DeconvolutionFields(NapariFieldGroup): tooltip="PSFs must be in the same order as the image channels", layout="vertical" ) - psf_num_iter = field(int, label = "Number of Iterations") + decon_num_iter = field(int, label = "Number of Iterations") background = field(ComboBox).with_choices( [it.value for it in BackgroundSource] ).with_options(label="Background") @@ -397,7 +397,7 @@ def _enable_custom_background(self, background: str) -> bool: fields = [ decon_processing, psf, - psf_num_iter, + decon_num_iter, background ] ) @@ -418,7 +418,7 @@ def _make_model(self) -> Optional[DeconvolutionParams]: background=background, # Filter out unset PSFs psf=[psf for psf in self.psf.value if psf.is_file()], - psf_num_iter=self.psf_num_iter.value + decon_num_iter=self.decon_num_iter.value ) @magicclass