diff --git a/model_server/extensions/chaeo/roiset.py b/model_server/base/roiset.py similarity index 94% rename from model_server/extensions/chaeo/roiset.py rename to model_server/base/roiset.py index c478ff2598527216e85857b9f448eeea73372ae7..bf40227a0629c2d3747e1faf20360565fa664a63 100644 --- a/model_server/extensions/chaeo/roiset.py +++ b/model_server/base/roiset.py @@ -1,9 +1,11 @@ from math import sqrt, floor from pathlib import Path +from typing import List, Union from uuid import uuid4 import numpy as np import pandas as pd +from pydantic import BaseModel from scipy.stats import moment from skimage.filters import sobel @@ -15,10 +17,51 @@ from model_server.base.accessors import GenericImageDataAccessor, InMemoryDataAc from model_server.base.models import InstanceSegmentationModel from model_server.base.process import pad, rescale, resample_to_8bit, make_rgb from model_server.extensions.chaeo.annotators import draw_box_on_patch, draw_contours_on_patch, draw_boxes_on_3d_image -from model_server.extensions.chaeo.params import RoiFilter, RoiSetMetaParams, RoiSetExportParams from model_server.extensions.chaeo.accessors import write_patch_to_file, MonoPatchStack, Multichannel3dPatchStack from model_server.extensions.chaeo.process import mask_largest_object +class PatchParams(BaseModel): + draw_bounding_box: bool = False + draw_contour: bool = False + draw_mask: bool = False + rescale_clip: float = 0.001 + focus_metric: str = 'max_sobel' + rgb_overlay_channels: List[Union[int, None]] = [None, None, None] + rgb_overlay_weights: List[float] = [1.0, 1.0, 1.0] + pad_to: int = 256 + + +class AnnotatedZStackParams(BaseModel): + draw_label: bool = False + + +class RoiFilterRange(BaseModel): + min: float + max: float + + +class RoiFilter(BaseModel): + area: Union[RoiFilterRange, None] = None + solidity: Union[RoiFilterRange, None] = None + + +class RoiSetMetaParams(BaseModel): + filters: Union[RoiFilter, None] = None + expand_box_by: List[int] = [128, 0] + + +class RoiSetExportParams(BaseModel): + pixel_probabilities: bool = False + patches_3d: Union[PatchParams, None] = None + annotated_patches_2d: Union[PatchParams, None] = None + patches_2d: Union[PatchParams, None] = None + patch_masks: Union[PatchParams, None] = None + annotated_zstacks: Union[AnnotatedZStackParams, None] = None + object_classes: bool = False + dataframe: bool = False + + + def _get_label_ids(acc_seg_mask: GenericImageDataAccessor) -> InMemoryDataAccessor: return InMemoryDataAccessor(label(acc_seg_mask.data[:, :, 0, 0]).astype('uint16')) @@ -516,5 +559,3 @@ def project_stack_from_focal_points( ) - - diff --git a/model_server/extensions/chaeo/params.py b/model_server/extensions/chaeo/params.py deleted file mode 100644 index 9e06b286498a21bf1115b15f26f99b10547d9808..0000000000000000000000000000000000000000 --- a/model_server/extensions/chaeo/params.py +++ /dev/null @@ -1,44 +0,0 @@ -from typing import List, Union - -from pydantic import BaseModel - - -class PatchParams(BaseModel): - draw_bounding_box: bool = False - draw_contour: bool = False - draw_mask: bool = False - rescale_clip: float = 0.001 - focus_metric: str = 'max_sobel' - rgb_overlay_channels: List[Union[int, None]] = [None, None, None] - rgb_overlay_weights: List[float] = [1.0, 1.0, 1.0] - pad_to: int = 256 - - -class AnnotatedZStackParams(BaseModel): - draw_label: bool = False - -class RoiFilterRange(BaseModel): - min: float - max: float - -class RoiFilter(BaseModel): - area: Union[RoiFilterRange, None] = None - solidity: Union[RoiFilterRange, None] = None - - -class RoiSetMetaParams(BaseModel): - filters: Union[RoiFilter, None] = None - expand_box_by: List[int] = [128, 0] - - -class RoiSetExportParams(BaseModel): - pixel_probabilities: bool = False - patches_3d: Union[PatchParams, None] = None - annotated_patches_2d: Union[PatchParams, None] = None - patches_2d: Union[PatchParams, None] = None - patch_masks: Union[PatchParams, None] = None - annotated_zstacks: Union[AnnotatedZStackParams, None] = None - object_classes: bool = False - dataframe: bool = False - - diff --git a/model_server/extensions/chaeo/products.py b/model_server/extensions/chaeo/products.py deleted file mode 100644 index b28b04f643122b019e912540f228c8ed20be9eeb..0000000000000000000000000000000000000000 --- a/model_server/extensions/chaeo/products.py +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/model_server/extensions/chaeo/tests/test_roiset_workflow.py b/model_server/extensions/chaeo/tests/test_roiset_workflow.py new file mode 100644 index 0000000000000000000000000000000000000000..369701def197a92e470e456142bcfade57ef2c9b --- /dev/null +++ b/model_server/extensions/chaeo/tests/test_roiset_workflow.py @@ -0,0 +1,71 @@ +import unittest + +from model_server.base.models import DummyInstanceSegmentationModel +from model_server.base.roiset import RoiSetMetaParams, RoiSetExportParams +from model_server.conf.testing import output_path +from model_server.extensions.chaeo.conf.testing import multichannel_zstack, pipeline_params +from model_server.extensions.chaeo.workflows import infer_object_map_from_zstack + +from tests.test_roiset import BaseTestRoiSetMonoProducts + +class TestRoiSetWorkflow(BaseTestRoiSetMonoProducts, unittest.TestCase): + + def test_object_map_workflow(self): + pp = pipeline_params + models = [ + self.pxmodel, + DummyInstanceSegmentationModel(), + ] + + models = { + 'pixel_classifier': { + 'model': self.pxmodel, + 'params': { + 'px_class': 0, + 'px_prob_threshold': 0.6, + } + }, + 'object_classifier': { + 'name': 'dummy', + 'model': DummyInstanceSegmentationModel(), + } + } + + roi_params = RoiSetMetaParams(**{ + 'mask_type': 'boxes', + 'filters': { + 'area': {'min': 1e3, 'max': 1e8} + }, + 'expand_box_by': [128, 2] + }) + + export_params = RoiSetExportParams(**{ + 'pixel_probabilities': True, + 'patches_3d': {}, + 'annotated_patches_2d': { + 'draw_bounding_box': True, + 'rgb_overlay_channels': [3, None, None], + 'rgb_overlay_weights': [0.2, 1.0, 1.0], + 'pad_to': 512, + }, + 'patches_2d': { + 'draw_bounding_box': False, + 'draw_mask': False, + }, + 'patch_masks': { + 'pad_to': 256, + }, + 'annotated_zstacks': {}, + 'object_classes': True, + 'dataframe': True, + }) + + infer_object_map_from_zstack( + multichannel_zstack['path'], + output_path / 'roiset' / 'workflow', + models, + segmentation_channel=pp['segmentation_channel'], + patches_channel=pp['patches_channel'], + export_params=export_params, + roi_params=roi_params, + ) \ No newline at end of file diff --git a/model_server/extensions/chaeo/workflows.py b/model_server/extensions/chaeo/workflows.py index 08d06adf07dbba82c84286a32d7154d1c1070ea1..b837877979017384effdb840c1f171d7e6d1d808 100644 --- a/model_server/extensions/chaeo/workflows.py +++ b/model_server/extensions/chaeo/workflows.py @@ -8,9 +8,9 @@ from skimage.measure import label from skimage.morphology import dilation from sklearn.model_selection import train_test_split -from extensions.chaeo.params import RoiSetExportParams, RoiSetMetaParams +from base.roiset import RoiSetMetaParams, RoiSetExportParams from model_server.extensions.chaeo.process import mask_largest_object -from model_server.extensions.chaeo.roiset import _get_label_ids, RoiSet +from base.roiset import _get_label_ids, RoiSet from model_server.base.accessors import generate_file_accessor, InMemoryDataAccessor, write_accessor_data_to_file from model_server.base.models import Model, InstanceSegmentationModel, SemanticSegmentationModel @@ -68,7 +68,8 @@ def infer_object_map_from_zstack( 'output_path': output_folder_path, } -# TODO: to app-specific ecotaxa module + + def transfer_ecotaxa_labels_to_patch_stacks( where_masks: str, where_patches: str, @@ -172,6 +173,4 @@ def transfer_ecotaxa_labels_to_patch_stacks( for k in zstacks.keys(): write_accessor_data_to_file(Path(where_output) / f'zstack_{k}.tif', InMemoryDataAccessor(zstacks[k])) - pd.DataFrame(stack_meta).to_csv(Path(where_output) / f'{dfk}_stack.csv', index=False) - - + pd.DataFrame(stack_meta).to_csv(Path(where_output) / f'{dfk}_stack.csv', index=False) \ No newline at end of file diff --git a/model_server/extensions/chaeo/tests/test_zstack.py b/tests/test_roiset.py similarity index 81% rename from model_server/extensions/chaeo/tests/test_zstack.py rename to tests/test_roiset.py index f3b82997482884e70e12bd341dbc732c5aa745dc..df77c111ec69b372d6778465cc119d385aae3316 100644 --- a/model_server/extensions/chaeo/tests/test_zstack.py +++ b/tests/test_roiset.py @@ -6,9 +6,8 @@ from pathlib import Path from model_server.conf.testing import output_path from model_server.extensions.chaeo.conf.testing import multichannel_zstack, pixel_classifier, pipeline_params -from extensions.chaeo.params import RoiSetExportParams, RoiSetMetaParams -from model_server.extensions.chaeo.workflows import infer_object_map_from_zstack -from model_server.extensions.chaeo.roiset import _get_label_ids, RoiSet +from model_server.base.roiset import RoiSetMetaParams +from model_server.base.roiset import _get_label_ids, RoiSet from model_server.base.accessors import generate_file_accessor, InMemoryDataAccessor, write_accessor_data_to_file from model_server.extensions.ilastik.models import IlastikPixelClassifierModel from model_server.base.models import DummyInstanceSegmentationModel @@ -131,7 +130,7 @@ class TestRoiSetMonoProducts(BaseTestRoiSetMonoProducts, unittest.TestCase): roiset = RoiSet(self.stack_ch_pa, id_map, params=RoiSetMetaParams(mask_type='boxes')) df = roiset.get_df() - from model_server.extensions.chaeo.roiset import project_stack_from_focal_points + from base.roiset import project_stack_from_focal_points img = project_stack_from_focal_points( df['centroid-0'].to_numpy(), @@ -159,66 +158,6 @@ class TestRoiSetMonoProducts(BaseTestRoiSetMonoProducts, unittest.TestCase): self.assertTrue(all(roiset.get_df()['classify_by_dummy_class'].unique() == [1])) self.assertTrue(all(np.unique(roiset.object_class_maps['dummy_class'].data) == [0, 1])) - def test_object_map_workflow(self): - pp = pipeline_params - models = [ - self.pxmodel, - DummyInstanceSegmentationModel(), - ] - - models = { - 'pixel_classifier': { - 'model': self.pxmodel, - 'params': { - 'px_class': 0, - 'px_prob_threshold': 0.6, - } - }, - 'object_classifier': { - 'name': 'dummy', - 'model': DummyInstanceSegmentationModel(), - } - } - - roi_params = RoiSetMetaParams(**{ - 'mask_type': 'boxes', - 'filters': { - 'area': {'min': 1e3, 'max': 1e8} - }, - 'expand_box_by': [128, 2] - }) - - export_params = RoiSetExportParams(**{ - 'pixel_probabilities': True, - 'patches_3d': {}, - 'annotated_patches_2d': { - 'draw_bounding_box': True, - 'rgb_overlay_channels': [3, None, None], - 'rgb_overlay_weights': [0.2, 1.0, 1.0], - 'pad_to': 512, - }, - 'patches_2d': { - 'draw_bounding_box': False, - 'draw_mask': False, - }, - 'patch_masks': { - 'pad_to': 256, - }, - 'annotated_zstacks': {}, - 'object_classes': True, - 'dataframe': True, - }) - - infer_object_map_from_zstack( - multichannel_zstack['path'], - output_path / 'roiset' / 'workflow', - models, - segmentation_channel=pp['segmentation_channel'], - patches_channel=pp['patches_channel'], - export_params=export_params, - roi_params=roi_params, - ) - class TestRoiSetMultichannelProducts(BaseTestRoiSetMonoProducts, unittest.TestCase):