--- a +++ b/slideslicer/cocohacks.py @@ -0,0 +1,127 @@ +import numpy as np +import json +from pycocotools.mask import encode, decode +from warnings import warn +from .slideutils import convert_contour2mask + + +def remove_upper_channel(lo, hi): + """ + take difference between two channels: + # rule: + lo ) 0 0 1 1 + up ) 0 1 0 1 + -> 0 0 1 0 + """ + lo = lo.astype(bool) + hi = hi.astype(bool) + return (lo ^ (lo & hi).astype(bool)).astype(bool) + + +def convert_cocorle2onehotmask(rois, tissuedict): + """constructs a dense mask given a list of `rois` + and a dictionary mapping roi names to channel + numbers in tissuedict are expected to start at one + as the default class is constructed + and assigned to zeroth channel + + Inputs + rois : an MS-COCO formated list with RLE 'counts' + tissuedict : a mapping from roi names to integers, + can come in 2 possible formats: + + dictionary : {'tissue_1': 1, 'tissue_2': 2, ...} + + list : ['tissue_1', 'tissue_2', ... ] + + Calls `pycocotools.mask.decode` + """ + if isinstance(tissuedict, list): + tissuedict = {xx: ii+1 for ii, xx in enumerate(tissuedict)} + + nchannels = 1+max(tissuedict.values()) + maskarr = np.zeros(rois[-1]["size"] + [nchannels], dtype=bool) + + for roi_ in rois: + mask = decode(roi_) + name = roi_["name"] + if name in tissuedict: + channel = tissuedict[name] + maskarr[..., channel] |= mask.astype(bool) + + for nn in range(maskarr.shape[-1]-2, 0, -1): + maskarr[..., nn] = remove_upper_channel( + maskarr[..., nn], + maskarr[...,nn+1:].any(-1) + ) + maskarr[..., 0] = ~maskarr[...,1:].any(-1) + + if not maskarr.sum(-1).max() == 1: + print("maskarr.sum(-1).max()", maskarr.sum(-1).max()) + raise ValueError() + + return maskarr + + +def convert_cocorle2intmask(rois, tissuedict): + """constructs an interger mask given a list of `rois` + and a dictionary mapping roi names to channel + numbers in tissuedict are expected to start at one + as the default class is constructed + and assigned to zeroth channel + + Inputs + rois : an MS-COCO formated list with RLE 'counts' + tissuedict : a mapping from roi names to integers, + can come in 2 possible formats: + + dictionary : {'tissue_1': 1, 'tissue_2': 2, ...} + + list : ['tissue_1', 'tissue_2', ... ] + + Calls `pycocotools.mask.decode` + """ + if isinstance(tissuedict, list): + tissuedict = {xx: ii+1 for ii, xx in enumerate(tissuedict)} + + nchannels = 1+max(tissuedict.values()) + maskarr = np.zeros(rois[-1]["size"], dtype=bool) + + for roi_ in rois: + mask = decode(roi_) + name = roi_["name"] + if name in tissuedict: + channel = tissuedict[name] + maskarr = np.maximum(maskarr, channel*mask.astype(np.uint8)) + return maskarr + + +def convert_contour2cocorle(verts, w, h, format=None): + mask = convert_contour2mask(verts, w,h, order='F')[...,np.newaxis] + entry = encode(mask)[0] + if format is str: + entry['counts'] = entry['counts'].decode('ascii') + return entry + + +def construct_sparse_mask(*args): + warn('use convert_vertices2intmask instead of construct_sparse_mask', + DeprecationWarning) + return construct_sparse_mask(*args) + +def construct_dense_mask(rois, tissuedict): + warn('use convert_cocorle2onehotmask instead of construct_dense_mask', + DeprecationWarning) + return convert_cocorle2onehotmask(rois, tissuedict) + +def dense_to_sparse(maskarr): + return (np.arange(maskarr.shape[-1]).reshape([1,1,-1]) * + maskarr).sum(-1) + + +def read_roi_to_sparse(jsonfile, roidict): + with open(jsonfile) as fh: + rois = json.load(fh) + return construct_sparse_mask(rois, roidict) + + +def read_roi_to_dense(jsonfile, roidict): + with open(jsonfile) as fh: + rois = json.load(fh) + return construct_dense_mask(rois, roidict)