diff --git a/model_server/extensions/chaeo/zmask.py b/model_server/extensions/chaeo/zmask.py index a457516b17cad744797af6305aa444f34d514e66..f77ac431ab9cd5fc67f6ef5a83809e5153df75da 100644 --- a/model_server/extensions/chaeo/zmask.py +++ b/model_server/extensions/chaeo/zmask.py @@ -32,9 +32,41 @@ class RoiSet(object): acc_raw: GenericImageDataAccessor, params: RoiSetMetaParams = RoiSetMetaParams(), ): - self.zmask_meta, self.df, self.interm = build_zmask_from_object_mask( + # parse filters + filters = params.filters + query_str = 'label > 0' # always true + if filters is not None: + for k, val in filters.dict(exclude_unset=True).items(): + assert k in ('area', 'solidity') + vmin = val['min'] + vmax = val['max'] + assert vmin >= 0 + query_str = query_str + f' & {k} > {vmin} & {k} < {vmax}' + + # build dataframe of objects, assign z index to each object + argmax = acc_raw.data.argmax(axis=3, keepdims=True)[:, :, 0, 0].astype('uint16') + df = ( + pd.DataFrame( + regionprops_table( + acc_obj_ids, + intensity_image=argmax, + properties=('label', 'area', 'intensity_mean', 'solidity', 'bbox', 'centroid') + ) + ) + .rename( + columns={'bbox-0': 'y0', 'bbox-1': 'x0', 'bbox-2': 'y1', 'bbox-3': 'x1',} + ) + ) + df['zi'] = df['intensity_mean'].round().astype('int') + df['keeper'] = False + df.loc[df.query(query_str).index, 'keeper'] = True + self.df = df + + # remaining zmask_meta write ops + self.zmask_meta, _, self.interm = build_zmask_from_object_mask( acc_obj_ids, acc_raw, + df, params=params, ) self.acc_obj_ids = acc_obj_ids @@ -61,6 +93,19 @@ class RoiSet(object): def get_object_mask_by_class(self, class_id): return self.object_id_labels == class_id + + + # def loc_mask(self, i): + # # compute contours + # # obmask = (lamap == ob.label) # TODO: on-the-fly + # # contour = find_contours(obmask) # TODO: on-the-fly + # # mask = obmask[ob.y0: ob.y1, ob.x0: ob.x1] + # ob = self.df.loc[i] + # return + # + # def loc_contour(self, i): + # pass + def get_patch_masks(self, **kwargs) -> MonoPatchStack: return get_patch_masks(self, **kwargs) @@ -177,68 +222,40 @@ class RoiSet(object): def build_zmask_from_object_mask( obmask: GenericImageDataAccessor, zstack: GenericImageDataAccessor, + df, params: RoiSetMetaParams = RoiSetMetaParams(), ): - """ - Given a 2D mask of objects, build a 3D mask, where each object's z-position is determined by the index of - maximum intensity in z. Return this zmask and a list of each object's meta information. - :param obmask: GenericImageDataAccessor 2D map of objects IDs - :param zstack: GenericImageDataAccessor monochrome zstack of same Y, X dimension as obmask - :param params: RoiSetMetaParams - filters: dictionary of form {attribute: (min, max)}; valid attributes are 'area' and 'solidity' - mask_type: if 'boxes', zmask is True in each object's complete bounding box; otherwise 'contours' - expand_box_by: (xy, z) expands bounding box by (xy, z) pixels except where this hits a boundary - :return: tuple (zmask, meta) - np.ndarray: - boolean mask of same size as stack - List containing one Dict per object, with keys: - info: object's properties from skimage.measure.regionprops_table, including bounding box (y0, y1, x0, x1) - slice: named slice (np.s_) of (optionally) expanded bounding box - relative_bounding_box: bounding box (y0, y1, x0, x1) in relative frame of (optionally) expanded bounding box - contour: object's contour returned by skimage.measure.find_contours - mask: mask of object in relative frame of (optionally) expanded bounding box - pd.DataFrame: objects, including bounding, box information after filtering - Dict of intermediate image products: - label_map: np.ndarray (h x w) where each unique object has an integer label - argmax: np.ndarray (h x w x 1 x 1) z-index of highest intensity in zstack - """ + # """ + # Given a 2D mask of objects, build a 3D mask, where each object's z-position is determined by the index of + # maximum intensity in z. Return this zmask and a list of each object's meta information. + # :param obmask: GenericImageDataAccessor 2D map of objects IDs + # :param zstack: GenericImageDataAccessor monochrome zstack of same Y, X dimension as obmask + # :param params: RoiSetMetaParams + # filters: dictionary of form {attribute: (min, max)}; valid attributes are 'area' and 'solidity' + # mask_type: if 'boxes', zmask is True in each object's complete bounding box; otherwise 'contours' + # expand_box_by: (xy, z) expands bounding box by (xy, z) pixels except where this hits a boundary + # :return: tuple (zmask, meta) + # np.ndarray: + # boolean mask of same size as stack + # List containing one Dict per object, with keys: + # info: object's properties from skimage.measure.regionprops_table, including bounding box (y0, y1, x0, x1) + # slice: named slice (np.s_) of (optionally) expanded bounding box + # relative_bounding_box: bounding box (y0, y1, x0, x1) in relative frame of (optionally) expanded bounding box + # contour: object's contour returned by skimage.measure.find_contours + # mask: mask of object in relative frame of (optionally) expanded bounding box + # pd.DataFrame: objects, including bounding, box information after filtering + # Dict of intermediate image products: + # label_map: np.ndarray (h x w) where each unique object has an integer label + # argmax: np.ndarray (h x w x 1 x 1) z-index of highest intensity in zstack + # """ filters = params.filters expand_box_by = params.expand_box_by # validate inputs assert zstack.hw == obmask.shape lamap = obmask - query_str = 'label > 0' # always true - if filters is not None: - for k, val in filters.dict(exclude_unset=True).items(): - assert k in ('area', 'solidity') - vmin = val['min'] - vmax = val['max'] - assert vmin >= 0 - query_str = query_str + f' & {k} > {vmin} & {k} < {vmax}' - - # build dataframe of objects, assign z index to each object + argmax = zstack.data.argmax(axis=3, keepdims=True)[:, :, 0, 0].astype('uint16') - df = ( - pd.DataFrame( - regionprops_table( - lamap, - intensity_image=argmax, - properties=('label', 'area', 'intensity_mean', 'solidity', 'bbox', 'centroid') - ) - ) - .rename( - columns={ - 'bbox-0': 'y0', - 'bbox-1': 'x0', - 'bbox-2': 'y1', - 'bbox-3': 'x1', - } - ) - ) - df['zi'] = df['intensity_mean'].round().astype('int') - df['keeper'] = False - df.loc[df.query(query_str).index, 'keeper'] = True # convert bounding boxes to numpy slice objects ebxy, ebz = expand_box_by