def test_detect_format(self): self.assertTrue( OpenSlide.detect_format(file_path('__missing_file')) is None) self.assertTrue( OpenSlide.detect_format(file_path('../setup.py')) is None) self.assertEqual( OpenSlide.detect_format(file_path('boxes.tiff')), 'generic-tiff')
def test_operations_on_closed_handle(self): osr = OpenSlide(file_path('boxes.tiff')) props = osr.properties associated = osr.associated_images osr.close() self.assertRaises(ArgumentError, lambda: osr.read_region((0, 0), 0, (100, 100))) self.assertRaises(ArgumentError, lambda: osr.close()) self.assertRaises(ArgumentError, lambda: props['openslide.vendor']) self.assertRaises(ArgumentError, lambda: associated['label'])
def __init__(self, root): pn = '/Users/nathaning/Documents/Python/Image Processing/B2 H&E 20X_001.svs' level = 1 i = OpenSlide(pn) # Load an SVS file # dims = i.level_dimensions[level] # k = i.read_region((0,0), level, dims) #Extract the level 1 image as a PIL.Image Object k = i.get_thumbnail(size=(600, 600)) # k.show() self.pi = ImageTk.PhotoImage(k) disp = Label(root, image=self.pi) disp.pack() print 'done'
def read_wsi_normal(wsi_path): """ # ===================================================================================== # read WSI image and resize # Due to memory constraint, we use down sampled (4th level, 1/32 resolution) image # ====================================================================================== """ try: wsi_image = OpenSlide(wsi_path) level_used = wsi_image.level_count - 1 rgb_image = np.array(wsi_image.read_region((0, 0), level_used, wsi_image.level_dimensions[level_used])) except OpenSlideUnsupportedFormatError: print('Exception: OpenSlideUnsupportedFormatError') return None, None, None return wsi_image, rgb_image, level_used
def read_tumor_wsi(self, wsi_path, mask_path): """ # ===================================================================================== # read WSI image and resize # Due to memory constraint, we use down sampled (4th level, 1/32 resolution) image # ====================================================================================== """ try: self.wsi_path = wsi_path self.wsi_image = OpenSlide(wsi_path) self.mask_image = OpenSlide(mask_path) level_used = self.wsi_image.level_count - 1 self.rgb_image_pil = self.wsi_image.read_region((0, 0), level_used, self.wsi_image.level_dimensions[level_used]) self.rgb_image = np.array(self.rgb_image_pil) mask_level = self.mask_image.level_count - 1 self.rgb_mask_pil = self.mask_image.read_region((0, 0), mask_level, self.mask_image.level_dimensions[mask_level]) resize_factor = float(1.0 / pow(2, level_used - mask_level)) self.mask = cv2.resize(np.array(self.rgb_mask_pil), (0, 0), fx=resize_factor, fy=resize_factor) # self.rgb_image = cv2.resize(self.rgb_image, (0, 0), fx=0.50, fy=0.50) except OpenSlideUnsupportedFormatError: print('Exception: OpenSlideUnsupportedFormatError') return False return True
def read_wsi_mask(mask_path, level=def_level): try: wsi_mask = OpenSlide(mask_path) mask_image = np.array(wsi_mask.read_region((0, 0), level, wsi_mask.level_dimensions[level])) except OpenSlideUnsupportedFormatError: print('Exception: OpenSlideUnsupportedFormatError') return None, None return wsi_mask, mask_image
def next_batch(self, n, **kwargs): assert self.initialized == True, "Data Source not initialized" flat = 'flat' in kwargs and kwargs['flat'] is True noise = 'noise' in kwargs and kwargs['noise'] is True nc = kwargs['nc'] if 'nc' in kwargs else 0.05 level = kwargs['level'] if 'level' in kwargs else 0 isfloat = kwargs['isfloat'] if 'isfloat' in kwargs else False selected_image = np.random.randint(len(self.images)) chunks = np.random.random((3,n)) im_path = self.images[selected_image][0] osr = OpenSlide(im_path) mask = self.masks[selected_image] imsize = osr.level_dimensions[level] ratios = [imsize[0]*1./mask.shape[0], imsize[1]*1./mask.shape[1]] n_px_in_mask = mask.sum() indexes = np.arange(n_px_in_mask)+1 mask_indexes = np.zeros(mask.shape) mask_indexes[mask] = indexes chunks[0,:] *= n_px_in_mask # index in mask chunks[1,:] *= (ratios[0]-self.chunksize[0]) # y pixel offset in higher def image from top-left of region chunks[2,:] *= (ratios[1]-self.chunksize[1]) # x pixel offset in higher def image from top-left of region if isfloat: chunks = chunks.astype('float') if chunks.max() > 1: chunks = chunks / 255. else: if chunks.max() < 1: chunks = chunks*255 chunks = np.round(chunks).astype('uint16') lowres_coordinates = [np.where(mask_indexes==c) for c in chunks[0,:]] highres_coordinates = [ ( int((lr[0]*ratios[0] + chunks[1,i])[0]), int((lr[1]*ratios[1] + chunks[2,i])[0]) ) for i,lr in enumerate(lowres_coordinates) ] if noise: if flat: toAdd = np.random.rand(n, self.chunksize[0]*self.chunksize[1]*3)*nc else: toAdd = np.random.rand(n, self.chunksize[0], self.chunksize[1], 3)*nc else: if flat: toAdd = np.zeros((n, self.chunksize[0]*self.chunksize[1]*3)) else: toAdd = np.zeros((n, self.chunksize[0], self.chunksize[1], 3)) if flat : return [np.asarray(osr.read_region((chunk[0], chunk[1]), level, self.chunksize)).flatten().astype(np.float32)[:,:,:3]/255.+toAdd[i] for i,chunk in enumerate(highres_coordinates)] else: return [np.asarray(osr.read_region((chunk[0], chunk[1]), level, self.chunksize)).astype(np.float32)[:,:,:3]/255.+toAdd[i] for i,chunk in enumerate(highres_coordinates)]
def initUI(self): # initialize the widget, make the openslide object pointing to the # multilevel image and initialize the view to the top left corner archivo=u'/home/martin/Downloads/CMU-1-JP2K-33005.svs' # openSlide object self.osr = OS(archivo) self.level_count = self.osr.level_count self.current_x=0 self.current_y=0 self.current_zoom=self.level_count-1 self.level_dimensions=self.osr.level_dimensions print self.level_dimensions print self.osr.level_downsamples #width, height = osr.dimensions self.hbox.addWidget(self.lbl) #self.move(300, 200) self.updatePixmap() self.show()
def get(self, id): """ Get slide thumbnail --- tags: - Thumbnail parameters: - in: path name: id description: MonogDB ObjectId -- Example 57bf3c092f9b2e1595b29730 type: string - in: query name: size description: Thumbnail size [small, medium, large] type: string responses: 200: description: Returns the slide information 404: description: Invalid slide Id or slide not found """ if not ObjectId.is_valid(id): resp = {"status": 404, "message": "Invalid slide Id " + id} return Response(dumps(resp), status=404, mimetype='application/json') thumbSize = request.args.get("size", "small") image = self.slides.find_one({'_id': ObjectId(id)}) path = image["path"] filename = os.path.splitext(os.path.basename(path))[0] + "." + str(thumbSize) + ".jpg" if not self.gfs.exists(filename=filename): width = int(image["scanProperties"]["openslide_level[0]_width"]) height = int(image["scanProperties"]["openslide_level[0]_height"]) thumbHeight = float(self.config["thumb_" + thumbSize + "_height"]) thumbWidth = int(round(thumbHeight/float(height) * int(width))) try: osr = OpenSlide(path) thumb = osr.get_thumbnail((thumbWidth,thumbHeight)) except OpenSlideError, e: resp = {"status": 404, "message": "OpenSlideError: Thumbnail failed to load"} return Response(dumps(resp), status=404, mimetype='application/json') except ValueError: resp = {"status": 404, "message": "ValueError: Thumbnail failed to load"} return Response(dumps(resp), status=404, mimetype='application/json')
def read_wsi_mask(self, wsi_path, mask_path): try: self.cur_wsi_path = wsi_path self.wsi_image = OpenSlide(wsi_path) self.mask_image = OpenSlide(mask_path) self.level_used = min(self.def_level, self.wsi_image.level_count - 1, self.mask_image.level_count - 1) self.mask_pil = self.mask_image.read_region((0, 0), self.level_used, self.mask_image.level_dimensions[self.level_used]) self.mask = np.array(self.mask_pil) except OpenSlideUnsupportedFormatError: print('Exception: OpenSlideUnsupportedFormatError') return False return True
def __init__(self, basedir, relpath=''): self.name = os.path.basename(relpath) self.children = [] for name in sorted(os.listdir(os.path.join(basedir, relpath))): cur_relpath = os.path.join(relpath, name) cur_path = os.path.join(basedir, cur_relpath) if os.path.isdir(cur_path): cur_dir = _Directory(basedir, cur_relpath) if cur_dir.children: self.children.append(cur_dir) elif OpenSlide.detect_format(cur_path): self.children.append(_SlideFile(cur_relpath))
def read_wsi_tumor(wsi_path, mask_path): """ # ===================================================================================== # read WSI image and resize # Due to memory constraint, we use down sampled (4th level, 1/32 resolution) image # ====================================================================================== """ try: wsi_image = OpenSlide(wsi_path) wsi_mask = OpenSlide(mask_path) level_used = wsi_image.level_count - 1 rgb_image = np.array(wsi_image.read_region((0, 0), level_used, wsi_image.level_dimensions[level_used])) mask_level = wsi_mask.level_count - 1 mask_image = wsi_mask.read_region((0, 0), mask_level, wsi_image.level_dimensions[mask_level]) resize_factor = float(1.0 / pow(2, level_used - mask_level)) # print('resize_factor: %f' % resize_factor) mask_image = cv2.resize(np.array(mask_image), (0, 0), fx=resize_factor, fy=resize_factor) wsi_mask.close() except OpenSlideUnsupportedFormatError: print('Exception: OpenSlideUnsupportedFormatError') return None, None, None, None return wsi_image, rgb_image, mask_image, level_used
def walk_slides(pool, tempdir, in_root, in_relpath, out_root, out_relpath): """Build a directory of tiled images from a directory of slides.""" slides = [] for in_name in sorted(os.listdir(os.path.join(in_root, in_relpath))): in_cur_relpath = os.path.join(in_relpath, in_name) in_cur_path = os.path.join(in_root, in_cur_relpath) out_name = os.path.splitext(in_name)[0] out_cur_relpath = os.path.join(out_relpath, out_name.lower()) if OpenSlide.can_open(in_cur_path): slides.append(tile_slide(pool, in_cur_relpath, in_cur_path, out_name, out_root, out_cur_relpath)) elif os.path.splitext(in_cur_path)[1] == '.zip': temp_path = mkdtemp(dir=tempdir) print 'Extracting %s...' % out_cur_relpath zipfile.ZipFile(in_cur_path).extractall(path=temp_path) for sub_name in os.listdir(temp_path): sub_path = os.path.join(temp_path, sub_name) if OpenSlide.can_open(sub_path): slides.append(tile_slide(pool, in_cur_relpath, sub_path, out_name, out_root, out_cur_relpath)) break return slides
def generate_thumbnail(image_filepath, thumbnail_filepath): ''' Generates a thumbnail from the specified image. ''' # open image with OpenSlide library image_file = OpenSlide(image_filepath) # extract image dimensions image_dims = image_file.dimensions # make thumbnail 100 times smaller thumb_dims = tuple( (x/100 for x in image_dims) ) # create thumbnail thumb_file = image_file.get_thumbnail(thumb_dims) # save file with desired path, format thumb_file.save(thumbnail_filepath, "png") # cleanup image_file.close()
def getThumbnail(path): """This will return the 0/0 tile later whch in the case of an SVS image is actually the thumbnail..... """ path = os.path.abspath(os.path.join(dz.config['slides_dir'], path)) osr = OpenSlide(path) format = 'jpeg' format = format.lower() if format != 'jpeg' and format != 'png': # Not supported by Deep Zoom abort(404) try: thumb = osr.get_thumbnail( (300,300)) except ValueError: # Invalid level or coordinates abort(404) buf = PILBytesIO() thumb.save(buf, 'jpeg', quality=90) resp = make_response(buf.getvalue()) resp.mimetype = 'image/%s' % format return resp
def read_wsi_tumor(self, wsi_path, mask_path): """ # ===================================================================================== # read WSI image and resize # Due to memory constraint, we use down sampled (4th level, 1/32 resolution) image # ====================================================================================== """ try: self.cur_wsi_path = wsi_path self.wsi_image = OpenSlide(wsi_path) self.mask_image = OpenSlide(mask_path) self.level_used = min(self.def_level, self.wsi_image.level_count - 1, self.mask_image.level_count - 1) self.rgb_image_pil = self.wsi_image.read_region((0, 0), self.level_used, self.wsi_image.level_dimensions[self.level_used]) self.rgb_image = np.array(self.rgb_image_pil) except OpenSlideUnsupportedFormatError: print('Exception: OpenSlideUnsupportedFormatError') return False return True
def initUI(self): # initialize the widget, make the openslide object pointing to the # multilevel image and initialize the view to the top left corner if self.filename is not None: # openSlide object self.osr = OS(self.filename) self.level_count = self.osr.level_count self.current_x=0 self.current_y=0 self.current_zoom=self.level_count-1 self.level_dimensions=self.osr.level_dimensions self.step =int(64*self.osr.level_downsamples[self.current_zoom]) self.hbox.addWidget(self.lbl) self.updatePixmap() self.show() self.resizeLock=False
def read_normal_wsi(self, wsi_path): """ # ===================================================================================== # read WSI image and resize # Due to memory constraint, we use down sampled (4th level, 1/32 resolution) image # ====================================================================================== """ try: self.wsi_path = wsi_path self.wsi_image = OpenSlide(wsi_path) level = min(self.def_level, self.wsi_image.level_count - 1) print('level used: %d' % level) print(self.wsi_image.level_dimensions[level]) self.rgb_image_pil = self.wsi_image.read_region((0, 0), level, self.wsi_image.level_dimensions[level]) self.rgb_image = np.array(self.rgb_image_pil) except OpenSlideUnsupportedFormatError: print('Exception: OpenSlideUnsupportedFormatError') return False return True
class _SlideTest(object): def setUp(self): self.osr = OpenSlide(file_path(self.FILENAME)) def tearDown(self): self.osr.close()
def load_slide(): slidefile = app.config['DEEPZOOM_SLIDE'] if slidefile is None: raise ValueError('No slide file specified') app.slide = OpenSlide(slidefile) app.dz = DeepZoomGenerator(app.slide)
class WSIData: def __init__( self, tif_path, centre, is_excluded, resource_group, label_tif_path=None, label_xml_path=None, patient=None, ): self._wsi_slide = OpenSlide(str(tif_path)) self._tif_path = tif_path self._is_excluded = is_excluded self._label_tif_path = label_tif_path self._label_xml_path = label_xml_path self._label_slide = None self._patient = patient self._resource_group = resource_group if not isinstance(centre, _StainNormalizer.Centre): raise TypeError('centre must be an instance of {}.Centre.'.format( self.__class__.__name__)) self._centre = centre self._name = tif_path.stem def _get_label_slide(self): if self._label_slide is None and self._label_tif_path is not None: self._label_slide = (self._label_tif_path and OpenSlide(str(self._label_tif_path))) return self._label_slide @staticmethod def _threshold_sv(img_np): '''Performs thresholding on the WSI image to extract the tissue region. RGB image is converted to HSV color space. The H and S channels are then thresholded via Otsu's Threshold, then combined via bitwise AND. Morphological transormation was applied by removing small holes and regions, and was filtered using median blur. Parameters ---------- img_np: np.uint8[H, W, 3] np.array RGB WSI as an np.array image Returns ------- bool[H, W] np.array ROI mask of the WSI. ''' img_hsv = rgb2hsv(img_np) # channel_h = img_hsv[:, :, 0] channel_s = img_hsv[:, :, 1] channel_v = img_hsv[:, :, 2] # thresh_h = threshold_otsu(channel_h) thresh_s = threshold_otsu(channel_s) thresh_v = threshold_otsu(channel_v) # binary_h = channel_h > thresh_h binary_s = channel_s > min(thresh_s, 0.1) binary_v = channel_s > min(thresh_v, 0.2) # binary = np.bitwise_and(binary_h, binary_s) binary = np.bitwise_and(binary_s, binary_v) binary = morphology.remove_small_objects(binary, _SMALL_OBJECT_AREA) binary = morphology.remove_small_holes(binary, _SMALL_HOLE_AREA) binary = median(binary, morphology.disk(_MEDIAN_DISK)) return binary.astype(bool) @staticmethod def _threshold_hs(img_np): '''Performs thresholding on the WSI image to extract the tissue region. RGB image is converted to HSV color space. The H and S channels are then thresholded via Otsu's Threshold, then combined via bitwise AND. Morphological transormation was applied by removing small holes and regions, and was filtered using median blur. Parameters ---------- img_np: np.uint8[H, W, 3] np.array RGB WSI as an np.array image Returns ------- bool[H, W] np.array ROI mask of the WSI. ''' img_hsv = rgb2hsv(img_np) channel_h = img_hsv[:, :, 0] channel_s = img_hsv[:, :, 1] thresh_h = threshold_otsu(channel_h) thresh_s = threshold_otsu(channel_s) binary_h = channel_h > thresh_h binary_s = channel_s > thresh_s binary = np.bitwise_and(binary_h, binary_s) binary = morphology.remove_small_objects(binary, _SMALL_OBJECT_AREA) binary = morphology.remove_small_holes(binary, _SMALL_HOLE_AREA) binary = median(binary, morphology.disk(_MEDIAN_DISK)) return binary.astype(bool) @staticmethod def _threshold_gray(img_np): binary = np.full(img_np.shape[:2], False) binary[np.where(rgb2gray(img_np) < 0.8)] = True binary = morphology.remove_small_objects(binary, _SMALL_OBJECT_AREA) binary = morphology.remove_small_holes(binary, _SMALL_HOLE_AREA) binary = median(binary, morphology.disk(_MEDIAN_DISK)) return binary def _roi_threshold(self, img_np): # return self._threshold_sv(img_np) # return self._threshold_gray(img_np) return self._threshold_hs(img_np) def _mark_metastases_regions_in_label(self, blank_mask_np, label_np): '''Marks the blank mask using the given label image. This is to be overriden due to the different nature of the labels from the CAMELYON 16 and 17 data set. Parameters ---------- blank_mask_np: bool[H, W] np.array Mask to serve as the new label label_np: np.uint[H, W] np.array Gray scale image with the mask values. Returns ------- bool[H, W] np.array Mask to serve as the new label ''' blank_mask_np[np.where(label_np == 1)] = True blank_mask_np[np.where(label_np == 2)] = False @property def name(self): return self._name @property def centre(self): return self._centre @property def label_xml_path(self): return self._label_xml_path @property def tif_path(self): return self._tif_path @property def is_excluded(self): return self._is_excluded @property def patient(self): return self._patient @property def resource_group(self): return self._resource_group def get_full_wsi_image(self, level): '''Returns the whole WSI as an RGB image. Parameters ---------- level: int Level downsample to read the WSI. (values: 0..8) Returns ------- np.uint8[H, W, 3] np.array Whole WSI RGB image. ''' wsi_dim = self._wsi_slide.level_dimensions[level] wsi_img = self._wsi_slide.read_region((0, 0), level, wsi_dim) return np.array(wsi_img.convert('RGB')) def get_level_dimension(self, level): '''Returns the dimensions of the WSI for a given level. Parameters ---------- level: int Level downsample to get dimension. (values: 0..8) Returns ------- (width: int, height: int) Dimension of the WSI. ''' return self._wsi_slide.level_dimensions[level] def get_level_downsample(self, level): '''Returns the dimensions of the WSI for a given level. Parameters ---------- level: int Level downsample to get dimension. (values: 0..8) Returns ------- (width: int, height: int) Dimension of the WSI. ''' return self._wsi_slide.level_downsamples[level] def get_roi_mask(self, level): '''Returns the ROI of the WSI i.e. the tissue region. Parameters ---------- level: int Level downsample to read the WSI. (values: 0..8) Returns ------- bool[H, W] np.array Tissue ROI mask of the WSI. ''' wsi_img = self.get_full_wsi_image(level) wsi_img_np = np.array(wsi_img, dtype=np.uint8) roi_mask = self._roi_threshold(wsi_img_np) metastases_roi_mask, label_np = self._get_metastases_mask(level) roi_mask[np.where(metastases_roi_mask)] = True roi_mask[np.where(label_np == 3)] = False return roi_mask def _get_metastases_mask(self, level): '''Get metastases ROI of the WSI. Parameters ---------- level: int Level downsample to read the WSI. (values: 0..8) Returns ------- bool[H, W] np.array Metastases ROI mask of the WSI. ''' wsi_w, wsi_h = self._wsi_slide.level_dimensions[level] metastases_mask = np.full((wsi_h, wsi_w), False) label_slide = self._get_label_slide() if label_slide is not None: label_dim = label_slide.level_dimensions[level] label_img = label_slide.read_region((0, 0), level, label_dim) label_np = np.array(label_img.convert('L')) label_w, label_h = label_dim label_np = label_np[:min(wsi_h, label_h), :min(wsi_w, label_w)] self._mark_metastases_regions_in_label(metastases_mask, label_np) else: label_np = np.full((wsi_h, wsi_w), 0) return metastases_mask, label_np def get_metastases_mask(self, level): return self._get_metastases_mask(level)[0] def read_region_and_label(self, coordinates, level, dimension): '''Extracts a RGB region from the WSI. The given coordinates will be the center of the region, with the given dimension. Parameters ---------- coordinates: (x: int, y: int) Coordinates in the WSI at level 0. Upper-left of the region. level: int Level downsample to read the WSI. (values: 0..8) dimension: (w: int, h: int) Dimension of the region to extract. Returns ------- np.uint8[H, W, 3] np.array RGB region from WSI. bool[H, W] np.array Label mask for the given region. ''' scale_factor = 2**level w, h = dimension w_0, h_0 = w * scale_factor, h * scale_factor x, y = coordinates ## coordinate is at the center of the patches # x, y = x - (w * 2**level // 2), y - (h * 2**level // 2) args = (x, y), 0, (w_0, h_0) patch_img = self._wsi_slide.read_region(*args) patch_np = np.array(patch_img.convert('RGB')) patch_np = resize(patch_np, dimension, mode='reflect') metastases_np = np.full((w, h), False) label_slide = self._get_label_slide() if label_slide is not None: label_img = label_slide.read_region(*args) label_np = np.array(label_img.convert('L')) label_np = label_np[::scale_factor, ::scale_factor] self._mark_metastases_regions_in_label(metastases_np, label_np) return patch_np, metastases_np def _get_roi_patch_positions(self, level, get_roi, stride=256, patch_side=513, ds_level=5): '''Positions returned are in level 0.''' assert ds_level > level, 'level must be less than ds_level' width, height = self._wsi_slide.level_dimensions[level] ds_roi = get_roi(ds_level) ds = 2**(ds_level - level) upscale_factor = 2**level for y in range(0, math.ceil(height / stride) * stride, stride): for x in range(0, math.ceil(width / stride) * stride, stride): y_ds = y // ds x_ds = x // ds y_ds_end = (y + stride) // ds x_ds_end = (x + stride) // ds roi_patch = ds_roi[y_ds:y_ds_end, x_ds:x_ds_end] if (np.sum(roi_patch) / roi_patch.size > _ROI_PATCH_COVER_PERCENTAGE): yield x * upscale_factor, y * upscale_factor def get_roi_patch_positions(self, level, stride=256, patch_side=513, ds_level=5, force_not_excluded=False): if not self._is_excluded or force_not_excluded: get_roi = self.get_roi_mask else: get_roi = self.get_metastases_mask return self._get_roi_patch_positions( level, get_roi, stride, patch_side, ds_level, ) def close(self): self._wsi_slide.close() self._label_slide is not None and self._label_slide.close() def __repr__(self): return '<Centre: {}, Name: {}>'.format(self._centre, self._name)
if __name__ == "__main__": svs = [] for filename in os.listdir("./GBM_TOP/"): svs.append(filename) slide_counter = 0 for num in range(0, len(svs)): # len(svs)): temp_svs = "./GBM_TOP/" + str( svs[num] ) # patches collected already from 1004347, 1004366, 1004346" # print(temp_xm print(temp_svs) Sample_list = total_data() if str(svs[num][:15]) in Sample_list: path = "./GBM_top_patches2/Pretrain11/" + str(svs[num][:15]) print(path) try: os.mkdir(path) print(num) slide = OpenSlide(temp_svs) patches = reading_WSI(slide=slide) path_extraction(patches, path) except: print('already_exist') # print(a) else: print("%s:::doesnotexist" % str(svs[num]))
parser = ArgumentParser() parser.add_argument("--level", type=int, default=11) parser.add_argument("--x", type=int, default=0) parser.add_argument("--y", type=int, default=2) parser.add_argument("--file-path", type=str, default="/files/data/CMU-1.svs") parser.add_argument("--output-file-path", type=str, default="/files/output/out.jpeg") parser.add_argument("--img-format", type=str, default="jpeg") parser.add_argument("--img-quality", type=int, default=100) args = parser.parse_args() file_path = Path(args.file_path).expanduser() slide = OpenSlide(str(file_path)) mpp_x = slide.properties[PROPERTY_NAME_MPP_X] mpp_y = slide.properties[PROPERTY_NAME_MPP_Y] dzg = DeepZoomGenerator( slide, tile_size=DEEPZOOM_TILESIZE, overlap=DEEPZOOM_OVERLAP, limit_bounds=DEEPZOOM_LIMIT_BOUNDS, ) location = (args.x, args.y) region = dzg.get_tile(args.level, location) region.save(args.output_file_path, format=args.img_format, quality=args.img_quality)
def __init__(self, path: str, tilesize: int = 512): self._slide = OpenSlide(path) self._tilesize = tilesize self._store = create_meta_store(self._slide, tilesize)
def get_bounds(patches, slide_location): """ Input: Image patches as dictionary of n keys with matrix of (Y,X,Z) Coordinates per patch Output: an image mask for each valid bounding box """ slide = OpenSlide(slide_location) print(slide.level_dimensions) slide_size = slide.level_dimensions[0] #mask_zero = np.zeros(shape = slide_size, dtype=float) boxes = [] masks = [] values = patches.values() i = 0 mask =PIL.Image.new('L', slide_size, 0) good_region_mask= PIL.Image.new('L', slide_size, 1) for patch in values: first_coord = patch[0] last_coord = patch[len(patch)-1] area = abs(first_coord[0]-last_coord[0])* abs(first_coord[1]-last_coord[1]) if area < 40000: # annotation is circle y_max = max(patch[:,0]) y_min = min(patch[:,0]) x_max = max(patch[:,1]) x_min = min(patch[:,1]) #make sure annotations are in slide scope # if max(patch[:,1])< slide_size[0]: #draw the polygon on the mask and fill with ones # print ((patch[:,0])) # print((patch[:,1])) ImageDraw.Draw(mask).polygon(zip(patch[:,1],patch[:,0]), outline=1, fill=1) #store the result in mask_result for further computation mask_result = np.array(mask) #change the outline and fill to white for further use ImageDraw.Draw(good_region_mask).polygon(zip(patch[:,1],patch[:,0]), outline=0, fill=0) #resize the mask to the region of interest dimensions mask_result = mask_result[y_min:y_max,x_min:x_max] #mask_result = np.expand_dims(mask_result, axis = 3) mask_result = cv2.cvtColor(mask_result, cv2.COLOR_GRAY2RGB) #mask_result = mask_result this_region = slide.read_region((x_min,y_min),0,(x_max-x_min , y_max-y_min)).convert('RGB') this_region.save("o"+str(i)+".png") this_region = cv2.imread("o"+str(i)+".png")#np.array(this_region) # print("Error ============> Annotations out of bounds for slide"+str(slide_location)+str(max(patch[:,0]))+"slide size==>"+str(slide_size[1])) print ("Original croped mask shape {}".format(mask_result.shape)) print ("Original croped region shape {}".format(this_region.shape)) if mask_result.shape == this_region.shape: result = np.multiply(mask_result,this_region) boxes.append(result) masks.append(mask_result) else: print("slide "+str(i)+" shape is not equal") i = i+1 else: #change the outline and fill to white for further use ImageDraw.Draw(good_region_mask).rectangle([(min(patch[:,1])-100,min(patch[:,0])-1000),(max(patch[:,1])+1000,max(patch[:,0])+1000)], outline=0, fill=0) return boxes, good_region_mask, slide, values
def tile(image_file, outdir, path_to_slide="../Neutrophil/"): slide = OpenSlide(path_to_slide + image_file) assert 'openslide.bounds-height' in slide.properties assert 'openslide.bounds-width' in slide.properties assert 'openslide.bounds-x' in slide.properties assert 'openslide.bounds-y' in slide.properties x = int(slide.properties['openslide.bounds-x']) y = int(slide.properties['openslide.bounds-y']) bounds_height = int(slide.properties['openslide.bounds-height']) bounds_width = int(slide.properties['openslide.bounds-width']) half_width_region = 49 full_width_region = 299 stepsize = full_width_region - half_width_region n_x = int((bounds_width - 1) / stepsize) n_y = int((bounds_height - 1) / stepsize) residue_x = int((bounds_width - n_x * stepsize) / 50) residue_y = int((bounds_height - n_y * stepsize) / 50) lowres = slide.read_region( (x, y), 2, (int(n_x * stepsize / 16), int(n_y * stepsize / 16))) lowres = np.array(lowres)[:, :, :3] imloc = [] counter = 0 svcounter = 0 if not os.path.exists(outdir): os.makedirs(outdir) for i in range(n_x - 1): for j in range(n_y - 1): target_x = stepsize * i target_y = stepsize * j image_x = target_x + x image_y = target_y + y the_image = slide.read_region( (image_x, image_y), 0, (full_width_region, full_width_region)) the_imagea = np.array(the_image)[:, :, :3] the_imagea = np.nan_to_num(the_imagea) mask = (the_imagea[:, :, :3] > 200).astype(np.uint8) maskb = (the_imagea[:, :, :3] < 5).astype(np.uint8) mask = mask[:, :, 0] * mask[:, :, 1] * mask[:, :, 2] maskb = maskb[:, :, 0] * maskb[:, :, 1] * maskb[:, :, 2] white = (np.sum(mask) + np.sum(maskb)) / (299 * 299) if white < 0.5: the_image.save( outdir + "/region_x-{}-y-{}.png".format(target_x, target_y)) strr = outdir + "/region_x-{}-y-{}.png".format( target_x, target_y) imloc.append( [svcounter, counter, target_x, target_y, i, j, strr]) svcounter += 1 else: pass # print('Ignore white!') counter += 1 imlocpd = pd.DataFrame( imloc, columns=["Num", "Count", "X", "Y", "X_pos", "Y_pos", "Loc"]) imlocpd.to_csv(outdir + "/dict.csv", index=False) return n_x, n_y, lowres, residue_x, residue_y
class WSI(object): """ # ================================ # Class to annotate WSIs with ROIs # ================================ """ index = 0 negative_patch_index = 118456 positive_patch_index = 2230 wsi_paths = [] mask_paths = [] def_level = 7 key = 0 def extract_patches_mask(self, bounding_boxes): """ Extract positive patches targeting annotated tumor region Save extracted patches to desk as .png image files :param bounding_boxes: list of bounding boxes corresponds to tumor regions :return: """ mag_factor = pow(2, self.level_used) print('No. of ROIs to extract patches from: %d' % len(bounding_boxes)) for i, bounding_box in enumerate(bounding_boxes): b_x_start = int(bounding_box[0]) * mag_factor b_y_start = int(bounding_box[1]) * mag_factor b_x_end = (int(bounding_box[0]) + int(bounding_box[2])) * mag_factor b_y_end = (int(bounding_box[1]) + int(bounding_box[3])) * mag_factor X = np.random.random_integers(b_x_start, high=b_x_end, size=500) Y = np.random.random_integers(b_y_start, high=b_y_end, size=500) # X = np.arange(b_x_start, b_x_end-256, 5) # Y = np.arange(b_y_start, b_y_end-256, 5) for x, y in zip(X, Y): mask = self.mask_image.read_region((x, y), 0, (PATCH_SIZE, PATCH_SIZE)) mask_gt = np.array(mask) mask_gt = cv2.cvtColor(mask_gt, cv2.COLOR_BGR2GRAY) white_pixel_cnt_gt = cv2.countNonZero(mask_gt) if white_pixel_cnt_gt > ((PATCH_SIZE * PATCH_SIZE) * 0.90): # mask = Image.fromarray(mask) patch = self.wsi_image.read_region((x, y), 0, (PATCH_SIZE, PATCH_SIZE)) patch.save(PROCESSED_PATCHES_FROM_USE_MASK_POSITIVE_PATH + PATCH_TUMOR_PREFIX + str(self.positive_patch_index), 'PNG') self.positive_patch_index += 1 patch.close() mask.close() def extract_patches_normal(self, bounding_boxes): """ Extract negative patches from Normal WSIs Save extracted patches to desk as .png image files :param bounding_boxes: list of bounding boxes corresponds to detected ROIs :return: """ mag_factor = pow(2, self.level_used) print('No. of ROIs to extract patches from: %d' % len(bounding_boxes)) for i, bounding_box in enumerate(bounding_boxes): b_x_start = int(bounding_box[0]) * mag_factor b_y_start = int(bounding_box[1]) * mag_factor b_x_end = (int(bounding_box[0]) + int(bounding_box[2])) * mag_factor b_y_end = (int(bounding_box[1]) + int(bounding_box[3])) * mag_factor X = np.random.random_integers(b_x_start, high=b_x_end, size=500) Y = np.random.random_integers(b_y_start, high=b_y_end, size=500) # X = np.arange(b_x_start, b_x_end-256, 5) # Y = np.arange(b_y_start, b_y_end-256, 5) for x, y in zip(X, Y): patch = self.wsi_image.read_region((x, y), 0, (PATCH_SIZE, PATCH_SIZE)) patch_array = np.array(patch) patch_hsv = cv2.cvtColor(patch_array, cv2.COLOR_BGR2HSV) # [20, 20, 20] lower_red = np.array([20, 20, 20]) # [255, 255, 255] upper_red = np.array([200, 200, 200]) mask = cv2.inRange(patch_hsv, lower_red, upper_red) white_pixel_cnt = cv2.countNonZero(mask) if white_pixel_cnt > ((PATCH_SIZE * PATCH_SIZE) * 0.50): # mask = Image.fromarray(mask) patch.save(PROCESSED_PATCHES_NORMAL_NEGATIVE_PATH + PATCH_NORMAL_PREFIX + str(self.negative_patch_index), 'PNG') # mask.save(PROCESSED_PATCHES_NORMAL_PATH + PATCH_NORMAL_PREFIX + str(self.patch_index), # 'PNG') self.negative_patch_index += 1 patch.close() def extract_patches_tumor(self, bounding_boxes): """ Extract both, negative patches from Normal area and positive patches from Tumor area Save extracted patches to desk as .png image files :param bounding_boxes: list of bounding boxes corresponds to detected ROIs :return: """ mag_factor = pow(2, self.level_used) print('No. of ROIs to extract patches from: %d' % len(bounding_boxes)) for i, bounding_box in enumerate(bounding_boxes): b_x_start = int(bounding_box[0]) * mag_factor b_y_start = int(bounding_box[1]) * mag_factor b_x_end = (int(bounding_box[0]) + int(bounding_box[2])) * mag_factor b_y_end = (int(bounding_box[1]) + int(bounding_box[3])) * mag_factor X = np.random.random_integers(b_x_start, high=b_x_end, size=500) Y = np.random.random_integers(b_y_start, high=b_y_end, size=500) # X = np.arange(b_x_start, b_x_end-256, 5) # Y = np.arange(b_y_start, b_y_end-256, 5) for x, y in zip(X, Y): patch = self.wsi_image.read_region((x, y), 0, (PATCH_SIZE, PATCH_SIZE)) mask = self.mask_image.read_region((x, y), 0, (PATCH_SIZE, PATCH_SIZE)) mask_gt = np.array(mask) # mask_gt = cv2.cvtColor(mask_gt, cv2.COLOR_BGR2GRAY) mask_gt = cv2.cvtColor(mask_gt, cv2.COLOR_BGR2GRAY) patch_array = np.array(patch) white_pixel_cnt_gt = cv2.countNonZero(mask_gt) if white_pixel_cnt_gt == 0: # mask_gt does not contain tumor area patch_hsv = cv2.cvtColor(patch_array, cv2.COLOR_BGR2HSV) lower_red = np.array([20, 20, 20]) upper_red = np.array([200, 200, 200]) mask_patch = cv2.inRange(patch_hsv, lower_red, upper_red) white_pixel_cnt = cv2.countNonZero(mask_patch) if white_pixel_cnt > ((PATCH_SIZE * PATCH_SIZE) * 0.50): # mask = Image.fromarray(mask) patch.save(PROCESSED_PATCHES_TUMOR_NEGATIVE_PATH + PATCH_NORMAL_PREFIX + str(self.negative_patch_index), 'PNG') # mask.save(PROCESSED_PATCHES_NORMAL_PATH + PATCH_NORMAL_PREFIX + str(self.patch_index), # 'PNG') self.negative_patch_index += 1 else: # mask_gt contains tumor area if white_pixel_cnt_gt >= ((PATCH_SIZE * PATCH_SIZE) * 0.85): patch.save(PROCESSED_PATCHES_POSITIVE_PATH + PATCH_TUMOR_PREFIX + str(self.positive_patch_index), 'PNG') self.positive_patch_index += 1 patch.close() mask.close() def read_wsi_mask(self, wsi_path, mask_path): try: self.cur_wsi_path = wsi_path self.wsi_image = OpenSlide(wsi_path) self.mask_image = OpenSlide(mask_path) self.level_used = min(self.def_level, self.wsi_image.level_count - 1, self.mask_image.level_count - 1) self.mask_pil = self.mask_image.read_region((0, 0), self.level_used, self.mask_image.level_dimensions[self.level_used]) self.mask = np.array(self.mask_pil) except OpenSlideUnsupportedFormatError: print('Exception: OpenSlideUnsupportedFormatError') return False return True def read_wsi_normal(self, wsi_path): """ # ===================================================================================== # read WSI image and resize # Due to memory constraint, we use down sampled (4th level, 1/32 resolution) image # ====================================================================================== """ try: self.cur_wsi_path = wsi_path self.wsi_image = OpenSlide(wsi_path) self.level_used = min(self.def_level, self.wsi_image.level_count - 1) self.rgb_image_pil = self.wsi_image.read_region((0, 0), self.level_used, self.wsi_image.level_dimensions[self.level_used]) self.rgb_image = np.array(self.rgb_image_pil) except OpenSlideUnsupportedFormatError: print('Exception: OpenSlideUnsupportedFormatError') return False return True def read_wsi_tumor(self, wsi_path, mask_path): """ # ===================================================================================== # read WSI image and resize # Due to memory constraint, we use down sampled (4th level, 1/32 resolution) image # ====================================================================================== """ try: self.cur_wsi_path = wsi_path self.wsi_image = OpenSlide(wsi_path) self.mask_image = OpenSlide(mask_path) self.level_used = min(self.def_level, self.wsi_image.level_count - 1, self.mask_image.level_count - 1) self.rgb_image_pil = self.wsi_image.read_region((0, 0), self.level_used, self.wsi_image.level_dimensions[self.level_used]) self.rgb_image = np.array(self.rgb_image_pil) except OpenSlideUnsupportedFormatError: print('Exception: OpenSlideUnsupportedFormatError') return False return True def find_roi_n_extract_patches_mask(self): mask = cv2.cvtColor(self.mask, cv2.COLOR_BGR2GRAY) contour_mask, bounding_boxes = self.get_image_contours_mask(np.array(mask), np.array(self.mask)) # contour_mask = cv2.resize(contour_mask, (0, 0), fx=0.40, fy=0.40) # cv2.imshow('contour_mask', np.array(contour_mask)) self.mask_pil.close() self.extract_patches_mask(bounding_boxes) self.wsi_image.close() self.mask_image.close() def find_roi_n_extract_patches_normal(self): hsv = cv2.cvtColor(self.rgb_image, cv2.COLOR_BGR2HSV) # [20, 20, 20] lower_red = np.array([20, 50, 20]) # [255, 255, 255] upper_red = np.array([200, 150, 200]) mask = cv2.inRange(hsv, lower_red, upper_red) # (50, 50) close_kernel = np.ones((25, 25), dtype=np.uint8) image_close = Image.fromarray(cv2.morphologyEx(np.array(mask), cv2.MORPH_CLOSE, close_kernel)) # (30, 30) open_kernel = np.ones((30, 30), dtype=np.uint8) image_open = Image.fromarray(cv2.morphologyEx(np.array(image_close), cv2.MORPH_OPEN, open_kernel)) contour_rgb, bounding_boxes = self.get_image_contours_normal(np.array(image_open), self.rgb_image) # contour_rgb = cv2.resize(contour_rgb, (0, 0), fx=0.40, fy=0.40) # cv2.imshow('contour_rgb', np.array(contour_rgb)) self.rgb_image_pil.close() self.extract_patches_normal(bounding_boxes) self.wsi_image.close() def find_roi_n_extract_patches_tumor(self): hsv = cv2.cvtColor(self.rgb_image, cv2.COLOR_BGR2HSV) lower_red = np.array([20, 20, 20]) upper_red = np.array([255, 255, 255]) mask = cv2.inRange(hsv, lower_red, upper_red) # (50, 50) close_kernel = np.ones((50, 50), dtype=np.uint8) image_close = Image.fromarray(cv2.morphologyEx(np.array(mask), cv2.MORPH_CLOSE, close_kernel)) # (30, 30) open_kernel = np.ones((30, 30), dtype=np.uint8) image_open = Image.fromarray(cv2.morphologyEx(np.array(image_close), cv2.MORPH_OPEN, open_kernel)) contour_rgb, bounding_boxes = self.get_image_contours_tumor(np.array(image_open), self.rgb_image) # contour_rgb = cv2.resize(contour_rgb, (0, 0), fx=0.40, fy=0.40) # cv2.imshow('contour_rgb', np.array(contour_rgb)) self.rgb_image_pil.close() self.extract_patches_tumor(bounding_boxes) self.wsi_image.close() self.mask_image.close() @staticmethod def get_image_contours_mask(cont_img, mask_img): _, contours, _ = cv2.findContours(cont_img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) bounding_boxes = [cv2.boundingRect(c) for c in contours] contours_mask_image_array = np.array(mask_img) line_color = (255, 0, 0) # blue color code cv2.drawContours(contours_mask_image_array, contours, -1, line_color, 1) return contours_mask_image_array, bounding_boxes @staticmethod def get_image_contours_normal(cont_img, rgb_image): _, contours, _ = cv2.findContours(cont_img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) bounding_boxes = [cv2.boundingRect(c) for c in contours] contours_rgb_image_array = np.array(rgb_image) line_color = (255, 0, 0) # blue color code cv2.drawContours(contours_rgb_image_array, contours, -1, line_color, 3) return contours_rgb_image_array, bounding_boxes @staticmethod def get_image_contours_tumor(cont_img, rgb_image): _, contours, _ = cv2.findContours(cont_img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) bounding_boxes = [cv2.boundingRect(c) for c in contours] contours_rgb_image_array = np.array(rgb_image) line_color = (255, 0, 0) # blue color code cv2.drawContours(contours_rgb_image_array, contours, -1, line_color, 3) # cv2.drawContours(mask_image, contours_mask, -1, line_color, 3) return contours_rgb_image_array, bounding_boxes def wait(self): self.key = cv2.waitKey(0) & 0xFF print('key: %d' % self.key) if self.key == 27: # escape return False elif self.key == 81: # <- (prev) self.index -= 1 if self.index < 0: self.index = len(self.wsi_paths) - 1 elif self.key == 83: # -> (next) self.index += 1 if self.index >= len(self.wsi_paths): self.index = 0 return True
r.raise_for_status() for buf in r.iter_content(10 << 20): if not buf: break fh.write(buf) hash.update(buf) count += len(buf) if count != int(r.headers['Content-Length']): raise IOError('Short read fetching %s' % slide_relpath) if hash.hexdigest() != slide_info['sha256']: raise IOError('Hash mismatch fetching %s' % slide_relpath) # Open slide slide = None try: slide = OpenSlide(slide_path) except OpenSlideError: if urlpath.splitext(slide_relpath)[1] == '.zip': # Unzip slide print 'Extracting %s...' % slide_relpath temp_path = mkdtemp(dir=tempdir) with ZipFile(slide_path) as zf: zf.extractall(path=temp_path) # Find slide in zip for sub_name in os.listdir(temp_path): try: slide_path = os.path.join(temp_path, sub_name) slide = OpenSlide(slide_path) except OpenSlideError: pass else:
def setUp(self): self.osr = OpenSlide(file_path(self.FILENAME))
os.makedirs('../Neutrophil/Tiles_final/pos') if not os.path.exists('../Neutrophil/Tiles_final/neg'): os.makedirs('../Neutrophil/Tiles_final/neg') coords = pd.read_excel('../Neutrophil/all_features_circa_July.xlsx', header=0) sample = coords.loc[(coords['Slide'] == 'Slide80.scn')] sample = sample.loc[(coords['Review'] == '+') | (coords['Review'] == '-')] sample = sample.sample(frac=1).reset_index(drop=True) pos = coords.loc[(coords['Review'] == '+') & (coords['Slide'] == 'Slide80.scn')] sample.to_csv('../Neutrophil/Tiles_final/sample.csv', header=0, index=False) pos.to_csv('../Neutrophil/Tiles_final/pos.csv', header=0, index=False) slide = OpenSlide( "../Neutrophil/ImageCollection_0000026280_2016-10-27 14_13_01.scn") assert 'openslide.bounds-height' in slide.properties assert 'openslide.bounds-width' in slide.properties assert 'openslide.bounds-x' in slide.properties assert 'openslide.bounds-y' in slide.properties xo = int(slide.properties['openslide.bounds-x']) yo = int(slide.properties['openslide.bounds-y']) bounds_height = int(slide.properties['openslide.bounds-height']) bounds_width = int(slide.properties['openslide.bounds-width']) sample.loc[:, 'X'] = sample.loc[:, 'X'] + xo sample.loc[:, 'Y'] = sample.loc[:, 'Y'] + yo pos.loc[:, 'X'] = pos.loc[:, 'X'] + xo pos.loc[:, 'Y'] = pos.loc[:, 'Y'] + yo
class OpenSlideStore(Mapping): """Wraps an OpenSlide object as a multiscale Zarr Store. Parameters ---------- path: str The file to open with OpenSlide. tilesize: int Desired "chunk" size for zarr store. """ def __init__(self, path: str, tilesize: int = 512): self._slide = OpenSlide(path) self._tilesize = tilesize self._store = create_meta_store(self._slide, tilesize) def __getitem__(self, key: str): if key in self._store: # key is for metadata return self._store[key] # key should now be a path to an array chunk # e.g '3/4.5.0' -> '<level>/<chunk_key>' try: x, y, level = _parse_chunk_path(key) location = self._ref_pos(x, y, level) size = (self._tilesize, self._tilesize) tile = self._slide.read_region(location, level, size) except ArgumentError as err: # Can occur if trying to read a closed slide raise err except: # TODO: probably need better error handling. # If anything goes wrong, we just signal the chunk # is missing from the store. raise KeyError(key) return np.array(tile).tobytes() def __contains__(self, key: str): return key in self._store def __eq__(self, other): return (isinstance(other, OpenSlideStore) and self._slide._filename == other._slide._filename) def __iter__(self): return iter(self.keys()) def __len__(self): return sum(1 for _ in self) def __enter__(self): return self def __exit__(self, *args): self.close() def _ref_pos(self, x: int, y: int, level: int): dsample = self._slide.level_downsamples[level] xref = int(x * dsample * self._tilesize) yref = int(y * dsample * self._tilesize) return xref, yref def keys(self): return self._store.keys() def close(self): self._slide.close()
def read_openslide_tile(slide: openslide.OpenSlide, downsample, tile_rect): level = slide.get_best_level_for_downsample(downsample) tile = slide.read_region((tile_rect[0], tile_rect[1]), level, (tile_rect[2], tile_rect[3])) return tile
def run(file_path, location_path, level, padding, camel_17): slide = OpenSlide(file_path) print('==> making contours of tissue region..') ### Pad with 255 if padding == True: x_lv_, y_lv_ = 0, 0 w_lv_, h_lv_ = slide.level_dimensions[level] wsi_pil_lv_ = slide.read_region((0,0), level,\ (w_lv_, h_lv_)) wsi_ary_lv_ = np.array(wsi_pil_lv_) wsi_bgr_lv_ = cv2.cvtColor(wsi_ary_lv_, cv2.COLOR_RGBA2BGR) margin_top = int(round(h_lv_ / 12.)) margin_bottom = int(round(h_lv_ / 32.)) wsi_bgr_lv_[0:margin_top, :] = 255 wsi_bgr_lv_[h_lv_ - margin_bottom:h_lv_, :] = 255 else: wsi_pil_lv_ = slide.read_region((0, 0), level,\ slide.level_dimensions[level]) wsi_ary_lv_ = np.array(wsi_pil_lv_) wsi_bgr_lv_ = cv2.cvtColor(wsi_ary_lv_, cv2.COLOR_RGBA2BGR) if camel_17: wsi_bgr_lv_black = (wsi_bgr_lv_ == 0) wsi_bgr_lv_[wsi_bgr_lv_black] = 255 ### Remove black region. """ wsi_bgr_lv_sum = np.sum(wsi_bgr_lv_, 2) wsi_criterion = (wsi_bgr_lv_sum / 3) < 38 wsi_bgr_lv_[wsi_criterion] = np.array([255, 255, 255]) """ ### Visualizing # origin = wsi_bgr_lv_.copy() # plt.subplot(1, 2, 1), plt.imshow(origin) # plt.title("origin"), plt.xticks([]), plt.yticks([]) # plt.subplot(1, 2, 2), plt.imshow(wsi_bgr_lv_4 ) # plt.title("after"), plt.xticks([]), plt.yticks([]) # plt.show() # exit() wsi_gray_lv_ = cv2.cvtColor(wsi_bgr_lv_, cv2.COLOR_BGR2GRAY) # ret, wsi_bin_0255_lv_4 = cv2.threshold( \ # wsi_gray_lv_4, \ # 127, 255, \ # cv2.THRESH_BINARY ) ### Visualizing # plt.subplot(1, 2, 1), plt.imshow(wsi_bgr_lv_4 ) # plt.title("bgr"), plt.xticks([]), plt.yticks([]) # plt.subplot(1, 2, 2), plt.imshow(wsi_gray_lv_4, 'gray') # plt.title("gray"), plt.xticks([]), plt.yticks([]) # plt.show() # exit() # blur_lv_ = cv2.GaussianBlur(wsi_gray_lv_, (5, 5), 0) ret, wsi_bin_0255_lv_ = cv2.threshold( \ wsi_gray_lv_, 0, 255, \ cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU) ### Visualizing # plt.subplot(1, 2, 1), plt.imshow(wsi_bgr_lv_4 ) # plt.title("bgr"), plt.xticks([]), plt.yticks([]) # plt.subplot(1, 2, 2), plt.imshow(wsi_bin_0255_lv_4, 'gray') # plt.title("gray"), plt.xticks([]), plt.yticks([]) # plt.show() # exit() ### Morphology kernel_o = np.ones((2, 2), dtype=np.uint8) kernel_c = np.ones((4, 4), dtype=np.uint8) wsi_bin_0255_lv_ = cv2.morphologyEx( \ wsi_bin_0255_lv_, \ cv2.MORPH_CLOSE, \ kernel_c) wsi_bin_0255_lv_ = cv2.morphologyEx( \ wsi_bin_0255_lv_, \ cv2.MORPH_OPEN, \ kernel_o) _, contours_tissue_lv_, hierarchy = \ cv2.findContours(\ wsi_bin_0255_lv_, \ cv2.RETR_TREE, \ cv2.CHAIN_APPROX_SIMPLE) print('==> making tissue mask..') mask_shape_lv_ = wsi_gray_lv_.shape tissue_mask_lv_ = make_mask(mask_shape_lv_, contours_tissue_lv_) print('==> saving slide_lv_' + str(level) + ' at ' + location_path) cv2.imwrite(location_path, tissue_mask_lv_)
def fetch_patches_from_slide(self, wsi_filepath, count, src_size=512, patch_size=512, tissue_threshold=0.8, blur=0, he_augmentation=False, rotations=None): slide = OpenSlide(wsi_filepath) # load image with lower resolution desirable_long_edge = 2000 level_downsample = 0 for i, (w, h) in enumerate(slide.level_dimensions): if abs(max(w, h) - desirable_long_edge) < \ abs(max(slide.level_dimensions[level_downsample]) - desirable_long_edge): level_downsample = i magnification = slide.level_downsamples[level_downsample] image_downsample = slide.read_region( (0, 0), level_downsample, (slide.level_dimensions[level_downsample])) # Otsu binarization # set transparent region to white alpha = np.asarray(image_downsample, dtype=np.uint8)[:, :, 3] src = np.average(np.asarray(image_downsample, dtype=np.uint8)[:, :, :3], axis=2) src[alpha == 0] = 255 src = 255 - cv2.convertScaleAbs(src) th, binarized = cv2.threshold(src, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU) # dilation kernel = np.ones((2, 2), np.uint8) dilated = cv2.dilate(binarized, kernel, iterations=1) ret_images = [] src_downsampled_size = 512 / magnification padding = int(src_size / 2**0.5 / magnification) + 10 for i in range(count): # select region to crop using tissue binary map while True: cx = random.randint( padding, slide.level_dimensions[level_downsample][0] - padding) cy = random.randint( padding, slide.level_dimensions[level_downsample][1] - padding) angle = random.random() * 2 * math.pi # crop crop_size = int( src_downsampled_size * 2**0.5 * max(abs(math.cos(angle)), abs(math.sin(angle)))) cropped = dilated[int(cy - crop_size / 2):int(cy + crop_size / 2), int(cx - crop_size / 2):int(cx + crop_size / 2)] mat = cv2.getRotationMatrix2D((crop_size / 2, crop_size / 2), 45 + 360 * angle / (2 * math.pi), 1) rotated = cv2.warpAffine(cropped, mat, (crop_size, crop_size)) result = rotated[ int(crop_size / 2 - src_downsampled_size / 2):int(crop_size / 2 + src_downsampled_size / 2), \ int(crop_size / 2 - src_downsampled_size / 2):int(crop_size / 2 + src_downsampled_size / 2)] if np.average(result) / 255 > tissue_threshold: break # transform to raw scale cx = cx * magnification cy = cy * magnification # real cropping angles = [angle] if rotations is not None: for rot in rotations: angles.append(angle + rot / 180 * math.pi) def crop(angle): crop_size = int( src_size * 2**0.5 * max(abs(math.cos(angle)), abs(math.sin(angle)))) cropped = np.asarray(slide.read_region( (int(cx - crop_size / 2), int(cy - crop_size / 2)), 0, (crop_size, crop_size)), dtype=np.float32)[:, :, :3] mat = cv2.getRotationMatrix2D((crop_size / 2, crop_size / 2), 45 + 360 * angle / (2 * math.pi), 1) rotated = cv2.warpAffine(cropped, mat, (crop_size, crop_size)) result = rotated[int(crop_size / 2 - src_size / 2):int(crop_size / 2 + src_size / 2), \ int(crop_size / 2 - src_size / 2):int(crop_size / 2 + src_size / 2)] result = cv2.resize(result, (patch_size, patch_size)).transpose( (2, 0, 1)) / 255 return result results = [crop(angle) for angle in angles] # color matching if self.use_color_matching: results = [ self.match_color(result.transpose(1, 2, 0)).transpose(2, 0, 1) for result in results ] # blurring effect if blur > 0: blur_size = random.randint(1, blur) results = [ cv2.blur(result.transpose(1, 2, 0), (blur_size, blur_size)).transpose((2, 0, 1)) for result in results ] if he_augmentation: def he_aug(result): hed = rgb2hed(np.clip(result.transpose(1, 2, 0), -1.0, 1.0)) ah = 0.95 + random.random() * 0.1 bh = -0.05 + random.random() * 0.1 ae = 0.95 + random.random() * 0.1 be = -0.05 + random.random() * 0.1 hed[:, :, 0] = ah * hed[:, :, 0] + bh hed[:, :, 1] = ae * hed[:, :, 1] + be result = hed2rgb(hed).transpose(2, 0, 1) return result results = [he_aug(result) for result in results] if rotations is None: ret_images.append(np.clip(results[0], 1e-7, 1.0 - 1e-7)) else: results = [ np.clip(result, 1e-7, 1.0 - 1e-7) for result in results ] ret_images.append(tuple(results)) return ret_images
def extract_features_test(heatmap_prob_name_postfix_first_model, heatmap_prob_name_postfix_second_model, f_test): print( '************************** extract_features_test() ***************************' ) print('heatmap_prob_name_postfix_first_model: %s' % heatmap_prob_name_postfix_first_model) print('heatmap_prob_name_postfix_second_model: %s' % heatmap_prob_name_postfix_second_model) print('f_test: %s' % f_test) test_wsi_paths = glob.glob( os.path.join( '/media/jiaojiao/Seagate Backup Plus Drive1/CAMELYON16/TrainingData/Train_Tumor', '*.tif')) #test_wsi_paths = glob.glob(os.path.join(utils.TUMOR_WSI_PATH, '*.tif')) test_wsi_paths.sort() features_file_test = open(f_test, 'w') wr_test = csv.writer(features_file_test, quoting=csv.QUOTE_NONNUMERIC) wr_test.writerow( utils.heatmap_feature_names[:len(utils.heatmap_feature_names) - 1]) for wsi_path in test_wsi_paths: wsi_name = utils.get_filename_from_path(wsi_path) level_used = OpenSlide( os.path.join( '/media/jiaojiao/Seagate Backup Plus Drive1/CAMELYON16/TrainingData/Train_Tumor', wsi_path)).level_count - 1 if level_used > 8: level_used = 8 print('extracting features for: %s' % wsi_name) heatmap_prob_path = glob.glob( os.path.join( utils.HEAT_MAP_DIR, '*%s*%s' % (wsi_name, heatmap_prob_name_postfix_first_model))) # print(heatmap_prob_path) image_open = wsi_ops.get_image_open(wsi_path) heatmap_prob = cv2.imread(heatmap_prob_path[0]) if heatmap_prob_name_postfix_second_model is not None: heatmap_prob_path_second_model = glob.glob( os.path.join( utils.HEAT_MAP_DIR, '*%s*%s' % (wsi_name, heatmap_prob_name_postfix_second_model))) heatmap_prob_second_model = cv2.imread( heatmap_prob_path_second_model[0]) for row in range(heatmap_prob.shape[0]): for col in range(heatmap_prob.shape[1]): if heatmap_prob[ row, col, 0] >= 0.90 * 255 and heatmap_prob_second_model[ row, col, 0] < 0.50 * 255: heatmap_prob[row, col, :] = heatmap_prob_second_model[ row, col, :] features = extract_features(heatmap_prob, image_open) print(features) wr_test.writerow(features) csv_name = wsi_name[0:9] + '.csv' csv_path = os.path.join(utils.results, csv_name) csv_file = open(csv_path, 'w') csv_test = csv.writer(csv_file, quoting=csv.QUOTE_NONNUMERIC) get_result(heatmap_prob, csv_test, level_used)
class NDPI_Slide: def __init__(self, ndpi_path): self.ndpi_slide = OpenSlide(ndpi_path) def read_image(self, level_select): """ Read the whole image at the selected level :param level_select: :return: """ level_count = self.ndpi_slide.level_count level_dims = self.ndpi_slide.level_dimensions # for level in range(level_count): # print("level: {} \t dim: {}".format(level, level_dims[level])) im_low_res = self.ndpi_slide.read_region(location=(0, 0), level=level_select, size=level_dims[level_select]) im_low_res = np.array(im_low_res) if im_low_res.shape[2] > 3: im_low_res = im_low_res[:, :, 0:3] return im_low_res def read_region(self, location, level, size): """ Read image region :param location: (x, y) :param level: :param size: (w, h) :return: """ image = self.ndpi_slide.read_region(location, level, size) image = np.array(image) if image.shape[2] > 3: image = image[:, :, 0:3] # convert from RGBA to RGB return image def read_ndpa_annotation(self, ndpa_path): mmp_x = float( self.ndpi_slide.properties['openslide.mpp-x']) # pixel size in um mmp_y = float( self.ndpi_slide.properties['openslide.mpp-y']) # pixel size in um # Distance in X from the center of the entire slide (i.e., the macro image) to the center of the main image, in nm offset_x = float( self.ndpi_slide.properties['hamamatsu.XOffsetFromSlideCentre']) # Distance in Y from the center of the entire slide to the center of the main image, in nm offset_y = float( self.ndpi_slide.properties['hamamatsu.YOffsetFromSlideCentre']) level_0_dim = self.ndpi_slide.level_dimensions[0] level_0_width = level_0_dim[0] level_0_height = level_0_dim[1] # Steps to convert from NDPA coordinates to pixel coordinates # 0. The point coordinates are in physical units, relative to the center of the entire slide # 1. Subtract the [XY]OffsetFromSlideCentre ==> physical units, relative to the center of the main image # 2. Divide by (mpp-[xy] * 1000) ==> pixel coordinates, relative to the center of the main image # 3. Subtract (level-0 dimensions / 2) ==> level 0 pixel coordinates root = ET.parse(ndpa_path).getroot() polygons = [] rects = [] titles = [] for ndpviewstate_node in root.findall('ndpviewstate'): titles.append(ndpviewstate_node.find('title').text) annotation_node = ndpviewstate_node.find('annotation') pointlist_node = annotation_node.find('pointlist') points = [] for point_node in pointlist_node.findall('point'): x, y = float(point_node.find('x').text), float( point_node.find('y').text) # physical position x, y = x - offset_x, y - offset_y # physical position with respect to image center x, y = x / (mmp_x * 1000), y / ( mmp_y * 1000 ) # pixel position with respect to image center x, y = x + level_0_width / 2, y + level_0_height / 2 # pixel position with respect to image origin points.append((int(x), int(y))) points = np.array(points) rect = cv2.boundingRect(points) polygons.append(points) rects.append(rect) return polygons, rects, titles
class Opener: def __init__(self, path): self.warning = '' self.path = path self.reader = None self.tilesize = 1024 self.ext = check_ext(path) self.default_dtype = np.uint16 if self.ext == '.ome.tif' or self.ext == '.ome.tiff': self.io = TiffFile(self.path, is_ome=False) self.group = zarr.open(self.io.series[0].aszarr()) self.reader = 'tifffile' self.ome_version = self._get_ome_version() print("OME ", self.ome_version) num_channels = self.get_shape()[0] tile_0 = self.get_tifffile_tile(num_channels, 0, 0, 0, 0, 1024) if tile_0 is not None: self.default_dtype = tile_0.dtype if (num_channels == 3 and tile_0.dtype == 'uint8'): self.rgba = True self.rgba_type = '3 channel' elif (num_channels == 1 and tile_0.dtype == 'uint8'): self.rgba = True self.rgba_type = '1 channel' else: self.rgba = False self.rgba_type = None print("RGB ", self.rgba) print("RGB type ", self.rgba_type) elif self.ext == '.svs': self.io = OpenSlide(self.path) self.dz = DeepZoomGenerator(self.io, tile_size=1024, overlap=0, limit_bounds=True) self.reader = 'openslide' self.rgba = True self.rgba_type = None self.default_dtype = np.uint8 print("RGB ", self.rgba) print("RGB type ", self.rgba_type) else: self.reader = None def _get_ome_version(self): try: software = self.io.pages[0].tags[305].value sub_ifds = self.io.pages[0].tags[330].value if "Faas" in software or sub_ifds is None: return 5 m = re.search('OME\\sBio-Formats\\s(\\d+)\\.\\d+\\.\\d+', software) if m is None: return 5 return int(m.group(1)) except Exception as e: print(e) return 5 def load_xml_markers(self): if self.ext == '.ome.tif' or self.ext == '.ome.tiff': try: metadata = ome_types.from_tiff(self.path) except Exception as e: return [] if not metadata or not metadata.images or not metadata.images[0]: return [] metadata_pixels = metadata.images[0].pixels if not metadata_pixels or not metadata_pixels.channels: return [] return [c.name for c in metadata_pixels.channels] else: return [] def close(self): self.io.close() def is_rgba(self, rgba_type=None): if rgba_type is None: return self.rgba else: return self.rgba and rgba_type == self.rgba_type def get_level_tiles(self, level, tile_size): if self.reader == 'tifffile': # Negative indexing to support shape len 3 or len 2 ny = int(np.ceil(self.group[level].shape[-2] / tile_size)) nx = int(np.ceil(self.group[level].shape[-1] / tile_size)) return (nx, ny) elif self.reader == 'openslide': l = self.dz.level_count - 1 - level return self.dz.level_tiles[l] def get_shape(self): def parse_shape(shape): if len(shape) >= 3: (num_channels, shape_y, shape_x) = shape[-3:] else: (shape_y, shape_x) = shape num_channels = 1 return (num_channels, shape_x, shape_y) if self.reader == 'tifffile': (num_channels, shape_x, shape_y) = parse_shape(self.group[0].shape) all_levels = [parse_shape(v.shape) for v in self.group.values()] num_levels = len( [shape for shape in all_levels if max(shape[1:]) > 512]) return (num_channels, num_levels, shape_x, shape_y) elif self.reader == 'openslide': (width, height) = self.io.dimensions def has_one_tile(counts): return max(counts) == 1 small_levels = list(filter(has_one_tile, self.dz.level_tiles)) level_count = self.dz.level_count - len(small_levels) + 1 return (3, level_count, width, height) def read_tiles(self, level, channel_number, tx, ty, tilesize): ix = tx * tilesize iy = ty * tilesize num_channels = self.get_shape()[0] try: if num_channels == 1: tile = self.group[level][iy:iy + tilesize, ix:ix + tilesize] else: tile = self.group[level][channel_number, iy:iy + tilesize, ix:ix + tilesize] tile = np.squeeze(tile) return tile except Exception as e: G['logger'].error(e) return None def get_tifffile_tile(self, num_channels, level, tx, ty, channel_number, tilesize=1024): if self.reader == 'tifffile': tile = self.read_tiles(level, channel_number, tx, ty, tilesize) if tile is None: return np.zeros((tilesize, tilesize), dtype=self.default_dtype) return tile def get_tile(self, num_channels, level, tx, ty, channel_number, fmt=None): if self.reader == 'tifffile': if self.is_rgba('3 channel'): tile_0 = self.get_tifffile_tile(num_channels, level, tx, ty, 0, 1024) tile_1 = self.get_tifffile_tile(num_channels, level, tx, ty, 1, 1024) tile_2 = self.get_tifffile_tile(num_channels, level, tx, ty, 2, 1024) tile = np.zeros((tile_0.shape[0], tile_0.shape[1], 3), dtype=np.uint8) tile[:, :, 0] = tile_0 tile[:, :, 1] = tile_1 tile[:, :, 2] = tile_2 _format = 'I;8' else: tile = self.get_tifffile_tile(num_channels, level, tx, ty, channel_number, 1024) _format = fmt if fmt else 'I;16' if (_format == 'RGBA' and tile.dtype != np.uint32): tile = tile.astype(np.uint32) if (_format == 'I;16' and tile.dtype != np.uint16): if tile.dtype == np.uint8: tile = 255 * tile.astype(np.uint16) else: # TODO: real support for uint32, signed values, and floats tile = np.clip(tile, 0, 65535).astype(np.uint16) return Image.fromarray(tile, _format) elif self.reader == 'openslide': l = self.dz.level_count - 1 - level img = self.dz.get_tile(l, (tx, ty)) return img def save_mask_tiles(self, filename, mask_params, logger, tile_size, level, tx, ty): should_skip_tile = {} def get_empty_path(path): basename = os.path.splitext(path)[0] return pathlib.Path(f'{basename}_tmp.txt') for image_params in mask_params['images']: output_file = str(image_params['out_path'] / filename) path_exists = os.path.exists(output_file) or os.path.exists( get_empty_path(output_file)) should_skip = path_exists and image_params['is_up_to_date'] should_skip_tile[output_file] = should_skip if all(should_skip_tile.values()): logger.warning(f'Not saving tile level {level} ty {ty} tx {tx}') logger.warning( f'Every mask {filename} exists with same rendering settings') return if self.reader == 'tifffile': num_channels = self.get_shape()[0] tile = self.get_tifffile_tile(num_channels, level, tx, ty, 0, tile_size) for image_params in mask_params['images']: output_file = str(image_params['out_path'] / filename) if should_skip_tile[output_file]: continue target = np.zeros(tile.shape + (4, ), np.uint8) skip_empty_tile = True for channel in image_params['settings']['channels']: rgba_color = [ int(255 * i) for i in (colors.to_rgba(channel['color'])) ] ids = channel['ids'] if len(ids) > 0: bool_tile = np.isin(tile, ids) # Signal that we must actually save the image if not skip_empty_tile or np.any(bool_tile): skip_empty_tile = False target[bool_tile] = rgba_color else: # Note, any channel without ids to map will override all others target = colorize_mask(target, tile) skip_empty_tile = False if skip_empty_tile: empty_file = get_empty_path(output_file) if not os.path.exists(empty_file): with open(empty_file, 'w') as fp: pass else: img = Image.frombytes('RGBA', target.T.shape[1:], target.tobytes()) img.save(output_file, quality=85) def save_tile(self, output_file, settings, tile_size, level, tx, ty): if self.reader == 'tifffile' and self.is_rgba('3 channel'): num_channels = self.get_shape()[0] tile_0 = self.get_tifffile_tile(num_channels, level, tx, ty, 0, tile_size) tile_1 = self.get_tifffile_tile(num_channels, level, tx, ty, 1, tile_size) tile_2 = self.get_tifffile_tile(num_channels, level, tx, ty, 2, tile_size) tile = np.zeros((tile_0.shape[0], tile_0.shape[1], 3), dtype=np.uint8) tile[:, :, 0] = tile_0 tile[:, :, 1] = tile_1 tile[:, :, 2] = tile_2 img = Image.fromarray(tile, 'RGB') img.save(output_file, quality=85) elif self.reader == 'tifffile' and self.is_rgba('1 channel'): num_channels = self.get_shape()[0] tile = self.get_tifffile_tile(num_channels, level, tx, ty, 0, tile_size) img = Image.fromarray(tile, 'RGB') img.save(output_file, quality=85) elif self.reader == 'tifffile': target = None for i, (marker, color, start, end) in enumerate( zip(settings['Channel Number'], settings['Color'], settings['Low'], settings['High'])): num_channels = self.get_shape()[0] tile = self.get_tifffile_tile(num_channels, level, tx, ty, int(marker), tile_size) if (tile.dtype != np.uint16): if tile.dtype == np.uint8: tile = 255 * tile.astype(np.uint16) else: tile = tile.astype(np.uint16) if i == 0 or target is None: target = np.zeros(tile.shape + (3, ), np.float32) composite_channel(target, tile, colors.to_rgb(color), float(start), float(end)) if target is not None: np.clip(target, 0, 1, out=target) target_u8 = (target * 255).astype(np.uint8) img = Image.frombytes('RGB', target.T.shape[1:], target_u8.tobytes()) img.save(output_file, quality=85) elif self.reader == 'openslide': l = self.dz.level_count - 1 - level img = self.dz.get_tile(l, (tx, ty)) img.save(output_file, quality=85)
def tile(image_file, outdir, std_img, stepsize, full_width_region, path_to_slide="../images/"): slide = OpenSlide(path_to_slide + image_file) slp = str(path_to_slide + image_file) assert 'openslide.bounds-height' in slide.properties assert 'openslide.bounds-width' in slide.properties assert 'openslide.bounds-x' in slide.properties assert 'openslide.bounds-y' in slide.properties x = int(slide.properties['openslide.bounds-x']) y = int(slide.properties['openslide.bounds-y']) bounds_height = int(slide.properties['openslide.bounds-height']) bounds_width = int(slide.properties['openslide.bounds-width']) n_x = int((bounds_width - 1) / stepsize) n_y = int((bounds_height - 1) / stepsize) residue_x = int((bounds_width - n_x * stepsize) / 50) residue_y = int((bounds_height - n_y * stepsize) / 50) lowres = slide.read_region( (x, y), 2, (int(n_x * stepsize / 16), int(n_y * stepsize / 16))) lowres = np.array(lowres)[:, :, :3] x0 = 0 # create multiporcessing pool print(mp.cpu_count()) pool = mp.Pool(processes=8) tasks = [] while x0 < n_x: task = tuple( (slp, n_y, x, y, full_width_region, stepsize, x0, outdir, std_img)) tasks.append(task) x0 += 1 # slice images with multiprocessing temp = pool.starmap(v_slide, tasks) tempdict = list(zip(*temp))[0] tempimglist = list(zip(*temp))[1] temp = None pool.close() pool.join() tempdict = list(filter(None, tempdict)) imloc = [] list(map(imloc.extend, tempdict)) imlocpd = pd.DataFrame(imloc, columns=[ "X_pos", "Y_pos", "X", "Y", "X_relative", "Y_relative", "file" ]) imlocpd = imlocpd.sort_values(["X_pos", "Y_pos"], ascending=[True, True]) imlocpd = imlocpd.reset_index(drop=True) imlocpd = imlocpd.reset_index(drop=False) imlocpd.columns = [ "Num", "X_pos", "Y_pos", "X", "Y", "X_relative", "Y_relative", "file" ] imlocpd.to_csv(outdir + "/dict.csv", index=False) tempdict = None tempimglist = list(filter(None, tempimglist)) imglist = [] list(map(imglist.extend, tempimglist)) ct = len(imloc) tempimglist = None imglist = np.asarray(imglist) return n_x, n_y, lowres, residue_x, residue_y, imglist, ct
def _get_label_slide(self): if self._label_slide is None and self._label_tif_path is not None: self._label_slide = (self._label_tif_path and OpenSlide(str(self._label_tif_path))) return self._label_slide
class QtCustomWindow( qt.QWidget ): def __init__( self, parent = None ) : qt.QWidget.__init__( self, parent ) self.current_x=0 self.current_y=0 #read region always works in the level 0 reference for reading locations self.current_zoom=0 self.image_width=750 self.image_height=750 self.setWindowTitle('Large scale image viewing module') self.resize(self.image_width,self.image_height) self.hbox = qt.QHBoxLayout(self) self.lbl = qt.QLabel(self) sp=qt.QSizePolicy() # Size policy setting sp.setHorizontalPolicy(qt.QSizePolicy.Ignored) sp.setVerticalPolicy(qt.QSizePolicy.Ignored) self.lbl.setSizePolicy(sp) self.setLayout(self.hbox) self.filename=None # #self.mouseInitialPosition self.panningLock=False self.zoomingLock=False self.resizeLock=True self.initUI() def initUI(self): # initialize the widget, make the openslide object pointing to the # multilevel image and initialize the view to the top left corner if self.filename is not None: # openSlide object self.osr = OS(self.filename) self.level_count = self.osr.level_count self.current_x=0 self.current_y=0 self.current_zoom=self.level_count-1 self.level_dimensions=self.osr.level_dimensions self.step =int(64*self.osr.level_downsamples[self.current_zoom]) self.hbox.addWidget(self.lbl) self.updatePixmap() self.show() self.resizeLock=False def updatePixmap(self): self.panningLock=True im=self.osr.read_region((self.current_x,self.current_y),self.current_zoom,(self.image_width,self.image_height)) data = im.tostring('raw', 'RGBA') image = qt.QImage(data, im.size[0], im.size[1], qt.QImage.Format_ARGB32) pix = qt.QPixmap.fromImage(image) self.lbl.setPixmap(pix) self.lbl.show() self.panningLock=False def keyPressEvent( self, event ) : key = event.text() if key=="s": self.current_y += self.step self.current_y = min(self.current_y,int( self.level_dimensions[0][1]-self.image_height*self.osr.level_downsamples[self.current_zoom])) elif key=="w": #larger steps for zoomed out images self.current_y -= self.step self.current_y = max(0, self.current_y) elif key=="a": #larger steps for zoomed out images self.current_x -= self.step self.current_x = max(0, self.current_x) elif key=="d": #larger steps for zoomed out images self.current_x += self.step self.current_x = min(self.current_x,int( self.level_dimensions[0][0]-self.image_width*self.osr.level_downsamples[self.current_zoom])) elif key=="q": new_zoom = min(self.current_zoom+1,self.level_count-1) self.updateCorner(new_zoom) elif key=="e": new_zoom = max(self.current_zoom-1,0) self.updateCorner(new_zoom) # print "x: %s" % self.current_x # print "y: %s" % self.current_y # print "zoom: %s" % self.current_zoom # print "downsample: %s" % self.osr.level_downsamples[self.current_zoom] self.updatePixmap() def updateCorner(self,new_zoom, new_center=None): #updates the current_x and current_y values to preserve the desired image center if new_center: center_x=new_center[0] center_y=new_center[1] else: center_x=self.image_width /2 center_y=self.image_height /2 #while zooming self.current_x=int(self.current_x + center_x *self.osr.level_downsamples[self.current_zoom]- self.image_width /2 * self.osr.level_downsamples[new_zoom]) self.current_y=int(self.current_y + center_y *self.osr.level_downsamples[self.current_zoom]- self.image_height /2 * self.osr.level_downsamples[new_zoom]) #update step size and current zoom self.current_zoom=new_zoom #larger steps for zoomed out images self.step =int(64*self.osr.level_downsamples[self.current_zoom]) ################################################################################ ################################################################################ def mousePressEvent(self, QMouseEvent): #cursor =qt.QCursor() position = QMouseEvent.pos() xpos = QMouseEvent.x() ypos = QMouseEvent.y() self.mouseInitialPosition=np.array([QMouseEvent.x(), QMouseEvent.y()]) def mouseMoveEvent(self, QMouseEvent): #cursor =qt.QCursor() if self.panningLock: return currentPosition=np.array([QMouseEvent.x(), QMouseEvent.y()]) difference=self.mouseInitialPosition-currentPosition self.mouseInitialPosition=currentPosition self.current_x= self.current_x + int(difference[0]*self.osr.level_downsamples[self.current_zoom]) self.current_x = max(0, self.current_x) self.current_x = min(self.current_x,int( self.level_dimensions[0][0]-self.image_width*self.osr.level_downsamples[self.current_zoom])) self.current_y= self.current_y + int(difference[1]*self.osr.level_downsamples[self.current_zoom]) self.current_y = max(0, self.current_y) self.current_y = min(self.current_y,int( self.level_dimensions[0][1]-self.image_height*self.osr.level_downsamples[self.current_zoom])) self.updatePixmap() def wheelEvent(self,QMouseEvent): new_zoom = min(max(self.current_zoom- QMouseEvent.delta()/120,0), self.level_count-1) if new_zoom != self.current_zoom: new_center=[0,0] new_center[0]= int(QMouseEvent.x()) new_center[1]= int(QMouseEvent.y()) self.updateCorner(new_zoom, new_center) self.updatePixmap() def resizeEvent(self,event): if self.resizeLock: return self.resizeLock=True difw=event.size().width()-event.oldSize().width() difh = event.size().height()-event.oldSize().height() self.image_width+=difw self.image_height+=difh self.updatePixmap() self.resizeLock=False
def __downscale_slide(self, slide: OpenSlide, slide_path: Path): # logging.info("Downscaling a slide...") original_width, original_height = slide.dimensions # logging.info(f"original dims: {(original_width, original_height)}") if self._min_dim_size: downscale_factor = self._calc_downscale_factor(slide) else: downscale_factor = self._downscale_factor # logging.info(f"downscale factor: {downscale_factor}") level = slide.get_best_level_for_downsample(downscale_factor) # logging.info(f"level: {level}") level_width, level_height = slide.level_dimensions[level] # logging.info(f"level width={level_width}, height={level_height}") # logging.info("calc target dims...") target_width, target_height = self._calc_downscaled_sizes( slide, downscale_factor) # logging.info(f"target dims = {(target_width, target_height)}") if level_width * level_height < 1e9: # logging.info("Reading region...") whole_slide_image = slide.read_region(location=(0, 0), level=level, size=(level_width, level_height)) # logging.info("Converting to RGB...") if self._new_slide_ext.lower() in self._RGB_EXTENSIONS: whole_slide_image = whole_slide_image.convert("RGB") # logging.info("Resizing...") whole_slide_image = whole_slide_image.resize( (target_width, target_height), PIL.Image.ANTIALIAS) # logging.info("Calculating path...") output_path = self._calc_new_wsi_path(slide_path) output_path.parent.mkdir(parents=True, exist_ok=True) # logging.info("Saving...") whole_slide_image.save(output_path) else: n_vertical_splits = self._find_best_n_splits(level_height) n_horizontal_splits = self._find_best_n_splits(level_width) # logging.info(f"n_vertical_splits={n_vertical_splits}, n_horizontal_splits={n_horizontal_splits}") original_tile_height = math.ceil(original_height / n_vertical_splits) original_tile_width = math.ceil(original_width / n_horizontal_splits) level_tile_height = math.ceil(level_height / n_vertical_splits) level_tile_width = math.ceil(level_width / n_horizontal_splits) # logging.info(f"level_tile_width:{level_width / n_horizontal_splits} " # f"approx:{math.ceil(level_width / n_horizontal_splits)}") # target_tile_height = math.ceil(target_height / n_vertical_splits) # target_tile_width = math.ceil(target_width / n_horizontal_splits) for i in range(n_vertical_splits): for j in range(n_horizontal_splits): y_i = original_tile_height * i x_j = original_tile_width * j # logging.info(f"x_j={x_j}, y_i={y_i}") level_height_i = min(level_tile_height, level_height - level_tile_height * i) # target_height_i = min(target_tile_height, target_tile_height * (n_vertical_splits - 1)) level_width_j = min(level_tile_width, level_width - level_tile_width * j) # target_width_j = min(target_tile_width, target_tile_width * (n_horizontal_splits - 1)) # logging.info(f"level height_i={level_height_i}, width_j={level_width_j}") # logging.info(f"target height_i={target_height_i}, width_j={target_width_j}") # logging.info(f"Reading region {i}_{j}...") whole_slide_image_tile = slide.read_region( location=(x_j, y_i), level=level, size=(level_width_j, level_height_i)) # logging.info(f"Converting to RGB {i}_{j}...") whole_slide_image_tile = whole_slide_image_tile.convert( "RGB") # logging.info("Resizing...") # whole_slide_image_tile = whole_slide_image_tile.resize((target_width_j, target_height_i), # PIL.Image.ANTIALIAS) # logging.info("Calculating path...") output_path = self._calc_new_wsi_path(slide_path, part=f"{i}_{j}") output_path.parent.mkdir(parents=True, exist_ok=True) # logging.info("Saving...") whole_slide_image_tile.save(output_path)
def run(filename, split, level, dred, dbrown, debug): # Create OpenSlide object ndpi = OpenSlide(filename) ndpi_width = ndpi.dimensions[0] ndpi_height = ndpi.dimensions[1] total_width = ndpi.level_dimensions[level][0] total_height = ndpi.level_dimensions[level][1] red_sum = 0.0 brown_sum = 0.0 surface_sum = 0.0 startTime = time.time() if debug: print "filename: {}".format(filename) print "split: {}".format(split) print "level: {}".format(level) print "dred: {}".format(dred) print "dbrown: {}".format(dbrown) print "debug: {}".format(debug) if debug: print "================ START =================" print "LOAD {}".format(filename) print "width:".ljust(20) + str(ndpi_width) print "height:".ljust(20) + str(ndpi_height) print "level count:".ljust(20) + str(ndpi.level_count) print "split image {}x{} , level:{} , factor:{}".format(total_width,total_height,level,split) bar = Bar('Processing', max=split**2) for i in range(split): for j in range(split): x = i * ndpi_width / split y = j * ndpi_height / split w = total_width / split h = total_height / split if debug: print "\n>SLICE [{}][{}]".format(i,j) print "x:{:3} y:{:3} w:{:3}px h:{:3}px:".format(x,y,w,h) region = ndpi.read_region((x,y), level, (w, h)) red = bgsa.get_red(region, brightness=-dred) brown = bgsa.get_brown(region, brightness=-dbrown) # surface = bgsa.get_surface(region) region.save("output/normal_slice{}{}.png".format(i,j)) red.save("output/red_slice_{}{}.png".format(i,j)) brown.save("output/brown_slice_{}{}.png".format(i,j)) # surface.save("output/surface_slice_{}{}.png".format(i,j)) red_sum += bgsa.get_white_pixels(red) brown_sum += bgsa.get_white_pixels(brown) # surface_sum+= bgsa.get_white_pixels(surface) if debug: bar.next() # print "white:{}% black{}%".format(results["white"], results["black"]) if debug: bar.finish() print "Finished....in {:.2f} sec".format(time.time() - startTime) print "total red :".ljust(20) + str(red_sum) print "total brown :".ljust(20) + str(brown_sum) return {"red":red_sum, "brown":brown_sum}
def inferenceLoopPath(inferenceDataFromPickle, headers, device, parameters, outputDir): from GANDLF.inference_dataloader_histopath import InferTumorSegDataset from openslide import OpenSlide ''' This is the main inference loop for histopathology ''' # extract variables form parameters dict patch_size = parameters['patch_size'] stride = parameters['stride_size'] augmentations = parameters['data_augmentation'] preprocessing = parameters['data_preprocessing'] which_model = parameters['model']['architecture'] class_list = parameters['model']['class_list'] base_filters = parameters['model']['base_filters'] amp = parameters['model']['amp'] batch_size = parameters['batch_size'] n_channels = len(headers['channelHeaders']) if not ('n_channels' in parameters['model']): n_channels = len(headers['channelHeaders']) else: n_channels = parameters['model']['n_channels'] n_classList = len(class_list) # Report the time stamp training_start_time = time.asctime() startstamp = time.time() print("\nHostname :" + str(os.getenv("HOSTNAME"))) print("\nStart Time :" + str(training_start_time)) print("\nStart Stamp:" + str(startstamp)) sys.stdout.flush() # PRINT PARSED ARGS print("\n\n") print("Output directory :", outputDir) if 'n_channels' in parameters['model']: print("Number of channels :", parameters['model']['n_channels']) print("Modalities :", parameters['modality']) #print("Number of classes :", parameters['modality']['num_classes'] print("Batch Size :", parameters['batch_size']) print("Patch Size :", parameters['patch_size']) print("Sampling Stride :", parameters['stride_size']) print("Base Filters :", parameters['model']['base_filters']) #print("Load Weights :", parameters['load_weights']) sys.stdout.flush() # We generate CSV for training if not provided print("Reading CSV Files") n_classList = len(class_list) #test_csv = parameters['test_csv'] # Defining our model here according to parameters mentioned in the configuration file print("Number of dims : ", parameters['model']['dimension']) if 'n_channels' in parameters['model']: print("Number of channels : ", parameters['model']['n_channels']) print("Number of classes : ", n_classList) model = get_model( which_model, n_dimensions=parameters['model']['dimension'], n_channels=n_channels, n_classes=n_classList, base_filters=base_filters, final_convolution_layer=parameters['model']['final_layer'], psize=patch_size, batch_size=batch_size) # Loading the weights into the model main_dict = torch.load( os.path.join(outputDir, str(which_model) + "_best_val.pth.tar")) model.load_state_dict(main_dict['model_state_dict']) print('Loaded Weights successfully.') sys.stdout.flush() model, amp, device = send_model_to_device(model, amp, device, optimizer=None) model.eval() # print stats print('Using device:', device) sys.stdout.flush() test_df = inferenceDataFromPickle # Patch blocks for index, row in test_df.iterrows(): subject_name = row[headers['subjectIDHeader']] print("Patient Slide : ", row[headers['subjectIDHeader']]) print("Patient Location : ", row[headers['channelHeaders']]) print(row[headers['channelHeaders']].values[0]) os_image = OpenSlide(row[headers['channelHeaders']].values[0]) level_width, level_height = os_image.level_dimensions[int( parameters['slide_level'])] subject_dest_dir = os.path.join(outputDir, subject_name) os.makedirs(subject_dest_dir, exist_ok=True) probs_map = np.zeros((level_height, level_width), dtype=np.float16) count_map = np.zeros((level_height, level_width), dtype=np.uint8) patient_dataset_obj = InferTumorSegDataset( row[headers['channelHeaders']].values[0], patch_size=patch_size, stride_size=stride, selected_level=parameters['slide_level'], mask_level=4) dataloader = DataLoader(patient_dataset_obj, batch_size=int(parameters['batch_size']), shuffle=False, num_workers=2) for image_patches, (x_coords, y_coords) in tqdm(dataloader): x_coords, y_coords = y_coords.numpy(), x_coords.numpy() with autocast(): output = model(image_patches.half().cuda()) output = output.cpu().detach().numpy() for i in range(int(output.shape[0])): count_map[x_coords[i]:x_coords[i] + patch_size[0], y_coords[i]:y_coords[i] + patch_size[1]] += 1 probs_map[x_coords[i]:x_coords[i] + patch_size[0], y_coords[i]:y_coords[i] + patch_size[1]] += output[i][0] probs_map = probs_map / count_map count_map = (count_map / count_map.max()) out = count_map * probs_map count_map = np.array(count_map * 255, dtype=np.uint16) out_thresh = np.array((out > 0.5) * 255, dtype=np.uint16) imsave( os.path.join(subject_dest_dir, row[headers['subjectIDHeader']] + '_prob.png'), out) imsave( os.path.join(subject_dest_dir, row[headers['subjectIDHeader']] + '_seg.png'), out_thresh) imsave( os.path.join(subject_dest_dir, row[headers['subjectIDHeader']] + '_count.png'), count_map)
def osh(self): return OpenSlide(self.file_name)
def main(dirr, imgfile, bs, md, modeltoload, meta_cut, pdmd, LOG_DIR, METAGRAPH_DIR): start_time = time.time() if pdmd == 'histology': pos_score = "Serous_score" neg_score = "Endometrioid_score" elif pdmd == 'MSIst': pos_score = "MSI-H_score" neg_score = "MSS_score" else: pos_score = "POS_score" neg_score = "NEG_score" # make directories if not exist for DIR in (LOG_DIR, METAGRAPH_DIR, data_dir, out_dir): try: os.mkdir(DIR) except FileExistsError: pass if "TCGA" in imgfile: ft = 1 level = 1 else: level = 0 ft = 2 slide = OpenSlide("../images/" + imgfile) bounds_width = slide.level_dimensions[level][0] bounds_height = slide.level_dimensions[level][1] x = 0 y = 0 half_width_region = 49 * ft full_width_region = 299 * ft stepsize = (full_width_region - half_width_region) n_x = int((bounds_width - 1) / stepsize) n_y = int((bounds_height - 1) / stepsize) lowres = slide.read_region( (x, y), level + 1, (int(n_x * stepsize / 4), int(n_y * stepsize / 4))) raw_img = np.array(lowres)[:, :, :3] fct = ft if not os.path.isfile(data_dir + '/level1/dict.csv'): cutter(imgfile, meta_cut, cut) if not os.path.isfile(data_dir + '/test.tfrecords'): loader(data_dir, imgfile) if not os.path.isfile(data_dir + '/out/Overlay.png'): test(bs, 2, modeltoload, LOG_DIR, METAGRAPH_DIR) slist = pd.read_csv(data_dir + '/te_sample.csv', header=0) # load dictionary of predictions on tiles teresult = pd.read_csv(out_dir + '/Test.csv', header=0) # join 2 dictionaries joined = pd.merge(slist, teresult, how='inner', on=['Num']) joined = joined.drop(columns=['Num']) tile_dict = pd.read_csv(data_dir + '/level1/dict.csv', header=0) tile_dict = tile_dict.rename(index=str, columns={"Loc": "L0path"}) joined_dict = pd.merge(joined, tile_dict, how='inner', on=['L0path']) if joined_dict[pos_score].mean() > 0.5: print("Positive! Prediction score = " + str(joined_dict[pos_score].mean().round(5))) else: print("Negative! Prediction score = " + str(joined_dict[pos_score].mean().round(5))) # save joined dictionary joined_dict.to_csv(out_dir + '/finaldict.csv', index=False) # output heat map of pos and neg. # initialize a graph and for each RGB channel opt = np.full((n_x, n_y), 0) hm_R = np.full((n_x, n_y), 0) hm_G = np.full((n_x, n_y), 0) hm_B = np.full((n_x, n_y), 0) # Positive is labeled red in output heat map for index, row in joined_dict.iterrows(): opt[int(row["X_pos"]), int(row["Y_pos"])] = 255 if row[pos_score] >= 0.5: hm_R[int(row["X_pos"]), int(row["Y_pos"])] = 255 hm_G[int(row["X_pos"]), int(row["Y_pos"])] = int( (1 - (row[pos_score] - 0.5) * 2) * 255) hm_B[int(row["X_pos"]), int(row["Y_pos"])] = int( (1 - (row[pos_score] - 0.5) * 2) * 255) else: hm_B[int(row["X_pos"]), int(row["Y_pos"])] = 255 hm_G[int(row["X_pos"]), int(row["Y_pos"])] = int( (1 - (row[neg_score] - 0.5) * 2) * 255) hm_R[int(row["X_pos"]), int(row["Y_pos"])] = int( (1 - (row[neg_score] - 0.5) * 2) * 255) # expand 5 times opt = opt.repeat(50, axis=0).repeat(50, axis=1) # small-scaled original image ori_img = cv2.resize(raw_img, (np.shape(opt)[0], np.shape(opt)[1])) ori_img = ori_img[:np.shape(opt)[1], :np.shape(opt)[0], :3] tq = ori_img[:, :, 0] ori_img[:, :, 0] = ori_img[:, :, 2] ori_img[:, :, 2] = tq cv2.imwrite(out_dir + '/Original_scaled.png', ori_img) # binary output image topt = np.transpose(opt) opt = np.full((np.shape(topt)[0], np.shape(topt)[1], 3), 0) opt[:, :, 0] = topt opt[:, :, 1] = topt opt[:, :, 2] = topt cv2.imwrite(out_dir + '/Mask.png', opt * 255) # output heatmap hm_R = np.transpose(hm_R) hm_G = np.transpose(hm_G) hm_B = np.transpose(hm_B) hm_R = hm_R.repeat(50, axis=0).repeat(50, axis=1) hm_G = hm_G.repeat(50, axis=0).repeat(50, axis=1) hm_B = hm_B.repeat(50, axis=0).repeat(50, axis=1) hm = np.dstack([hm_B, hm_G, hm_R]) cv2.imwrite(out_dir + '/HM.png', hm) # superimpose heatmap on scaled original image overlay = ori_img * 0.5 + hm * 0.5 cv2.imwrite(out_dir + '/Overlay.png', overlay)
def _get_openslide_wrapper(self, original_file_source, file_mimetype): img_path = self._get_image_path(original_file_source, file_mimetype) if img_path: return OpenSlide(img_path) else: return None
def __init__(self, path, root, src_size, patch_size, fetch_mode='area', label_to_use=0, rotation=True, flip=False, blur=0, he_augmentation=False, scale_augmentation=False, color_matching=None, dump_patch=None, verbose=1): self.path = path self.root = root self.src_size = src_size self.patch_size = patch_size self.fetch_mode = fetch_mode self.label_to_use = label_to_use if self.fetch_mode not in OpenSlideGenerator.fetch_modes: raise Exception('invalid fetch_mode %r' % self.fetch_mode) self.rotation = rotation self.flip = flip self.blur = blur self.he_augmentation = he_augmentation self.scale_augmentation = scale_augmentation self.dump_patch = dump_patch self.verbose = verbose self.use_color_matching = False if color_matching is not None: self.match_color_prepare(cv2.imread(color_matching) / 255.0) self.use_color_matching = True self.slide_names = [] self.labels = [] # labels[LABEL_CATEGORY][LABEL] self.label_of_region = [] self.structure = [] self.shifted_structure = [] self.triangulation = [] self.regions_of_label = [] # dict() self.regions_of_label_slide = [] # dict() self.src_sizes = [] self.total_weight = 0 self.slide_weights = [] # total weight of a slide self.label_weights = [] # total weight of a label self.label_slide_weights = [ ] # total weight of regions of certain label in a slide. self.weights = [] # overall weight self.weights_in_slide = [] # weight in a slide self.weights_in_label = [] # weight in the same label self.weights_in_label_slide = [] # weight in the same label and slide self.total_area = 0 self.slide_areas = [] # total area of a slide self.label_areas = [] # total area of a label self.total_triangles = 0 self.slide_triangles = [] # total triangle number for each slide self.label_triangles = [] # total triangle number for each label self.label_slide_triangles = [ ] # total triangule number for each label-slide pair self.serialized_index = [ ] # serialized_index[ID] -> (SLIDE_ID, REGION_ID, TRIANGLE_ID) self.serialized_index_slide = [ ] # serialized_index_slide[SLIDE_ID][ID] -> (REGION_ID, TRIANGLE_ID) self.serialized_index_label = [ ] # serialized_index_label[label][ID] -> (SLIDE_ID, REGION_ID, TRIANGLE_ID) self.serialized_index_label_slide = [ ] # *[label][SLIDE_ID][ID] -> (REGION_ID, TRIANGLE_ID) # variables for Walker's alias method self.a_area = [] self.p_area = [] self.a_slide = [] self.p_slide = [] self.a_label = [] self.p_label = [] self.a_label_slide = [] self.p_label_slide = [] # OpenSlide objects self.slides = [] # log self.fetch_count = [] # region-wise # states for parsing input text file # 0: waiting for new file entry # 1: waiting for region header or svs entry # 2: reading a region state = 0 left_points = 0 label_buffer = [] # label_buffer[SLIDE_ID][REGION_ID][LABEL_CATEGORY] slide_id = -1 region_id = -1 with open(path) as f: for line in map(lambda l: l.split("#")[0].strip(), f.readlines()): if len(line) == 0: continue is_svs_line = (line[0] == "@") if is_svs_line: line = line[1:] else: try: items = list(map(int, line.split())) except Exception: raise Exception('invalid dataset file format!') if state == 0: if not is_svs_line: raise Exception('invalid dataset file format!') slide_id += 1 region_id = 0 svs_name = line.split()[0] if len(line.split()) > 1 and line.split()[1].isdigit: svs_src_size = int(line.split()[1]) else: svs_src_size = self.src_size self.slide_names.append(svs_name) self.src_sizes.append(svs_src_size) self.structure.append([]) label_buffer.append([]) state = 1 elif state == 1: if is_svs_line: # new file slide_id += 1 region_id = 0 svs_name = line.split()[0] if len(line.split()) > 1 and line.split()[1].isdigit: svs_src_size = int(line.split()[1]) else: svs_src_size = self.src_size # default src_size self.slide_names.append(svs_name) self.src_sizes.append(svs_src_size) self.structure.append([]) label_buffer.append([]) state = 1 else: # region header label_buffer[slide_id].append([]) for label_cat, label in enumerate(items[:-1]): label_buffer[slide_id][region_id].append(label) # handling newly found label category if len(self.labels) < label_cat + 1: self.labels.append([]) self.regions_of_label.append(dict()) self.regions_of_label_slide.append(dict()) self.a_label.append(dict()) self.p_label.append(dict()) self.a_label_slide.append(dict()) self.p_label_slide.append(dict()) self.label_areas.append(dict()) self.label_weights.append(dict()) self.label_slide_weights.append(dict()) self.label_triangles.append(dict()) self.label_slide_triangles.append(dict()) self.serialized_index_label.append(dict()) self.serialized_index_label_slide.append( dict()) # handling newly found label if label not in self.labels[label_cat]: self.labels[label_cat].append(label) self.regions_of_label[label_cat][label] = [] self.a_label[label_cat][label] = [] self.p_label[label_cat][label] = [] self.a_label_slide[label_cat][label] = [] self.p_label_slide[label_cat][label] = [] self.label_areas[label_cat][label] = 0 self.label_weights[label_cat][label] = 0 self.label_slide_weights[label_cat][label] = [] self.label_triangles[label_cat][label] = 0 self.label_slide_triangles[label_cat][ label] = [] self.serialized_index_label[label_cat][ label] = [] self.serialized_index_label_slide[label_cat][ label] = [] self.regions_of_label[label_cat][label].append( (slide_id, region_id)) self.structure[slide_id].append([]) left_points = items[-1] if items[-1] < 3: raise Exception( 'regions should consist of more than 3 points!' ) state = 2 elif state == 2: if is_svs_line or len(items) != 2: raise Exception('invalid dataset file format!') self.structure[-1][-1].append((items[0], items[1])) left_points -= 1 if left_points == 0: state = 1 region_id += 1 if state != 1: # dataset file should end with a completed region entry raise Exception('invalid dataset file format!') # set label_of_region for label_cat in range(len(self.labels)): self.label_of_region.append([]) for slide_id, label_of_regions in enumerate(label_buffer): self.label_of_region[label_cat].append([]) for region_id, label_of_categories in enumerate( label_of_regions): if label_cat < len(label_of_categories): self.label_of_region[label_cat][slide_id].append( label_of_categories[label_cat]) else: self.label_of_region[label_cat][slide_id].append(-1) # calculate regions_of_label_slide for label_cat in range(len(self.labels)): for label in self.labels[label_cat]: self.regions_of_label_slide[label_cat][label] = [] for i in range(len(self.structure)): self.regions_of_label_slide[label_cat][label].append([]) # prepare shifted (offset) structure self.shifted_structure = copy.deepcopy(self.structure) for i in range(len(self.shifted_structure)): for j in range(len(self.shifted_structure[i])): pco = pyclipper.PyclipperOffset() pco.AddPath(self.shifted_structure[i][j], pyclipper.JT_ROUND, pyclipper.ET_CLOSEDPOLYGON) # offsetting shifted_region = pco.Execute(-self.src_sizes[i] / 2) # shifted_region = pco.Execute(0) if len(shifted_region) == 0: self.shifted_structure[i][j] = [] # collapsed to a point else: self.shifted_structure[i][j] = shifted_region[0] for label_cat in range(len(self.labels)): label = self.label_of_region[label_cat][i][j] if label != -1: self.regions_of_label_slide[label_cat][label][ i].append(j) # load slides for name in self.slide_names: try: self.slides.append(OpenSlide(os.path.join(self.root, name))) except Exception as exc: raise Exception( 'an error has occurred while reading slide "{}"'.format( name)) for label_cat in range(len(self.labels)): self.weights_in_label.append([]) self.weights_in_label_slide.append([]) # region triangulation total_region_count = 0 for i in range(len(self.shifted_structure)): self.triangulation.append([]) self.weights.append([]) self.weights_in_slide.append([]) for label_cat in range(len(self.labels)): self.weights_in_label[label_cat].append([]) self.weights_in_label_slide[label_cat].append([]) self.serialized_index_slide.append([]) self.a_slide.append([]) self.p_slide.append([]) self.slide_weights.append(0) self.slide_triangles.append(0) w, h = self.slides[i].dimensions # slide width/height for label_cat in range(len(self.labels)): for label in self.labels[label_cat]: self.a_label_slide[label_cat][label].append([]) self.p_label_slide[label_cat][label].append([]) self.serialized_index_label_slide[label_cat][label].append( []) self.label_slide_weights[label_cat][label].append(0) self.label_slide_triangles[label_cat][label].append(0) for j in range(len(self.shifted_structure[i])): region = self.shifted_structure[i][j] total_region_count += 1 # triangulation self.triangulation[-1].append(tripy.earclip(region)) for x, y in region: if w < x or h < y: raise Exception( 'invalid polygon vertex position (%d, %d) in %s!' % (x, y, self.slide_names[i])) # triangle area calculation self.weights[i].append([]) self.weights_in_slide[i].append([]) self.slide_triangles[i] += len(self.triangulation[i][j]) for label_cat in range(len(self.labels)): self.weights_in_label[label_cat][i].append([]) self.weights_in_label_slide[label_cat][i].append([]) label = self.label_of_region[label_cat][i][j] if label != -1: self.label_triangles[label_cat][label] += len( self.triangulation[i][j]) self.label_slide_triangles[label_cat][label][i] += len( self.triangulation[i][j]) for (x1, y1), (x2, y2), (x3, y3) in self.triangulation[i][j]: a = x2 - x1 b = y2 - y1 c = x3 - x1 d = y3 - y1 area = abs(a * d - b * c) / 2 weight = area / (self.src_sizes[i]**2) self.weights[i][j].append(weight) self.weights_in_slide[i][j].append(weight) self.total_weight += weight self.slide_weights[i] += weight for label_cat in range(len(self.labels)): self.weights_in_label[label_cat][i][j].append(weight) self.weights_in_label_slide[label_cat][i][j].append( weight) label = self.label_of_region[label_cat][i][j] if label != -1: self.label_weights[label_cat][label] += weight self.label_slide_weights[label_cat][label][ i] += weight # calculate raw slide size for i in range(len(self.structure)): self.slide_areas.append(0) for j in range(len(self.structure[i])): region = self.structure[i][j] triangles = tripy.earclip(region) for (x1, y1), (x2, y2), (x3, y3) in triangles: a = x2 - x1 b = y2 - y1 c = x3 - x1 d = y3 - y1 area = abs(a * d - b * c) / 2 self.total_area += area self.slide_areas[-1] += area for label_cat in range(len(self.labels)): label = self.label_of_region[label_cat][i][j] if label != -1: self.label_areas[label_cat][label] += area # calculate the set of triangle weights for each fetch_mode for i in range(len(self.weights)): # svs for j in range(len(self.weights[i])): # region for k in range(len(self.weights[i][j])): # triangle self.weights[i][j][k] /= self.total_weight self.weights_in_slide[i][j][k] /= self.slide_weights[i] self.serialized_index.append((i, j, k)) self.serialized_index_slide[i].append((j, k)) for label_cat in range(len(self.labels)): label = self.label_of_region[label_cat][i][j] if label != -1: self.weights_in_label[label_cat][i][j][ k] /= self.label_weights[label_cat][label] if self.label_slide_weights[label_cat][label][ i] > 0: self.weights_in_label_slide[label_cat][i][j][ k] /= self.label_slide_weights[label_cat][ label][i] self.serialized_index_label[label_cat][ label].append((i, j, k)) self.serialized_index_label_slide[label_cat][ label][i].append((j, k)) self.total_triangles += 1 # Walker's alias method for weighted sampling of triangles def walker_precomputation(probs): EPS = 1e-10 # normalization prob_sum = 0 for prob in probs: prob_sum += prob prob_sum *= (1 + EPS) for i in range(len(probs)): probs[i] /= prob_sum a = [-1] * len(probs) p = [0] * len(probs) fixed = 0 while fixed < len(probs): # block assignment of small items for i in range(len(probs)): if p[i] == 0 and probs[i] * len(probs) <= (1.0 + EPS): p[i] = probs[i] * len(probs) probs[i] = 0 fixed += 1 # packing of large items for i in range(len(probs)): if probs[i] * len(probs) > 1.0: for j in range(len(probs)): if p[j] != 0 and a[j] == -1: a[j] = i probs[i] -= (1.0 - p[j]) / len(probs) if probs[i] * len(probs) <= (1.0 + EPS): break # fill -1 a for i in range(len(probs)): if a[i] == -1: a[i] = i return a, p # pre-computation for 'area' mode - all triangles are treated in single array probs = [] for i in range(len(self.weights)): # svs for j in range(len(self.weights[i])): # region for k in range(len(self.weights[i][j])): # triangle probs.append(self.weights[i][j][k]) self.a_area, self.p_area = walker_precomputation(probs) # pre-computaiton for 'slide' mode for i in range(len(self.weights)): # svs probs = [] for j in range(len(self.weights[i])): # region for k in range(len(self.weights[i][j])): # triangle probs.append(self.weights_in_slide[i][j][k]) self.a_slide[i], self.p_slide[i] = walker_precomputation(probs) # pre-computation for 'label' mode for label_cat in range(len(self.labels)): for label in self.labels[label_cat]: probs = [] for slide_id, region_id in self.regions_of_label[label_cat][ label]: for tri_id in range( len(self.weights_in_label[label_cat][slide_id] [region_id])): probs.append(self.weights_in_label[label_cat][slide_id] [region_id][tri_id]) self.a_label[label_cat][label], self.p_label[label_cat][ label] = walker_precomputation(probs) # pre-computation for 'label-slide' mode for label_cat in range(len(self.labels)): for label in self.labels[label_cat]: for slide_id in range(len(self.weights)): probs = [] for region_id in self.regions_of_label_slide[label_cat][ label][slide_id]: for tri_id in range( len(self.weights_in_label_slide[label_cat] [slide_id][region_id])): probs.append(self.weights_in_label_slide[label_cat] [slide_id][region_id][tri_id]) self.a_label_slide[label_cat][label][ slide_id], self.p_label_slide[label_cat][label][ slide_id] = walker_precomputation(probs) if self.verbose > 0: print('loaded {} slide(s).'.format(len(self.shifted_structure))) for i in range(len(self.shifted_structure)): print('[{}] {}'.format(i, self.slide_names[i])) print('- {} regions'.format(len(self.shifted_structure[i]))) print('- {} px2'.format(self.slide_areas[i])) print('- patch scale:', self.src_sizes[i]) weight_sum = 0 for region in self.weights[i]: for w_triangle in region: weight_sum += w_triangle print('- fetch probability (area mode):', weight_sum) print('there are total {} regions.'.format(total_region_count, int(self.total_area))) self.patch_per_epoch = 0 for i in range(len(self.src_sizes)): self.patch_per_epoch += self.slide_areas[i] / (self.src_sizes[i]** 2) self.patch_per_epoch = int(self.patch_per_epoch) if self.verbose > 0: print('patches per epoch is set to {}.'.format( self.patch_per_epoch)) print() self.reset_fetch_count()
def open_slide(slide): return OpenSlide(slide)
from openslide import OpenSlide svs_path = 'Slide 10.svs' # Open whole slide image img = OpenSlide(svs_path) # Get shape original_dimensions = img.dimensions # Get thumbnail image (t times smaller) t = 512 resize_width = original_dimensions[0] / t resize_height = original_dimensions[1] / t resized_img = img.get_thumbnail( (resize_width, resize_height)) # PIL.Image type resized_img.save('thumbnail_images_sample/' + svs_path.split('.')[0] + '.png') # Get a region of the original image (here [10000:10100,10000:10100]) cropped_img = img.read_region((10000, 10000), 0, (100, 100)) # PIL Image RGBA cropped_img = cropped_img.convert('RGB') # PIL Image RGB cropped_img.save('tmp.png')
def extract_patch_on_slide( file_path_tif, \ file_path_tis_mask, \ file_path_jpg, \ save_location_path_patch_position_visualize, \ save_location_path_patch_position_csv, \ size_patch): """ -- Intput : file_path_tif : full path file_path_tis_mask : full path file_path_jpg : full path save_location_path_patch_position_visualize : full path save_location_path_patch_position_csv : full path -- Result : Draw patch position. Save coordinate of patch at level_0. """ patch_level = 0 contours_level = 4 mask_level = 4 slide = OpenSlide(file_path_tif) slide_w_lv_4, slide_h_lv_4 = slide.level_dimensions[4] downsample = slide.level_downsamples[4] size_patch_lv_4 = int(size_patch / downsample) # Make integral image of slide tissue_mask = cv2.imread(file_path_tis_mask, 0) integral_image_tissue = integral_image(tissue_mask.T / 255) # Load original bgr_jpg_lv_4 for visualizing patch position wsi_bgr_jpg = cv2.imread(file_path_jpg) wsi_jpg_visualizing_patch_position = wsi_bgr_jpg.copy() print('==> making contours of tissue region from jpg ..') # Find and Draw contours_tissue - (color : blue) _, contours_tissue, _ = cv2.findContours( \ tissue_mask, \ cv2.RETR_TREE, \ cv2.CHAIN_APPROX_SIMPLE) cv2.drawContours(wsi_jpg_visualizing_patch_position, \ contours_tissue, -1, (255, 0, 0), 2) # Make csv_writer csv_file = open(save_location_path_patch_position_csv, 'w') fieldnames = ['X', 'Y'] csv_writer = csv.DictWriter(csv_file, fieldnames=fieldnames) csv_writer.writeheader() print('==> Extracting patches randomly on tissue region...') patch_cnt = 0 ### Extract random patches on tissue region for contour in contours_tissue: # Check if contour area is samller than patch area area = cv2.contourArea(contour) area_patch_lv_4 = size_patch_lv_4**2 if area < area_patch_lv_4: continue # Determine number of patches to extract number_patches = int(round(area / area_patch_lv_4 * 1.5)) number_patches = min(50, number_patches) print('contour area : ', area, ' num_patch : ', number_patches) # Get coordinates of contour (level : 4) coordinates = (np.squeeze(contour)).T coords_x = coordinates[0] coords_y = coordinates[1] # Bounding box vertex p_x_left = np.min(coords_x) p_x_right = np.max(coords_x) p_y_top = np.min(coords_y) p_y_bottom = np.max(coords_y) # Make candidates of patch coordinate (level : 4) candidate_x = \ np.arange(round(p_x_left), round(p_x_right)).astype(int) candidate_y = \ np.arange(round(p_y_top), round(p_y_bottom)).astype(int) # Pick coordinates randomly len_x = candidate_x.shape[0] len_y = candidate_y.shape[0] number_patches = min(number_patches, len_x) number_patches = min(number_patches, len_y) random_index_x = np.random.choice(len_x, number_patches, replace=False) random_index_y = np.random.choice(len_y, number_patches, replace=True) for i in range(number_patches): patch_x = candidate_x[random_index_x[i]] patch_y = candidate_y[random_index_y[i]] # Check if out of range if (patch_x + size_patch_lv_4 > slide_w_lv_4) or \ (patch_y + size_patch_lv_4 > slide_h_lv_4): continue # Check ratio of tumor region tissue_integral = integrate(integral_image_tissue, \ (patch_x, patch_y), \ (patch_x + size_patch_lv_4 - 1, patch_y + size_patch_lv_4 - 1)) tissue_ratio = tissue_integral / (size_patch_lv_4**2) if tissue_ratio < 0.9: continue # Save patches position to csv file. patch_x_lv_0 = int(round(patch_x * downsample)) patch_y_lv_0 = int(round(patch_y * downsample)) csv_writer.writerow({'X': patch_x_lv_0, 'Y': patch_y_lv_0}) patch_cnt += 1 # save cut patches im = slide.read_region((patch_x_lv_0, patch_y_lv_0), 0, (size_patch, size_patch)) im_rgba = np.array(im) im_rgb = cv2.cvtColor(im_rgba, cv2.COLOR_RGBA2RGB) cur_patient_name = file_path_tif.split('/')[4] cur_patient_name = cur_patient_name.split('.')[0] global ID cur_cut_name = save_cut_path_positive_patch_17 + cur_patient_name + '_' + str( ID) + '.jpg' ID += 1 print('cur_cut_name', cur_cut_name) cv2.imwrite(cur_cut_name, im_rgb) # Draw patch position (color : Green) cv2.rectangle(wsi_jpg_visualizing_patch_position, \ (patch_x, patch_y), \ (patch_x + size_patch_lv_4, patch_y + size_patch_lv_4), \ (0, 255, 0), \ thickness=1) print('slide :\t', file_path_tif) print('patch_cnt:\t', patch_cnt) # Save visualizing image. cv2.imwrite(save_location_path_patch_position_visualize, \ wsi_jpg_visualizing_patch_position) csv_file.close()
def __init__(self, ndpi_path): self.ndpi_slide = OpenSlide(ndpi_path)
def tile(path_to_slide="../Neutrophil/", image_file="ImageCollection_0000026280_2016-10-27 14_13_01.scn", outdir="../Neutrophil/Outputs/"): slide = OpenSlide(path_to_slide + image_file) assert 'openslide.bounds-height' in slide.properties assert 'openslide.bounds-width' in slide.properties assert 'openslide.bounds-x' in slide.properties assert 'openslide.bounds-y' in slide.properties x = int(slide.properties['openslide.bounds-x']) y = int(slide.properties['openslide.bounds-y']) bounds_height = int(slide.properties['openslide.bounds-height']) bounds_width = int(slide.properties['openslide.bounds-width']) half_width_region = 49 full_width_region = 299 stepsize = full_width_region - half_width_region n_x = int((bounds_width - 1) / stepsize) n_y = int((bounds_height - 1) / stepsize) dat = np.empty((0, int(299**2 * 3)), dtype='uint8') imloc = [] counter = 0 svcounter = 0 ct = 0 if not os.path.exists(outdir): os.makedirs(outdir) if not os.path.exists(outdir + '/Tiles'): os.makedirs(outdir + '/Tiles') if not os.path.exists(outdir + '/data'): os.makedirs(outdir + '/data') for i in range(n_x - 1): for j in range(n_y - 1): target_x = stepsize * i target_y = stepsize * j image_x = target_x + x image_y = target_y + y the_image = slide.read_region( (image_x, image_y), 0, (full_width_region, full_width_region)) the_imagea = np.array(the_image)[:, :, :3] mask = (the_imagea[:, :, :3] > 200).astype(np.uint8) mask = mask[:, :, 0] * mask[:, :, 1] * mask[:, :, 2] white = np.sum(mask) / (299 * 299) if white < 0.5: # the_image.save(outdir + "Tiles/region_x-{}-y-{}.png".format(target_x, target_y)) imloc.append([svcounter, counter, target_x, target_y]) if svcounter % 5000 == 0 and svcounter != 0: ct = int(svcounter / 5000) np.savetxt(outdir + '/data/data-{}.txt'.format(ct), dat, fmt='%i', delimiter='\t') dat = np.empty((0, int(299**2 * 3)), dtype='uint8') pix = np.array(the_image)[:, :, 0:3] dat = np.vstack([dat, pix.flatten()]) svcounter += 1 else: pass # print('Ignore white!') counter += 1 ct += 1 np.savetxt(outdir + '/data/data-{}.txt'.format(ct), dat, fmt='%i', delimiter='\t') dat = np.empty((0, int(299**2 * 3)), dtype='uint8') imlocpd = pd.DataFrame(imloc, columns=["Num", "Count", "X", "Y"]) imlocpd.to_csv(outdir + "/data/dict.csv", index=False)
class WSI(object): """ # ================================ # Class to annotate WSIs with ROIs # ================================ """ index = 0 wsi_paths = [] mask_paths = [] def_level = 7 key = 0 def read_normal_wsi(self, wsi_path): """ # ===================================================================================== # read WSI image and resize # Due to memory constraint, we use down sampled (4th level, 1/32 resolution) image # ====================================================================================== """ try: self.wsi_path = wsi_path self.wsi_image = OpenSlide(wsi_path) level = min(self.def_level, self.wsi_image.level_count - 1) print('level used: %d' % level) print(self.wsi_image.level_dimensions[level]) self.rgb_image_pil = self.wsi_image.read_region((0, 0), level, self.wsi_image.level_dimensions[level]) self.rgb_image = np.array(self.rgb_image_pil) except OpenSlideUnsupportedFormatError: print('Exception: OpenSlideUnsupportedFormatError') return False return True def read_tumor_wsi(self, wsi_path, mask_path): """ # ===================================================================================== # read WSI image and resize # Due to memory constraint, we use down sampled (4th level, 1/32 resolution) image # ====================================================================================== """ try: self.wsi_path = wsi_path self.wsi_image = OpenSlide(wsi_path) self.mask_image = OpenSlide(mask_path) level_used = self.wsi_image.level_count - 1 self.rgb_image_pil = self.wsi_image.read_region((0, 0), level_used, self.wsi_image.level_dimensions[level_used]) self.rgb_image = np.array(self.rgb_image_pil) mask_level = self.mask_image.level_count - 1 self.rgb_mask_pil = self.mask_image.read_region((0, 0), mask_level, self.mask_image.level_dimensions[mask_level]) resize_factor = float(1.0 / pow(2, level_used - mask_level)) self.mask = cv2.resize(np.array(self.rgb_mask_pil), (0, 0), fx=resize_factor, fy=resize_factor) # self.rgb_image = cv2.resize(self.rgb_image, (0, 0), fx=0.50, fy=0.50) except OpenSlideUnsupportedFormatError: print('Exception: OpenSlideUnsupportedFormatError') return False return True def find_roi_normal(self): # self.mask = cv2.cvtColor(self.mask, cv2.CV_32SC1) hsv = cv2.cvtColor(self.rgb_image, cv2.COLOR_BGR2HSV) # [20, 20, 20] lower_red = np.array([30, 30, 30]) # [255, 255, 255] upper_red = np.array([200, 200, 200]) mask = cv2.inRange(hsv, lower_red, upper_red) res = cv2.bitwise_and(self.rgb_image, self.rgb_image, mask=mask) # (50, 50) close_kernel = np.ones((50, 50), dtype=np.uint8) close_kernel_tmp = np.ones((30, 30), dtype=np.uint8) image_close = Image.fromarray(cv2.morphologyEx(np.array(mask), cv2.MORPH_CLOSE, close_kernel)) image_close_tmp = Image.fromarray(cv2.morphologyEx(np.array(mask), cv2.MORPH_CLOSE, close_kernel_tmp)) # (30, 30) open_kernel = np.ones((30, 30), dtype=np.uint8) open_kernel_tmp = np.ones((30, 30), dtype=np.uint8) image_open = Image.fromarray(cv2.morphologyEx(np.array(image_close), cv2.MORPH_OPEN, open_kernel)) image_open_tmp = Image.fromarray(cv2.morphologyEx(np.array(image_close_tmp), cv2.MORPH_OPEN, open_kernel_tmp)) contour_rgb, bounding_boxes, contour_rgb_tmp = self.get_normal_image_contours(np.array(image_open), self.rgb_image, np.array(image_open_tmp)) # self.draw_bbox(bounding_boxes) self.display(contour_rgb, contour_rgb_tmp) def find_roi_tumor(self): # self.mask = cv2.cvtColor(self.mask, cv2.CV_32SC1) hsv = cv2.cvtColor(self.rgb_image, cv2.COLOR_BGR2HSV) lower_red = np.array([20, 20, 20]) upper_red = np.array([200, 200, 200]) mask = cv2.inRange(hsv, lower_red, upper_red) res = cv2.bitwise_and(self.rgb_image, self.rgb_image, mask=mask) # (50, 50) close_kernel = np.ones((20, 20), dtype=np.uint8) close_kernel_tmp = np.ones((50, 50), dtype=np.uint8) image_close = Image.fromarray(cv2.morphologyEx(np.array(mask), cv2.MORPH_CLOSE, close_kernel)) image_close_tmp = Image.fromarray(cv2.morphologyEx(np.array(mask), cv2.MORPH_CLOSE, close_kernel_tmp)) # (30, 30) open_kernel = np.ones((5, 5), dtype=np.uint8) open_kernel_tmp = np.ones((30, 30), dtype=np.uint8) image_open = Image.fromarray(cv2.morphologyEx(np.array(image_close), cv2.MORPH_OPEN, open_kernel)) image_open_tmp = Image.fromarray(cv2.morphologyEx(np.array(image_close_tmp), cv2.MORPH_OPEN, open_kernel_tmp)) contour_rgb, contour_mask, bounding_boxes, contour_rgb_tmp, contour_mask_tmp = self.get_tumor_image_contours( np.array(image_open), self.rgb_image, self.mask, np.array(image_open_tmp)) wsi_name = utils.get_filename_from_path(self.wsi_path) # cv2.imwrite(os.path.join(utils.THESIS_FIGURE_DIR, wsi_name) + '_hsv_mask.png', mask) # cv2.imwrite(os.path.join(utils.THESIS_FIGURE_DIR, wsi_name) + '_mask.png', self.mask) # cv2.imwrite(os.path.join(utils.THESIS_FIGURE_DIR, wsi_name) + '_image_close.png', np.array(image_close)) # cv2.imwrite(os.path.join(utils.THESIS_FIGURE_DIR, wsi_name) + '_image_open.png', np.array(image_open)) # rgb_bbox = self.rgb_image.copy() # for i, bounding_box in enumerate(bounding_boxes): # x = int(bounding_box[0]) # y = int(bounding_box[1]) # cv2.rectangle(rgb_bbox, (x, y), (x + bounding_box[2], y + bounding_box[3]), color=(0, 0, 255), # thickness=2) # cv2.imshow('contour_rgb', contour_rgb) # cv2.imshow('contour_bbox', rgb_bbox) # cv2.imwrite(os.path.join(utils.THESIS_FIGURE_DIR, wsi_name) + 'contour.png', contour_rgb) # cv2.imwrite(os.path.join(utils.THESIS_FIGURE_DIR, wsi_name) + '_bbox.png', rgb_bbox) cv2.imwrite(os.path.join(utils.THESIS_FIGURE_DIR, wsi_name) + '_hsv.png', hsv) cv2.imshow('hsv', hsv) cv2.waitKey(0) # self.display(contour_rgb, contour_rgb_tmp, contour_mask) def get_normal_image_contours(self, cont_img, rgb_image, cont_img_tmp): _, contours, _ = cv2.findContours(cont_img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) _, contours_tmp, _ = cv2.findContours(cont_img_tmp, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) # print(contours) boundingBoxes = [cv2.boundingRect(c) for c in contours] # print(boundingBoxes) contours_rgb_image_array = np.array(rgb_image) contours_rgb_image_array_tmp = np.array(rgb_image) line_color = (255, 0, 0) # blue color code cv2.drawContours(contours_rgb_image_array, contours, -1, line_color, 3) cv2.drawContours(contours_rgb_image_array_tmp, contours_tmp, -1, line_color, 3) # cv2.drawContours(mask_image, contours_mask, -1, line_color, 3) return contours_rgb_image_array, boundingBoxes, contours_rgb_image_array_tmp def get_tumor_image_contours(self, cont_img, rgb_image, mask_image, cont_img_tmp): _, contours, _ = cv2.findContours(cont_img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) _, contours_tmp, _ = cv2.findContours(cont_img_tmp, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) # _, contours_mask, _ = cv2.findContours(mask_image, cv2.RETR_FLOODFILL, cv2.CHAIN_APPROX_SIMPLE) # print(contours) boundingBoxes = [cv2.boundingRect(c) for c in contours] # print(boundingBoxes) contours_rgb_image_array = np.array(rgb_image) contours_rgb_image_array_tmp = np.array(rgb_image) contours_mask_image_array = np.array(mask_image) contours_mask_image_array_tmp = np.array(mask_image) line_color = (255, 0, 0) # blue color code cv2.drawContours(contours_rgb_image_array, contours, -1, line_color, 3) cv2.drawContours(contours_rgb_image_array_tmp, contours_tmp, -1, line_color, 3) cv2.drawContours(contours_mask_image_array, contours, -1, line_color, 3) cv2.drawContours(contours_mask_image_array_tmp, contours_tmp, -1, line_color, 3) # cv2.drawContours(mask_image, contours_mask, -1, line_color, 3) return contours_rgb_image_array, contours_mask_image_array, boundingBoxes, contours_rgb_image_array_tmp, \ contours_mask_image_array_tmp def display(self, contour_rgb, contour_rgb_tmp, contour_mask=None): # cv2.imshow('rgb', self.rgb_image) # cv2.imshow('mask', mask) # cv2.imshow('image_close',np.array(image_close)) # cv2.imshow('image_open', np.array(image_open)) contour_rgb = cv2.resize(contour_rgb, (0, 0), fx=0.60, fy=0.60) cv2.imshow('contour_rgb', np.array(contour_rgb)) contour_rgb_tmp = cv2.resize(contour_rgb_tmp, (0, 0), fx=0.60, fy=0.60) cv2.imshow('contour_rgb_tmp', np.array(contour_rgb_tmp)) if contour_mask is not None: contour_mask = cv2.resize(contour_mask, (0, 0), fx=0.60, fy=0.60) cv2.imshow('contour_mask', np.array(contour_mask)) # contour_mask_tmp = cv2.resize(contour_mask_tmp, (0, 0), fx=0.40, fy=0.40) # cv2.imshow('contour_mask_tmp', np.array(contour_mask_tmp)) # cv2.imshow('contour_mask_only', np.array(contour_mask_only)) # cv2.imshow('wsi_mask', self.mask) # cv2.imshow('self_bb', np.array(self.rgb_image_pil)) def draw_bbox(self, bounding_boxes): draw = ImageDraw.Draw(self.rgb_image_pil) for i, bounding_box in enumerate(bounding_boxes): x = int(bounding_box[0]) y = int(bounding_box[1]) thickness = 5 for offset in range(thickness): draw.rectangle([x + offset, y + offset, x + offset + bounding_box[2], y + offset + bounding_box[3]], outline=(255, 0, 0)) def wait(self): self.key = cv2.waitKey(0) & 0xFF print('key: %d' % self.key) if self.key == 27: # escape return False elif self.key == 81: # <- (prev) self.index -= 1 if self.index < 0: self.index = len(self.wsi_paths) - 1 elif self.key == 83: # -> (next) self.index += 1 if self.index >= len(self.wsi_paths): self.index = 0 return True
class QtCustomWindow( qt.QWidget ): def __init__( self, parent = None ) : qt.QWidget.__init__( self, parent ) self.current_x=0 self.current_y=0 #read region always works in the level 0 reference for reading locations self.current_zoom=0 self.image_width=750 self.image_height=750 self.setWindowTitle('Red Rock') self.hbox = qt.QHBoxLayout(self) self.lbl = qt.QLabel(self) self.setLayout(self.hbox) self.initUI() def initUI(self): # initialize the widget, make the openslide object pointing to the # multilevel image and initialize the view to the top left corner archivo=u'/home/martin/Downloads/CMU-1-JP2K-33005.svs' # openSlide object self.osr = OS(archivo) self.level_count = self.osr.level_count self.current_x=0 self.current_y=0 self.current_zoom=self.level_count-1 self.level_dimensions=self.osr.level_dimensions print self.level_dimensions print self.osr.level_downsamples #width, height = osr.dimensions self.hbox.addWidget(self.lbl) #self.move(300, 200) self.updatePixmap() self.show() def updatePixmap(self): im=self.osr.read_region((self.current_x,self.current_y),self.current_zoom,(self.image_width,self.image_height)) data = im.tostring('raw', 'RGBA') image = qt.QImage(data, im.size[0], im.size[1], qt.QImage.Format_ARGB32) pix = qt.QPixmap.fromImage(image) self.lbl.setPixmap(pix) self.lbl.show() def keyPressEvent( self, event ) : key = event.text() #larger steps for zoomed out images step =int(64*self.osr.level_downsamples[self.current_zoom]) print type(step) print type(self.current_x) if key=="s": self.current_y += step print type(self.current_y) self.current_y = min(self.current_y,int( self.level_dimensions[0][1]-self.image_height*self.osr.level_downsamples[self.current_zoom])) elif key=="w": #larger steps for zoomed out images self.current_y -= step self.current_y = max(0, self.current_y) elif key=="a": #larger steps for zoomed out images self.current_x -= step self.current_x = max(0, self.current_x) elif key=="d": #larger steps for zoomed out images self.current_x += step self.current_x = min(self.current_x,int( self.level_dimensions[0][0]-self.image_width*self.osr.level_downsamples[self.current_zoom])) elif key=="q": new_zoom = min(self.current_zoom+1,self.level_count-1) self.updateCorner(new_zoom) self.current_zoom=new_zoom elif key=="e": new_zoom = max(self.current_zoom-1,0) self.updateCorner(new_zoom) self.current_zoom=new_zoom print "x: %s" % self.current_x print "y: %s" % self.current_y print "zoom: %s" % self.current_zoom self.updatePixmap() def updateCorner(self,new_zoom): #updates the current_x and current_y values to preserve the image center #while zooming self.current_x=int(self.current_x + self.image_width /2 *(self.osr.level_downsamples[self.current_zoom]-self.osr.level_downsamples[new_zoom])) self.current_y=int(self.current_y + self.image_height /2 *(self.osr.level_downsamples[self.current_zoom]-self.osr.level_downsamples[new_zoom]))