diff --git a/model_server/base/accessors.py b/model_server/base/accessors.py index 3725cb23f9a133748accb8ca7b8333cd6782b6c8..7f1e763d2c7e15340fda7673e56deaa401b7af81 100644 --- a/model_server/base/accessors.py +++ b/model_server/base/accessors.py @@ -739,8 +739,8 @@ class PatchStack(InMemoryDataAccessor): df = pd.DataFrame([ { 'label': i, - 'area': (mask.iat_data(i).data > 0).sum(), - 'intensity_sum': (self.iat(i).data * (mask.iat_data(i).data > 0)).sum() + 'area': (mask.iat(i).data > 0).sum(), + 'intensity_sum': (self.iat(i).data * (mask.iat(i).data > 0)).sum() } for i in range(0, self.count) ]) df['intensity_mean'] = df['intensity_sum'] / df['area'] diff --git a/model_server/base/roiset.py b/model_server/base/roiset.py index 9b5a0360e22bd58bec118c10075455fcc647f6b4..f97f12a62cf5d871f59f998945f181bc900f1738 100644 --- a/model_server/base/roiset.py +++ b/model_server/base/roiset.py @@ -9,7 +9,7 @@ from uuid import uuid4 import glasbey import numpy as np import pandas as pd -from pydantic import BaseModel +from pydantic import BaseModel, Field from scipy.stats import moment from skimage.filters import sobel @@ -79,7 +79,10 @@ class RoiSetExportParams(BaseModel): object_classes: bool = False labels_overlay: Union[RoiSetLabelsOverlayParams, None] = None derived_channels: bool = False - make_unique_subdirectory: bool = False + write_patches_to_subdirectory: bool = Field( + False, + description='Write all patches to a subdirectory with prefix as name' + ) def get_label_ids(acc_seg_mask: GenericImageDataAccessor, allow_3d=False, connect_3d=True) -> InMemoryDataAccessor: @@ -963,49 +966,56 @@ class RoiSet(object): for k in params.dict().keys(): pr = prefix - subdir = where / k - if params.make_unique_subdirectory: + subdir = Path(k) + if 'patches' in k and params.write_patches_to_subdirectory: subdir = subdir / pr kp = params.dict()[k] if kp is None: continue if k == 'patches_3d': df_exp = self.export_patches( - subdir, prefix=pr, make_3d=True, **kp + where / subdir, prefix=pr, make_3d=True, **kp ) record[k] = [str(Path(k) / fn) for fn in df_exp.patch_path] if k == 'annotated_patches_2d': df_exp = self.export_patches( - subdir, prefix=pr, make_3d=False, **kp, + where / subdir, prefix=pr, make_3d=False, **kp, ) record[k] = [str(Path(k) / fn) for fn in df_exp.patch_path] if k == 'patches_2d': df_exp = self.export_patches( - subdir, prefix=pr, make_3d=False, **kp + where / subdir, prefix=pr, make_3d=False, **kp ) - self._df = self._df.join(df_exp.patch_path.apply(lambda x: str(Path('patches_2d') / x))) + self._df = self._df.join(df_exp.patch_path.apply(lambda x: str(subdir / x))) self._df['patch_id'] = self._df.apply(lambda _: uuid4(), axis=1) record[k] = [str(Path(k) / fn) for fn in df_exp.patch_path] if k == 'annotated_zstacks': - record[k] = str(Path(k) / self.export_annotated_zstack(subdir, prefix=pr, **kp)) + record[k] = str(Path(k) / self.export_annotated_zstack(where / subdir, prefix=pr, **kp)) if k == 'object_classes': for n in self.classification_columns: - fp = subdir / n / (pr + '.tif') + fp = where / subdir / n / (pr + '.tif') write_accessor_data_to_file(fp, self.get_object_class_map(n)) record[f'{k}_{n}'] = str(fp) if k == 'derived_channels': record[k] = [] for di, dacc in enumerate(self.accs_derived): - fp = subdir / f'dc{di:01d}.tif' + fp = where / subdir / f'dc{di:01d}.tif' fp.parent.mkdir(exist_ok=True, parents=True) dacc.export_pyxcz(fp) record[k].append(str(fp)) if k == 'labels_overlay': - fn = self.export_object_identities_overlay_map(subdir, prefix=prefix, **kp) + fn = self.export_object_identities_overlay_map(where / subdir, prefix=prefix, **kp) record[k] = str(Path(k) / fn) # export dataframe and patch masks - record = {**record, **self.serialize(where, prefix=prefix)} + record = { + **record, + **self.serialize( + where, + prefix=prefix, + write_patches_to_subdirectory=params.write_patches_to_subdirectory, + ), + } return record @@ -1042,25 +1052,29 @@ class RoiSet(object): return interm - def serialize(self, where: Path, prefix='roiset', allow_overwrite=True) -> dict: + def serialize(self, where: Path, prefix='roiset', allow_overwrite=True, write_patches_to_subdirectory=False) -> 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 :param prefix: (optional) prefix :param allow_overwrite: freely overwrite CSV file of same name if True + :param write_patches_to_subdirectory: if True, write all patches to a subdirectory with prefix as name :return: nested dict of Path objects describing the locations of export products """ record = {} if not self._df.binary_mask.apply(lambda x: np.all(x)).all(): # binary masks aren't just all True + subdir = Path('tight_patch_masks') + if write_patches_to_subdirectory: + subdir = subdir / prefix df_exp = self.export_patch_masks( - where / 'tight_patch_masks', + where / subdir, prefix=prefix, pad_to=None, expanded=False ) # record patch masks paths to dataframe, then save static columns to CSV se_pa = df_exp.patch_mask_path.apply( - lambda x: str(Path('tight_patch_masks') / x) + lambda x: str(subdir / x) ).rename('tight_patch_masks_path') self._df = self._df.join(se_pa) record['tight_patch_masks'] = list(se_pa) diff --git a/tests/base/test_roiset.py b/tests/base/test_roiset.py index 9fc1ff74da677b53a0c2b480bffeaf014dc8c00d..e024e68b3dafc2d05e6c13cd8b7eb0a2715e6e02 100644 --- a/tests/base/test_roiset.py +++ b/tests/base/test_roiset.py @@ -463,7 +463,10 @@ class TestRoiSetMultichannelProducts(BaseTestRoiSetMonoProducts, unittest.TestCa for k, v in res.items(): if isinstance(v, list): for f in v: - self.assertFalse(Path(f).is_absolute()) + try: + self.assertFalse(Path(f).is_absolute()) + except Exception as e: + print(e) self.assertTrue((where / f).exists()) else: self.assertFalse(Path(v).is_absolute())