From 041cd363ec0e984a8b376b91b7cec86237591e22 Mon Sep 17 00:00:00 2001 From: Tim Monko <47310455+TimMonko@users.noreply.github.com> Date: Mon, 7 Oct 2024 12:32:55 -0500 Subject: [PATCH 01/18] complete UI overhaul has UI skeleton of goals, but things are absolutely not hooked up correctly. --- .../widgets/test_utilities_container.py | 6 +- .../widgets/_utilities_container.py | 273 ++++++++++-------- 2 files changed, 160 insertions(+), 119 deletions(-) diff --git a/src/napari_ndev/_tests/widgets/test_utilities_container.py b/src/napari_ndev/_tests/widgets/test_utilities_container.py index cfb821c..dab5ebf 100644 --- a/src/napari_ndev/_tests/widgets/test_utilities_container.py +++ b/src/napari_ndev/_tests/widgets/test_utilities_container.py @@ -61,7 +61,7 @@ def test_save_shapes_as_labels( container._viewer.layers.selection.active = viewer.layers['test_shape'] container._squeezed_dims = test_dims - container._save_directory.value = tmp_path + container._output_directory.value = tmp_path container._save_name.value = 'test.tiff' shapes_as_labels = container.save_shapes_as_labels() @@ -84,7 +84,7 @@ def test_save_labels(make_napari_viewer, tmp_path: Path, test_data): container = UtilitiesContainer(viewer) container._squeezed_dims = test_dims - container._save_directory.value = tmp_path + container._output_directory.value = tmp_path container._save_name.value = 'test.tiff' labels = container.save_labels() @@ -105,7 +105,7 @@ def test_save_ome_tiff(make_napari_viewer, test_data, tmp_path: Path): container._concatenate_image_layers.value = True container._viewer.layers.selection.active = viewer.layers['test_image'] container._channel_names.value = ['0'] - container._save_directory.value = tmp_path + container._output_directory.value = tmp_path container._save_name.value = 'test.tiff' container.save_ome_tiff() diff --git a/src/napari_ndev/widgets/_utilities_container.py b/src/napari_ndev/widgets/_utilities_container.py index 4ce155a..25a689b 100644 --- a/src/napari_ndev/widgets/_utilities_container.py +++ b/src/napari_ndev/widgets/_utilities_container.py @@ -7,6 +7,18 @@ from typing import TYPE_CHECKING import numpy as np +from magicclass.widgets import ( + CollapsibleContainer, + DraggableContainer, + FrameContainer, # no title + GroupBoxContainer, # with title + HCollapsibleContainer, + MainWindow, + ScrollableContainer, # with scroll area + SplitterContainer, + TabbedContainer, + ToolBoxContainer, +) from magicgui.widgets import ( CheckBox, Container, @@ -28,7 +40,8 @@ from napari.layers import Image as ImageLayer -class UtilitiesContainer(Container): +# class UtilitiesContainer(ScrollableContainer): +class UtilitiesContainer(ScrollableContainer): """ A widget to work with images and labels in the napari viewer. @@ -125,7 +138,7 @@ def __init__(self, viewer: napari.viewer.Viewer = None): The napari viewer instance. """ - super().__init__() + super().__init__(labels=False) self._viewer = viewer if viewer is not None else None self._img_data = None @@ -135,77 +148,137 @@ def __init__(self, viewer: napari.viewer.Viewer = None): self._squeezed_dims = None self._init_widgets() + self._init_save_name_container() + self._init_file_options_container() self._init_open_image_container() - self._init_info_container() - self._init_concatenate_container() - self._init_save_container() + self._init_metadata_container() + self._init_concatenate_files_container() + self._init_save_layers_container() self._init_scene_container() self._init_scale_container() self._init_layout() self._connect_events() - def _init_widgets(self): - """Initialize non-Container widgets.""" - self._file_metadata_update = PushButton(label='File') - self._layer_metadata_update = PushButton(label='Selected Layer') - self._metadata_container = Container( - layout='horizontal', label='Update Metadata from' + def _init_layout(self): + """Initialize the layout of the widget.""" + self.extend( + [ + self._save_directory, + self._files, + self._save_name_container, + self._file_options_container, + self._metadata_container, + self._open_image_container, + self._concatenate_files_container, + self._scene_container, + self._save_layers_container, + self._results, + ] ) - self._metadata_container.append(self._layer_metadata_update) - self._metadata_container.append(self._file_metadata_update) + def _init_widgets(self): + """Initialize widgets.""" + self._save_directory = FileEdit( + mode='d', + tooltip='Directory where images will be saved.', + ) self._files = FileEdit( - label='File(s)', mode='rm', tooltip='Select file(s) to load.', ) - self._save_directory = FileEdit( - label='Save Directory', - mode='d', - tooltip='Directory where images will be saved.', - ) + self._results = TextEdit(label='Info') + + def _init_save_name_container(self): + """Initialize the save name container.""" + self._save_name_container = Container(layout='horizontal') self._save_name = LineEdit( - label='File Save Name', - tooltip='Name of saved file. Helpful to include a' + label='Save Name', + tooltip='Name of the saved file. Helpful to include a' '.ome/.tif/.tiff extension.', ) + self._append_scene_button = PushButton( + label='Append Scene to Name', + ) + self._save_name_container.extend([ + self._save_name, + self._append_scene_button + ]) - self._channel_names = LineEdit( - label='Channel Name(s)', - tooltip='Enter channel names as a list. If left blank or the ' - 'channel names are not the proper length, then default channel ' - 'names will be used.', + def _init_file_options_container(self): + """Initialize the file options collapsible container.""" + self._file_options_container = CollapsibleContainer( + layout='vertical', + text='File Options', + collapsed=True, + ) + self._update_scale = CheckBox( + value=True, + label='Update Scale on File Select', + tooltip='Update the scale when files are selected.', + ) + self._update_channel_names = CheckBox( + value=True, + label='Update Channel Names on File Select', + tooltip='Update the channel names when files are selected.', + ) + self._save_directory_prefix = LineEdit( + label='Save Directory Prefix', + tooltip='Prefix for the save directories.', + value=None, ) - self._results = TextEdit(label='Info') + self._file_options_container.extend([ + self._update_scale, + self._update_channel_names, + self._save_directory_prefix, + ]) def _init_open_image_container(self): """Initialize the open image container.""" self._open_image_container = Container(layout='horizontal') - - self._open_image_update_metadata = CheckBox( - value=True, - label='Update Metadata', - tooltip='Update metadata during initial file selection.', - ) self._open_image_button = PushButton(label='Open File(s)') self._select_next_image_button = PushButton( label='Select Next', - tooltip='Select the next file(s) in the directory. ' - 'The files are sorted alphabetically and numerically,' - 'which may not be consistent ' + tooltip='Select the next file(s) in the directory. \n' + 'Note that the files are sorted alphabetically and numerically.' + ) + self._open_image_container.append(self._open_image_button) + self._open_image_container.append(self._select_next_image_button) + + def _init_concatenate_files_container(self): + self._concatenate_files_container = Container( + layout='horizontal', + ) + self._concatenate_image_button = PushButton(label='Concat. Files') + self._concatenate_batch_button = PushButton( + label='Batch Concat.', + tooltip='Concatenate files in the selected directory by iterating' + ' over the remaing files in the directory based on the number of' + ' files selected. The files are sorted ' + 'alphabetically and numerically, which may not be consistent ' 'with your file viewer. But, opening related consecutive files ' 'should work as expected.', ) + self._concatenate_files_container.extend([ + self._concatenate_image_button, + self._concatenate_batch_button, + ]) - self._open_image_container.append(self._open_image_update_metadata) - self._open_image_container.append(self._open_image_button) - self._open_image_container.append(self._select_next_image_button) - def _init_info_container(self): - """Initialize the info container containing dims and scenes.""" - self._info_container = Container(layout='horizontal') + def _init_metadata_container(self): + self._metadata_container = CollapsibleContainer( + layout='vertical', # label='Update Metadata from', + text='Metadata', + collapsed=True, + ) + self._file_metadata_update = PushButton( + label='Update Metadata from File' + ) + self._layer_metadata_update = PushButton( + label='Update Metadata from Selected Layer' + ) + self._dim_order = Label( label='Dimension Order: ', tooltip='Sanity check for available dimensions.', @@ -214,33 +287,39 @@ def _init_info_container(self): label='Number of Scenes: ', ) - self._info_container.append(self._dim_order) - self._info_container.append(self._scenes) - - def _init_scale_container(self): - """Initialize the scale container.""" - self._scale_container = Container( - layout='vertical', - label='Scale, ZYX', - tooltip='Pixel size, usually in μm', + self._channel_names = LineEdit( + label='Channel Name(s)', + tooltip='Enter channel names as a list. If left blank or the ' + 'channel names are not the proper length, then default channel ' + 'names will be used.', ) self._scale_tuple = TupleEdit( + label='Scale, ZYX', + tooltip='Pixel size, usually in μm', value=(0.0000, 1.0000, 1.0000), options={'step': 0.0001}, ) - self._scale_layers = PushButton( + self._scale_layers_button = PushButton( label='Scale Layer(s)', tooltip='Scale the selected layer(s) based on the given scale.', ) - self._scale_container.append(self._scale_tuple) - self._scale_container.append(self._scale_layers) + + + self._metadata_container.extend([ + self._file_metadata_update, + self._layer_metadata_update, + self._dim_order, + self._scenes, + self._channel_names, + self._scale_tuple, + self._scale_layers_button, + ]) def _init_scene_container(self): """Initialize the scene container, allowing scene saving.""" self._scene_container = Container( layout='horizontal', - label='Extract Scenes', tooltip='Must be in list index format. Ex: [0, 1, 2] or [5:10]', ) self._scenes_to_extract = LineEdit( @@ -255,67 +334,29 @@ def _init_scene_container(self): self._scene_container.append(self._scenes_to_extract) self._scene_container.append(self._extract_scenes) - def _init_concatenate_container(self): - """Initialize the container to concatenate image and layers.""" - self._concatenate_image_files = CheckBox( - value=True, - label='Concatenate Files', - tooltip='Concatenate files in the selected directory. Removes ' - 'blank channels.', - ) - self._concatenate_image_layers = CheckBox( - label='Concatenate Image Layers', - tooltip='Concatenate image layers in the viewer. Removes empty.', - ) - self._concatenate_container = Container( - layout='horizontal', - label='Image Save Options', - ) - self._concatenate_container.append(self._concatenate_image_files) - self._concatenate_container.append(self._concatenate_image_layers) - - def _init_save_container(self): + def _init_save_layers_container(self): """Initialize the container to save images, labels, and shapes.""" - self._save_container = Container( + self._save_layers_container = Container( layout='horizontal', label='Save Selected Layers', ) - - self._save_image_button = PushButton( - label='Images', - tooltip='Save the concatenated image data as OME-TIFF.', - ) - self._save_labels_button = PushButton( - label='Labels', tooltip='Save the labels data as OME-TIFF.' - ) - self._save_shapes_button = PushButton( - label='Shapes as Labels', - tooltip='Save the shapes data as labels (OME-TIFF) according to ' - 'selected image layer dimensions.', - ) - - self._save_container.append(self._save_image_button) - self._save_container.append(self._save_labels_button) - self._save_container.append(self._save_shapes_button) - - def _init_layout(self): - """Initialize the layout of the widget.""" - self.extend( - [ - self._save_directory, - self._files, - self._open_image_container, - self._save_name, - self._metadata_container, - self._info_container, - self._channel_names, - self._scale_container, - self._scene_container, - self._concatenate_container, - self._save_container, - self._results, - ] - ) + self._save_layers_button = PushButton( + label='Save Selected Layers', + tooltip='Concatenate and save all selected layers as OME-TIFF.' + 'Layers will save to corresponding directories based on the layer' + 'type, e.g. Images, Labels, ShapesAsLabels. Shapes are saved as' + 'labels based on the selected image layer dimensions. If multiple' + 'layer types are selected, then the image will save to Layers.', + ) + self._export_figure_button = PushButton( + label='Export Figure', + tooltip='Export the current canvas figure to the save directory. ' + 'Saves image as a PNG to Figures directory.', + ) + self._save_layers_container.extend([ + self._save_layers_button, + self._export_figure_button, + ]) def _connect_events(self): """Connect the events of the widgets to respective methods.""" @@ -328,11 +369,11 @@ def _connect_events(self): self._file_metadata_update.clicked.connect( self.update_metadata_from_file ) - self._scale_layers.clicked.connect(self.rescale_by) + self._scale_layers_button.clicked.connect(self.rescale_by) self._extract_scenes.clicked.connect(self.save_scenes_ome_tiff) - self._save_image_button.clicked.connect(self.save_ome_tiff) - self._save_labels_button.clicked.connect(self.save_labels) - self._save_shapes_button.clicked.connect(self.save_shapes_as_labels) + # self._save_image_button.clicked.connect(self.save_ome_tiff) + # self._save_labels_button.clicked.connect(self.save_labels) + # self._save_shapes_button.clicked.connect(self.save_shapes_as_labels) self._results._on_value_change() def _update_metadata(self, img: AICSImage | BioImage): From fe1cf5194292a780b0c4e310a594003b2539de79 Mon Sep 17 00:00:00 2001 From: Tim Monko <47310455+TimMonko@users.noreply.github.com> Date: Mon, 7 Oct 2024 14:01:05 -0500 Subject: [PATCH 02/18] fix metadata logic --- .../widgets/test_utilities_container.py | 8 +- .../widgets/_utilities_container.py | 211 ++++++++++-------- 2 files changed, 125 insertions(+), 94 deletions(-) diff --git a/src/napari_ndev/_tests/widgets/test_utilities_container.py b/src/napari_ndev/_tests/widgets/test_utilities_container.py index dab5ebf..2e9c748 100644 --- a/src/napari_ndev/_tests/widgets/test_utilities_container.py +++ b/src/napari_ndev/_tests/widgets/test_utilities_container.py @@ -61,7 +61,7 @@ def test_save_shapes_as_labels( container._viewer.layers.selection.active = viewer.layers['test_shape'] container._squeezed_dims = test_dims - container._output_directory.value = tmp_path + container._save_directory.value = tmp_path container._save_name.value = 'test.tiff' shapes_as_labels = container.save_shapes_as_labels() @@ -84,7 +84,7 @@ def test_save_labels(make_napari_viewer, tmp_path: Path, test_data): container = UtilitiesContainer(viewer) container._squeezed_dims = test_dims - container._output_directory.value = tmp_path + container._save_directory.value = tmp_path container._save_name.value = 'test.tiff' labels = container.save_labels() @@ -105,7 +105,7 @@ def test_save_ome_tiff(make_napari_viewer, test_data, tmp_path: Path): container._concatenate_image_layers.value = True container._viewer.layers.selection.active = viewer.layers['test_image'] container._channel_names.value = ['0'] - container._output_directory.value = tmp_path + container._save_directory.value = tmp_path container._save_name.value = 'test.tiff' container.save_ome_tiff() @@ -130,7 +130,7 @@ def test_update_metadata_from_file(make_napari_viewer, test_rgb_image): path, _ = test_rgb_image container._files.value = path - container.update_metadata_from_file() + container.update_metadata_on_file_select() assert container._save_name.value == 'RGB.tiff' assert container._img.dims.order == 'TCZYXS' diff --git a/src/napari_ndev/widgets/_utilities_container.py b/src/napari_ndev/widgets/_utilities_container.py index 25a689b..5be4720 100644 --- a/src/napari_ndev/widgets/_utilities_container.py +++ b/src/napari_ndev/widgets/_utilities_container.py @@ -155,7 +155,6 @@ def __init__(self, viewer: napari.viewer.Viewer = None): self._init_concatenate_files_container() self._init_save_layers_container() self._init_scene_container() - self._init_scale_container() self._init_layout() self._connect_events() @@ -360,14 +359,14 @@ def _init_save_layers_container(self): def _connect_events(self): """Connect the events of the widgets to respective methods.""" - self._files.changed.connect(self.update_metadata_from_file) + self._files.changed.connect(self.update_metadata_on_file_select) self._open_image_button.clicked.connect(self.open_images) self._select_next_image_button.clicked.connect(self.select_next_images) self._layer_metadata_update.clicked.connect( self.update_metadata_from_layer ) self._file_metadata_update.clicked.connect( - self.update_metadata_from_file + self.update_metadata_on_file_select ) self._scale_layers_button.clicked.connect(self.rescale_by) self._extract_scenes.clicked.connect(self.save_scenes_ome_tiff) @@ -376,75 +375,68 @@ def _connect_events(self): # self._save_shapes_button.clicked.connect(self.save_shapes_as_labels) self._results._on_value_change() - def _update_metadata(self, img: AICSImage | BioImage): + @property + def p_sizes(self): """ - Update the metadata based on the given image. + Get the physical pixel sizes. - Parameters - ---------- - img : AICSImage | BioImage - The image from which to update the metadata. + Returns + ------- + PhysicalPixelSizes + The physical pixel sizes. """ - self._dim_order.value = img.dims.order - - self._squeezed_dims = helpers.get_squeezed_dim_order(img) - self._channel_names.value = helpers.get_channel_names(img) + from bioio_base.types import PhysicalPixelSizes - self._scale_tuple.value = ( - img.physical_pixel_sizes.Z or 1, - img.physical_pixel_sizes.Y or 1, - img.physical_pixel_sizes.X or 1, + return PhysicalPixelSizes( + self._scale_tuple.value[0], + self._scale_tuple.value[1], + self._scale_tuple.value[2], ) - def _read_image_file(self, file: str | Path) -> AICSImage | BioImage: + def _update_metadata_from_Image( + self, + img: AICSImage | BioImage, + update_channel_names: bool = True, + update_scale: bool = True, + ): """ - Read the image file with BioImage or AICSImage. + Update the metadata based on the given image. Parameters ---------- - file : str or Path - The file path. - - Returns - ------- - AICSImage or BioImage - The image object. + img : AICSImage | BioImage + The image from which to update the metadata. + update_channel_names : bool, optional + Update the channel names, by default True. + update_scale : bool, optional + Update the scale, by default True. """ - from bioio import BioImage - from bioio_base.exceptions import UnsupportedFileFormatError - - try: - img = BioImage(file) - except UnsupportedFileFormatError: - from aicsimageio import AICSImage - img = AICSImage(file) - return img + self._dim_order.value = img.dims.order - def _bioimage_metadata(self): - """ - Update the metadata from the selected file. + self._squeezed_dims = helpers.get_squeezed_dim_order(img) - Attemps to read from BioImage first, then AICSImage. + if update_channel_names: + self._channel_names.value = helpers.get_channel_names(img) + if update_scale: + self._scale_tuple.value = ( + img.physical_pixel_sizes.Z or 1, + img.physical_pixel_sizes.Y or 1, + img.physical_pixel_sizes.X or 1, + ) - """ + def update_metadata_on_file_select(self): + """Update self._save_name.value and metadata if selected.""" from napari_ndev.helpers import get_Image - # from aicsimageio import AICSImage - # from bioio import BioImage - # from bioio_base.exceptions import UnsupportedFileFormatError + self._save_name.value = str(self._files.value[0].stem) img = get_Image(self._files.value[0]) - self._img = img - self._update_metadata(img) - self._scenes.value = len(img.scenes) - - def update_metadata_from_file(self): - """Update self._save_name.value and metadata if selected.""" - self._save_name.value = str(self._files.value[0].stem + '.tiff') - - if self._open_image_update_metadata.value: - self._bioimage_metadata() + self._update_metadata_from_Image( + img, + update_channel_names=self._update_channel_names.value, + update_scale=self._update_scale.value, + ) def update_metadata_from_layer(self): """ @@ -454,7 +446,9 @@ def update_metadata_from_layer(self): """ selected_layer = self._viewer.layers.selection.active try: - self._update_metadata(selected_layer.metadata['aicsimage']) + img = selected_layer.metadata['aicsimage'] + self._update_metadata_from_Image(img) + except AttributeError: self._results.value = ( 'Tried to update metadata, but no layer selected.' @@ -488,7 +482,6 @@ def select_next_images(self): """Open the next set of images in the directyory.""" from napari_ndev.helpers import get_Image - # TODO: sort files consistent with windows and mac folder explorers num_files = self._files.value.__len__() # get the parent directory of the first file @@ -513,14 +506,10 @@ def select_next_images(self): # set the nwe save names, and update the file value img = get_Image(next_files[0]) - image_id = helpers.create_id_string(img, next_files[0].stem) - self._save_name.value = str(image_id + '.tiff') + self._save_name.value = helpers.create_id_string(img, next_files[0].stem) self._files.value = next_files - if self._open_image_update_metadata.value: - self._bioimage_metadata() - - # self._viewer.open(next_files, plugin='napari-aicsimageio') + self._update_metadata_on_file_select() def rescale_by(self): """Rescale the selected layers based on the given scale.""" @@ -533,6 +522,77 @@ def rescale_by(self): # are missing in the new layer layer.scale = scale_tup[-scale_len:] + def concatenate_files( + self, + files: str | Path | list[str | Path], + ) -> np.ndarray: + """ + Concatenate the image data from the selected files. + + Removes "empty" channels, which are channels with no values above 0. + This is present in some microscope formats where it will image in RGB, + and then leave empty channels not represented by the color channels. + + Parameters + ---------- + files : str or Path or list of str or Path + The file(s) to concatenate. + + Returns + ------- + numpy.ndarray + The concatenated image data. + + """ + from napari_ndev.helpers import get_Image + + array_list = [] + + for file in files: + img = get_Image(file) + + if 'S' in img.dims.order: + img_data = img.get_image_data('TSZYX') + else: + img_data = img.data + + # iterate over all channels and only keep if not blank + for idx in range(img_data.shape[1]): + array = img_data[:, [idx], :, :, :] + if array.max() > 0: + array_list.append(array) + return np.concatenate(array_list, axis=1) + + def concatenate_layers( + self, + layers: ImageLayer | list[ImageLayer], + ): + """ + Concatenate the image data from the selected layers. + + Adapts all layers to 5D arrays for compatibility with image dims. + + Parameters + ---------- + layers : napari.layers.Image or list of napari.layers.Image + The selected image layers. + + Returns + ------- + numpy.ndarray + The concatenated image data. + + """ + array_list = [] + for layer in layers: + layer_data = layer.data + # convert to 5D array for compatability with image dims + while len(layer_data.shape) < 5: + layer_data = np.expand_dims(layer_data, axis=0) + array_list.append(layer_data) + + return np.concatenate(array_list, axis=1) + def concatenate_images( self, concatenate_files: bool, @@ -582,35 +642,6 @@ def concatenate_images( if array.max() > 0: array_list.append(array) - # <- fix if RGB image is the layer data - if concatenate_layers: - for layer in layers: - layer_data = layer.data - # convert to 5D array for compatability with image dims - while len(layer_data.shape) < 5: - layer_data = np.expand_dims(layer_data, axis=0) - array_list.append(layer_data) - - return np.concatenate(array_list, axis=1) - - @property - def p_sizes(self): - """ - Get the physical pixel sizes. - - Returns - ------- - PhysicalPixelSizes - The physical pixel sizes. - - """ - from bioio_base.types import PhysicalPixelSizes - - return PhysicalPixelSizes( - self._scale_tuple.value[0], - self._scale_tuple.value[1], - self._scale_tuple.value[2], - ) def _get_save_loc( self, root_dir: Path, parent: str, file_name: str From 092dc0ca1104375ad2d8ecf5d885bf3d32c537fd Mon Sep 17 00:00:00 2001 From: Tim Monko <47310455+TimMonko@users.noreply.github.com> Date: Mon, 7 Oct 2024 17:25:05 -0500 Subject: [PATCH 03/18] chaos --- .../widgets/_utilities_container.py | 317 +++++++++--------- 1 file changed, 152 insertions(+), 165 deletions(-) diff --git a/src/napari_ndev/widgets/_utilities_container.py b/src/napari_ndev/widgets/_utilities_container.py index 5be4720..13267ad 100644 --- a/src/napari_ndev/widgets/_utilities_container.py +++ b/src/napari_ndev/widgets/_utilities_container.py @@ -26,10 +26,15 @@ Label, LineEdit, PushButton, + SpinBox, TextEdit, TupleEdit, ) +from napari.layers import Image as ImageLayer +from napari.layers import Labels as LabelsLayer +from napari.layers import Shapes as ShapesLayer + from napari_ndev import helpers if TYPE_CHECKING: @@ -37,7 +42,7 @@ from bioio import BioImage import napari - from napari.layers import Image as ImageLayer + from napari.layers import Layer # class UtilitiesContainer(ScrollableContainer): @@ -155,6 +160,7 @@ def __init__(self, viewer: napari.viewer.Viewer = None): self._init_concatenate_files_container() self._init_save_layers_container() self._init_scene_container() + self._init_figure_options_container() self._init_layout() self._connect_events() @@ -170,6 +176,7 @@ def _init_layout(self): self._open_image_container, self._concatenate_files_container, self._scene_container, + self._figure_options_container, self._save_layers_container, self._results, ] @@ -357,6 +364,49 @@ def _init_save_layers_container(self): self._export_figure_button, ]) + def _init_figure_options_container(self): + """Initialize the container for figure options.""" + self._figure_options_container = CollapsibleContainer( + layout='vertical', + text='Figure Options', + collapsed=True, + ) + self._figure_scale_factor = SpinBox( + label='Scale Factor', + min=0, + step=1, + value=1, + ) + self._use_current_canvas_size = CheckBox( + label='Use current canvas dimensions', + value=True + ) + self._current_canvas_dims = Label(label='Canvas Dimensions: ') + self._canvas_size = TupleEdit( + label='Canvas Size', + value=(0, 0), + ) + # use this to automatically change the camera parameters + self._camera_zoom = SpinBox( + label='Camera Zoom', + min=0, + step=0.1, + value=self._viewer.camera.zoom, + ) + self._camera_angle = TupleEdit( + label='Camera Angle', + value=(0, 0, 90), + options={'step': 1}, + ) + self._figure_options_container.extend([ + self._figure_scale_factor, + self._use_current_canvas_size, + self._current_canvas_dims, + self._canvas_size, + self._camera_zoom, + self._camera_angle, + ]) + def _connect_events(self): """Connect the events of the widgets to respective methods.""" self._files.changed.connect(self.update_metadata_on_file_select) @@ -533,6 +583,8 @@ def concatenate_files( This is present in some microscope formats where it will image in RGB, and then leave empty channels not represented by the color channels. + Does not currently handle scenes. + Parameters ---------- files : str or Path or list of str or Path @@ -565,12 +617,14 @@ def concatenate_files( def concatenate_layers( self, - layers: ImageLayer | list[ImageLayer], - ): + layers: Layer | list[Layer], + ) -> np.ndarray: """ Concatenate the image data from the selected layers. Adapts all layers to 5D arrays for compatibility with image dims. + If the layer is a shapes layer, it will look for a corresponding image + layer to get the dimensions for the shapes layer. Parameters ---------- @@ -583,9 +637,21 @@ def concatenate_layers( The concatenated image data. """ + # if any layers are shapes, get the corresponding image layer dims + # if any layers in the list of layers are napari.layers.Shapes + # then get the corresponding image layer dims + if any(isinstance(layer, ShapesLayer) for layer in layers): + label_dim = self._get_dims_for_shape_layer(layers) + array_list = [] + for layer in layers: - layer_data = layer.data + if isinstance(layer, ShapesLayer): + layer_data = layer.to_labels(labels_shape=label_dim) + layer_data = layer_data.astype(np.int16) + else: + layer_data = layer.data + # convert to 5D array for compatability with image dims while len(layer_data.shape) < 5: layer_data = np.expand_dims(layer_data, axis=0) @@ -593,55 +659,24 @@ def concatenate_layers( return np.concatenate(array_list, axis=1) - def concatenate_images( - self, - concatenate_files: bool, - files: list[str | Path], - concatenate_layers: bool, - layers: list[ImageLayer], - ): - """ - Concatenate the image data based on the selected options. - - Intended also to remove "blank" channels and layers. This is present - in some microscope formats where it will image in RGB, and then - leave empty channels not represented by the color channels. - - Parameters - ---------- - concatenate_files : bool - Concatenate files in the selected directory. Removes blank channels. - files : list[str | Path] - The list of files to concatenate. - concatenate_layers : bool - Concatenate image layers in the viewer. Removes empty. - layers : list[ImageLayer] - The list of layers to concatenate. - - Returns - ------- - numpy.ndarray - The concatenated image data. - - """ - from napari_ndev.helpers import get_Image - - array_list = [] - if concatenate_files: - for file in files: - img = get_Image(file) - - if 'S' in img.dims.order: - img_data = img.get_image_data('TSZYX') - else: - img_data = img.data - - # iterate over all channels and only keep if not blank - for idx in range(img_data.shape[1]): - array = img_data[:, [idx], :, :, :] - if array.max() > 0: - array_list.append(array) - + def _get_dims_for_shape_layer(self, layers) -> tuple[int]: + # get first instance of a napari.layers.Image or napari.layers.Labels + dim_layer = next( + (layer for layer in layers if isinstance(layer, (ImageLayer, LabelsLayer))), + None, + ) + # if none of these layers is selected, get it from the first instance in the viewer + if dim_layer is not None: + dim_layer = next( + (layer for layer in self._viewer.layers if isinstance(layer, (ImageLayer, LabelsLayer))), + None, + ) + if dim_layer is None: + raise ValueError('No image or labels present to convert shapes layer.') + label_dim = dim_layer.data.shape + # drop last axis if represents RGB image + label_dim = label_dim[:-1] if label_dim[-1] == 3 else label_dim + return label_dim def _get_save_loc( self, root_dir: Path, parent: str, file_name: str @@ -675,7 +710,7 @@ def _common_save_logic( dim_order: str, channel_names: list[str], image_name: str | list[str | None] | None, - layer: str, + result_str: str, ) -> None: """ Save data as OME-TIFF with bioio based on common logic. @@ -696,8 +731,8 @@ def _common_save_logic( The channel names saved to OME metadata image_name : str | list[str | None] | None The image name saved to OME metadata - layer : str - The layer name. + result_str : str + The string used for the result widget. """ # from aicsimageio.writers import OmeTiffWriter @@ -721,7 +756,7 @@ def _common_save_logic( image_name=image_name or None, physical_pixel_sizes=self.p_sizes, ) - self._results.value = f'Saved {layer}: ' + str( + self._results.value = f'Saved {result_str}: ' + str( self._save_name.value ) + f'\nAt {time.strftime("%H:%M:%S")}' # if ValueError is raised, save with default channel names @@ -742,6 +777,65 @@ def _common_save_logic( ) return + def save_files_as_ome_tiff(self) -> None: + img_data = self.concatenate_files(self._files.value) + img_save_loc = self._get_save_loc( + self._save_directory.value, + 'ConcatenatedImages', + self._save_name.value + ) + cnames = self._channel_names.value + channel_names = ast.literal_eval(cnames) if cnames else None + + self._common_save_logic( + data=img_data, + uri=img_save_loc, + dim_order='TCZYX', + channel_names=channel_names, + image_name=self._save_name.value, + result_str='Image', + ) + + def save_layers_as_ome_tiff(self) -> None: + layer_data = self.concatenate_layers( + list(self._viewer.layers.selection) + ) + # get the types of layers, to know where to save the image + layer_types = [type(layer).__name__ for layer in self._viewer.layers.selection] + + # if there are multiple layer types, save to Layers directory + layer_save_type = 'Layers' if len(set(layer_types)) > 1 else layer_types[0] + + layer_save_loc = self._get_save_loc( + self._save_directory.value, layer_save_type, self._save_name.value + ) + + # only get channel names if layer_save_type is not shapes or labels layer + if layer_save_type not in ['Shapes', 'Labels']: + cnames = self._channel_names.value + channel_names = ast.literal_eval(cnames) if cnames else None + else: + channel_names = layer_save_type + + if layer_save_type == 'Shapes': + layer_data = layer_data.astype(np.int16) + + if layer_save_type == 'Labels': + if layer_data.max() > 65535: + layer_data = layer_data.astype(np.int32) + else: + layer_data = layer_data.astype(np.int16) + + self._common_save_logic( + data=layer_data, + uri=layer_save_loc, + dim_order='TCZYX', + channel_names=channel_names, + image_name=self._save_name.value, + result_str=layer_save_type, + ) + + def save_scenes_ome_tiff(self) -> None: """ Save selected scenes as OME-TIFF. @@ -783,113 +877,6 @@ def save_scenes_ome_tiff(self) -> None: dim_order='TCZYX', channel_names=channel_names, image_name=image_id, - layer=f'Scene: {img.current_scene}', + result_str=f'Scene: {img.current_scene}', ) return - - def save_ome_tiff(self) -> np.ndarray: - """ - Save the concatenated image data as OME-TIFF. - - The concatenated image data is concatenated and then saved as OME-TIFF - based on the selected options and the given save location. - - Returns - ------- - numpy.ndarray - The concatenated image data. - - """ - self._img_data = self.concatenate_images( - self._concatenate_image_files.value, - self._files.value, - self._concatenate_image_layers.value, - list(self._viewer.layers.selection), - ) - img_save_loc = self._get_save_loc( - self._save_directory.value, 'Images', self._save_name.value - ) - # get channel names from widget if truthy - cnames = self._channel_names.value - channel_names = ast.literal_eval(cnames) if cnames else None - - self._common_save_logic( - data=self._img_data, - uri=img_save_loc, - dim_order='TCZYX', - channel_names=channel_names, - image_name=self._save_name.value, - layer='Image', - ) - return self._img_data - - def save_labels(self) -> np.ndarray: - """ - Save the selected labels layer as OME-TIFF. - - Returns - ------- - numpy.ndarray - The labels data. - - """ - label_data = self._viewer.layers.selection.active.data - - if label_data.max() > 65535: - label_data = label_data.astype(np.int32) - else: - label_data = label_data.astype(np.int16) - - label_save_loc = self._get_save_loc( - self._save_directory.value, 'Labels', self._save_name.value - ) - - self._common_save_logic( - data=label_data, - uri=label_save_loc, - dim_order=self._squeezed_dims, - channel_names=['Labels'], - image_name=self._save_name.value, - layer='Labels', - ) - return label_data - - def save_shapes_as_labels(self) -> np.ndarray: - """ - "Save the selected shapes layer as labels. - - Returns - ------- - numpy.ndarray - The shapes data as labels. - - """ - from napari.layers import Image as ImageLayer - - # inherit shape from selected image layer or else a default - image_layers = [ - x for x in self._viewer.layers if isinstance(x, ImageLayer) - ] - label_dim = image_layers[0].data.shape - - # drop last axis if represents RGB image - label_dim = label_dim[:-1] if label_dim[-1] == 3 else label_dim - - shapes = self._viewer.layers.selection.active - shapes_as_labels = shapes.to_labels(labels_shape=label_dim) - shapes_as_labels = shapes_as_labels.astype(np.int16) - - shapes_save_loc = self._get_save_loc( - self._save_directory.value, 'ShapesAsLabels', self._save_name.value - ) - - self._common_save_logic( - data=shapes_as_labels, - uri=shapes_save_loc, - dim_order=self._squeezed_dims, - channel_names=['Shapes'], - image_name=self._save_name.value, - layer='Shapes', - ) - - return shapes_as_labels From b7cded4476418b9315e56d6212c213ba770a5e59 Mon Sep 17 00:00:00 2001 From: TimMonko <47310455+TimMonko@users.noreply.github.com> Date: Mon, 7 Oct 2024 20:34:59 -0500 Subject: [PATCH 04/18] Update setup.cfg --- setup.cfg | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/setup.cfg b/setup.cfg index 9393ced..595a7b9 100644 --- a/setup.cfg +++ b/setup.cfg @@ -38,9 +38,10 @@ classifiers = [options] packages = find: install_requires = - numpy >= 1.26 # required for python 3.12 to build + numpy >= 1.26, < 2.0 # required for python 3.12 to build siphash24 >= 1.6 magicgui >= 0.8.3 + magic-class qtpy aicsimageio napari[all] >= 0.4.19 @@ -88,7 +89,7 @@ extras = napari-simpleitk-image-processing gpl_extras = - napari-aicsimageio + napari-aicsimageio >= 0.7.2 bioio-czi bioio-lif From 64fd7f3ae24360c7eb5e2970c463f13e6e19f0b9 Mon Sep 17 00:00:00 2001 From: TimMonko <47310455+TimMonko@users.noreply.github.com> Date: Mon, 7 Oct 2024 20:38:57 -0500 Subject: [PATCH 05/18] Update _utilities_container.py --- .../widgets/_utilities_container.py | 33 ++++--------------- 1 file changed, 7 insertions(+), 26 deletions(-) diff --git a/src/napari_ndev/widgets/_utilities_container.py b/src/napari_ndev/widgets/_utilities_container.py index 13267ad..289ce70 100644 --- a/src/napari_ndev/widgets/_utilities_container.py +++ b/src/napari_ndev/widgets/_utilities_container.py @@ -9,15 +9,7 @@ import numpy as np from magicclass.widgets import ( CollapsibleContainer, - DraggableContainer, - FrameContainer, # no title - GroupBoxContainer, # with title - HCollapsibleContainer, - MainWindow, - ScrollableContainer, # with scroll area - SplitterContainer, - TabbedContainer, - ToolBoxContainer, + ScrollableContainer, ) from magicgui.widgets import ( CheckBox, @@ -31,10 +23,11 @@ TupleEdit, ) -from napari.layers import Image as ImageLayer -from napari.layers import Labels as LabelsLayer -from napari.layers import Shapes as ShapesLayer - +from napari.layers import ( + Image as ImageLayer, + Labels as LabelsLayer, + Shapes as ShapesLayer, +) from napari_ndev import helpers if TYPE_CHECKING: @@ -275,12 +268,9 @@ def _init_concatenate_files_container(self): def _init_metadata_container(self): self._metadata_container = CollapsibleContainer( layout='vertical', # label='Update Metadata from', - text='Metadata', + text='Metadata', collapsed=True, ) - self._file_metadata_update = PushButton( - label='Update Metadata from File' - ) self._layer_metadata_update = PushButton( label='Update Metadata from Selected Layer' ) @@ -415,14 +405,8 @@ def _connect_events(self): self._layer_metadata_update.clicked.connect( self.update_metadata_from_layer ) - self._file_metadata_update.clicked.connect( - self.update_metadata_on_file_select - ) self._scale_layers_button.clicked.connect(self.rescale_by) self._extract_scenes.clicked.connect(self.save_scenes_ome_tiff) - # self._save_image_button.clicked.connect(self.save_ome_tiff) - # self._save_labels_button.clicked.connect(self.save_labels) - # self._save_shapes_button.clicked.connect(self.save_shapes_as_labels) self._results._on_value_change() @property @@ -637,9 +621,6 @@ def concatenate_layers( The concatenated image data. """ - # if any layers are shapes, get the corresponding image layer dims - # if any layers in the list of layers are napari.layers.Shapes - # then get the corresponding image layer dims if any(isinstance(layer, ShapesLayer) for layer in layers): label_dim = self._get_dims_for_shape_layer(layers) From 50208d36364b75afeee5be1f0ce54c164c91d8d4 Mon Sep 17 00:00:00 2001 From: TimMonko <47310455+TimMonko@users.noreply.github.com> Date: Sun, 13 Oct 2024 13:53:02 -0500 Subject: [PATCH 06/18] Update _utilities_container.py --- .../widgets/_utilities_container.py | 32 ++++++++----------- 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/src/napari_ndev/widgets/_utilities_container.py b/src/napari_ndev/widgets/_utilities_container.py index 289ce70..c22fb09 100644 --- a/src/napari_ndev/widgets/_utilities_container.py +++ b/src/napari_ndev/widgets/_utilities_container.py @@ -31,7 +31,6 @@ from napari_ndev import helpers if TYPE_CHECKING: - from aicsimageio import AICSImage from bioio import BioImage import napari @@ -303,7 +302,7 @@ def _init_metadata_container(self): self._metadata_container.extend([ - self._file_metadata_update, + # self._file_metadata_update, self._layer_metadata_update, self._dim_order, self._scenes, @@ -430,7 +429,7 @@ def p_sizes(self): def _update_metadata_from_Image( self, - img: AICSImage | BioImage, + img: BioImage, update_channel_names: bool = True, update_scale: bool = True, ): @@ -439,7 +438,7 @@ def _update_metadata_from_Image( Parameters ---------- - img : AICSImage | BioImage + img : BioImage The image from which to update the metadata. update_channel_names : bool, optional Update the channel names, by default True. @@ -476,11 +475,16 @@ def update_metadata_from_layer(self): """ Update metadata from the selected layer. - Current code expects images to be opened with napari-aicsimageio. + Expects images to be opened with napari-ndev reader. + + Note: + ---- + This should also support napari-bioio in the future, when released. + """ selected_layer = self._viewer.layers.selection.active try: - img = selected_layer.metadata['aicsimage'] + img = selected_layer.metadata['bioimage'] self._update_metadata_from_Image(img) except AttributeError: @@ -497,20 +501,13 @@ def update_metadata_from_layer(self): ) self._results.value = ( 'Tried to update metadata, but could only update scale' - ' because layer not opened with aicsimageio' + ' because layer not opened with neuralDev reader.' f'\nAt {time.strftime("%H:%M:%S")}' ) def open_images(self): - """ - Open the selected images in the napari viewer. - - If napari-aicsimageio is not installed, then the images will likely - be opened by the base napari reader, or a different compatabible - reader. - - """ - self._viewer.open(self._files.value, plugin='napari-aicsimageio') + """Open the selected images in the napari viewer with napari-ndev.""" + self._viewer.open(self._files.value, plugin='napari-ndev') def select_next_images(self): """Open the next set of images in the directyory.""" @@ -716,11 +713,10 @@ def _common_save_logic( The string used for the result widget. """ - # from aicsimageio.writers import OmeTiffWriter # TODO: add image_name to save method from bioio.writers import OmeTiffWriter - # AICSImage does not allow saving labels as np.int64 + # BioImage does not allow saving labels as np.int64 # napari generates labels differently depending on the OS # so we need to convert to np.int32 in case np.int64 generated # see: https://github.com/napari/napari/issues/5545 From 895019bca56dedb47e8291184ff5298b32929277 Mon Sep 17 00:00:00 2001 From: TimMonko <47310455+TimMonko@users.noreply.github.com> Date: Sun, 13 Oct 2024 14:12:30 -0500 Subject: [PATCH 07/18] fix reader delim --- src/napari_ndev/_napari_reader.py | 22 ++-------------------- src/napari_ndev/nimage.py | 9 +++++++-- 2 files changed, 9 insertions(+), 22 deletions(-) diff --git a/src/napari_ndev/_napari_reader.py b/src/napari_ndev/_napari_reader.py index cf7d2a8..2b63a0e 100644 --- a/src/napari_ndev/_napari_reader.py +++ b/src/napari_ndev/_napari_reader.py @@ -110,10 +110,9 @@ def napari_reader_function( logger.info("Bioio: Expected a single path, got a list of paths.") return None - in_memory = _determine_in_memory(path) if in_memory is None else in_memory - logger.info('Bioio: Reading in-memory: %s', in_memory) - img = nImage(path, reader=reader) + in_memory = img._determine_in_memory(path) if in_memory is None else in_memory + logger.info('Bioio: Reading in-memory: %s', in_memory) if len(img.scenes) > 1 and not open_first_scene_only: _get_scenes(path=path, img=img, in_memory=in_memory) @@ -126,23 +125,6 @@ def napari_reader_function( return [(img_data.data, img_meta, layer_type)] -def _determine_in_memory( - path: PathLike, - max_mem_bytes: int = 4e9, - max_mem_percent: int = 0.3 -) -> bool: - """Determine whether to read the file in memory.""" - from bioio_base.io import pathlike_to_fs - from psutil import virtual_memory - - fs, path = pathlike_to_fs(path) - filesize = fs.size(path) - available_mem = virtual_memory().available - return ( - filesize <= max_mem_bytes - and filesize / available_mem <= max_mem_percent - ) - def _widget_is_checked(widget_name: str) -> bool: import napari diff --git a/src/napari_ndev/nimage.py b/src/napari_ndev/nimage.py index 3c78594..08500a8 100644 --- a/src/napari_ndev/nimage.py +++ b/src/napari_ndev/nimage.py @@ -16,7 +16,7 @@ logger = logging.getLogger(__name__) -LABEL_DELIMITER = " ||" +LABEL_DELIMITER = " || " class nImage(BioImage): """ @@ -53,7 +53,12 @@ def __init__( self.napari_metadata = {} self.path = image if isinstance(image, (str, Path)) else None - def _determine_in_memory(self, path=None, max_in_mem_bytes: int = 4e9, max_in_mem_percent: int = 0.3) -> bool: + def _determine_in_memory( + self, + path=None, + max_in_mem_bytes: int = 4e9, + max_in_mem_percent: int = 0.3 + ) -> bool: """ Determine whether the image should be loaded into memory or not. From 5bf13b1c7161dbeb169e7c652f572750ba9b92cd Mon Sep 17 00:00:00 2001 From: TimMonko <47310455+TimMonko@users.noreply.github.com> Date: Sun, 13 Oct 2024 15:03:39 -0500 Subject: [PATCH 08/18] remove get_Image calls --- .../widgets/_utilities_container.py | 20 ++++++------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/src/napari_ndev/widgets/_utilities_container.py b/src/napari_ndev/widgets/_utilities_container.py index c22fb09..372a9ef 100644 --- a/src/napari_ndev/widgets/_utilities_container.py +++ b/src/napari_ndev/widgets/_utilities_container.py @@ -28,7 +28,7 @@ Labels as LabelsLayer, Shapes as ShapesLayer, ) -from napari_ndev import helpers +from napari_ndev import helpers, nImage if TYPE_CHECKING: from bioio import BioImage @@ -37,7 +37,6 @@ from napari.layers import Layer -# class UtilitiesContainer(ScrollableContainer): class UtilitiesContainer(ScrollableContainer): """ A widget to work with images and labels in the napari viewer. @@ -163,9 +162,9 @@ def _init_layout(self): self._save_directory, self._files, self._save_name_container, + self._open_image_container, self._file_options_container, self._metadata_container, - self._open_image_container, self._concatenate_files_container, self._scene_container, self._figure_options_container, @@ -461,9 +460,8 @@ def _update_metadata_from_Image( def update_metadata_on_file_select(self): """Update self._save_name.value and metadata if selected.""" - from napari_ndev.helpers import get_Image self._save_name.value = str(self._files.value[0].stem) - img = get_Image(self._files.value[0]) + img = nImage(self._files.value[0]) self._update_metadata_from_Image( img, @@ -511,8 +509,6 @@ def open_images(self): def select_next_images(self): """Open the next set of images in the directyory.""" - from napari_ndev.helpers import get_Image - num_files = self._files.value.__len__() # get the parent directory of the first file @@ -535,7 +531,7 @@ def select_next_images(self): idx = files.index(first_file) next_files = files[idx + num_files : idx + num_files + num_files] # set the nwe save names, and update the file value - img = get_Image(next_files[0]) + img = nImage(next_files[0]) self._save_name.value = helpers.create_id_string(img, next_files[0].stem) self._files.value = next_files @@ -577,12 +573,10 @@ def concatenate_files( The concatenated image data. """ - from napari_ndev.helpers import get_Image - array_list = [] for file in files: - img = get_Image(file) + img = nImage(file) if 'S' in img.dims.order: img_data = img.get_image_data('TSZYX') @@ -824,9 +818,7 @@ def save_scenes_ome_tiff(self) -> None: will be extracted. """ - from napari_ndev.helpers import get_Image - - img = get_Image(self._files.value[0]) + img = nImage(self._files.value[0]) scenes = self._scenes_to_extract.value scenes_list = ast.literal_eval(scenes) if scenes else None From e4b21bdef753fd0a5f0956f9ef95364e4f3b5620 Mon Sep 17 00:00:00 2001 From: TimMonko <47310455+TimMonko@users.noreply.github.com> Date: Sun, 13 Oct 2024 16:09:56 -0500 Subject: [PATCH 09/18] add append_scene_to_name --- .../widgets/_utilities_container.py | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/napari_ndev/widgets/_utilities_container.py b/src/napari_ndev/widgets/_utilities_container.py index 372a9ef..641ae9a 100644 --- a/src/napari_ndev/widgets/_utilities_container.py +++ b/src/napari_ndev/widgets/_utilities_container.py @@ -399,6 +399,7 @@ def _connect_events(self): """Connect the events of the widgets to respective methods.""" self._files.changed.connect(self.update_metadata_on_file_select) self._open_image_button.clicked.connect(self.open_images) + self._append_scene_button.clicked.connect(self.append_scene_to_name) self._select_next_image_button.clicked.connect(self.select_next_images) self._layer_metadata_update.clicked.connect( self.update_metadata_from_layer @@ -426,6 +427,7 @@ def p_sizes(self): self._scale_tuple.value[2], ) + # Converted def _update_metadata_from_Image( self, img: BioImage, @@ -458,8 +460,10 @@ def _update_metadata_from_Image( img.physical_pixel_sizes.X or 1, ) + # Converted def update_metadata_on_file_select(self): """Update self._save_name.value and metadata if selected.""" + # TODO: get true stem of file, in case .ome.tiff self._save_name.value = str(self._files.value[0].stem) img = nImage(self._files.value[0]) @@ -469,6 +473,28 @@ def update_metadata_on_file_select(self): update_scale=self._update_scale.value, ) + def append_scene_to_name(self): + """Append the scene to the save name.""" + if self._viewer.layers.selection.active is not None: + try: + img = self._viewer.layers.selection.active.metadata['bioimage'] + # remove bad characters from scene name + scene = re.sub(r'[^\w\s]', '-', img.current_scene) + self._save_name.value = f'{self._save_name.value}_{scene}' + except AttributeError: + self._results.value = ( + 'Tried to append scene to name, but layer not opened with' + ' neuralDev reader.' + ) + else: + self._results.value = ( + 'Tried to append scene to name, but no layer selected.' + ' So the first scene from the first file will be appended.' + ) + img = nImage(self._files.value[0]) + scene = re.sub(r'[^\w\s]', '-', img.current_scene) + self._save_name.value = f'{self._save_name.value}_{scene}' + def update_metadata_from_layer(self): """ Update metadata from the selected layer. From 395bfc7cec219ec21cf732db66b170bb5e6862ce Mon Sep 17 00:00:00 2001 From: TimMonko <47310455+TimMonko@users.noreply.github.com> Date: Sun, 13 Oct 2024 16:24:43 -0500 Subject: [PATCH 10/18] default to scenes splitting channels --- src/napari_ndev/_napari_reader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/napari_ndev/_napari_reader.py b/src/napari_ndev/_napari_reader.py index 2b63a0e..cf26cf0 100644 --- a/src/napari_ndev/_napari_reader.py +++ b/src/napari_ndev/_napari_reader.py @@ -155,7 +155,7 @@ def _get_scenes(path: PathLike, img: nImage, in_memory: bool) -> None: # Create a checkbox widget to set "Unpack Channels" or not channel_unpack_checkbox = QCheckBox(UNPACK_CHANNELS_TO_LAYERS) - channel_unpack_checkbox.setChecked(False) + channel_unpack_checkbox.setChecked(True) # Create a checkbox widget to set "Mosaic Merge" or not dont_merge_mosaics_checkbox = QCheckBox(DONT_MERGE_MOSAICS) From 5b1863e83ad9de5bd8db2ab645626c8260c528fe Mon Sep 17 00:00:00 2001 From: TimMonko <47310455+TimMonko@users.noreply.github.com> Date: Sun, 13 Oct 2024 16:39:09 -0500 Subject: [PATCH 11/18] Update _utilities_container.py --- .../widgets/_utilities_container.py | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/napari_ndev/widgets/_utilities_container.py b/src/napari_ndev/widgets/_utilities_container.py index 641ae9a..e8dad73 100644 --- a/src/napari_ndev/widgets/_utilities_container.py +++ b/src/napari_ndev/widgets/_utilities_container.py @@ -136,6 +136,7 @@ def __init__(self, viewer: napari.viewer.Viewer = None): """ super().__init__(labels=False) + self.min_width = 500 # TODO: remove this hardcoded value self._viewer = viewer if viewer is not None else None self._img_data = None self._image_save_dims = None @@ -160,8 +161,8 @@ def _init_layout(self): self.extend( [ self._save_directory, - self._files, self._save_name_container, + self._files, self._open_image_container, self._file_options_container, self._metadata_container, @@ -191,8 +192,8 @@ def _init_save_name_container(self): self._save_name_container = Container(layout='horizontal') self._save_name = LineEdit( label='Save Name', - tooltip='Name of the saved file. Helpful to include a' - '.ome/.tif/.tiff extension.', + tooltip='Name of the saved file. ' + 'Proper extension will be added when saved.', ) self._append_scene_button = PushButton( label='Append Scene to Name', @@ -202,6 +203,7 @@ def _init_save_name_container(self): self._append_scene_button ]) + def _init_file_options_container(self): """Initialize the file options collapsible container.""" self._file_options_container = CollapsibleContainer( @@ -277,7 +279,7 @@ def _init_metadata_container(self): label='Dimension Order: ', tooltip='Sanity check for available dimensions.', ) - self._scenes = Label( + self._num_scenes = Label( label='Number of Scenes: ', ) @@ -304,7 +306,7 @@ def _init_metadata_container(self): # self._file_metadata_update, self._layer_metadata_update, self._dim_order, - self._scenes, + self._num_scenes, self._channel_names, self._scale_tuple, self._scale_layers_button, @@ -448,6 +450,7 @@ def _update_metadata_from_Image( """ self._dim_order.value = img.dims.order + self._num_scenes.value = str(len(img.scenes)) self._squeezed_dims = helpers.get_squeezed_dim_order(img) @@ -473,6 +476,7 @@ def update_metadata_on_file_select(self): update_scale=self._update_scale.value, ) + # Added def append_scene_to_name(self): """Append the scene to the save name.""" if self._viewer.layers.selection.active is not None: @@ -495,6 +499,7 @@ def append_scene_to_name(self): scene = re.sub(r'[^\w\s]', '-', img.current_scene) self._save_name.value = f'{self._save_name.value}_{scene}' + # Converted def update_metadata_from_layer(self): """ Update metadata from the selected layer. @@ -529,10 +534,12 @@ def update_metadata_from_layer(self): f'\nAt {time.strftime("%H:%M:%S")}' ) + # Converted def open_images(self): """Open the selected images in the napari viewer with napari-ndev.""" self._viewer.open(self._files.value, plugin='napari-ndev') + # Converted def select_next_images(self): """Open the next set of images in the directyory.""" num_files = self._files.value.__len__() @@ -562,8 +569,9 @@ def select_next_images(self): self._save_name.value = helpers.create_id_string(img, next_files[0].stem) self._files.value = next_files - self._update_metadata_on_file_select() + self.update_metadata_on_file_select() + # Converted def rescale_by(self): """Rescale the selected layers based on the given scale.""" layers = self._viewer.layers.selection From d7d4844d589c9f64a06e2fba99f7a3a78f03861e Mon Sep 17 00:00:00 2001 From: TimMonko <47310455+TimMonko@users.noreply.github.com> Date: Sun, 13 Oct 2024 17:15:58 -0500 Subject: [PATCH 12/18] hook up saving --- .../widgets/_utilities_container.py | 150 +++++++++++------- 1 file changed, 94 insertions(+), 56 deletions(-) diff --git a/src/napari_ndev/widgets/_utilities_container.py b/src/napari_ndev/widgets/_utilities_container.py index e8dad73..d24156a 100644 --- a/src/napari_ndev/widgets/_utilities_container.py +++ b/src/napari_ndev/widgets/_utilities_container.py @@ -152,7 +152,7 @@ def __init__(self, viewer: napari.viewer.Viewer = None): self._init_concatenate_files_container() self._init_save_layers_container() self._init_scene_container() - self._init_figure_options_container() + # self._init_figure_options_container() # TODO: add figure saving self._init_layout() self._connect_events() @@ -168,7 +168,7 @@ def _init_layout(self): self._metadata_container, self._concatenate_files_container, self._scene_container, - self._figure_options_container, + # self._figure_options_container, self._save_layers_container, self._results, ] @@ -249,7 +249,7 @@ def _init_concatenate_files_container(self): self._concatenate_files_container = Container( layout='horizontal', ) - self._concatenate_image_button = PushButton(label='Concat. Files') + self._concatenate_files_button = PushButton(label='Concat. Files') self._concatenate_batch_button = PushButton( label='Batch Concat.', tooltip='Concatenate files in the selected directory by iterating' @@ -260,7 +260,7 @@ def _init_concatenate_files_container(self): 'should work as expected.', ) self._concatenate_files_container.extend([ - self._concatenate_image_button, + self._concatenate_files_button, self._concatenate_batch_button, ]) @@ -344,14 +344,14 @@ def _init_save_layers_container(self): 'labels based on the selected image layer dimensions. If multiple' 'layer types are selected, then the image will save to Layers.', ) - self._export_figure_button = PushButton( - label='Export Figure', - tooltip='Export the current canvas figure to the save directory. ' - 'Saves image as a PNG to Figures directory.', - ) + # self._export_figure_button = PushButton( + # label='Export Figure', + # tooltip='Export the current canvas figure to the save directory. ' + # 'Saves image as a PNG to Figures directory.', + # ) self._save_layers_container.extend([ self._save_layers_button, - self._export_figure_button, + # self._export_figure_button, ]) def _init_figure_options_container(self): @@ -400,14 +400,20 @@ def _init_figure_options_container(self): def _connect_events(self): """Connect the events of the widgets to respective methods.""" self._files.changed.connect(self.update_metadata_on_file_select) - self._open_image_button.clicked.connect(self.open_images) self._append_scene_button.clicked.connect(self.append_scene_to_name) + self._open_image_button.clicked.connect(self.open_images) self._select_next_image_button.clicked.connect(self.select_next_images) + self._layer_metadata_update.clicked.connect( self.update_metadata_from_layer ) self._scale_layers_button.clicked.connect(self.rescale_by) + + self._concatenate_files_button.clicked.connect(self.save_files_as_ome_tiff) + self._concatenate_batch_button.clicked.connect(self.batch_concatenate_files) self._extract_scenes.clicked.connect(self.save_scenes_ome_tiff) + self._save_layers_button.clicked.connect(self.save_layers_as_ome_tiff) + # self._export_figure_button.clicked.connect(self.export_figure) self._results._on_value_change() @property @@ -782,11 +788,19 @@ def _common_save_logic( ) return + def _determine_save_directory(self, save_dir: str | None = None) -> str: + if self._save_directory_prefix.value is not None: + save_dir = f'{self._save_directory_prefix.value}_{save_dir}' + else: + save_dir = f'{save_dir}' + return save_dir + def save_files_as_ome_tiff(self) -> None: img_data = self.concatenate_files(self._files.value) + save_dir = self._determine_save_directory('ConcatenatedImages') img_save_loc = self._get_save_loc( self._save_directory.value, - 'ConcatenatedImages', + save_dir, self._save_name.value ) cnames = self._channel_names.value @@ -798,54 +812,37 @@ def save_files_as_ome_tiff(self) -> None: dim_order='TCZYX', channel_names=channel_names, image_name=self._save_name.value, - result_str='Image', - ) - - def save_layers_as_ome_tiff(self) -> None: - layer_data = self.concatenate_layers( - list(self._viewer.layers.selection) + result_str='Concatenated Image', ) - # get the types of layers, to know where to save the image - layer_types = [type(layer).__name__ for layer in self._viewer.layers.selection] - - # if there are multiple layer types, save to Layers directory - layer_save_type = 'Layers' if len(set(layer_types)) > 1 else layer_types[0] - layer_save_loc = self._get_save_loc( - self._save_directory.value, layer_save_type, self._save_name.value - ) - - # only get channel names if layer_save_type is not shapes or labels layer - if layer_save_type not in ['Shapes', 'Labels']: - cnames = self._channel_names.value - channel_names = ast.literal_eval(cnames) if cnames else None - else: - channel_names = layer_save_type - - if layer_save_type == 'Shapes': - layer_data = layer_data.astype(np.int16) - - if layer_save_type == 'Labels': - if layer_data.max() > 65535: - layer_data = layer_data.astype(np.int32) - else: - layer_data = layer_data.astype(np.int16) - - self._common_save_logic( - data=layer_data, - uri=layer_save_loc, - dim_order='TCZYX', - channel_names=channel_names, - image_name=self._save_name.value, - result_str=layer_save_type, - ) + def batch_concatenate_files(self) -> None: + """ + Concatenate files in the selected directory. + + Save the concatenated files as OME-TIFF, then select the next set of + files in the directory to be concatenated. This is done by iterating + over the remaining files in the directory based on the number of files + selected. The files are sorted alphabetically and numerically. The + files will be concatenated until no more files are left in the parent + directory. + """ + # get total number of sets of files in the directory + parent_dir = self._files.value[0].parent + total_num_files = len(list(parent_dir.glob(f'*{self._files.value[0].suffix}'))) + num_files = self._files.value.__len__() + num_file_sets = total_num_files // num_files + # save first set of files + self.save_files_as_ome_tiff() + # iterate through the remaining sets of files in the directory + for _ in range(num_file_sets): + self.select_next_images() + self.save_files_as_ome_tiff() def save_scenes_ome_tiff(self) -> None: """ Save selected scenes as OME-TIFF. - Not currently interacting with the viewer labels. This method is intended to save scenes from a single file. The scenes are extracted based on the scenes_to_extract widget value, which is a list of scene indices. If the widget is left blank, then all scenes @@ -855,14 +852,16 @@ def save_scenes_ome_tiff(self) -> None: img = nImage(self._files.value[0]) scenes = self._scenes_to_extract.value - scenes_list = ast.literal_eval(scenes) if scenes else None - save_directory = self._save_directory.value / 'Images' + scenes_list = ast.literal_eval(scenes) if scenes else img.scenes + save_dir = self._determine_save_directory('ExtractedScenes') + save_directory = self._save_directory.value / save_dir save_directory.mkdir(parents=False, exist_ok=True) - for scene in scenes_list: + + for scene_idx, _scene in enumerate(scenes_list): # TODO: fix this to not have an issue if there are identical scenes # presented as strings, though the asssumption is most times the # user will input a list of integers. - img.set_scene(scene) + img.set_scene(scene_idx) base_save_name = self._save_name.value.split('.')[0] image_id = helpers.create_id_string(img, base_save_name) @@ -883,3 +882,42 @@ def save_scenes_ome_tiff(self) -> None: result_str=f'Scene: {img.current_scene}', ) return + + def save_layers_as_ome_tiff(self) -> None: + layer_data = self.concatenate_layers( + list(self._viewer.layers.selection) + ) + # get the types of layers, to know where to save the image + layer_types = [type(layer).__name__ for layer in self._viewer.layers.selection] + + # if there are multiple layer types, save to Layers directory + layer_save_type = 'Layers' if len(set(layer_types)) > 1 else layer_types[0] + layer_save_dir = self._determine_save_directory(layer_save_type) + layer_save_loc = self._get_save_loc( + self._save_directory.value, layer_save_dir, self._save_name.value + ) + + # only get channel names if layer_save_type is not shapes or labels layer + if layer_save_type not in ['Shapes', 'Labels']: + cnames = self._channel_names.value + channel_names = ast.literal_eval(cnames) if cnames else None + else: + channel_names = layer_save_type + + if layer_save_type == 'Shapes': + layer_data = layer_data.astype(np.int16) + + if layer_save_type == 'Labels': + if layer_data.max() > 65535: + layer_data = layer_data.astype(np.int32) + else: + layer_data = layer_data.astype(np.int16) + + self._common_save_logic( + data=layer_data, + uri=layer_save_loc, + dim_order='TCZYX', + channel_names=channel_names, + image_name=self._save_name.value, + result_str=layer_save_type, + ) From d0d21c391f275735b33c633fdae5c7e6340d31ed Mon Sep 17 00:00:00 2001 From: TimMonko <47310455+TimMonko@users.noreply.github.com> Date: Sun, 13 Oct 2024 17:25:32 -0500 Subject: [PATCH 13/18] update scene saving and save dir prefix --- src/napari_ndev/widgets/_utilities_container.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/napari_ndev/widgets/_utilities_container.py b/src/napari_ndev/widgets/_utilities_container.py index d24156a..430ef23 100644 --- a/src/napari_ndev/widgets/_utilities_container.py +++ b/src/napari_ndev/widgets/_utilities_container.py @@ -224,7 +224,6 @@ def _init_file_options_container(self): self._save_directory_prefix = LineEdit( label='Save Directory Prefix', tooltip='Prefix for the save directories.', - value=None, ) self._file_options_container.extend([ @@ -789,7 +788,7 @@ def _common_save_logic( return def _determine_save_directory(self, save_dir: str | None = None) -> str: - if self._save_directory_prefix.value is not None: + if self._save_directory_prefix.value != '': save_dir = f'{self._save_directory_prefix.value}_{save_dir}' else: save_dir = f'{save_dir}' @@ -857,11 +856,11 @@ def save_scenes_ome_tiff(self) -> None: save_directory = self._save_directory.value / save_dir save_directory.mkdir(parents=False, exist_ok=True) - for scene_idx, _scene in enumerate(scenes_list): + for scene in scenes_list: # TODO: fix this to not have an issue if there are identical scenes # presented as strings, though the asssumption is most times the # user will input a list of integers. - img.set_scene(scene_idx) + img.set_scene(scene) base_save_name = self._save_name.value.split('.')[0] image_id = helpers.create_id_string(img, base_save_name) From 6c964db066a7737cebfb6e33c80c834a9a21a749 Mon Sep 17 00:00:00 2001 From: TimMonko <47310455+TimMonko@users.noreply.github.com> Date: Sun, 13 Oct 2024 17:37:02 -0500 Subject: [PATCH 14/18] I think all saving is hooked up --- .../widgets/_utilities_container.py | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/napari_ndev/widgets/_utilities_container.py b/src/napari_ndev/widgets/_utilities_container.py index 430ef23..7c37837 100644 --- a/src/napari_ndev/widgets/_utilities_container.py +++ b/src/napari_ndev/widgets/_utilities_container.py @@ -671,13 +671,14 @@ def concatenate_layers( return np.concatenate(array_list, axis=1) def _get_dims_for_shape_layer(self, layers) -> tuple[int]: + # TODO: Fix this not getting the first instance of the image layer # get first instance of a napari.layers.Image or napari.layers.Labels dim_layer = next( (layer for layer in layers if isinstance(layer, (ImageLayer, LabelsLayer))), None, ) - # if none of these layers is selected, get it from the first instance in the viewer - if dim_layer is not None: + # if none of these layers is selected, get it from the first instance in the viewer + if dim_layer is None: dim_layer = next( (layer for layer in self._viewer.layers if isinstance(layer, (ImageLayer, LabelsLayer))), None, @@ -700,7 +701,7 @@ def _get_save_loc( root_dir : Path The root directory. parent : str - The parent directory. eg. 'Images', 'Labels', 'ShapesAsLabels' + The parent directory. eg. 'Image', 'Labels', 'ShapesAsLabels' file_name : str The file name. @@ -795,13 +796,16 @@ def _determine_save_directory(self, save_dir: str | None = None) -> str: return save_dir def save_files_as_ome_tiff(self) -> None: + """Save the selected files as OME-TIFF using BioImage.""" img_data = self.concatenate_files(self._files.value) save_dir = self._determine_save_directory('ConcatenatedImages') + img_save_name = f'{self._save_name.value}.tiff' img_save_loc = self._get_save_loc( self._save_directory.value, save_dir, - self._save_name.value + img_save_name, ) + cnames = self._channel_names.value channel_names = ast.literal_eval(cnames) if cnames else None @@ -865,7 +869,7 @@ def save_scenes_ome_tiff(self) -> None: base_save_name = self._save_name.value.split('.')[0] image_id = helpers.create_id_string(img, base_save_name) - img_save_name = f'{image_id}.ome.tiff' + img_save_name = f'{image_id}.tiff' img_save_loc = save_directory / img_save_name # get channel names from widget if truthy @@ -883,6 +887,11 @@ def save_scenes_ome_tiff(self) -> None: return def save_layers_as_ome_tiff(self) -> None: + """ + Save the selected layers as OME-TIFF. + + Determines types of layers and saves to corresponding directories. + """ layer_data = self.concatenate_layers( list(self._viewer.layers.selection) ) @@ -892,8 +901,9 @@ def save_layers_as_ome_tiff(self) -> None: # if there are multiple layer types, save to Layers directory layer_save_type = 'Layers' if len(set(layer_types)) > 1 else layer_types[0] layer_save_dir = self._determine_save_directory(layer_save_type) + layer_save_name = f'{self._save_name.value}.tiff' layer_save_loc = self._get_save_loc( - self._save_directory.value, layer_save_dir, self._save_name.value + self._save_directory.value, layer_save_dir, layer_save_name ) # only get channel names if layer_save_type is not shapes or labels layer From 6864be0f9d60b790a393df731787fb640e8457d9 Mon Sep 17 00:00:00 2001 From: TimMonko <47310455+TimMonko@users.noreply.github.com> Date: Sun, 13 Oct 2024 22:21:46 -0500 Subject: [PATCH 15/18] add test cov for napari_reader multiscene widget --- src/napari_ndev/_napari_reader.py | 8 +-- .../_tests/resources/0T-4C-0Z-7pos.czi | Bin 0 -> 1405120 bytes src/napari_ndev/_tests/test_napari_reader.py | 65 +++++++++++++++--- src/napari_ndev/nimage.py | 2 +- 4 files changed, 60 insertions(+), 15 deletions(-) create mode 100644 src/napari_ndev/_tests/resources/0T-4C-0Z-7pos.czi diff --git a/src/napari_ndev/_napari_reader.py b/src/napari_ndev/_napari_reader.py index cf26cf0..09f20ab 100644 --- a/src/napari_ndev/_napari_reader.py +++ b/src/napari_ndev/_napari_reader.py @@ -157,16 +157,16 @@ def _get_scenes(path: PathLike, img: nImage, in_memory: bool) -> None: channel_unpack_checkbox = QCheckBox(UNPACK_CHANNELS_TO_LAYERS) channel_unpack_checkbox.setChecked(True) - # Create a checkbox widget to set "Mosaic Merge" or not - dont_merge_mosaics_checkbox = QCheckBox(DONT_MERGE_MOSAICS) - dont_merge_mosaics_checkbox.setChecked(False) + # # Create a checkbox widget to set "Mosaic Merge" or not + # dont_merge_mosaics_checkbox = QCheckBox(DONT_MERGE_MOSAICS) + # dont_merge_mosaics_checkbox.setChecked(False) # Add all scene management state to a single box scene_manager_group = QGroupBox() scene_manager_group_layout = QVBoxLayout() scene_manager_group_layout.addWidget(scene_clear_checkbox) scene_manager_group_layout.addWidget(channel_unpack_checkbox) - scene_manager_group_layout.addWidget(dont_merge_mosaics_checkbox) + # scene_manager_group_layout.addWidget(dont_merge_mosaics_checkbox) scene_manager_group.setLayout(scene_manager_group_layout) scene_manager_group.setFixedHeight(100) diff --git a/src/napari_ndev/_tests/resources/0T-4C-0Z-7pos.czi b/src/napari_ndev/_tests/resources/0T-4C-0Z-7pos.czi new file mode 100644 index 0000000000000000000000000000000000000000..6da31751b2ce789d9ddd361ff9e72fd65c7d9013 GIT binary patch literal 1405120 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 z zS(yNjIGL;E#?h61TAL{G$*R#|k$AfYoO=jLRY9SUJn;S=OE8okX_iIlzHl+rIu$Jp zZ8B-9#n;uVWe&X>&mc@z&y{1wp+aI_M@^Y$t~x|Plox0(C{mx~1;d4i1ULCgNCSM{{;5sP&|Z9to@`Em_o#Q$NT-0pTY zkt3@k8+&alq1iyt$--E5+iFvJ|Oz;qMSaBiruEvxfy%lzq5)5!MKriz7ckRU!xr*HVu6Qq{F0nz z;aXfMAXWVIp~9{dH9I#NPPq!IMLSl7q?0X$zgh^21d0@R!Y*7ZjqK#oW;*w3NC4+* z$Vd6`vQ4hDPP;n-N0wAY#zp$Pfq?6+U67jyxeU`{gUH2Aw3hH?Spc}uCQD=K&5}V| z1X3=hjlA?0ir(0TbHmABf-R@N+zA6@>4k>55IB7iD!ahXM1hlir+RA#eq2A+t0)Ce z-pmDcL*62-YCk^SHd3#;R5P|(G_1_G4W*iac1txY#{bmwoa-|gDQszB`#E@2iDIHS zLQ2r0xeIiIJsV_$Y9YMxFU%HI{4V|i$4659F8+Gd`G$9~*?+{~Gkip!E~GZlmJG04 zTe9LN+Ol$V$eeTwRzVng2kM8P+Z`oj#x#vXx|=~pf-JtpZotx1=KIsxYVyl;{#ipC z-F3XkR(%th!A@ODnvIJW2w@ISeLqm}~na_&^bq+{= zrZo{Fv4)#HS%k@~M3$=3N_9ezNy5-w3?aJsqnwMHeDN!@Mm;YOqA*nsBm=x5N@fkR zDDg9Bom87Vnca}tUaUh-0Dgwiv|H&5GW*cYDziotNv)1yopAir5a%F5Z+!;oYeBY1 zu@;zp8fNsfX%6S9AB#)gkX}+3+F~g5S~AXhS!0Q{0`T@gmRCFWHfc4GXeg|%_Fg(R zlO&@ni_SCZ&}IeSXIPtiNg>K<3AiQX4PMpOB+&GuT~4NX@pAvIxoo2wQmr84sg@8$ zQ`r|;Ab2`xV2^gby?Os0|F5UAgMHGo39O`;+>ZZ4r_pYmYPgc8iNQkK4AFo$l3kH5 z-VVgW(1z1cgI~7xz;8A$H1PUbp%oM71h#5b@@)>aZpas}>f(bk=lOyqMANl445(R2 z=7L$e$=q;2sp+WE97($0k}i4*QurUtjN!MTjW+vyu;ABZPPm)bP}optE&W&ox2SdY z+hyV$Z7eiV!>`2Am5*Yo{#auN+~)jb=#5{laKgIq#6e}G-O+QflbgKIU# z6YmmK(bfT*EH$MSE`j!mOR9^DOlxYzCB2Wh6pbBl$>O3QX3D}jLq9BQtIvQ;(F$}0f!xUxBWkzY| zLYO8s?G*0BOXQ=lIU!Os4Wxfmz^UQ(a*+^#X2MetFB-d$-AsH6;1+drncu?und22i zG1jA-0u!VLGK$jY4Ma+B?Sf1esmCeW#uaTgO(YjLv5JKk%L2fKIMt-AF#aY%S4lB^ z0a8t_BI?JoT#PJj9W>Z(TwC-ZRC_mtLqG|zaT8lFBT9_AzpGfq;2WyQ=%kVTT9EOr z_zUb>I|-PF{_kEDj8# z&iy2x$+1P6EY1d99>|Kaa5fo;+mz*a@gZMHHHm^No9D%gqdE*_u#8^F;*di4i`hZh z+}{UjSxsOQXTsN(R!arS0^384ZIG_nXl4bmVAiWVoGeBMqP?FzO%@0q31)U?n=l4O zo6Hynq$Np}jZ|~tF#2vfXDzG$33UB_j4f%7i%zXeup$hHwTub zD$QDltz?9TVsQi}0LbX;WyCoz9}`+Zc>5sFcoTP?=J1LuqUd@;YymrF1pUgfv*#n)%+_wV5juk~#^P?j|< zWH!}QC5p6Se^Ed-L4hps^_1IYKpnc9=yxK_CR}JZMd~oH*f4cXNOk~W3^b#lwp)sc zQ08|cz-q#Cn#w2-o@@J_tI>T*eTY`NZZfI$`cA6!iuaz-R)F;GH=fK!zZToo%hE;) znj2~)h;-`jGl>lb)J)ZQ5dm zViCUldZ%~7BZAaQGP~3g;%uy9fh^5E(8-gnbopq|Ei+83(NLc4tvv)ZQotH?-C)y- zn=I>9%#R(2Ou^H;yXn{SF+ODguSkQ52HBx!Mp^)f>a^CYheQ+^d6Qx9iUA_@M;0O% zO*W&;fl@bFz!&W&? zmLV~nre;^0h_4fhpLd%It=XNv2rwBf`JfpHeC?L2$Y{#niBejv+eTw2WU^$6m;sn| zB}Fz(tMuGZC~a+>h!aJT!K_44>B7*YN@)`TtX6Cr2qz7j3ZBx5E^}|^@FKDK+!}^C z@*)`_{*5SO@(F^{)C!X(3Uk@lr@}}dOIt!4P?0z#I{R#ixr}tpGNkEQuw7V%vnCay<4Dr z(|D3l=;W$~Oe+}IydiP6NE%sd>w-(3FN?+OlEEaRX=H2T^r4*2-m z$oU~DPYJw5e&ya9im;`ri%m2^=7H`qU|pC`=5WVuww|w-(o35b@Ti)dbyYMS0EyzL z_|2U?GGMfB8$~jxsV0(d&(@3Os`xL&Fq}cW`o;BlCjRlL`|Z_wx|-k}locns0b)tJOvR-|HOGu)EGnlyaHTNTz>VJNSbl|w8rwCG-=k0$H6I-sH(t5$R= zh35UaCrhJ_X@VuawBhbo7p}7x&ryrb;Em<4x?qjq{XD5ymMHl ztxb_Ay*3$5ZVlQ1I5BUyq$;q>woceY+vC-EGHU@~bs$Z5fzmf=PXIbxZ{$Rlbc&!4 zpLSrNb~dM+4{(#hXeuN6+r_h!kK@@apR!;>{VIn=f3wh&i@Q9(fvf({$Ez{C-lYNY zbwHdgvIAHLZ)jhm0(sJyxZCE90l&%I5mB`+C|+)nCsAp?8=Y8Yl2L~^o1k5*6qXi* zoY}#6si7%yF1SVqy7Ja4=od>Y!u#vw!$&5&2Kvgy6CLpVh1R@;V)U`O(53{TO&i&N zE}<1rJi{!mb7bQ8>&F2*r z4YEcrb_Sk-=652fJ<|kPGNXNcHxO&8^No9opxbgLfQ3<9Kq`zlA3nxBmdzf=11Or! zRo0T(X#qlGZGw&5JR&VSGe0|qfw{HR1`*< z3yPnT$?|SG{`zJCu@Ciz$K%lmWXtFX0*V9>Z*RnzSai^;m}cqIn2h*H<1L&~^0XG{ z{aMWQXAF7b9(!dOc?UeAd{tgU9~?@5WLYaL1y^?^7)p-~HM6Tni*|NZCCUXi4G;`} zP6`rkvT=qmBm0<|ejZoFCdyCoT>Ai07~W*M7cN{^kCwF;JT>fmQMx_#$9OS=YZ!(P zz1)a-TFBIRsx_V@b+D-Yk|#?&sWwIJLz5;wl3iT~Or{xvg67jT?iIPYU$3l763wPE z;AfK=iUJ!z;gd zXs}*BIVdhE@OT$r6Dnw;AWPPk2TwzgeHd~n)QGdp)>6|@fRV{=B1~1Jix&lpY2dBj zY+^m0fO0El)T;Gt0S@-n3{L66D)SA9X~g?#H?>O%u;c-`{Fu)dIcy-t(@O~Wn!Xx; zn8B76e6nUt7l;9Sg?YTnZ=M|BdT6_Woxu5^7pl_zcKRH(N24?a{c6%6_f~3Zh>gL= zk$;;_EbmSeDE#-oZjTO4SaMHq=aUoI{O1dJQxbyc|6xujLO+kQY|!p?G5v`K&ePRD zH|r`%MvyVs?CPI#SB$$);rhM(X(0khopuHudhawa$LM{nrs=VHEITo<`X8g^uBN$5$$t!>tw z*__a4Irlc}QEyD>PakM)iaErvWT@U)?(ca%h0Lpq7hlHLt6~>`R}*XKs%hkhuTyU) z#q2_F=kEm#sm4?&jr{O+o>HmM+sVDCdmFu`y$63AH*|$m3TUN)8?LS?D4_K=0y?}# z!7oF?nc%cIwXI4mh2Xq=a&USJzF&+8XGT!II1o)*ts}4?z+ok-lzrRLwAWRk;bv|D zlLuyz09yLS9I#8n^>kU^RO?F61@}K8skDxC1z4--0CT>8oOJ{!MLiV=yfr+T!^ydz z`R$r{G@LlO8Yo~n-ha#&f5LjoqM~$&zk$X39^#8{jt)iVa(x-#A7Cgh&hk(B^d+41 zbLcw6KG=))nldcNl6X_qdiow9J0apt6JLE9?&Wkg zfMl&I!vd5Q6=?^f*ODQ?y#0`0F2;ZiWLSb@s?`{b;+Kp0?b&#_5`%d@pBQ;}j{E(= zpxy66h+7jVa_^_hXX|%B{);J&FcX*O3pTs%?jZkIesc_(qqfd}{zr@b*>DYdb@@z8 z(Bx#kSd?qRR9lX4S=AYQe$l%8IDs47SSV8_7Ci9ytn-fEE*Zk^Nvq>-u1A~V#TB)& z@_IPC)a{-yyjbq9)XIGPb#xymu-_Xc!(OkK_J;Thtkyt=1xvAg(c?m07K=6#F->d- zdEB!7xZ%7t#;w=x;**dMY_z%$WVHB9(Aicl2)tMbD%hkNC-s&vQASBN9Arti)6II| zWYQYQuwqOUrr-+eJW-5oxY0SWEY~e`d_6h%+rjauosI_CXfW*edV>K3Iq()TNkzOn z8iSIz8?+VsE-G$e!b^0djp<0b{WKj7lTk8&5sFF&Q(PO3M+$z-tk=)FoiyuUsB2U@ z*p%LgjwEYm$*7YKx&xH2heJn0xf%>%>i5(BAnRqlF2rQT7!QayORm(3jaygSwXJGq z_uzJXcQ_pMV13UpnoKkiGnVbf>o&<{xo89|*u*CQ>pGB;{upFrKL#1yAApRPXCPLU zZLd*uHom>HS@>m};yyVDhV87%+Bu=@$PrYab0v( z=5v|UgyuM$=)Rm^ua{uuy@Paje}UT$w(KY1K)`ndX4mia`1PE9)*!KOPCyrv53}*~|Ez&L^CCAW7li<& zvJ>Q+JIo&go<5uUWT#acSfzms_Eg9e2*By=41(NOxecmK0Oa~I&SUfR0_ zU(NNO7&1L| zJ-^Fejc4G|UMM~RX7r7htFlb|%vT@h#-ZA8vp;9^&*0J()e4B>Cu=2e2H6rNMMay! zf_Z5G#CkHAGYknUQFOx4E6D&(;Tc*uahomk1zUgIE?`Sd4bXZ43T*WfUoR+DP(5}L zKvZ?q#?Qxp{WZm82CKp$;Dn%8RKdukVrc$+FPBKp;bNW;wWKjz^z&tJ>1QwsT zZUm@MHn7#-!XlO=Z49dcpBqfK^?re?mvedkAih3U+|x zjoO{;!3dI*NnSyEoDM<7P?_|&+i!Q0ChHmuLcg7=w!&_vTfvVlt!%LN)2Cb7Z+A;m zfhI~BEdHYiv#lYEyn+M)F`YuiP!@j(1L_V7B~N4#Y2B_9`UMN23M{+n}8_ zG9iJdomA2CF(;anMRih?G>{^gXzI4K#ym>Xc2=5SLXWFTzS4x1L2-Xih<&0tHMUoZ z(oP#gnTZx;3aY4y0f`*yM6e$la>y@4un!@x`;XHMLPV8Syheyn)5TcOx?A)&!Wk(| zfQpJV9j(;ejqYj46R+6(*gSFSW3RFy^8^f}K@+>f0qxlD@Q{9u4C(zRGT2z`mcfzA_A-!ZnhhGzle=&*`1U=NDq7Vvn5P(aq}3$Sd63XX1c z35^=7^ah@AfS5+|ANBF7`?6)k91#^J&JhFri7z0)yT&j#n;q7FYr%T9^emz+lxBo<7 zLs2ZZfft0A7rCI%7Q|6ThQa$(W?dN+?aCKQU6Gb;JhisyLw5KgL<&Of%Q|v5qV-b< zpM|Tr*1P3Nkz(FjR|a;v{1xJVKYz^g>GPbu2!YR3X`{?vX|q>W!NCQ62;2FC1xDH@ zq&cg~mtZj5$Y?A>my0zNEkGZSI_1fT+)jpfy8P`9LUuRB*3wvpE*BH99dK;rb}~Ga zz7vR6e&>uawlD#r=s7sHn1 z_A*?*PNShNYasn9BTNFhn20(*be5aSSX)#Q5%P`|93uM9raDU;pl{2@g4y+O6qO`X zVj(Z>Fnx$!hL#NC`_xt`YAadk?bQ{R`tM6qajd^*O;u)Sp&DbCXZ)(o#cF=nK)JzN z7_ive4KeH12I_9>{fo=9U(xxK&+yKtg0r|4ELM=hF2lEiM47CaT(?HW)kt1JZ&W&3 z)N3J@g}X2b5K7TPz}ei|zC1gfPS^aYs3OIZ!Ub*|>r6nWpJf^1-(G{`M{ja6UVF^Sen7?E@xJ1!ZNp&e)IQ6{w~+B`vVu7sSRUoB zAQWuW8Nr-OvM>|x#qDH?&-K2XOv6*Ac2?1rhM}!|WaIW7yjZ(P@ckj8TTg44;bS?Q zG~J*fOY8;S@RNCg_D1~zt#EEnb77jalAF42`-=FGqzzUqHfQ{c5 zu!0o$iq#EVM)!Plpm}l(Zcb<`v&A^C$*o|f#;6C0CtwMNx|@wb5db{I=f!KW5>t8f-B)>^8PD(kh>WfX>>C1UG@#f z0N=ciF$Mx%d{^XreS7z21`neIwvWr*jp+Lh`n_WBv2Ye_X`3y2C?9?Q;RwValzj1g z4&gcS%;jveFMHq$Q1kz^^4MP82uvFt&=w?3hXSOk|v_VFS*OmK{zvkHD~)# zgVkCNJb5{uQTS2XBFvIuXmQ;llp3fT9$nio7WD1q+tus&N+d-jZL2Tl?^mD4iyTvV zz?HxCQlY~89wiUEke;j*RKN!GbXk|{r!1En2{i2QS4v5Jw3=2R${UjU5&<$HU?>%> zpq|2({p)hFd_G={bx0B*xtf5p;gAaK_Cq3A334Uaasf|vt@00FZ2_cQtfirmtJ~qp z&y!w$#C^tmY6NEXoN^(R*JQif&e z)db?e=Bf<&Ye0BAS;8Z9FK#}-L!(+%I~2&&H~wmX0cKXMiz@c!jE+>Fd3HKdI|!u1 zfZ(RX0fixHfHCQCG-q^VG&(Xj9R>t99S$gr4g-uyhpqYPOdm*#mrorVXd+R())A>7y&@SFUT$V3ZlBNl3{wvHBw%pyoK|ZM&xPGQ9xYBX~59{W)q$bdl?^S9R ziObi0T@p(BnLQ5uK^!Xz*1^Oq5Ghl;U?7AMJ%HY-uKTztv63e`Evg9}rz<0VH?CxW?5wi*<(_ymuDbui<&^ z4{LZIvE-rI#t#;9-*L9+QVuWWmhqrj_fZkH^axJ7;LjtNI3|WNC~~KUZrQw3MF)A& zGZew*ohk~$+i6!9l+8Qs%Iqyde)v?s_&ix%f6SM9XRqf)377~htD;_CK-^qiL2kRR zyV>1iPk`rkV>jNOy`1FJ8;mk&a%lkeTUi%gp-Um73#2)MW8n%j!BkXaca|4^aUS7! zQE_wbOXxR=9|rW{afVSpNr%axGaRuOD=nclq0z^)_39>LvjJ#5#UpY|YWOKHl4ya8 z;BNUX8}z=x=s-YwHW2@dGde1H%?8LxUN1rN;0eU6C;k_KJob?C*#sU^M#EwJ2`pte zB)^-_z~iS_5O7)S{p7>CxLzbg95XB1H3CEGJlp2z{96!i}|1V?8yN$ z+OSUX0W68-96n=n2uLhFTYyvXD`!`4AXEZFO{B)pA)M?Q>Wfb=CyQG&yI}VW_#d?R z?*h^4_wPkQW~_3-5*)l<-@Zf82Q7tFMZ0dF-B^JayyVRfY8E6J=I6EWg-E<*Pk^x+ zpqeoyGjz0stO~8a=kOx_=?s_(_wim%zT}|!vSh55HhCYCS*w$iTKt|Gspyhjt&tw5NNU!w5f5x^ zgdlHZi-vR|2}#m0VVb)QA89bPXi*nTZ4A_iO=2S$1JXn+`{L}RHDZh; zfoF`Q)+C}#2*(p4nuKv7fD_SuWN=b^D@8V{h$YA~Bgb12I;`4O2N`O#QzIa1tP!E9 zh=s96`ZNLH3Z@~zsKQ7n?AC)b8YtDbXL85|0W&37KnYSHG_FQeiUVlc4$sO7ZG7k1 zE~P`V%dzr@w3_pBC`3M^MSZP>lc0mh57TXfeXH&vlLKwiIkxc# z2SG zIrY1u*hzKa&5%4R=9Y`&y1l=5Np%h7rC-GE&Jj-Tz*4|DyKBDG@W#&+-Sf619xqb3p)LY}88u!wuhepuJkjYR< z5J}K#A=N_hLh!b(3bj3%*aR1W5O8~C3df)M&DrF`$JIU-!rVzo8%>RsYF)utcZdh^ zoSE7q^A*UxA~jBhhayP9a9#oma+-8bL|`8a!LB1phtd032zBn1qUV+6d!7q5m1`P; z-&HIGvUkg21l!;$=d@LUx&pijnvBDB{k=`xu@49S)l&dlJCfcu<|OR5>HQ8t0ELLH zjR1Cre?k$$VB6H8D-wDO^MMg0{U;=CRQljTRshOa@74K1pdK6=LAw*iuGj^`AgqK+ z!eZBc8m-|*lVcKEW_dqMf^74%yqOqqKsgE$sXI6TtM-$)y$Md9buBILat7~QnKVBd zZ@n%BE?e99dxsKZl@>+!gx0c5AM9X7;R9o(80H7wVBXIU&ZW)%cJUVPeD8U?8BdE= zKc4Q{2}L{6q`@iK0TtGntL>QJU4%fBSTe{0^+4WH1fvn>@h<3O{n+elWmRW6(1P*0 z)L9Q;RZqeRZ9$|#>W^miI%ZD!s7YWEV!~XB(T~R4;4250(H*QHqAQ#30cpX9Z30V+ zq+SC{%Fg(?6A9S~HNhzfPDM}(f?^*G^I%v9!8i!ETgP1iC_13tj#=SJ^p%HX4WHM9|L=v=GNVQPB5WKCcLe0Vz zZj~Zr5T2HrE!orMv4aLL&n!806j zvKrKyb33ox2qsdlrq)8L#mNi7+qx>$I4F3|-~XE8d;dX#UM;(}3N6bhRE2(=;kn9B zll*fKoVA{d@fJLjW;CALlpA z_4?*f>%|w4Qg~x{wkmV{9v#|%ID0vt->vVK$2iCU z`r~?a18jQKI-R|rKWgEdu&2v6cR741x_z5p&tcZACUf{y^^G}~rjUwALFjzEhNSLt zHOy~z2Pv8!wVqFw02Q88g~ThX_3}~c_sJ3-aGv}HSp4?;^Bhv1zAtWpJZcq5TC8 zm;sI|A4@hVY&>1UVBIh(SY8`wB=S8SmpM7Tb#!R!#RA3=-%_{1v6X5No{#_fYnq=g z=9m-;NU?x%SJ7a}l6sj#O0C6s@znybrk(~BGH$=0UyrBNGjbJ3p5@wCrxx7BWC}dQ z8OI)6r$Bc$F*~~k*o#{1J+%lRo#;sXF?w(|GCRDRL&nZBznE3Ckaps=tnxW#QTQU5-pTE@aA5pf~&twC!#FHitd% za>cb9v{Sz;tzhMN_g|=pKIJH9Oh?)Tj>|QzcGX3_X;nVIU7F7Pmuy4vZ#c4B%h~r( zEhc4SqwP9usp1MV6a^lvTC{|%_NT4oR@r&K6o|XsX-sB3Gc4{JUP(VIxdqD-QZS2p znDtcttye_DhrK(0+-WB)4I`#OF8-vfX;njMd-?Tl0!wXGYHe@SQea>*(?2FRtB=?@ z=oax)sl{o5nYB9p_y3$!i9p#GrS8T04r~c96|rcEwZEbFPWh-b;K1KPvT5z{e&8hg zirb>qlLLTd##$V~AYk6?ay-~SW%$gYRHno}F2}-B>FAL4B<<{=bCCwvz^z_4=~)(w z$;Q4GmgyMMdRix(-g3K>Z9D@z{2}h?ecFqyEj($f&=;-pGf0P?s+E;o-OeFN>~`6A zuXL?R=dhE4S0u4|MgH;6|M&65HD>hTNnI<4$e`UzN4i5qoeVe;_*{bY>O;^VL=DC3 zN=i$fL8#4SCDZ8?8m;=K)HW0jIApMe@m*Uy_B7GIZdK+N zTBg)Q67wyZrdS?WLB)7Wy@nbI!A@qc=AZH+>o>5mkIRSrP2o6mNQ?YQsmx$9 z7XSNSw}(s6=$KmEI7O3`J0k1S$jTft;ZE4gyN}Uj%USH>s9jaJV z&Hj~Q1qd)p!?NO{R-m>Uf6S6<7@)Q)z#+cY%YD(oBVbM=CRR4`KmO5tl;a_{1&9|`UE^)?+Q5-77)j>u9^lD zT#YcL`~W?56llP+Um=V3=Z|>~)+6NYo)n3XIBL#v1uOwcGFXW*w|9!BOIJv#K&s6f>le$4cM zUR*h_3q$aE{vK9~RqnB2P>f0o=Kj3v#5Lh?6xApTL*`dLPRTaK0K z4o9EG5-i#CGVl40R}4zCyMdf+%ws>*Z=)(Ff*TDU zgPtKJ)=_K#?u9y|o;Bi~7~6O(#XqW>!DigET3~iDL;bPfc=*7q9!3_R#>VHDCg0A|xoHUS0{ z+2pLOJ%OK6Y)~!R*aan;ab0{g+ewynQAOgi=-bg)E$5R0H(ZtyvukPpq7bKHu9TTj zBm$J0De5Ka-hd5;Kw5@VQ3d-`cMStC+g^aMMc`5)_5{_gQhYM{VrGc6DG<9z=T?Dt zqihUBGv%w%Tisqj9B%n{78bakEr9^)sbfHNZVf7?+P4-eBwP|8;00HD#r&%b3yb~m z)nPwCCRh+DVaWY+Z$FgNbZu&I6n+YiF7Q2Y}0K09HJfG?tOW|?ja*~a+Ed=%=@qS((m>A%I(b5cU2C5#~_qdq9p7{0=ss zLq)I!OXGOaYbziYl}ka|0aqX8g@fv2K*cJyjKv;W?6I`(3V5a2CcXuwV`cp_`&Wt; zG&b-lAR{%(ByMUYN@r>r7ixkp0Y_RiR*0ec5fr2q2#tIQEJELYc*s0aWd8vG+8-}M zz_{4`C@b^P>C)o^XzC}xnPT?=(0v3AR&3b)>nk>Q6v!*~Pv#Zauh?)rpbEK=ebj`6 zlY{+=UHW?WE4C8Qq^*IbBVSqknw$Vs%N=XAU$OTAK|j!neYu!?V0T=L}(%^!2!xr?1YhN{pY)AyBC*S3+MpnGyQp3whRF87YmN%QJ{) z1{T3Z%9|Fyz^tBsa2}rV;s+N74XjKuyc`a}Ks-vQ5DE+-x}2iot4b;+rVvOBk<;Gz zA=tX`Z9Msg2JEL;z@uoWY@=ZRB?nFnvwo6xx}7A;27^)B@3N*<4W+HcU14c&)KXx` z&WkMTcDuu*-|1z&BpGzzmU~sKwBod|MdWYQ#-9CFYHD`T-?kPc{WnjDbg_t9qMi%? zF)QH1VySgc=Y=mwLUUZO(XF+BU>Gd%(Dl0*^^Kouj>E&Etuwd>?wHwEP+k=8KtZB^ z1MiDGhx^~xpn9Ghuzg%LL7zjk-jjpxdQy9Xp=?e^Y(Ss3AaDC=iUAVvr&2{WA0zk< zGH?<7okt;x--^nDuk8A-RIB){)WR78W0W7mY&kjirr)Jz`GUaSSoz56-JTAWcrB*vStT0h{4BV?qO7@&j?%+;5 zDt#c0!WLjJjW1yE?Vc~(f!T~P5SsQ8Z=^0PQdO@wejQKWpReyG)2Fi!)0}52E%}@E zJF?=yx-C|@#cH=ib<5Vs3it{kJ~RcAq=#@f8?1bo45pQj=L`5w&&g^5w^4a{W$VLT zfethz+xk>B0-V}hk*LfsN3-+q|FO-KU(Z|cf>k4Bp@I*mFz~@2e9Q(#6E{i9Z(VPw zNa?ymEiE~cI5`3%O5uo56Ds%}aRfwZ+Z;)q905`(90A`!p*W*(1lHbdb0l+e1W2WD z1RMboIg)LkBV8v)fK&=cdQmw7evoa;NYBX;AeF)qaMeYW5!fPbnY z7O5r4N?hAE4Pe`*Zre5`+O}z2+ZJPaw(W#V-L@@KOOlniwrv`~woTo(ZA!Fl)3~-R zKES_qS#jI8NG(ZL;@Y-p0NXZo+qU=;GKDKyT-!DcVB2PH+ZL%MaV4&8n+33KGq-J< z5pCNnu5FtIux&H9ZHv^BWF@X`n+33KGq-J<5pCNnu5FtIux&H9ZHv^BWF@X`n+33K zGq-J<5pCNnu5FtIux&H9ZHv^BWF@X`n+33KGq-J<5pCNnu5FtIux&H9ZHv^BWF@X` zn+33KGq-J<5pCOUT-!DaVB2=xwk=Xik(F*-+qN6Pw(YuYTcno6mAJNTH-K&1b=$UG zqHWuaYuk1M*tT7_ZHv^BWF@X`+YMmbcHOpZmuTB|WZ952H+Ya2e?SN?84&vIjg8;Vez-`+i zwIo@IYugS2*tP?=Z95>^wu88~?I3_{J8;{!NG(ZL;@Y-@0JiPGZQBlrw(TIUZ952H z+Ya2eEmBL8mAJO;Ab@QL$_@^B-*yaxVG&ufNeW; z+qOt8Nmk<8w!;9n?a*!84vDtyFs^Mo3}D+1-L@@KOOlniw(T&0Z98<^wnL(AJB(}F z4g=V>L$_^<)KX++6xX&L2C!{MZrc{AC2=LLZ958J+m76}?TBdGj^f(3qX4$;$Zgvq zwIo@IYukL!?&JSt}b7`s4}hciTTH|fN+cY%lyv&n7oGJSqK~0m&uHr$_%r6#kBN-Bs246 zGb5)ni=EFbNJ2ATMl*6sGtBZ8Rc1oiz`m?z zJ%d@pqRLDN8`zU2Eup4KOCsk<14KjU8BZ((HC0-I*~6mBOb8p;lP!%AOw%)wZQu|I z(-Kd{G$^C$naDP9h@@$WCu|;@7CWH;_$+pG_Cav0$^Q{4*A!NpLCyttOEx{~gQDr8C4eZIf#t5d# zOk^84MB=r?lX(rwsH=8l8#qMrwZxNs4a%sTiEIOhNWhkOGO$4zl{1lT;1EgJ5>FO3 zC?j$vavnB7G{D;A$;6gWQ?VtHbFl%UAvoi)fvM@(63kE*bJY%!kPV1(?+&Ob*^ZM_By9tt+?F0SZCesKZyOL1LS{TRFg0~sg4xWX%1j6w*pt1D z5lmXOBj;}eL_^4o#}7qL&WzLnL%dJQ>}fjLMnFHgJffZiy$W8^PsHxqO z$hqAB(GZ;R*ud2EZV6^Qi@9otNbm+kxpxQD6mLo79B)8G2$}KtfvIWU63l!SRc1oi zz@AKRj9`+b7dh7(AR0nuJbqwmy0-)~phcCL5Po1!#y3VVO=cq7z#)>pC7!HrP)4<9 zBHO?r62B#$%x_ReCX3b+KbqQ%s}A(FrW zQEp3*ng%Y3oCgkw2q7~b89oYsBksL1ZWQT(?DrX|wz#$UEC7uj% zP)6lUWE(g{lDNc^B@W7HoJk|6i33Epzcv9;PD_swOwqt;RGacy8gGz4edZ=jPh1)~PaKR)2+nwHU}~PYG;*Fe7?}{9@z}uBJaK8{ zJaI5GAvoi)fvI`o(#Uz@U}QpY#$y9h^Tef*^Tff(gy4+F2BzkTOC#rrgOLfr8IKK2 z%@dbK&JzbC6M!?GJaLR*((WK~o;W}>1ZO-pFcM7TOk_WB8o~zlJP%q9OE*#|Fm4cr=-b><12!Coc8miGwmKXX3^}0HPse#$y9xVmy+}M9veJhR73_ zdh*0U8I?1U{lFpe#HF4*aZpC(Ok^84M4q@b^Z0?OdE(N@dE$VG0D8ugCyo(J(=(B6 z;1GG@Qcs>ZD5K7%$Tn~m!Up!_iA$+@;?l@@;sDVQv&rKJrsj!DBj<^OkqN;Wj}43j z)AUSa8#qLsxYUy;4$7#UiEIOh$P<@(^29+Iku#C=!~voKW|JpRTuRLomqyML2Z)B? zjK>d5%@dbK&JzbC6M{1y8a-KLCnGl@u#6lp!q*Xg| zo;Vnp5S;PELQwO>rIGW*rQHxVuqRI(BbX*Lk^R6S^2DW{JaJG)olTK#;1GG@Qcs>Z zD5G*FvJD&}Ph9HB69;8P&P2`=2Z#pHz@9vDDK$@A8aYoKAR2-*9vhgNCoYYgCk{p? z1ZO-pFf~tH8aYoKj7$j5cx+%Kn6zp~&JzbC6M{1y8iH93z+} zGm&lJ5P9NKPo6j^qt2$tHgJeMaj7Ry9F$Qx6WIn1ktZ(ogPBj<^O zkqN;Wj}1)C6PHHL6PFG`*ub7Vag1P^%tZDBhsYC`dh*0U8Fe;Awt++BiAz0s;-HMm znaDP9h&*wrCr=!dQ8^RY1`d%YF7@PzgEAs#BIk(%L<6i%o;-0WHBVd`IZqrQ8iF$( z8L*$7|J$d4wjL4bDdEx-k02W;Q}e{7k@LjC$b{gG z#|Eb6iAy8riGz^|!5NPYj0BTb?Z|oJU}QpY#$y9h^Tef*^Teg25H_$UPaGqdCNq)! zz#;O)rJg)-P)40ik!|1*dE!z}o;WC@awf7393oF#>d6xaWmL{Ywt++BiAz0s;-HMi znJjXiI6!p!YZDOV%oE25rfA?Sa-KLqGz4edHZUWY#+kTpqXD8JIODc~Gisi=4AZ7X z%&P*3hTx3b24)1)WCq-#Q8^R92F^Ne8yL!{8aVFTXn<%4nQ{ApGisi=4AZ7Xm6-s3 zU?9q~HW6*$EOMSWAR>g!xZg%&1k?0PTpJh=4Z#_Aj*N_&CoYSeCk_w|!5R1az>Hv$ z%tX!;2Z#pXj3-ZAM$HqKMa~lkh=$;d#|Eb6iOVAAiGz^|!5NPYOwALQMa~lkBNKu% z9vhgNCoYSeCk{p?1ZO-pFf~tH7CBEGj7$j5cx+&5p13S>o;Vnp5S;PYz|=f(S>!x% zFft)HD1=84N9=ZS-n3BVaoo;XG@ zX?GAgPaGf`f-@c)n3^Xp!?bBJ{WBr*!~s!m{|q%xToyS`91sygW;`}9HBVfIY15+0 zOb8p;lP8W5Ow%)w{lFpe#ATj5aZpC|kYn1ks4^462KMBM%cyzcGEAEml`|o1;M8LS zQ}e`Sm^LjcXF}M(o;-1kV49wZ8w&x5hFF_CHZV0$ToyS`Toxiv91!KUXQ+ANGEAEm zb2f#@69+_jI77@6mtoqpsGJGm2lnKNV+51*OyoRqfM@_cI3OZ~p7Geg)I4z+rcH~w zHifW(J$d37!88pV*#-`gCoc2kiGwnto{5|%4iF76n>=~qGHRZj zPh1u`PaKR)2+nx?z|=f(S>!x%Fft)H|kmqpGK2Z)B?jK>D1=84N9=ZS-n3BehU z4NT1wmqpGK2O|@LGaeh5nkO!coF@)OCIn|ZHZV0$ToyS`9E?l|&UkEKB$#CBVcN8q zmR^WFahWGi9F$Qx6WIn1ktZ(mqd{ zbv%84zP_7GOCSfW*W=s#$wB%BH1uk|nEaLBw6Ic()oO`qzs)B9z0QluC*vhlZ3|eZ zvp4HiS@SCWaywrs`8b8lh`8CuRpI%NDx1Yz0{P}nVohIpFCpk<04;7#?#m9+jrRYfyCif>*&>Fc0OND5ZPle zRpp=6;9$M+7f}xkH~FVhujHj=gmIvc7{4!8KwLR`Y{2etSX=WoFdql|pye1cR__W% zOc|tC^Yv_%L%U*#IGVscz9d(@s?;--3ihD%JiWV{POjO&0sF_tM~Ch*ndp)-M%FQt zi=)HSS@Bb?hu;BxYf)8VvSu0Q29trz*l zYJMjaLNSAqL{=QUqE~J3N?c(8h`q*;6o@SBipIcOv74$4&UCKs+nv_!t!+B+iq9vo zE`XZ7O4}XUd=QF)g9tYpIC96JM8&^lzJZcFr;7wb!p5H%7OQ` z17;w0_vc5qJ)*J$s?xY29uC?eHvqZ&Go#-gq*QKTT^c(eV652X?vNdb-TnD7Y7eRG zfT}cZh^V+7as!aNKQoe~O%@KUOJj#{f$WeSh}{V=S${9NS+-0HoM8+PiFZ|A+za|gI}iO<+5yywKEAA z{_gVM*MAz&&&P{mWA=Q)26epns&tQDCIFGJ-q8H*!sN-p$^28kXuX@SR`c7|>f_}4&sn});%@cJlLMIgAgik<2mk&T zY+%9F_HsR)zD1Ad@8jtj-F@uh*Z7P6J!KDrO=fTN>-jBsPH*xXTQf&7YyCWbKVDB) zyd%s!taFXIu>>`#O4#Y`_`_uOq4hk!TYW6)DdwJs9@KRvH9dUW7^rukTQqVMvbHKM zR!WWz1tB0H+6Wfnk?LQWJPYyQb405pkG%<5W#M*Zg9U?_+^mz0vYCUQd)d&&w~&oY z|H^cmCL5sQv})0WqYwg9IFi&q*LXXJbQI0i>6XM)9f_YDuj*iJRa!Px9bIMIC0%1( z*}%Z8IoeXqmBQo!&lBAb* zhut26fIX{GPl0l=%J2S|+^jxg|DCiq>~;XeXp{}pZh|+~s|rdxemsVj$noDmcFWU0 z_FJiWvA(;TFW_Vii7alFPo9=Fi_OdJ_g^a=X4gF2;xbQ-;nK zR^U1-fSJt3#!~C(kaeWb&yq$9YIp$p3XHakyF9-sPe7~RG72ajRY@_z)o-OH1I8E% zEJMI1b7e(zJZKL(y?zSPGlUokh_XVLmSnI=jUsA>l0a?HOM2Zwx7Y0r`~6Yc!?&-EdJ2?ES1@JL=_kX{ zFd4$l4$L85l`4(+glEFN*CY=@ll0q3ci0VRE5fE0BJ7`bTSG&GM#ol%lz zBe0ITUC3_DO)Uc1{*2e7>wjRwPB z7l%(?!ZzD#cQ-eXA(F&rARCZ>Hifb`kilDvr=k1vN@;GEPL};CUwLr6ty4b9zA{fY zVW9rih9cTxp6r7+8 zGw@k9GsvJdJT}-LWP?t^j$rl%S!s@e3+KTbWW#>D+v$z^{Q;QegYFR1_iV`^E9QK& zvk$s1*`IyLszo^&fcei42afq+o%o>FRL((@PB-Zf``M`18}wjGH{aZK2k=fy24Vz( z3WGi=07$;*-4l-Q9PyyBiGKJvi=yOysb|>vwxNeD_x-+Qq@H zd~Z;kWOy$QzMe1hyGeeXUv(ecjX}6zoM0F#rixL+P%#Ac_2t|Alkb8tD9o*2ForoR zs}MzF)LbyG#-lJG&!^+ryL~1?ReEnJ6{evZMc_VUNja>+uf|w$wrSyowV~9 zoK4^bts-XXZyO6ZmM{nRg<{Y%?4GvmBC^Z?oQp^N=J%_7^>Kc)Jbt#AeE7I}ImxFt zk6Lffq@NI>UEUJsbq!!I^D$nLT^|1(;){+B#ZRN+Da4+#yXDK{rxU0t{Wj{p8h@Bg z-j}zjkMYuh`o(Af_tSxXh>PLD{}9&#XV2zmqvap-#h(zG`h2onjc4$JKmteSM~C`X zquJ%@+m}ygAEtQ~R)x_gw$Dae2%K84=7Ro{@f|)AAntzKOI0ZFi~qXqM!`g z&}F!XB8)GTlVmt)Aj4qOfGZS^A_NNGn+(66L)7)`Ll`k;TN_!f)ggt6X^Jb3)J9?q zq0T?H7#FH|M3v*LfgHn9_MOY|gVV*M&7_z;4frGLVz4LJ1zD0V?lzENFsyza8U6!a zglS|^;ER)uEedv5*i_Mm7T4u<_&salE#1*Og*SE<@4k1?SqE?xL!9fl3qf1Rh5EvxTqdh*7ke zpJJceW(}}DAG(H{s0xE{7ASwCX?ssL$udi1l-#5luY|F_OI}5I#p%u6YW``w(3DLk zdtolu@A7FrgPW;%-F0O>Qs;HJ4$1bO7%p$h22_b$2mjfoI;0%7GaQz}7X#}99|W87 zDBtGeRa)?T)0c)?qwF)B%1})XreWRtL3}BZi7xU$;W6wKrJ|BO@E#ebox&b?-H*r~ zp(u9D9(G4&YwYQ?d(9Yx4QTRHZ5NC2+LinOhJGv7EkXCR>Iyn-YmcU8g-yprvsAGy zxvg00yp|n@Ah+Zih%4p2VOKrJDQ`;@r5wSq<6~E>uY2n*7b0&{)f%^K0#8#+LUaG% zCf>7re7l6lSMhaRO#y+@uWmrg^;&;?%yW46))GA_SL2$c_wdZU-eC&=b3E?*?BXRO z(0BF*;<5F!H?|^G$M=d7DtYO8+!wC8Uc-aL)6>~|c+LclViu)k^Y;HdyJ$g_N9%vJ z;A|fL3y1sg-#%t!F#Tro=(W2;2nzwP89a@$!BL!vQMjf3&Eh6sfO836Sw7aVhGkqT zlv&D7^TdhUvkN%A%i#r?CA^d?z8dwPP3PBt{xSZ9Tb>WAk53oy_O#_Sm!r4&`)Lsa zqgB>yQHF$C#Zm)o*0cHNW4K&?bcp}Z(PB`3g>%T`E{1Kee_89icn{}-6OfOuaKtx) zGd}5q*#J)R6iO}q9@W+;afc(7I^28dL&beq%XfG;;9>F|9!03*`=AesXJ;}4&jgU? z#?j?sJX_vQmiVf=Drx$ruF*tdCnNrel#Ln);|}Lh7au6_quw1t93OQ&cZhlqQ*ZiF z@AV%h*CC><4t0oH+a1>-R)Rb;AiBjc4m3@EWY*y3!zG#zy$%`<*%1EQ>f?iG6w4uL zXgLUj+eBuEmP3X04`n%Ez8U}2>*8}0RIRa7%K^jM{kZoqZv7$RPS6|s)fbm;@#lyQ zV;6G~QwMa1OIUXVj@24j%%I8xL>*oxsYAVy5qn>*Z+i(-H^}Ak^?Kftx3skj*K9t6 z#6q@^(=z7}cbYyq5RU8D*hX7WVbkLdDW!*->Yiq;m!n=_bkz5NM1a~x;yjQu`8v1dk4JA4-|R0kXpxi zaE3on_{?Q)}4&hw{+h)&Z_MpeFiP`gT{FL9j*|Xavpn=T>FM-!hDDZ|8C)8Hf zuLj8X2K9;zjf>qaz73GyTMmXz(tvyL7)+hq!sFD$q2KKq1`n{;q2B91Ow{4U?mElu(x}1 z00(#n;tLvYPY(JCq$*P1mS>&@EpAUfF(}G8;KKc@nH)!IY02}m^H#rWeY}OSoflI$ zFOF2yjvg%M8QmP$g7ff(El{z2*1Jyhc<<%m3}L}-aFi*MN5?wi0uwh1;UY(LEW+=1 zw)gVv?8Wnw`RzN%6S_#!E0MPh&%EJ2cu{~SL^DWwDF^F6{qOa7Fy!yy=0Jd`aN(5zzdz=wW`XshCI#< zlCToFTc5JB7=yZ`l~Bu)wi3U3k(8o8j$yJnG$bRY*kYl@a{S`UHBL>4(t!XJ7G%Xf zYpgFy@Hh`%)#eq=)4ijj9`oX}R~MzHDIze&>G$T=nxMaDOv+=>+p&-T4N|6 z8fSq_Yp9vwP-|qS)_|wNO|6kpY>lLE-YeeQ8s${l+Zr>bMdYOnG|W$KYrte{pfxZe zhH7i{xZ#v<770s#Soa4Fdw-a+;*fgRpJ-bM~@kX^~G+8w!#P&frw#=t#38Q1T&Qz@6vPPqS{E#`mb zGwD2_`0y4UR++|jLW=&Em)_D zcCXJ)3GAW`F6{=worTz#e;2|HOmu2JUAXm8V%p^5;~I~uO5^2+7jD_3@fZT#<=^T8 zs5nAdRmm}`RaMV*^|z8>@xU|8lGBokAs_SbvjM(X9F)UVEeU1~*~IkKlVTYn?@KFX zwW0E22L)IAqk?;!6@&hlrvxkj@=s0d_uKdV_PzAvNcPruaa3?+IV`Ygj{R`~+M|>x zlfCA+`(tvm`Y5gM`{M#UFQFeYeiz3D7R><%0jBeBe`K)N983XJ;RpNhgz*;^tDxGE zs3VxKQCFRG&GB6v8CWz290r)0V}ES0*BpDzf&P~r1U7va#|9S7kzDZu18^Se1e5?;K*LCDk|E;6kzTvMimQ+v}ul>O>?9q%`x~s1Dy7ngB><1;sFX^ zzgxLo-yXwlY!0yKd zt{w6<4Cm=BK&h@_41)wE>}`&HkQ2%y`y&h*>v9hI?)wUIg2~oIbKo1P5KBq1IeG&T zyHpe8gl}{DYz~ri`&oRMJ_@HiO0XvTxt?? z>0d4|5U^B6>L~4J)9z=Jys*~?uYs-4u45O`T#}noy16Y!sqyzZls&>vc2%1ieWgSv z^4%(TU8$>+a=*cT!&<2#`>~FUyVrIAMl}Vg>?g{F41e9ypl(P%lnP)|#1vS~!^Tk-{p2wQY*kyGz`aM^%X&13cIzf8E zN74yWpjE9-_{lC}?{xy-%{MroXn2Y82ha(==qX(%)LgZebb{xiwW1T8*Q`l8fnB9G z>;x%mo#2a$(se@3MQTYWc&<^)I>CL3nxqrhMQKAPNWoFH zIw8z^X2H)FHh$-i7}2obJN$=G9!(!x_yOz$U&NEH6KbwWOFF@GNm|wk?km#uI>B=L zxlj+PW2kiB*|fvYeqJDNA>A&z#v_3~I({Wl9gEUQM;(iw=^n2<7H+3Dc`Wz^&}t-p zmiH$&4dgkJo}1j{{mFf>lU2&_gM#_+kv zM?C)b!&@^F>)b1^9!l(HT#^dbS$(SZNcqAQyZ5(Z@-02po-EvMk1I?9?NQMEyNT+f zaNohZiQsTy*WQieW65c2nzHf5a&-+pz6JPi4*N^Tg`M$2ERf z>G;@l#cuLDmV?t@b*d(D})(vF~-lUMEmOPd$DmLnrtzs!KY-b4^{= z34P}!^?IFf0&eid`035~4&KGTf~V7q59e^fsC?)B>ctl*nqjWzVuO)vY=HpI*nT({HB*-lw;~k0rz!SC!I(-@Y?9-r&dlFq^!eTo>=n;G_G>2l*`F=yJXqPoLi9i`9COAA=u7{h-t>(YrZ@ z1mkSXPH!Z4%2o#YbhaO8mXQRm0DtcTElprPZ__V4@k)NfXK*>k^%=Z`{4DK4%(xav zzueAOTb|2h9vCfMv&bV;mqva^6Ix0Oiql#8sZ3D$)HY@BDe==wX`X_=O-rAl6hiT< z$zm~Il&Ur_Rc|?5Hm~wIB;sCwQ3vbAG{4PfD@k)pc`shg*D%i^#ZsYwIg(EfK(oPl za1Obb7z(;W5pXLMALiG7dUrQ1b=>puY79!rT_$r0jR`mezP`?v%lQISm{@U0OAwwH zS8Y|mI5fyQFGDA#irbK(m4>7+6iy}{DnmITjTs87&qvIlwkTyx&ZJhq2)&%HCpTyL zr+oTykuN_=i)~?AoXl@PH?gvoC~UC?RuxsjyV3zl19bmPFAOi3eajcO6HFi^<+gfw zN($@%F~N24zpMDPx-}Np5LIo z2;*G+RyYS^Rh3{(`L|M&0b{OT7zPB){#{jRGOJH#*Xwu8^8IyvJGq&>|N6%mrVF<~ zis{XZTC9vVyPbgd(|kF>{i(9*DG_A*X>PqZ3bTntm9Rb*rxSnb_lInWixmhS3X2I z88c^sXlLu`YVTZt6?_NI1vb1FpC_y9j~6Sj%t|){jGoQ4pby<04E7bPE2-#ucY@yO z0SFB1o$sO)+okXjFc0hf91TZ@ddFHnM|wHu2Kat4-pnt@1v8*3g%`pTpm!$zsqMQ z(>d(&WCNo3;#oGOQQ~ze>!vu8c%yb}*{r+QLi#$1(-=BplpV8UC|Ehg2V+fDanhD< z2Fc_Uf}`;XYvRcmg-?}zq2ja)KB3*RDVy$RdAvP)dh=iF~|}>F)$d@Wi8{^ccQ4FqNZ3;&z(5v}j1Uwv-rJc^-xj`g; z%Nh%xmts30saEk@sfBY1Ln4kL`1t5h{Hp5I?-1amcrlqy{$i($tP5?~|Lu1+Ww}?f z@OD`j`QLs=4EFwlhwLvn-lV7qX1Rz{hA*v)+wpYT z`dyv=RWQMMelnl6<)%AW#HsO(N#_t_mODvS#Ud9cG7Rl2K)WiCgOocR2yrg%A@ zDTuK+U*^YZim|E05#XC-aN6yxSUo(`X(zpo@~4Q0tZGyWAGX}al5K5o)KXwn@k7w~ zM-C?{N5K+|(0paT7(?875vTroK7&oX^r7Nz{gPbGClJY(V^B+FG~b)_G5aYS&sAkH z`B3aVMvG)Rxtr_L+ltYWd6RdfrCy!r-|{sI)M{|!XX z{SH)vBmW{&RMBDVJh4e6Pr>f{Pq6xV^Y-%X>h*jjZj11FqkjE$JiD1<44piS9916B zB&wFCLK(c8T+dHmwjlG)$$YtLwf%RzdG+Q3HvjOo#lFgHS6f_NJWWQ= z(@xgOU?nbjuT}wf)iRVbz+y)f{sEpU4I=X)n!jRYF%Z>n4{J}CzfYEvchmfI_RDXl z{I0-j2>DpP_yQM}fIG7lbIldIFMgCy7!w|P-hTRJGKT?padry^PHEUIU=;Ju#ptOL zFV()f=q8=4KOAJ)Nbp@M|52D;4)9jEF78=K7lW>pp|~F3_L3B`(iTI3{De;}_+}17 zS@-H9O^3<|2q1g0XR7F8{(gnoC7-TW^Y`=X^^&vKQXnfDOYsBN?Ie|QC@ssfwl-KJ zeUMoqeg08dAwp@y#oA&nn%`x{n5A3g4{i-mqc*t#R3XdMlKX8`ojf+yz)LC5aJ(unZV|Il^ ztx%&AxcsZ8nQPVyG2PW$3!A8Zg+H=^0H*8P%U@yAGehbI6gsC~`8Zjw{(tuF10afH z{{uL-1Y05~8Z<_VfP$2}OJd2(fA$JoL%~Gjd;c%*8Mxh^OXSLcBq$^o12prc+ZA#f~&98|fbgJRunlM`}OWj1)27lE#wG z8*VyT>}YB{-JSZ6izBrt7mCZdOqNa>#rcBCvbI43vJPRg!}R1B*@9xl;v@%a z>uJ-bSxsYDC5k4KHfkH1b(o)TP;@LWfh$|RJe#16*(?RZWHDD5&6DM~rnb?Dykq2b z?krjQ+qARZiPY`cYdpBp6}%0*tXu*O45%^~DAurjstKh@UAf6r-%U#~RZk{O=n@k{ zhIb@Wv*|1bwH9lINiFAqbht1z-&+{Nn@**-k)&?^s5vd^j^=Ztd9H~hKq+QYovtZ2 zbqz!1ArHWbr~^+L$0VziPuG+ukjIS|ljfJ^&64kx+6+y(T?OKnjg);*PAA!bHt0&E z5~)y1Es#eJ-DqkTs0q}-D=A&`e8qK0X0_>1Qz_e&@UN5!TgmpcwM@{YJ0>q8iu*;C zAZo6pRpoCh6Vx{gNY>fTLLuEP6pA(!Wwk&-qf1ZywiXKdn=TaI!uA&l`A)q>MMAb4 z-hU}fNF=aQzaWO0CCIxl`SU&c(W zOsdw(O!cJ%q{Jule0jpj;#hB?Y-!Dgwe{YR-UdYpQnf%KusByW@yS8ni!v=Ijiy< zO?kaU+^8sWzpZJ-s`C`{ONx2(!B>i<)Q+nrZClDW7u4R^6>)P=ldiE{K~0)P9wVCX zofK{5m+yb?Es|pRLQs=N^q0jx<)cF7K~VEfR*GFMMM-6bCOhTOrI=NDj;6e-flD#B z>O95#|K&mLjor~T2Q}#$+ZEKLSrF84rRJ!1Io5KXs?Ab7O{JF!@+2)+W9r#k=Bejb zz9MVMt2|v(J_I#cqv5MGQ^{L?+uBSey;*(X#n0cO@h#&1}i5JY7>h+aT3S?_+-_;tPD?nI7SW|oI z^#9Qi+;)bwH~wG&?G+v@kmbQmC~iV=6Z-%2CbXSlO`7}-K3YJNuCaYrYtlRjYY1xy zYY1y?Xj^;Z4;Rp0;o$;V9)vZ7HH0;UwKjycH~x45d6_bK_{6$>S8I5_0Bvh%TSMDg zo4X9&_yY#CS9ricmIqgBxLU*28m`vbaJBZvA2Fc4!XpN_)`Y-S9r=m zmIq-CVGUsoVXX~e?TtTWK$otueOGIE$^bo~=m|wnXj^+i>2Lfg1KAqe7uN9o1qf>h zYyW@4n*3{a+Z@*3^iu}j!dLoQP&%G7Kwv{)LttxjV0+Wg8Bl@kRod6^Jq8GD2y6&! zZ4PX2`bh&Su)RtP8=f^lU_)R-U~6+=d(+PvP=W0g-sC~Y_Zc9tA+RB^wK=f8>8A~- z!1fC7@}T284G`E6*bvy-9N6CU^9EF4dxf`o(DA(n2y6&!2yAT)Y>YSky#`cZdxiIT z$hH%Mb1^sE{lp!1fAn^67 zHUu^Vwl)X0f8fc3SNZ->2A(`XU_)R-U~6+=`v;ypc$IGuW#GvJ1U3XV1hzH@wtwKs zgIDJj{jI*Sm zaI6(4DU@7mY0*{4u2`7Kp$H~tf?F(CNX`QJVW1`%G*8oT5ESxEq)m@bB!%zqJrZ1D z3YRdwLWzE(hrv%E3`k5CP(j0)#<5oX*m4NS;qv27S4yYwQ7N6w+#{Zsz!Qp-6%HB| zhBzu`jpQ&%ix~AM;UeKl<4Gf(t#F9^5>v=YOOi{Yj(p>sjA%v%(z95@x|GYHWQ(N5 zRglJuaY;&w7eq^f5(zC_XO4C2^Az`4vKVUnsMR`CC$_gRIweZ7AdYqO4CO2wYYm&H zq{Xpr-IDXtb0W~!Q^1Rl2@)qJg~?BfKwo*}<;9SwNoJFHlw>d@Y^X+Z#YId^NEH9w zXNa^Cc*U@9L${{WT8&kK@ouC}6LGy0$bYFypp|$-9G5=q6*CLC*}&q^tSQj(GqMdZw+ zk|sNwd_tC~JV`0Jd_jR!_*M%SlBrZ3Dt0n5Q9d-1vyL1dQi&uuOAn3YPsOE6({QIt zX3%7ZMMEl%wPZyqr{4=UjVc#@q>2wp;*si08aG=$D{cZ6I9ujWDHT5}rIaj80!+xL z1xx;JNz$&9KKx}TefUdH`f!}|LRra@7X{@KqIizWOINEYl}=g)gUz;MuxT_FjcIGg zwr%f8ub#ghCq09~B=N@9&Xz@;^0sz3>8Us&`DqDk8sgDgdeXb{xCw5FA`!VsA{AY8 zW#4>PWk4k`Xh~r}l90&Kd~YYaD^Xd@TX*i$=(3v=a*LsS$0ylSa{Ei&mrSJUFZ%SB zBrUfmbSs*T-9KI3)0s9(|7W-J%vZ`xmPPWnme(I zAkrH0`tCarg4%!U<4c-zMZN+)Pb^5_1#wda!pUxl2}y}U(gY>H4|;JL#c1^6lnPBy z3QgZCP2J6H)$+#h*4n=P$u_=HBVo%GPCJ^tTRH?t3f$YA9V?B~9ilN%pgLq;w zQIt=2OCc>q2`*x>ND!4GR=7_mXTo$|3}xcNr&@ZWLYHq}Kz-P=;B zf)mBuct7rBp@1)IfTQNxTiM&wW#5peIfXpGuV8YlIDjjTZNAB(^4koPz6(k7TzOva z1ny*B0NIOpcR{k4D~#q*c^Ou;VdS5rr$uuLd45k3X=w@~?T1PLl4a0`$)1^SJ&k-R zqe*u`JU2GGVZz70zodrKQe zFpcIwvvIJswX$Q-S$1|bOB-7UYNM3W%hxBvknC6*Pmr92kOlBrKsSeWE2CbvU8XD`wkPa{pKEmIVdjV2wY0wFh^T9$~H zOf?Vt@x-x-G0qXm;uvDwdV&`(C4fuC0ulz3oqZFNljF&<9Snj=r>Mq7Dx_9etXm4X zlNXXbZr(P9bgD~zVPb`ax|1U%r1n#}ft5pbFbH|^%2P>Mmra+P`?9|sNDQEgyOWWN zC(D+t;ikxAdbB_+In~B;Q+e?`;bhXr%wjS))*9!uY@V#?h4lPNj)h`w7HOQ)FkLOL z`wSil8h+7a3&}CCbWKd2M*3kbZETpN_)BMM%0pFCQRLczh6V{2&} zZA-I^A`NtwHe42u$Ft?~?4!vkOBSV&M{SXAiSdadXJb$DL+-OB#g&?%md-Oig-mvd z=S}AtNd7h;q#SGMWVO^D(~}aDQ$#$;`9_m;r%J-7(sZ?4-tqA%38a`)jY5)h!Xrt* zr`F3$j^>eCj&!%Grchgkq)~-*`^rua$@Vp8SDC4vxwUHs>G77-M}B@*)V*Ue>3E}h z{mF4q{AgusXK!O~V`)QUkpD=U@72<)t+ur)PGPMrQQ<{#LRKu*BhJg>kSwD@@T0De zoJpq(Ptxh86c;74)OJGg&?uY_s_Dq78bq$P;&}l)?i6PhT~etuf7Xy%)~ZQboh?~Z z!>@)`PRVgmDjP4@0MZdp4bC7%tR<1crGngCmMl>uyR1|VIFfa`3WP%Hl9N7+RDkji z8uno%H`$X^w8?>q0%xl4m%3*jblUASL!v9Dn6&x8poDguKpu64$V$-`6{V`)HYXI zwJ6p&gcLOnY8+Wy;Ji|e!(>VBF4|DzFl}utnQU@=Vn;4-Wi^Z~)5cO#;!xxK57#(kr20R4kE5Z+Ygdg!@=I$Rcb5R~w%+0}{!*hF z8Ik|jDjZt|Y1#TmDjXM@r{_y59Llk6wT!GGjk;M>XkSweC`uQ`Xh&;lMj(;)f3zKq zVb|7nGzOd0G1Of*>X`rGI)?cV*D)IEIL&oT^Hk;bRStOsC3=eTG;;Pzrb?RZB!7|S zI+aE_l1A2AN80vOvr~aUVYXyB3SC9&m2&gOX;_%#DVgXgD(hFVOKJDK^*^yT+U7Rr0l0X{klS!{E=@^v`QLrSvw5rn- zYi^FU@*+5jo#~RItD5`eOH^J2N3pA2vcw=US0sLUpLk0$#SJEM@+SwBEhLL3y&+uD z3|}%nMFmE>D`GJ>I+hyuKwU~YQ=33ml8}^4jFe|7uaP=htuDD`B)szWkOQW)4IIV6 z2$IbS3y&Dd6~^#lgrxu5IfVSh7YKPVirG}=sN|Dy%IT=5 zHIyePrsh~HZ!0HAX-^185a}6J7~Dkd4)r6QZs1LJWxC{nVd*7%08{;3B8dZelSzLV z)uqg^W7*iV88$Q)o5^Ca804~|d8XoqHLrzB9x5U|g?>EBPek^GJWHstgOIGojT~>D zsCmyV*|v0+6^qRnW@~T7rjzqSC4q(&Qa!k&_eyeA8%VnG$Rk+sq&rPmy<@?QN}?bi22KsJW>zKtn{{YPpvSv{wE?W1_bK-$1G4Xfjf zj*6p(#E^%BrDLd7mO(Dv$?X_5UvfR+Od|t5Z0J-MlIk4Ax|id}ixH3qom~XvvOxB? z(){MNlBGy)#+oOnj>8~>5nr%{9P;FGG__w!RXoRydY{SUcy4lXaI7GDisaIcW+&SOA1L%syn1g$J*G@qZ$6FV~)bfxvnCbyEwkBnKR-T~trkQh&1 za5IApvXorv$k*LGza>8@1L_uJzGDO$ zKdUXkT0Tc<37jD5uvPNz0?Cj`>a7{k_Ix&rJWp&H!=sT_DP|1Q(w@boTSl>?>AV;^ zpJ8iHt~q2Gq!H2A&%n}v8WK$g*)b;4Y?vf}a}xPDXzFb^7td%}12NCl66k!T^F`Jl-f7dXOo@yM4cb2V-jO0xqVNxO_1(_s4K)Nd0bOY(jh74p2(!32+KzP9dNn4$U%^=s9vQfwiTh}sC@!+&v zcPdULYi+*TlN__=Pc5-NAf79DJ__E49Wg4s{{p??Sz^h};>; zKBzeRCsL0bC_l>QOj2u{u9A0bqG$^B=%DN=JTe5D>RD5rs*))nB}owP#Y-fQ-H2v5 zy9q?mQf6B@4w{S8 z3ks9M1-`;&C`?r>AkCMeN*9=9o8%Xsl2Rl8&{AmV(+#MJ(Ai{?I`7iQ7i3G>1%8oLvfb13htu&(yZu?nyJu9_(lQQ5 zQyFWwyIM0TNA4`&VfkkV4dq6~sJ-s?>bv(cM4Hlb#nhptepI$YzUlJQO2e7@(ma|< zS@W3NzlC0{5x;eMP3PZ>PQNyoXgHcLHPNscFU|7`=N8qX@OKn@g_A+CWXfWY;v}*c z(nG3H8)&Es$e;&dGIeW8J@PLZzoQh~B6!=;(QV)+Ut5ja@|6e2Z_zNRJwDP&89^n7q8nv8&EhiB5 zo*1=Z51ar|B6%}SGU-%NXoBHTREm1^MI7Q`0t)2q`67{o?J~OmI^B` zFZrYw<7MX%$CbRnF(z5VR*>%jla(&dM3IY7NZtfXy-qAy_G+)z<|>y&X&9h_nk=Fy z$*av(+|`zul2F#1QDwS33nilZrd0BH2pJ#O{IXoCi1WnU81imGh5hBYq=*w$6H+0L z8Zt&6_@G|!r#@t?rSsmox#Ql-mSJPVu(z})Z&8)L6|Z?#^VX9Ek4_RNrgBBjvaffL zch(xvt!$=~zF^th7DtYB5QBUag3>1|8p)zGMQKg);iQrQZLRiVH*cjRnZk-?+oIe_ zOd3lyPFKh$-5|-*6h@oKH&k^kN}I2|E~;^yjPw$5Wjn7tS8a`w$DW&qDX^^Q!^l7R zB9v#!^ODgW8V*0lTg*$4mn`aJCl}GllKu!ujTojJ0A(?ptjosMj?QL~?`$H!=ropx zB{iB~J*ttxI+7ta)Yk}+lZy=Pa6V8;M#!j7mTxB60&;aP-2}xWET1OXtnjyEv&fmK zphu0@Ry+wQ$)tGh4A~g$=ADs0vZ|ymuYssxv^>rOc%$qV!(Z^9?zRH;8( z{D`3ZeYMg)CF*67%2SjQxAhTv>V0$4lCJvFQ27j{#RkQaui%oVr{2~|bebn9rBi(C zmn0G?zv5GEwo+!YX7UY4@~%tr2367rrZP=a`sUZfN?#5u%h57bH4hmN+~ya%QcMaj zX#J}%btS{zU;087?3vp2LLbrC6LCFi`Ua<^ne)-#LD}$}S^i91C z%G3VkGT1z}UHv6V|6~!AzG~?E&|dbMKL$}6B=W3g-P)lZs= zoiuIRi(PYt+;UaO2_|36;>VSInTf*Xon)l0Gxen{(owmJvpj^PR*xoMD=tX#6_9~N z&ZH_*o7NI_II?-tM@*@&KM3HF7fF*hT#%=kXrxi&Mf2s0A$O@7wv!Vo;7#L+0-Nup z<&R~_ev_L8l?2k|NiP1?ubMf&+!^FM4P-a7WXEN9w`AwZMR)?)-sUg9(83P{AOHaf zKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_ z009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz z00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_< z0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb z2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$## zAOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;| zfB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U< z00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa z0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV= z5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHaf zKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_ z009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz z00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_< z0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb z2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$## zAOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;| zfB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U< z00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa z0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV= z5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHaf zKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_ z009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz z00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_< z0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb z2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$## zAOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;| zfB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U< z00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa z0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV= z5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHaf zKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_ z009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz z00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_< z0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb z2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$## zAOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;| zfB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U< z00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa z0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV= z5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHaf zKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_ z009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz z00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_< z0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb z2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$## zAOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;| zfB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U< 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 M@bko%cm7lTUn*7fegFUf literal 0 HcmV?d00001 diff --git a/src/napari_ndev/_tests/test_napari_reader.py b/src/napari_ndev/_tests/test_napari_reader.py index 875b5b7..77e3bc3 100644 --- a/src/napari_ndev/_tests/test_napari_reader.py +++ b/src/napari_ndev/_tests/test_napari_reader.py @@ -1,7 +1,7 @@ from __future__ import annotations from pathlib import Path -from typing import TYPE_CHECKING, Any +from typing import Any import dask.array as da @@ -11,12 +11,10 @@ from napari_ndev._napari_reader import napari_get_reader -if TYPE_CHECKING: - from npe2._pytest_plugin import TestPluginManager - ############################################################################### RGB_TIFF = "RGB.tiff" # has two scense +MULTISCENE_CZI = r"0T-4C-0Z-7pos.czi" # PNG_FILE = "example.png" # GIF_FILE = "example.gif" # OME_TIFF = "pipeline-4.ome.tiff" @@ -49,9 +47,9 @@ def test_reader( filename: str, in_memory: bool, expected_shape: tuple[int, ...], - expected_dtype: type, + expected_dtype, expected_meta: dict[str, Any], - npe2pm: TestPluginManager, + # npe2pm: TestPluginManager, ) -> None: # Resolve filename to filepath if isinstance(filename, str): @@ -78,7 +76,54 @@ def test_reader( meta.pop('metadata', None) assert meta == expected_meta - # # confirm that this also works via npe2 - # with npe2pm.tmp_plugin(package='napari-ndev') as plugin: - # [via_npe2] = npe2.read([path], stack=False, plugin_name=plugin.name) - # assert via_npe2[0].shape == data.shape + +@pytest.mark.parametrize( + ("in_memory", "expected_dtype"), + [ + (True, np.ndarray), + (False, da.core.Array), + ], +) +@pytest.mark.parametrize( + ("filename", "expected_shape"), + [ + (RGB_TIFF, (120, 160, 3)), + (MULTISCENE_CZI, (32, 32)), + ], +) +def test_for_multiscene_widget( + make_napari_viewer, + resources_dir: Path, + filename: str, + in_memory: bool, + expected_dtype, + expected_shape: tuple[int, ...], +) -> None: + # Make a viewer + viewer = make_napari_viewer() + assert len(viewer.layers) == 0 + assert len(viewer.window._dock_widgets) == 0 + + # Resolve filename to filepath + if isinstance(filename, str): + path = str(resources_dir / filename) + + # Get reader + reader = napari_get_reader(path, in_memory) + + if reader is not None: + # Call reader on path + reader(path) + + if len(viewer.window._dock_widgets) != 0: + # Get the second scene + viewer.window._dock_widgets[f"{filename} :: Scenes"].widget().setCurrentRow( + 1 + ) + data = viewer.layers[0].data + assert isinstance(data, expected_dtype) + assert data.shape == expected_shape + else: + data, _, _ = reader(path)[0] + assert isinstance(data, expected_dtype) + assert data.shape == expected_shape diff --git a/src/napari_ndev/nimage.py b/src/napari_ndev/nimage.py index 08500a8..fa57c78 100644 --- a/src/napari_ndev/nimage.py +++ b/src/napari_ndev/nimage.py @@ -16,7 +16,7 @@ logger = logging.getLogger(__name__) -LABEL_DELIMITER = " || " +LABEL_DELIMITER = " :: " class nImage(BioImage): """ From 8134a18a482317620a03e209a6697161053afc0e Mon Sep 17 00:00:00 2001 From: TimMonko <47310455+TimMonko@users.noreply.github.com> Date: Mon, 14 Oct 2024 00:41:37 -0500 Subject: [PATCH 16/18] os_sorted for nat sort and finish tests --- setup.cfg | 1 + .../widgets/test_utilities_container.py | 163 ++++++++++++++---- .../widgets/_utilities_container.py | 57 ++++-- 3 files changed, 169 insertions(+), 52 deletions(-) diff --git a/setup.cfg b/setup.cfg index 212a474..ed69ee1 100644 --- a/setup.cfg +++ b/setup.cfg @@ -47,6 +47,7 @@ install_requires = pyclesperanto-prototype dask napari-workflows + natsort seaborn stackview tifffile >= 2023.3.15 # https://github.com/AllenCellModeling/aicsimageio/issues/523 maybe keep for legacy reasons? diff --git a/src/napari_ndev/_tests/widgets/test_utilities_container.py b/src/napari_ndev/_tests/widgets/test_utilities_container.py index 846feb9..c27f7f3 100644 --- a/src/napari_ndev/_tests/widgets/test_utilities_container.py +++ b/src/napari_ndev/_tests/widgets/test_utilities_container.py @@ -1,10 +1,12 @@ -import os +import re from pathlib import Path +import natsort + import numpy as np import pytest -from bioio import BioImage +from napari_ndev import nImage from napari_ndev.widgets._utilities_container import UtilitiesContainer image_2d = np.asarray([[0, 0, 1, 1], [0, 0, 1, 1], [2, 2, 1, 1], [2, 2, 1, 1]]) @@ -48,11 +50,10 @@ def test_data(request: pytest.FixtureRequest): return request.param -@pytest.mark.parametrize('image_layer', [True, False]) def test_save_shapes_as_labels( - make_napari_viewer, tmp_path: Path, test_data, image_layer: bool + make_napari_viewer, tmp_path: Path, test_data, ): - test_image, test_shape, test_labels, test_dims = test_data + test_image, test_shape, _, _ = test_data viewer = make_napari_viewer() viewer.add_image(test_image) @@ -60,21 +61,19 @@ def test_save_shapes_as_labels( container = UtilitiesContainer(viewer) container._viewer.layers.selection.active = viewer.layers['test_shape'] - container._squeezed_dims = test_dims container._save_directory.value = tmp_path - container._save_name.value = 'test.tiff' + container._save_name.value = 'test' - shapes_as_labels = container.save_shapes_as_labels() + shapes_layer = container.save_layers_as_ome_tiff() - expected_save_loc = tmp_path / 'ShapesAsLabels' / 'test.tiff' + expected_save_loc = tmp_path / 'Shapes' / 'test.tiff' assert expected_save_loc.exists() - assert shapes_as_labels.shape == test_image.shape - assert np.array_equal(shapes_as_labels, test_labels) - assert BioImage(expected_save_loc).channel_names == ['Shapes'] + assert shapes_layer.shape.__len__() == 5 + assert nImage(expected_save_loc).channel_names == ['Shapes'] def test_save_labels(make_napari_viewer, tmp_path: Path, test_data): - _, _, test_labels, test_dims = test_data + _, _, test_labels, _ = test_data viewer = make_napari_viewer() viewer.add_labels( @@ -83,44 +82,65 @@ def test_save_labels(make_napari_viewer, tmp_path: Path, test_data): viewer.layers.selection.active = viewer.layers['test_labels'] container = UtilitiesContainer(viewer) - container._squeezed_dims = test_dims container._save_directory.value = tmp_path - container._save_name.value = 'test.tiff' + container._save_name.value = 'test' - labels = container.save_labels() + layer_data = container.save_layers_as_ome_tiff() expected_save_loc = tmp_path / 'Labels' / 'test.tiff' + + assert isinstance(layer_data, np.ndarray) assert expected_save_loc.exists() - assert np.array_equal(labels, test_labels) - assert BioImage(expected_save_loc).channel_names == ['Labels'] + assert layer_data.shape.__len__() == 5 + assert nImage(expected_save_loc).channel_names == ['Labels'] -def test_save_ome_tiff(make_napari_viewer, test_data, tmp_path: Path): +def test_save_image_layer(make_napari_viewer, test_data, tmp_path: Path): test_image, _, _, _ = test_data viewer = make_napari_viewer() viewer.add_image(test_image) container = UtilitiesContainer(viewer) - container._concatenate_image_files.value = False - container._concatenate_image_layers.value = True container._viewer.layers.selection.active = viewer.layers['test_image'] container._channel_names.value = ['0'] container._save_directory.value = tmp_path - container._save_name.value = 'test.tiff' + container._save_name.value = 'test' + + layer_data = container.save_layers_as_ome_tiff() - container.save_ome_tiff() + expected_save_loc = tmp_path / 'Image' / 'test.tiff' - expected_save_loc = tmp_path / 'Images' / 'test.tiff' + assert isinstance(layer_data, np.ndarray) + assert layer_data.shape.__len__() == 5 assert expected_save_loc.exists() - assert len(container._img_data.shape) == 5 + assert nImage(expected_save_loc).channel_names == ['0'] + +def test_save_multi_layer(make_napari_viewer, test_data, tmp_path: Path): + test_image, _, test_labels, _ = test_data + viewer = make_napari_viewer() + viewer.add_image(test_image) + viewer.add_labels(test_labels) + container = UtilitiesContainer(viewer) + + container._viewer.layers.selection = [ + viewer.layers['test_labels'], + viewer.layers['test_image'], + ] + container._save_directory.value = tmp_path + container._save_name.value = 'test' + + layer_data = container.save_layers_as_ome_tiff() + expected_save_loc = tmp_path / 'Layers' / 'test.tiff' + + assert isinstance(layer_data, np.ndarray) + assert layer_data.shape.__len__() == 5 + assert expected_save_loc.exists() @pytest.fixture -def test_rgb_image(): - path = os.path.join( - 'src', 'napari_ndev', '_tests', 'resources', 'RGB.tiff' - ) - img = BioImage(path) +def test_rgb_image(resources_dir: Path): + path = resources_dir / 'RGB.tiff' + img = nImage(path) return path, img @@ -132,8 +152,8 @@ def test_update_metadata_from_file(make_napari_viewer, test_rgb_image): container._files.value = path container.update_metadata_on_file_select() - assert container._save_name.value == 'RGB.tiff' - assert container._img.dims.order == 'TCZYXS' + assert container._save_name.value == 'RGB' + assert container._dim_order.value == 'TCZYXS' assert container._squeezed_dims == 'YX' assert container._channel_names.value == "['red', 'green', 'blue']" @@ -149,10 +169,85 @@ def test_update_metadata_from_layer(make_napari_viewer, test_data): assert ( 'Tried to update metadata, but could only update scale' - ' because layer not opened with napari-bioio' + # ' because layer not opened with neuralDev reader' ) in container._results.value assert container._scale_tuple.value == (1, 2, 3) +@pytest.fixture +def test_czi_image(resources_dir: Path): + path = resources_dir / '0T-4C-0Z-7pos.czi' + img = nImage(path) + return path, img + +def test_save_files_as_ome_tiff(test_czi_image, tmp_path: Path): + path, _ = test_czi_image + container = UtilitiesContainer() + container._files.value = path + container._save_directory.value = tmp_path + save_dir = tmp_path / 'ConcatenatedImages' + + img_data = container.save_files_as_ome_tiff() + + # check that there is 1 file + assert len(list(tmp_path.iterdir())) == 1 + # check the name of the file is 0T-4C-0Z-7pos.tiff + assert (save_dir / '0T-4C-0Z-7pos.tiff').exists() + assert img_data.shape.__len__() == 5 + +@pytest.mark.parametrize('num_files', [1,3]) +def test_select_next_images(resources_dir: Path, num_files: int): + container = UtilitiesContainer() + + image_dir = resources_dir / 'test_czis' + # get all the files in the directory + all_image_files = list(image_dir.iterdir()) + # sort the files + all_image_files = natsort.os_sorted(all_image_files) + + container._files.value = all_image_files[:num_files] + + container.select_next_images() + + selected_files = container._files.value + if isinstance(selected_files, tuple): + selected_files = list(selected_files) + + assert len(selected_files) == num_files + + for i in range(num_files): + assert selected_files[i] == all_image_files[i + num_files] + +def test_batch_concatenate_files(tmp_path: Path, resources_dir: Path): + container = UtilitiesContainer() + image_dir = resources_dir / 'test_czis' + all_image_files = list(image_dir.iterdir()) + + all_image_files = natsort.os_sorted(all_image_files) + + container._files.value = all_image_files[:1] + + container._save_directory.value = tmp_path + container._save_directory_prefix.value = 'test' + container.batch_concatenate_files() + + expected_output_dir = tmp_path / 'test_ConcatenatedImages' + + assert expected_output_dir.exists() + assert len(list(expected_output_dir.iterdir())) == 8 + + +def test_save_scenes_ome_tiff(test_czi_image, tmp_path: Path): + path, _ = test_czi_image + container = UtilitiesContainer() + container._files.value = path + container._save_directory.value = tmp_path + save_dir = tmp_path / 'ExtractedScenes' + + container.save_scenes_ome_tiff() + + # check that there are 7 files in the save dir + assert len(list(save_dir.iterdir())) == 7 + def test_open_images(make_napari_viewer, test_rgb_image): viewer = make_napari_viewer() container = UtilitiesContainer(viewer) @@ -161,6 +256,6 @@ def test_open_images(make_napari_viewer, test_rgb_image): container._files.value = path container.open_images() - assert container._img.dims.order == "TCZYXS" + assert container._dim_order.value == "TCZYXS" assert container._squeezed_dims == "YX" assert container._channel_names.value == "['red', 'green', 'blue']" diff --git a/src/napari_ndev/widgets/_utilities_container.py b/src/napari_ndev/widgets/_utilities_container.py index 7c37837..f46e59b 100644 --- a/src/napari_ndev/widgets/_utilities_container.py +++ b/src/napari_ndev/widgets/_utilities_container.py @@ -50,8 +50,6 @@ class UtilitiesContainer(ScrollableContainer): ---------- _viewer: napari.viewer.Viewer The napari viewer instance. - _img_data: numpy.ndarray or None - The concatenated image data. _image_save_dims: str or None The dimension order for saving images. _label_save_dims: str or None @@ -138,10 +136,6 @@ def __init__(self, viewer: napari.viewer.Viewer = None): self.min_width = 500 # TODO: remove this hardcoded value self._viewer = viewer if viewer is not None else None - self._img_data = None - self._image_save_dims = None - self._label_save_dims = None - self._p_sizes = None self._squeezed_dims = None self._init_widgets() @@ -544,8 +538,16 @@ def open_images(self): """Open the selected images in the napari viewer with napari-ndev.""" self._viewer.open(self._files.value, plugin='napari-ndev') + @staticmethod + def _natural_sort_key(s): + return [ + int(text) if text.isdigit() else text.lower() + for text in re.split(r'(\d+)', s) + ] + # Converted def select_next_images(self): + from natsort import os_sorted """Open the next set of images in the directyory.""" num_files = self._files.value.__len__() @@ -555,19 +557,22 @@ def select_next_images(self): # get the list of files in the parent directory files = list(parent_dir.glob(f'*{first_file.suffix}')) - # sort the files naturally (case-insensitive and numbers in order) # like would be scene in windows file explorer default sorting - files.sort( - key=lambda f: [ - int(text) if text.isdigit() else text.lower() - for text in re.split('([0-9]+)', f.name) - ] - ) + # https://pypi.org/project/natsort/#sort-paths-like-my-file-browser-e-g-windows-explorer-on-windows + + files = os_sorted(files) # get the index of the first file in the list and then the next files idx = files.index(first_file) next_files = files[idx + num_files : idx + num_files + num_files] + + # if there are no more files, then return + if not next_files: + self._results.value = ( + 'No more file sets to select.' + ) + return # set the nwe save names, and update the file value img = nImage(next_files[0]) @@ -795,7 +800,7 @@ def _determine_save_directory(self, save_dir: str | None = None) -> str: save_dir = f'{save_dir}' return save_dir - def save_files_as_ome_tiff(self) -> None: + def save_files_as_ome_tiff(self) -> np.ndarray: """Save the selected files as OME-TIFF using BioImage.""" img_data = self.concatenate_files(self._files.value) save_dir = self._determine_save_directory('ConcatenatedImages') @@ -818,6 +823,8 @@ def save_files_as_ome_tiff(self) -> None: result_str='Concatenated Image', ) + return img_data + def batch_concatenate_files(self) -> None: """ Concatenate files in the selected directory. @@ -842,6 +849,11 @@ def batch_concatenate_files(self) -> None: self.select_next_images() self.save_files_as_ome_tiff() + self._results.value = ( + 'Batch concatenated files in directory.' + f'\nAt {time.strftime("%H:%M:%S")}' + ) + def save_scenes_ome_tiff(self) -> None: """ Save selected scenes as OME-TIFF. @@ -884,9 +896,14 @@ def save_scenes_ome_tiff(self) -> None: image_name=image_id, result_str=f'Scene: {img.current_scene}', ) + + self._results.value = ( + f'Saved extracted scenes: {scenes_list}' + f'\nAt {time.strftime("%H:%M:%S")}' + ) return - def save_layers_as_ome_tiff(self) -> None: + def save_layers_as_ome_tiff(self) -> np.ndarray: """ Save the selected layers as OME-TIFF. @@ -896,7 +913,9 @@ def save_layers_as_ome_tiff(self) -> None: list(self._viewer.layers.selection) ) # get the types of layers, to know where to save the image - layer_types = [type(layer).__name__ for layer in self._viewer.layers.selection] + layer_types = [ + type(layer).__name__ for layer in self._viewer.layers.selection + ] # if there are multiple layer types, save to Layers directory layer_save_type = 'Layers' if len(set(layer_types)) > 1 else layer_types[0] @@ -911,12 +930,12 @@ def save_layers_as_ome_tiff(self) -> None: cnames = self._channel_names.value channel_names = ast.literal_eval(cnames) if cnames else None else: - channel_names = layer_save_type + channel_names = [layer_save_type] if layer_save_type == 'Shapes': layer_data = layer_data.astype(np.int16) - if layer_save_type == 'Labels': + elif layer_save_type == 'Labels': if layer_data.max() > 65535: layer_data = layer_data.astype(np.int32) else: @@ -930,3 +949,5 @@ def save_layers_as_ome_tiff(self) -> None: image_name=self._save_name.value, result_str=layer_save_type, ) + + return layer_data From 6cd50d595b6de1ca7e472262241f7250f0c55e22 Mon Sep 17 00:00:00 2001 From: TimMonko <47310455+TimMonko@users.noreply.github.com> Date: Mon, 14 Oct 2024 00:42:12 -0500 Subject: [PATCH 17/18] Update test_utilities_container.py --- src/napari_ndev/_tests/widgets/test_utilities_container.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/napari_ndev/_tests/widgets/test_utilities_container.py b/src/napari_ndev/_tests/widgets/test_utilities_container.py index c27f7f3..d9d4d2f 100644 --- a/src/napari_ndev/_tests/widgets/test_utilities_container.py +++ b/src/napari_ndev/_tests/widgets/test_utilities_container.py @@ -1,8 +1,6 @@ -import re from pathlib import Path import natsort - import numpy as np import pytest From 28d09f2455ded3e1e9122f1af7e38f08ad20cbfd Mon Sep 17 00:00:00 2001 From: TimMonko <47310455+TimMonko@users.noreply.github.com> Date: Mon, 14 Oct 2024 01:17:44 -0500 Subject: [PATCH 18/18] fix tox test setup - was not downloading all properly --- setup.cfg | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/setup.cfg b/setup.cfg index ed69ee1..378e802 100644 --- a/setup.cfg +++ b/setup.cfg @@ -82,8 +82,8 @@ testing = pytest-qt # https://pytest-qt.readthedocs.io/en/latest/ napari pyqt5 - napari-ndev[all] # just to confirm that bioio dependencies don't conflict with expected readers in tests - # napari-bioio @ git+https://github.com/TimMonko/napari-bioio.git@main#egg=napari-bioio + bioio-czi >= 1.0.1 + napari_ndev[extras] extras = napari-pyclesperanto-assistant @@ -94,7 +94,6 @@ gpl_extras = # bioio GPL3 dependencies bioio-czi >= 1.0.1 bioio-lif >= 1 - # napari-bioio @ git+https://github.com/TimMonko/napari-bioio.git@main#egg=napari-bioio docs = mkdocs