diff --git a/model_server/base/roiset.py b/model_server/base/roiset.py index 8b7642774a4e84c9f476b5aa744ec62652ed551e..cfeec9eec6d4de1e2d56a358055140a58893b4a6 100644 --- a/model_server/base/roiset.py +++ b/model_server/base/roiset.py @@ -954,7 +954,7 @@ class RoiSet(object): return interm - def serialize(self, where: Path, prefix='') -> dict: + def serialize(self, where: Path, prefix='roiset') -> dict: """ Export the minimal information needed to recreate RoiSet object, i.e. CSV data file and tight patch masks :param where: path of directory in which to write files diff --git a/model_server/conf/testing.py b/model_server/conf/testing.py index 12d93c8b884dfad672a5e7adf0f5cbe05c60eeba..042a95512bb4417efd0e09f1359db11a37e4ff45 100644 --- a/model_server/conf/testing.py +++ b/model_server/conf/testing.py @@ -1,13 +1,18 @@ import json import os import unittest +from math import floor from multiprocessing import Process from pathlib import Path from shutil import copyfile +import numpy as np import requests from urllib3 import Retry +from ..base.accessors import GenericImageDataAccessor, InMemoryDataAccessor +from ..base.models import SemanticSegmentationModel, InstanceSegmentationModel + from ..base.accessors import generate_file_accessor class TestServerBaseClass(unittest.TestCase): @@ -77,9 +82,21 @@ def setup_test_data(): :return: meta (dict) of test data and paths """ + + def _winpath(f): + if not isinstance(f, str): + return f + p = f.split('/') + if len(p) > 1: + p[1] = p[1] + ':' + return '\\'.join(p[1:]) + else: + return f + # places to look for test data data_paths = [ os.environ.get('UNITTEST_DATA_ROOT'), + _winpath(os.environ.get('UNITTEST_DATA_ROOT')), Path.home() / 'model_server' / 'testing', os.getcwd(), ] @@ -100,8 +117,7 @@ def setup_test_data(): raise Exception('Could not find test data, try setting environmental variable UNITTEST_DATA_ROOT.') meta['root'] = Path(root) - op_ev = os.environ.get('UNITTEST_OUTPUT', (meta['root'] / 'test_output')) - meta['output_path'] = Path(op_ev) + meta['output_path'] = meta['root'] / 'test_output' meta['output_path'].mkdir(parents=True, exist_ok=True) # resolve relative paths @@ -117,4 +133,49 @@ def setup_test_data(): return meta # object containing test data paths and metadata, for import into unittest modules -meta = setup_test_data() \ No newline at end of file +meta = setup_test_data() + + +class DummySemanticSegmentationModel(SemanticSegmentationModel): + + model_id = 'dummy_make_white_square' + + def load(self): + return True + + def infer(self, img: GenericImageDataAccessor) -> (GenericImageDataAccessor, dict): + super().infer(img) + w = img.shape_dict['X'] + h = img.shape_dict['Y'] + result = np.zeros([h, w], dtype='uint8') + result[floor(0.25 * h) : floor(0.75 * h), floor(0.25 * w) : floor(0.75 * w)] = 255 + return InMemoryDataAccessor(data=result), {'success': True} + + def label_pixel_class( + self, img: GenericImageDataAccessor, **kwargs) -> GenericImageDataAccessor: + mask, _ = self.infer(img) + return mask + + +class DummyInstanceSegmentationModel(InstanceSegmentationModel): + + model_id = 'dummy_pass_input_mask' + + def load(self): + return True + + def infer( + self, img: GenericImageDataAccessor, mask: GenericImageDataAccessor + ) -> (GenericImageDataAccessor, dict): + return img.__class__( + (mask.data / mask.data.max()).astype('uint16') + ) + + def label_instance_class( + self, img: GenericImageDataAccessor, mask: GenericImageDataAccessor, **kwargs + ) -> GenericImageDataAccessor: + """ + Returns a trivial segmentation, i.e. the input mask with value 1 + """ + super(DummyInstanceSegmentationModel, self).label_instance_class(img, mask, **kwargs) + return self.infer(img, mask) diff --git a/pyproject.toml b/pyproject.toml index d00d683d3e31b3bf58975c2cd9342c4a0c8ab298..882ad79c5a6e8bf5d99116212482db65478df8a3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta" [project] name = "model_server" license = {file = "LICENSE"} -version = "2024.09.30" +version = "2024.10.01" authors = [ { name="Christopher Rhodes", email="christopher.rhodes@embl.de" }, ] diff --git a/tests/base/test_api.py b/tests/base/test_api.py index f368dc05212c59f1b2ece208a719b4e249cedf70..e31b4366f3d4baa9371fcd80c3338cc2c5fbb013 100644 --- a/tests/base/test_api.py +++ b/tests/base/test_api.py @@ -8,7 +8,7 @@ import model_server.conf.testing as conf from model_server.base.accessors import InMemoryDataAccessor from model_server.base.api import app from model_server.base.session import session -from tests.base.test_model import DummyInstanceSegmentationModel, DummySemanticSegmentationModel +from model_server.conf.testing import DummySemanticSegmentationModel, DummyInstanceSegmentationModel czifile = conf.meta['image_files']['czifile'] diff --git a/tests/base/test_model.py b/tests/base/test_model.py index fb5a8f21e263ea5ab5f08d31f727ddcd24386e63..d975f7cd8725e0215391b4a526feab7cd69eeb31 100644 --- a/tests/base/test_model.py +++ b/tests/base/test_model.py @@ -1,58 +1,12 @@ -from math import floor import unittest -import numpy as np - import model_server.conf.testing as conf -from model_server.base.accessors import CziImageFileAccessor, GenericImageDataAccessor, InMemoryDataAccessor -from model_server.base.models import CouldNotLoadModelError, InstanceSegmentationModel, SemanticSegmentationModel, BinaryThresholdSegmentationModel +from model_server.conf.testing import DummySemanticSegmentationModel, DummyInstanceSegmentationModel +from model_server.base.accessors import CziImageFileAccessor +from model_server.base.models import CouldNotLoadModelError, BinaryThresholdSegmentationModel czifile = conf.meta['image_files']['czifile'] -class DummySemanticSegmentationModel(SemanticSegmentationModel): - - model_id = 'dummy_make_white_square' - - def load(self): - return True - - def infer(self, img: GenericImageDataAccessor) -> (GenericImageDataAccessor, dict): - super().infer(img) - w = img.shape_dict['X'] - h = img.shape_dict['Y'] - result = np.zeros([h, w], dtype='uint8') - result[floor(0.25 * h) : floor(0.75 * h), floor(0.25 * w) : floor(0.75 * w)] = 255 - return InMemoryDataAccessor(data=result), {'success': True} - - def label_pixel_class( - self, img: GenericImageDataAccessor, **kwargs) -> GenericImageDataAccessor: - mask, _ = self.infer(img) - return mask - - -class DummyInstanceSegmentationModel(InstanceSegmentationModel): - - model_id = 'dummy_pass_input_mask' - - def load(self): - return True - - def infer( - self, img: GenericImageDataAccessor, mask: GenericImageDataAccessor - ) -> (GenericImageDataAccessor, dict): - return img.__class__( - (mask.data / mask.data.max()).astype('uint16') - ) - - def label_instance_class( - self, img: GenericImageDataAccessor, mask: GenericImageDataAccessor, **kwargs - ) -> GenericImageDataAccessor: - """ - Returns a trivial segmentation, i.e. the input mask with value 1 - """ - super(DummyInstanceSegmentationModel, self).label_instance_class(img, mask, **kwargs) - return self.infer(img, mask) - class TestCziImageFileAccess(unittest.TestCase): def setUp(self) -> None: diff --git a/tests/base/test_pipelines.py b/tests/base/test_pipelines.py index 2a97c5b4088976f96a4973e0f70e8472fc722047..9f1b0303cbacf9fe9fe9969f7beadfe1ce942711 100644 --- a/tests/base/test_pipelines.py +++ b/tests/base/test_pipelines.py @@ -4,7 +4,7 @@ from model_server.base.accessors import generate_file_accessor, write_accessor_d from model_server.base.pipelines import router, segment, segment_zproj import model_server.conf.testing as conf -from tests.base.test_model import DummySemanticSegmentationModel +from model_server.conf.testing import DummySemanticSegmentationModel czifile = conf.meta['image_files']['czifile'] zstack = conf.meta['image_files']['tifffile'] diff --git a/tests/base/test_roiset.py b/tests/base/test_roiset.py index 0a03973e78ca824083f2821157c70bb9adc77265..785358c92961662273f97f8f8c74037e03fc3cf3 100644 --- a/tests/base/test_roiset.py +++ b/tests/base/test_roiset.py @@ -11,7 +11,7 @@ from model_server.base.roiset import filter_df_overlap_bbox, filter_df_overlap_s from model_server.base.roiset import RoiSet from model_server.base.accessors import generate_file_accessor, InMemoryDataAccessor, write_accessor_data_to_file, PatchStack import model_server.conf.testing as conf -from tests.base.test_model import DummyInstanceSegmentationModel +from model_server.conf.testing import DummyInstanceSegmentationModel data = conf.meta['image_files'] output_path = conf.meta['output_path'] diff --git a/tests/base/test_roiset_derived.py b/tests/base/test_roiset_derived.py index 52e7f6fc0a917d719262dcd0c19f3170414f2f18..156ef9fe42c472dc829085ed3b8c26bb46ac003a 100644 --- a/tests/base/test_roiset_derived.py +++ b/tests/base/test_roiset_derived.py @@ -7,7 +7,7 @@ from model_server.base.roiset import RoiSetWithDerivedChannelsExportParams, RoiS from model_server.base.roiset import RoiSetWithDerivedChannels from model_server.base.accessors import generate_file_accessor, PatchStack import model_server.conf.testing as conf -from tests.base.test_model import DummyInstanceSegmentationModel +from model_server.conf.testing import DummyInstanceSegmentationModel data = conf.meta['image_files'] params = conf.meta['roiset']