def roi(self, val: Union[np.ndarray, ROIInfo]): if val is None: self._roi_info = ROIInfo(val) self._additional_layers = {} self.roi_clean.emit() return try: if isinstance(val, np.ndarray): self._roi_info = ROIInfo(self.image.fit_array_to_image(val)) else: self._roi_info = val.fit_to_image(self.image) except ValueError: raise ValueError(ROI_NOT_FIT) self._additional_layers = {} self.roi_changed.emit(self._roi_info)
class ImageInfo: image: Image layers: List[NapariImage] filter_info: List[Tuple[NoiseFilterType, float]] = field(default_factory=list) mask: Optional[Labels] = None mask_array: Optional[np.ndarray] = None roi: Optional[Labels] = None roi_info: ROIInfo = field(default_factory=lambda: ROIInfo(None)) roi_count: int = 0 def coords_in(self, coords: Union[List[int], np.ndarray]) -> bool: if not self.layers: return False fst_layer = self.layers[0] moved_coords = self.translated_coords(coords) return np.all(moved_coords >= 0) and np.all( moved_coords < fst_layer.data.shape) def translated_coords(self, coords: Union[List[int], np.ndarray]) -> np.ndarray: if not self.layers: return np.array(coords) fst_layer = self.layers[0] return np.subtract(coords, fst_layer.translate_grid).astype(np.int)
def __init__(self): super().__init__() self._image: Optional[Image] = None self._image_path = "" self._roi_info = ROIInfo(None) self._additional_layers = {} self._parent: Optional[QWidget] = None
class ProjectInfoBase(Protocol): """ This is base protocol for Project Information. :ivar str ~.file_path: path to current preceded file :ivar Image ~.image: project image :ivar numpy.ndarray ~.segmentation: numpy array representing current project ROI :ivar SegmentationInfo ~.segmentation_info: segmentation metadata :ivar Optional[numpy.ndarray] ~.mask: mask used in project :ivar str errors: information about problems with current project """ file_path: str image: Image roi: np.ndarray roi_info: ROIInfo = ROIInfo(None) mask: Optional[np.ndarray] errors: str = "" def get_raw_copy(self): """ Create copy with only image """ raise NotImplementedError def get_raw_mask_copy(self): raise NotImplementedError def is_raw(self): raise NotImplementedError def is_masked(self): raise NotImplementedError
def _set_roi_info(self, new_roi_info: ROIInfo, save_chosen=True, list_of_components=None, segmentation_parameters=None): if list_of_components is None: list_of_components = [] if segmentation_parameters is None: segmentation_parameters = defaultdict(lambda: None) state = self.get_project_info() try: new_roi_info = new_roi_info.fit_to_image(self.image) except ValueError: raise ValueError("ROI do not fit to image") if save_chosen: state2 = self.transform_state(state, new_roi_info, segmentation_parameters, list_of_components, save_chosen) self.chosen_components_widget.set_chose( list(sorted(state2.roi_extraction_parameters.keys())), state2.selected_components) self.roi = state2.roi_info self.components_parameters_dict = state2.roi_extraction_parameters else: selected_parameters = { i: segmentation_parameters[i] for i in new_roi_info.bound_info } self.chosen_components_widget.set_chose( list(sorted(selected_parameters.keys())), list_of_components) self.roi = new_roi_info self.components_parameters_dict = segmentation_parameters
def analysis_project() -> ProjectTuple: data = np.zeros((1, 50, 100, 100, 1), dtype=np.uint16) data[0, 10:40, 10:40, 10:90] = 50 data[0, 10:40, 50:90, 10:90] = 50 data[0, 15:35, 15:35, 15:85] = 70 data[0, 15:35, 55:85, 15:85] = 60 data[0, 10:40, 40:50, 10:90] = 40 image = Image( data, (10 / UNIT_SCALE[Units.nm.value], 5 / UNIT_SCALE[Units.nm.value], 5 / UNIT_SCALE[Units.nm.value]), "" ) mask = data[0, ..., 0] > 0 roi = np.zeros(data.shape, dtype=np.uint8) roi[data == 70] = 1 roi[data == 60] = 2 algorithm_parameters = { "algorithm_name": "Lower Threshold", "values": { "threshold": 60, "channel": 0, "noise_filtering": {"name": "None", "values": {}}, "minimum_size": 10, "side_connection": False, }, } roi_info = ROIInfo(roi.squeeze()).fit_to_image(image) return ProjectTuple( file_path="test_data.tiff", image=image, roi_info=roi_info, additional_layers={"denoised image": AdditionalLayerDescription(data=roi, layer_type="layer")}, mask=mask, algorithm_parameters=algorithm_parameters, )
def compare_action(self): if self.compare_btn.text() == "Compare": self._settings.set_segmentation_to_compare(self._settings.roi_info) self.compare_btn.setText("Remove") else: self._settings.set_segmentation_to_compare(ROIInfo(None)) self.compare_btn.setText("Compare")
def test_multiple_components(self, comp_num): data = np.zeros((10 * comp_num, 10), dtype=np.uint8) for i in range(comp_num): data[i * 10 + 2:i * 10 + 8, 2:8] = i + 1 si = ROIInfo(data) assert len(si.bound_info) == comp_num assert set(si.bound_info.keys()) == set(range(1, comp_num + 1)) for i in range(comp_num): assert np.all(si.bound_info[i + 1].lower == [i * 10 + 2, 2]) assert np.all(si.bound_info[i + 1].upper == [i * 10 + 7, 7]) assert len(si.sizes) == comp_num + 1 assert np.all(si.sizes[1:] == 36) data[-1, 8] = 1 si = ROIInfo(data) assert np.all(si.bound_info[1].lower == 2) assert np.all(si.bound_info[1].upper == [10 * comp_num - 1, 8])
def get_roi_info_and_mask(self) -> Tuple[ROIInfo, Optional[np.ndarray]]: self.arrays.seek(0) seg = np.load(self.arrays) self.arrays.seek(0) alternative = {name: array for name, array in seg.items() if name not in {"roi", "mask"}} roi_info = ROIInfo(seg["roi"], annotations=self.annotations, alternative=alternative) mask = seg["mask"] if "mask" in seg else None return roi_info, mask
def test_more_dims(self, dims): si = ROIInfo(np.ones((10, ) * dims, dtype=np.uint8)) assert len(si.bound_info[1].lower) == dims assert len(si.bound_info[1].upper) == dims assert np.all(si.bound_info[1].lower == 0) assert np.all(si.bound_info[1].upper == 9) assert len(si.sizes) == 2 assert np.all(si.sizes == [0, 10**dims])
def stack_segmentation1(stack_image: MaskProjectTuple, mask_segmentation_parameters): data = np.zeros([20, 40, 40], dtype=np.uint8) for i, (x, y) in enumerate(itertools.product([0, 20], repeat=2), start=1): data[1:-1, x + 2 : x + 18, y + 2 : y + 18] = i data = ROIInfo(stack_image.image.fit_array_to_image(data)) parameters = {i: deepcopy(mask_segmentation_parameters) for i in range(1, 5)} return dataclasses.replace( stack_image, roi_info=data, roi_extraction_parameters=parameters, selected_components=[1, 3] )
def test_mask(self, mask_prop): mask = np.zeros((10, 10), dtype=np.uint8) mask[1:-1, 1:-1] = 2 mask2 = np.copy(mask) mask2[2:-2, 2:-2] = 4 roi_info = ROIInfo(mask2) elem = HistoryElement.create(roi_info, mask, {}, mask_prop) roi_info2, mask2 = elem.get_roi_info_and_mask() assert np.all(mask == mask2) assert np.all(roi_info.roi == roi_info2.roi)
def test_set_roi(self, qtbot, part_settings, image): prop = ChannelProperty(part_settings, "test") viewer = ResultImageView(part_settings, prop, "test") qtbot.add_widget(prop) qtbot.add_widget(viewer) viewer.show() part_settings.image = image roi = ROIInfo((image.get_channel(0) > 0).astype(np.uint8)) roi = roi.fit_to_image(image) viewer.set_roi(roi, image) QCoreApplication.processEvents() assert not viewer.roi_alternative_select.isVisible() assert viewer.label1.isVisible() assert viewer.label2.isVisible() assert viewer.opacity.isVisible() assert viewer.only_border.isVisible() assert not viewer.roi_alternative_select.isVisible() assert viewer.any_roi() assert not viewer.available_alternatives() viewer.hide()
def test_create(self, mask_prop): roi_info = ROIInfo(np.zeros((10, 10), dtype=np.uint8)) elem = HistoryElement.create(roi_info, None, {}, mask_prop) assert elem.mask_property == mask_prop assert elem.roi_extraction_parameters == {} param = {"a": 1, "b": 2} elem2 = HistoryElement.create(roi_info, None, param, mask_prop) assert elem2.roi_extraction_parameters == param roi_info2, mask = elem2.get_roi_info_and_mask() assert np.all(roi_info2.roi == 0) assert mask is None
def image(self, value: Image): if value is None: return self._image = value if value.file_path is not None: self.image_changed[str].emit(value.file_path) self._image_changed() self._roi_info = ROIInfo(None) self.image_changed.emit(self._image) self.image_changed[int].emit(self._image.channels)
def test_annotations(self, mask_prop): data = np.zeros((10, 10), dtype=np.uint8) data[1:5, 1:5] = 1 data[5:-1, 1:5] = 2 data[1:5, 5:-1] = 3 data[5:-1, 5:-1] = 4 annotations = {1: "a", 2: "b", 3: "c", 4: "d"} roi_info = ROIInfo(data, annotations=annotations) elem = HistoryElement.create(roi_info, None, {}, mask_prop) roi_info2, mask2 = elem.get_roi_info_and_mask() assert mask2 is None assert np.all(roi_info2.roi == roi_info.roi)
def test_simple(self, num): data = np.zeros((10, 10), dtype=np.uint8) data[2:8, 2:8] = num si = ROIInfo(data) assert len(si.bound_info) == 1 assert num in si.bound_info assert isinstance(si.bound_info[num], BoundInfo) assert np.all(si.bound_info[num].lower == [2, 2]) assert np.all(si.bound_info[num].upper == [7, 7]) assert len(si.sizes) == num + 1 assert np.all(si.sizes[1:num] == 0) assert si.sizes[num] == 36
def test_update_alternatives(self, base_settings, image_view, tmp_path): roi = np.ones(base_settings.image.get_channel(0).shape, dtype=np.uint8) roi[..., 1, 1] = 0 base_settings.roi = ROIInfo( roi, alternative={"test": np.ones(roi.shape, dtype=np.uint8) - roi}) image_view.update_roi_border() assert np.all(image_view.image_info[str(tmp_path / "test2.tiff")].roi.data == roi) image_view.roi_alternative_selection = "test" image_view.update_roi_border() assert np.all(image_view.image_info[str(tmp_path / "test2.tiff")].roi.data != roi)
def perform_roi_info_history_test( self, project, save_path, mask_property, save_method: Type[SaveBase], load_method: Type[LoadBase] ): alt1 = np.copy(project.roi_info.roi) alt1[alt1 > 0] += 3 roi_info = ROIInfo( roi=project.roi_info.roi, annotations={i: f"a{i}" for i in range(1, 5)}, alternative={"test": alt1} ) history = [] for i in range(3): alt2 = np.copy(alt1) alt2[alt2 > 0] = i + 5 roi_info2 = ROIInfo( roi=project.roi_info.roi, annotations={i: f"a{i}_{j}" for j in range(1, 5)}, alternative={f"test{i}": alt2}, ) history.append( HistoryElement.create( roi_info2, alt1, {"algorithm_name": f"task_{i}", "values": {"a": 1}}, mask_property ) ) proj = dataclasses.replace(project, roi_info=roi_info, history=history) save_method.save(save_path / "data.tgz", proj, SaveROI.get_default_values()) proj2: ProjectInfoBase = load_method.load([save_path / "data.tgz"]) assert np.all(proj2.roi_info.roi == project.roi_info.roi) assert set(proj2.roi_info.annotations) == {1, 2, 3, 4} assert proj2.roi_info.annotations == {i: f"a{i}" for i in range(1, 5)} assert "test" in proj2.roi_info.alternative assert np.all(proj2.roi_info.alternative["test"] == alt1) assert len(proj2.history) == 3 for i in range(3): roi_info3, mask2 = proj2.history[i].get_roi_info_and_mask() assert np.all(mask2 == alt1) assert set(roi_info3.alternative) == {f"test{i}"} assert np.all(roi_info3.alternative[f"test{i}"][alt1 > 0] == i + 5) assert np.all(roi_info3.alternative[f"test{i}"][alt1 == 0] == 0) assert roi_info3.annotations == {i: f"a{i}_{j}" for j in range(1, 5)} assert proj2.history[i].roi_extraction_parameters == {"algorithm_name": f"task_{i}", "values": {"a": 1}}
def test_compare_view(part_settings, image2, qtbot): prop = ChannelProperty(part_settings, "test") view = CompareImageView(part_settings, prop, "test") qtbot.add_widget(prop) qtbot.add_widget(view) part_settings.image = image2 roi = np.ones(image2.get_channel(0).shape, dtype=np.uint8) assert view.image_info[str(image2.file_path)].roi is None part_settings.roi = roi assert view.image_info[str(image2.file_path)].roi is None part_settings.set_segmentation_to_compare(ROIInfo(roi)) assert view.image_info[str(image2.file_path)].roi is not None
def perform_roi_info_test(self, project, save_path, save_method: Type[SaveBase], load_method: Type[LoadBase]): alt1 = np.copy(project.roi_info.roi) alt1[alt1 > 0] += 3 roi_info = ROIInfo( roi=project.roi_info.roi, annotations={i: f"a{i}" for i in range(1, 5)}, alternative={"test": alt1} ) proj = dataclasses.replace(project, roi_info=roi_info) save_method.save(save_path / "data.tgz", proj, SaveROI.get_default_values()) proj2 = load_method.load([save_path / "data.tgz"]) assert np.all(proj2.roi_info.roi == project.roi_info.roi) assert set(proj2.roi_info.annotations) == {1, 2, 3, 4} assert proj2.roi_info.annotations == {i: f"a{i}" for i in range(1, 5)} assert "test" in proj2.roi_info.alternative assert np.all(proj2.roi_info.alternative["test"] == alt1)
def prev_mask(self): history: HistoryElement = self.settings.history_pop() history.arrays.seek(0) seg = np.load(history.arrays) history.arrays.seek(0) # TODO Check me # self.settings.roi = seg["segmentation"] self.settings._set_roi_info( # pylint: disable=W0212 ROIInfo(seg["segmentation"]), False, history.roi_extraction_parameters["selected"], history.roi_extraction_parameters["parameters"], ) self.settings.mask = seg["mask"] if "mask" in seg else None self.close()
def test_transform_state_simple(self, stack_image): roi = np.zeros(stack_image.image.get_channel(0).shape, dtype=np.uint8) roi[0, 1, 2:-2, 2:-2] = 1 roi[0, 2, 2:-2, 2:-2] = 2 roi[0, 3, 2:-2, 2:-2] = 3 roi_info = ROIInfo(roi).fit_to_image(stack_image.image) roi_extraction_parameters = defaultdict(lambda: ROIExtractionProfile("aa", "aa", {1: "aa"})) new_state = StackSettings.transform_state( state=stack_image, new_roi_info=roi_info, new_roi_extraction_parameters=roi_extraction_parameters, list_of_components=[2], ) assert len(new_state.roi_extraction_parameters) == 3 assert new_state.selected_components == [2]
def napari_write_labels(path: str, data: Any, meta: dict) -> Optional[str]: if not isinstance(data, numpy.ndarray): return ext = os.path.splitext(path)[1] if ext in SaveROI.get_extensions(): project = MaskProjectTuple(file_path="", image=None, roi_info=ROIInfo(data)) SaveROI.save(path, project, parameters={ "spacing": numpy.divide(meta["scale"], DEFAULT_SCALE_FACTOR)[-3:] }) return path
def set_segmentation_result(self, result: SegmentationResult): if result.info_text and self._parent is not None: QMessageBox().information(self._parent, "Algorithm info", result.info_text) self._additional_layers = result.additional_layers self.last_executed_algorithm = result.parameters.algorithm self.set(f"algorithms.{result.parameters.algorithm}", result.parameters.values) try: roi = self.image.fit_array_to_image(result.roi) alternative_list = { k: self.image.fit_array_to_image(v) for k, v in result.alternative_representation.items() } except ValueError: raise ValueError("roi do not fit to image") self._roi_info = ROIInfo(roi, result.roi_annotation, alternative_list) self.roi_changed.emit(self._roi_info)
def save( cls, save_location: typing.Union[str, BytesIO, Path], project_info: ProjectTuple, parameters: dict, range_changed=None, step_changed=None, ): if not os.path.exists(save_location): os.makedirs(save_location) if not os.path.isdir(save_location): raise OSError("save location exist and is not a directory") parameters = deepcopy(parameters) if parameters["clip"]: points = np.nonzero(project_info.roi_info.roi) lower_bound = np.min(points, axis=1) lower_bound = np.max([lower_bound - 3, [0, 0, 0]], axis=0) upper_bound = np.max(points, axis=1) upper_bound = np.max( [upper_bound + 3, np.array(project_info.roi_info.roi) - 1], axis=0) cut_area = tuple( slice(x, y) for x, y in zip(lower_bound, upper_bound)) # WARNING time image = project_info.image.cut_image((slice(None), ) + cut_area) roi_info = ROIInfo(project_info.roi_info.roi[cut_area]) mask = project_info.mask[cut_area] if project_info.mask else None project_info = dataclasses.replace(project_info, image=image, roi_info=roi_info, mask=mask) parameters["clip"] = False parameters.update({"separated_objects": False}) SaveCmap.save(os.path.join(save_location, "density.cmap"), project_info, parameters, range_changed, step_changed) parameters.update({"separated_objects": True}) SaveCmap.save(os.path.join(save_location, "density.cmap"), project_info, parameters, range_changed, step_changed) SaveROIAsTIFF.save(os.path.join(save_location, "segmentation.tiff"), project_info, {}, range_changed, step_changed) SaveROIAsNumpy.save(os.path.join(save_location, "segmentation.npy"), project_info, {}, range_changed, step_changed)
def test_additional(self, mask_prop): data = np.zeros((10, 10), dtype=np.uint8) data[1:-1, 1:-1] = 2 alternative = {} for i in range(4): alternative_array = np.copy(data) alternative_array[alternative_array > 0] = i + 2 alternative[f"add{i}"] = alternative_array roi_info = ROIInfo(data, alternative=alternative) elem = HistoryElement.create(roi_info, None, {}, mask_prop) roi_info2, mask2 = elem.get_roi_info_and_mask() assert np.all(roi_info2.roi == roi_info.roi) assert mask2 is None assert len(roi_info.alternative) == 4 assert set(roi_info.alternative) == {f"add{i}" for i in range(4)} for i in range(4): arr = roi_info.alternative[f"add{i}"] assert np.all(arr[arr > 0] == i + 2)
def analysis_project_reversed() -> ProjectTuple: data = np.zeros((1, 50, 100, 100, 1), dtype=np.uint16) data[0, 10:40, 10:40, 10:90] = 50 data[0, 10:40, 50:90, 10:90] = 50 data[0, 15:35, 15:35, 15:85] = 70 data[0, 15:35, 55:85, 15:85] = 60 data[0, 10:40, 40:50, 10:90] = 40 mask = data[0] > 0 roi = np.zeros(data.shape, dtype=np.uint8) roi[data == 70] = 1 roi[data == 60] = 2 data = 100 - data image = Image( data, (10 / UNIT_SCALE[Units.nm.value], 5 / UNIT_SCALE[Units.nm.value], 5 / UNIT_SCALE[Units.nm.value]), "" ) roi_info = ROIInfo(roi.squeeze()).fit_to_image(image) return ProjectTuple("test_data.tiff", image, roi_info=roi_info, mask=mask)
def test_image_settings(self, tmp_path, image, roi, qtbot): widget = base_settings.QWidget() qtbot.addWidget(widget) settings = base_settings.ImageSettings() settings.set_parent(widget) assert settings.image_spacing == () assert settings.image_shape == () assert settings.image_path == "" assert settings.channels == 0 settings.image = image assert settings.image_spacing == image.spacing assert settings.image_path == str(tmp_path / "test.tiff") assert settings.image_shape == (1, 1, 10, 10, 2) with qtbot.waitSignal(settings.image_spacing_changed): settings.image_spacing = (7, 7) assert image.spacing == (7, 7) with qtbot.waitSignal(settings.image_spacing_changed): settings.image_spacing = (1, 8, 8) assert image.spacing == (8, 8) with pytest.raises(ValueError): settings.image_spacing = (6, ) assert settings.is_image_2d() assert settings.has_channels with qtbot.waitSignal(settings.image_changed[str]): settings.image_path = str(tmp_path / "test2.tiff") assert image.file_path == str(tmp_path / "test2.tiff") with pytest.raises(ValueError, match="roi do not fit to image"): settings.roi = roi[:-2] with qtbot.waitSignal(settings.roi_changed): settings.roi = roi assert all(settings.sizes == [64, 18, 18]) assert all(settings.components_mask() == [0, 1, 1]) assert tuple(settings.roi_info.bound_info.keys()) == (1, 2) with qtbot.waitSignal(settings.roi_clean): settings.roi = None settings.roi = ROIInfo(roi) settings.image = None assert settings.image is not None
def test_with_roi_alternatives(self, part_settings, image2, qtbot): prop = ChannelProperty(part_settings, "test") view = ResultImageView(part_settings, prop, "test") qtbot.add_widget(prop) qtbot.add_widget(view) part_settings.image = image2 view.show() roi = np.ones(image2.get_channel(0).shape, dtype=np.uint8) part_settings.roi = roi assert not view.roi_alternative_select.isVisible() part_settings.roi = ROIInfo(roi, alternative={"t1": roi, "t2": roi}) assert view.roi_alternative_select.isVisible() assert view.roi_alternative_select.count() == 3 assert view.roi_alternative_select.currentText() == "ROI" view.roi_alternative_select.setCurrentText("t1") view.hide()