def test_prep(girderClient): # noqa cfg.gc = girderClient iteminfo = cfg.gc.get('/item', parameters={'text': "TCGA-A2-A0YE-01Z-00-DX1"})[0] # get RGB region at a small magnification MAG = 1.5 getStr = "/item/%s/tiles/region?left=%d&right=%d&top=%d&bottom=%d" % ( iteminfo['_id'], 46890, 50000, 40350, 43000) + "&magnification=%.2f" % MAG cfg.tissue_rgb = get_image_from_htk_response( cfg.gc.get(getStr, jsonResp=False)) # get mask of things to ignore cfg.mask_out, _ = get_tissue_mask(cfg.tissue_rgb, deconvolve_first=False, n_thresholding_steps=1, sigma=1.5, min_size=30) cfg.mask_out = resize(cfg.mask_out == 0, output_shape=cfg.tissue_rgb.shape[:2], order=0, preserve_range=True) == 1
def _get_rgb_and_pad_roi(gc, slide_id, bounds, appendStr, ROI): getStr = \ "/item/%s/tiles/region?left=%d&right=%d&top=%d&bottom=%d" \ % (slide_id, bounds['XMIN'], bounds['XMAX'], bounds['YMIN'], bounds['YMAX']) getStr += appendStr resp = gc.get(getStr, jsonResp=False) rgb = get_image_from_htk_response(resp) # sometimes there's a couple of pixel difference d.t. rounding, so pad pad_y = rgb.shape[0] - ROI.shape[0] pad_x = rgb.shape[1] - ROI.shape[1] assert all([np.abs(j) < 4 for j in (pad_y, pad_x)]), \ "too much difference in size between image and mask."\ "something is wrong!" if pad_y > 0: ROI = np.pad(ROI, pad_width=((0, pad_y), (0, 0)), mode='constant') elif pad_y < 0: ROI = ROI[:pad_y, :] if pad_x > 0: ROI = np.pad(ROI, pad_width=((0, 0), (0, pad_x)), mode='constant') elif pad_x < 0: ROI = ROI[:, :pad_x] return rgb, ROI
def test_get_image_from_htk_response(self): """Test get_image_from_htk_response.""" getStr = "/item/%s/tiles/region?left=%d&right=%d&top=%d&bottom=%d" % ( cfg.iteminfo['_id'], 59000, 59100, 35000, 35100) resp = cfg.gc.get(getStr, jsonResp=False) rgb = get_image_from_htk_response(resp) assert rgb.shape == (100, 100, 3)
def set_tissue_rgb(self): """Load RGB from server for single tissue piece.""" # load RGB for this tissue piece at saliency magnification getStr = "/item/%s/tiles/region?left=%d&right=%d&top=%d&bottom=%d&encoding=PNG" % ( self.cdt.slide_id, self.xmin, self.xmax, self.ymin, self.ymax) + "&magnification=%d" % self.cdt.MAG resp = self.cdt.gc.get(getStr, jsonResp=False) self.tissue_rgb = get_image_from_htk_response(resp)
def test_get_image_from_htk_response(self): """Test get_image_from_htk_response.""" getStr = "/item/%s/tiles/region?left=%d&right=%d&top=%d&bottom=%d" % ( SAMPLE_SLIDE_ID, 59000, 59100, 35000, 35100) resp = gc.get(getStr, jsonResp=False) rgb = get_image_from_htk_response(resp) self.assertTupleEqual(rgb.shape, (100, 100, 3))
def _get_rgb_for_interrater(gc, bounds, slide_id): """""" getStr = \ "/item/%s/tiles/region?left=%d&right=%d&top=%d&bottom=%d" \ % (slide_id, bounds['XMIN'], bounds['XMAX'], bounds['YMIN'], bounds['YMAX']) getStr += bounds['appendStr'] resp = gc.get(getStr, jsonResp=False) rgb = get_image_from_htk_response(resp) return rgb
def set_tissue_rgb(self): """Load RGB from server for single tissue piece.""" # load RGB for this tissue piece at saliency magnification getStr = "/item/%s/tiles/region?left=%d&right=%d&top=%d&bottom=%d&encoding=PNG" % ( self.cd.slide_id, self.xmin, self.xmax, self.ymin, self.ymax) + "&magnification=%d" % self.cd.MAG resp = self.cd.gc.get(getStr, jsonResp=False) self.tissue_rgb = get_image_from_htk_response(resp) # color normalization if desired if 'main' in self.cd.cnorm_params.keys(): self.tissue_rgb = np.uint8( reinhard(im_src=self.tissue_rgb, target_mu=self.cd.cnorm_params['main']['mu'], target_sigma=self.cd.cnorm_params['main']['sigma']))
def get_slide_thumbnail(gc, slide_id): """Get slide thumbnail using girder client. Parameters ------------- gc : object girder client to use slide_id : str girder ID of slide Returns --------- np array RGB slide thumbnail at lowest level """ getStr = "/item/%s/tiles/thumbnail" % (slide_id) resp = gc.get(getStr, jsonResp=False) return get_image_from_htk_response(resp)
def _get_visualization_zoomout( gc, slide_id, bounds, MPP, MAG, zoomout=4): """Get a zoomed out visualization of ROI RGB and annotation overlay. Parameters ---------- gc : girder_client.Girder_Client authenticated girder client slide_id : str girder ID of slide bounds : dict bounds of the region of interest. Must contain the keys XMIN, XMAX, YMIN, YMAX MPP : float Microns per pixel. MAG : float Magnification. MPP overrides this. zoomout : float how much to zoom out Returns ------- np.array Zoomed out visualization. Outpu from _visualize_annotations_on_rgb(). """ # get append string for server request if MPP is not None: getsf_kwargs = { 'MPP': MPP * (zoomout + 1), 'MAG': None, } elif MAG is not None: getsf_kwargs = { 'MPP': None, 'MAG': MAG / (zoomout + 1), } else: getsf_kwargs = { 'MPP': None, 'MAG': None, } sf, appendStr = get_scale_factor_and_appendStr( gc=gc, slide_id=slide_id, **getsf_kwargs) # now get low-magnification surrounding field x_margin = (bounds['XMAX'] - bounds['XMIN']) * zoomout / 2 y_margin = (bounds['YMAX'] - bounds['YMIN']) * zoomout / 2 getStr = \ "/item/%s/tiles/region?left=%d&right=%d&top=%d&bottom=%d" \ % (slide_id, max(0, bounds['XMIN'] - x_margin), bounds['XMAX'] + x_margin, max(0, bounds['YMIN'] - y_margin), bounds['YMAX'] + y_margin) getStr += appendStr resp = gc.get(getStr, jsonResp=False) rgb_zoomout = get_image_from_htk_response(resp) # plot a bounding box at the ROI region xmin = x_margin * sf xmax = xmin + (bounds['XMAX'] - bounds['XMIN']) * sf ymin = y_margin * sf ymax = ymin + (bounds['YMAX'] - bounds['YMIN']) * sf xmin, xmax, ymin, ymax = [str(int(j)) for j in (xmin, xmax, ymin, ymax)] contours_list = [{ 'color': 'rgb(255,255,0)', 'coords_x': ",".join([xmin, xmax, xmax, xmin, xmin]), 'coords_y': ",".join([ymin, ymin, ymax, ymax, ymin]), }] return _visualize_annotations_on_rgb(rgb_zoomout, contours_list)
def annotations_to_contours_no_mask(gc, slide_id, MPP=5.0, MAG=None, mode='min_bounding_box', bounds=None, idx_for_roi=None, slide_annotations=None, element_infos=None, linewidth=0.2, get_rgb=True, get_visualization=True, text=True): """Process annotations to get RGB and contours without intermediate masks. Parameters ---------- gc : object girder client object to make requests, for example: gc = girder_client.GirderClient(apiUrl = APIURL) gc.authenticate(interactive=True) slide_id : str girder id for item (slide) MPP : float or None Microns-per-pixel -- best use this as it's more well-defined than magnification which is more scanner or manufacturer specific. MPP of 0.25 often roughly translates to 40x MAG : float or None If you prefer to use whatever magnification is reported in slide. If neither MPP or MAG is provided, everything is retrieved without scaling at base (scan) magnification. mode : str This specifies which part of the slide to get the mask from. Allowed modes include the following - wsi: get scaled up or down version of mask of whole slide - min_bounding_box: get minimum box for all annotations in slide - manual_bounds: use given ROI bounds provided by the 'bounds' param - polygonal_bounds: use the idx_for_roi param to get coordinates bounds : dict or None if not None, has keys 'XMIN', 'XMAX', 'YMIN', 'YMAX' for slide region coordinates (AT BASE MAGNIFICATION) to get labeled image (mask) for. Use this with the 'manual_bounds' run mode. idx_for_roi : int index of ROI within the element_infos dataframe. Use this with the 'polygonal_bounds' run mode. slide_annotations : list or None Give this parameter to avoid re-getting slide annotations. If you do provide the annotations, though, make sure you have used scale_slide_annotations() to scale them up or down by sf BEFOREHAND. element_infos : pandas DataFrame. The columns annidx and elementidx encode the dict index of annotation document and element, respectively, in the original slide_annotations list of dictionaries. This can be obained by get_bboxes_from_slide_annotations() method. Make sure you have used scale_slide_annotations(). linewidth : float visualization line width get_rgb: bool get rgb image? get_visualization : bool get overlayed annotation bounds over RGB for visualization text : bool add text labels to visualization? Returns -------- dict Results dict containing one or more of the following keys - bounds: dict of bounds at scan magnification - rgb: (mxnx3 np array) corresponding rgb image - contours: dict - visualization: (mxnx3 np array) visualization overlay """ MPP, MAG, mode, bounds, idx_for_roi, get_rgb, get_visualization = \ _sanity_checks( MPP, MAG, mode, bounds, idx_for_roi, get_rgb, get_visualization) # calculate the scale factor sf, appendStr = get_scale_factor_and_appendStr(gc=gc, slide_id=slide_id, MPP=MPP, MAG=MAG) if slide_annotations is not None: assert element_infos is not None, "must also provide element_infos" else: # get annotations for slide slide_annotations = gc.get('/annotation/item/' + slide_id) # scale up/down annotations by a factor slide_annotations = scale_slide_annotations(slide_annotations, sf=sf) # get bounding box information for all annotations -> scaled by sf element_infos = get_bboxes_from_slide_annotations(slide_annotations) # Determine get region based on run mode, keeping in mind that it # must be at BASE MAGNIFICATION coordinates before it is passed # on to get_mask_from_slide() # if mode != 'polygonal_bound': bounds = _get_roi_bounds_by_run_mode(gc=gc, slide_id=slide_id, mode=mode, bounds=bounds, element_infos=element_infos, idx_for_roi=idx_for_roi, sf=sf) # only keep relevant elements and get uncropped bounds elinfos_roi, uncropped_bounds = _keep_relevant_elements_for_roi( element_infos, sf=sf, mode=mode, idx_for_roi=idx_for_roi, roiinfo=copy.deepcopy(bounds)) # find relevant portion from slide annotations to use # (with overflowing beyond edge) annotations_slice = _trim_slide_annotations_to_roi( copy.deepcopy(slide_annotations), elinfos_roi=elinfos_roi) # get roi polygon vertices rescaled_bounds = {k: int(v * sf) for k, v in bounds.items()} if mode == 'polygonal_bounds': roi_coords = _get_coords_from_element( copy.deepcopy(slide_annotations[int( element_infos.loc[idx_for_roi, 'annidx'])]['annotation']['elements'][int( element_infos.loc[idx_for_roi, 'elementidx'])])) cropping_bounds = None else: roi_coords = None cropping_bounds = rescaled_bounds # tabularize to use contours _, contours_df = parse_slide_annotations_into_tables( annotations_slice, cropping_bounds=cropping_bounds, cropping_polygon_vertices=roi_coords, use_shapely=mode in ('manual_bounds', 'polygonal_bounds'), ) contours_list = contours_df.to_dict(orient='records') # Final bounds (relative to slide at base magnification) bounds = {k: int(v / sf) for k, v in rescaled_bounds.items()} result = dict() # get RGB if get_rgb: getStr = \ "/item/%s/tiles/region?left=%d&right=%d&top=%d&bottom=%d&encoding=PNG" \ % (slide_id, bounds['XMIN'], bounds['XMAX'], bounds['YMIN'], bounds['YMAX']) getStr += appendStr resp = gc.get(getStr, jsonResp=False) rgb = get_image_from_htk_response(resp) result['rgb'] = rgb # Assign to results result.update({ 'contours': contours_list, 'bounds': bounds, }) # get visualization of annotations on RGB if get_visualization: result['visualization'] = _visualize_annotations_on_rgb( rgb=rgb, contours_list=contours_list, linewidth=linewidth, text=text) return result
def test_reinhard(self): """Test reinhard.""" # get RGB image at a small magnification slide_info = gc.get('item/%s/tiles' % SAMPLE_SLIDE_ID) getStr = "/item/%s/tiles/region?left=%d&right=%d&top=%d&bottom=%d" % ( SAMPLE_SLIDE_ID, 0, slide_info['sizeX'], 0, slide_info['sizeY'] ) + "&magnification=%.2f" % MAG tissue_rgb = get_image_from_htk_response( gc.get(getStr, jsonResp=False)) # # SANITY CHECK! normalize to LAB mean and std from SAME slide # mean_lab, std_lab = lab_mean_std(tissue_rgb) # tissue_rgb_normalized = reinhard( # tissue_rgb, target_mu=mean_lab, target_sigma=std_lab) # # # we expect the images to be (almost) exactly the same # assert np.mean(tissue_rgb - tissue_rgb_normalized) < 1 # Normalize to pre-set color standard tissue_rgb_normalized = reinhard( tissue_rgb, target_mu=cnorm['mu'], target_sigma=cnorm['sigma']) # check that it matches mean_lab, std_lab = lab_mean_std(tissue_rgb_normalized) self.assertTrue(all( np.abs(mean_lab - cnorm['mu']) < [0.1, 0.1, 0.1])) self.assertTrue(all( np.abs(std_lab - cnorm['sigma']) < [0.1, 0.1, 0.1])) # get tissue mask thumbnail_rgb = get_slide_thumbnail(gc, SAMPLE_SLIDE_ID) labeled, mask = get_tissue_mask( thumbnail_rgb, deconvolve_first=True, n_thresholding_steps=1, sigma=1.5, min_size=30) # # visualize result # vals = np.random.rand(256, 3) # vals[0, ...] = [0.9, 0.9, 0.9] # cMap = ListedColormap(1 - vals) # # f, ax = plt.subplots(1, 3, figsize=(20, 20)) # ax[0].imshow(thumbnail_rgb) # ax[1].imshow(labeled, cmap=cMap) # ax[2].imshow(mask, cmap=cMap) # plt.show() # Do MASKED normalization to preset standard mask_out = resize( labeled == 0, output_shape=tissue_rgb.shape[:2], order=0, preserve_range=True) == 1 tissue_rgb_normalized = reinhard( tissue_rgb, target_mu=cnorm['mu'], target_sigma=cnorm['sigma'], mask_out=mask_out) # check that it matches mean_lab, std_lab = lab_mean_std( tissue_rgb_normalized, mask_out=mask_out) self.assertTrue(all( np.abs(mean_lab - cnorm['mu']) < [0.1, 0.1, 0.1])) self.assertTrue(all( np.abs(std_lab - cnorm['sigma']) < [0.1, 0.1, 0.1]))
# and using reordered such that columns are the order: # Hamtoxylin, Eosin, Null W_target = np.array([[0.5807549, 0.08314027, 0.08213795], [0.71681094, 0.90081588, 0.41999816], [0.38588316, 0.42616716, -0.90380025]]) # %%=========================================================================== print("Getting images to be normalized ...") # get RGB image at a small magnification slide_info = gc.get('item/%s/tiles' % SAMPLE_SLIDE_ID) getStr = "/item/%s/tiles/region?left=%d&right=%d&top=%d&bottom=%d" % ( SAMPLE_SLIDE_ID, 0, slide_info['sizeX'], 0, slide_info['sizeY']) + "&magnification=%.2f" % MAG tissue_rgb = get_image_from_htk_response(gc.get(getStr, jsonResp=False)) # get mask of things to ignore thumbnail_rgb = get_slide_thumbnail(gc, SAMPLE_SLIDE_ID) mask_out, _ = get_tissue_mask(thumbnail_rgb, deconvolve_first=True, n_thresholding_steps=1, sigma=1.5, min_size=30) mask_out = resize(mask_out == 0, output_shape=tissue_rgb.shape[:2], order=0, preserve_range=True) == 1 # since this is a unit test, just work on a small image tissue_rgb = tissue_rgb[1000:1500, 2500:3000, :]