def _mask_anomalies_im(im, den_threshold=300): """ Operates on pre-balanced images. The den_threshold of 300 was found empirically on Val data Sets anomalies to nan """ import skimage.transform # Defer slow imports import cv2 check.array_t(im, is_square=True) # SLICE into square using numpy-foo by reshaping the image # into a four-dimensional array can then by np.mean on the inner dimensions. sub_mea = 4 # Size of the sub-sample region im_mea, _ = im.shape squares = im.reshape(im_mea // sub_mea, sub_mea, im_mea // sub_mea, sub_mea) # At this point, im is now 4-dimensional like: (256, 2, 256, 2) # But we want the small_dims next to each other for simplicity so swap the inner axes squares = squares.swapaxes(1, 2) # Now squares is (256, 256, 2, 2.) # squares is like: 256, 256, 2, 2. So we need the mean of the last two axes squares = np.mean(squares, axis=(2, 3)) bad_mask = (squares > den_threshold).astype(float) # EXPAND the bad areas by erosion and dilate. # Erosion gets rid of the single-pixel hits and dilation expands the bad areas kernel = np.ones((3, 3), np.uint8) mask = cv2.erode(bad_mask, kernel, iterations=1) mask = cv2.dilate(mask, kernel, iterations=3) scale = im.shape[0] // mask.shape[0] full_size_mask = skimage.transform.rescale( mask, scale=scale, multichannel=False, mode="constant", anti_aliasing=False).astype(bool) # FIND rect contours of bad areas contours, hierarchy = cv2.findContours(full_size_mask.astype("uint8"), cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE) bad_rects = [cv2.boundingRect(cnt) for cnt in contours] im = im.copy() for rect in bad_rects: imops.fill(im, loc=XY(rect[0], rect[1]), dim=WH(rect[2], rect[3]), val=np.nan) return im
def it_fills(): im = np.ones((3, 3)) im = imops.edge_fill(im, 1) # fmt: off assert im.tolist() == [ [0.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 0.0] ] # fmt: on dst = np.ones(WH(4, 4)) imops.fill(dst, loc=XY(1, 1), dim=WH(10, 10)) good = np.array([[1, 1, 1, 1], [1, 0, 0, 0], [1, 0, 0, 0], [1, 0, 0, 0]]) assert (dst == good).all()
def _mask_anomalies(cy_ims, bad_rects_by_cycle): """ Given a cycle stack of images and the list of bad rects, fill all these rects with background noise so that the aligner won't be confused by those anomalies. Arguments: cy_ims: array (n_cycles, height, width) bad_rects_by_cycle: List of bad rects for each cycle Returns: A copy of cy_ims with the bad rects masked with noise """ assert cy_ims.ndim == 3 and cy_ims.shape[1] == cy_ims.shape[2] n_cycles, _, _ = cy_ims.shape masked_ims = np.zeros_like(cy_ims) for cy in range(n_cycles): src_im = cy_ims[cy] bad_rects = bad_rects_by_cycle[cy] # MAKE a mask_im with 0 inside bad rects, 1 otherwise mask_im = np.ones_like(src_im) for rect in bad_rects: imops.fill(mask_im, loc=XY(rect[0], rect[1]), dim=WH(rect[2], rect[3]), val=0) # FIND the characteristics of a normal distribution that fits the # data that is not masked out (that is, we don't want the anomalies # in this distribution). If src_im is entirely masked, mean=std=0. # TASK: This could be accelerated by subsampling. mean = std = 0 if np.any(mask_im): mean, std = norm.fit(src_im[mask_im > 0]) bg_noise = norm.rvs(loc=mean, scale=std, size=src_im.shape, random_state=None) masked_ims[cy] = np.where(mask_im < 1, bg_noise, src_im) return masked_ims
def it_fills_with_clipping(): dst = np.ones(WH(4, 4)) imops.fill(dst, loc=XY(1, 1), dim=WH(10, 10)) good = np.array([[1, 1, 1, 1], [1, 0, 0, 0], [1, 0, 0, 0], [1, 0, 0, 0]]) assert (dst == good).all()