def test_generic_rag_3d(): labels = np.arange(8, dtype=np.uint8).reshape((2, 2, 2)) g = graph.RAG(labels) assert g.has_edge(0, 1) and g.has_edge(1, 3) and not g.has_edge(0, 3) h = graph.RAG(labels, connectivity=2) assert h.has_edge(0, 1) and h.has_edge(0, 3) and not h.has_edge(0, 7) k = graph.RAG(labels, connectivity=3) assert k.has_edge(0, 1) and k.has_edge(1, 2) and k.has_edge(2, 5)
def _quantify_2d(cls, tile, img_seg, nz, feature_calculators, **kwargs): feature_values = [] for z in range(nz): # Calculate properties of masked+labeled cell components cell_props = measure.regionprops(img_seg[z][CytometerBase.CELL_MASK_CHANNEL], cache=False) nucleus_props = measure.regionprops(img_seg[z][CytometerBase.NUCLEUS_MASK_CHANNEL], cache=False) if len(cell_props) != len(nucleus_props): raise ValueError( 'Expecting cell and nucleus properties to have same length (nucleus props = {}, cell props = {})' .format(len(nucleus_props), len(cell_props)) ) # Compute RAG for cells if necessary graph = None if kwargs.get('cell_graph'): labels = img_seg[z][CytometerBase.CELL_MASK_CHANNEL] # rag_boundary fails on all zero label matrices so default to empty graph if that is the case # see: https://github.com/scikit-image/scikit-image/blob/master/skimage/future/graph/rag.py#L386 if np.count_nonzero(labels) > 0: graph = label_graph.rag_boundary(labels, np.ones(labels.shape)) else: graph = label_graph.RAG() # Loop through each detected cell and compute features for i in range(len(cell_props)): props = ObjectProperties(cell=cell_props[i], nucleus=nucleus_props[i]) # Run each feature calculator and add results in order feature_values.append([ v for fc in feature_calculators for v in fc.get_feature_values(tile, img_seg, graph, props, z) ]) return feature_values
def __init__(self, block, fault_block, n_faults): """ :param block: :param fault_block: :param section: y-section (int) """ self.block = block.astype(int) self.block_original = block.astype(int) self.fault_block = fault_block.astype(int) self.fault_block = label(self.fault_block, neighbors=8, background=999) if 0 in self.block: # then this is a gempy model, numpy starts with 1 self.block[self.block == 0] = int( np.max(self.block) + 1) # set the 0 to highest value + 1 self.block -= n_faults # lower by n_faults to equal with pynoddy models # so the block starts at 1 and goes continuously to max self.ublock = (self.block.max() + 1) * self.fault_block + self.block self.lithologies = np.unique(self.block_original) self.labels, self.n_labels = self.get_labels() if 0 in np.unique(self.labels): self.labels += 1 self.labels_unique = np.unique(self.labels) self.G = graph.RAG(self.labels) self.centroids = self._get_centroids() self.lith_to_labels_lot = self._lithology_labels_lot() self.labels_to_lith_lot = self._labels_lithology_lot() self.classify_edges()
def hufbauer_beta(image, labels, connectivity=2): image = skc.rgb2lab(image)[:, :, [1, 2]] rag = graph.RAG(labels, connectivity=connectivity) for n in rag: rag.node[n].update({ 'labels': [n], 'pixel count': 0, 'total hue': np.array([0, 0], dtype=np.double) }) for index in np.ndindex(labels.shape): current = labels[index] rag.node[current]['pixel count'] += 1 rag.node[current]['total hue'] += image[index] for n in rag: rag.node[n]['mean hue'] = (rag.node[n]['total hue'] / rag.node[n]['pixel count']) for x, y, d in rag.edges(data=True): # TODO: might be wrong, check later diff = 1 / (1 + (rag.node[x]['mean hue'] - rag.node[y]['mean hue'])**2) diff = np.linalg.norm(diff) d['weight'] = diff return rag
def hufbauer_alpha(image, labels, connectivity=2, fudge=1e-8): rag = graph.RAG(labels, connectivity=connectivity) for n in rag: rag.node[n].update({ 'labels': [n], 'pixel count': 0, 'total color': np.array([0, 0, 0], dtype=np.double) }) for index in np.ndindex(labels.shape): current = labels[index] rag.node[current]['pixel count'] += 1 rag.node[current]['total color'] += image[index] for n in rag: rag.node[n]['mean color'] = (rag.node[n]['total color'] / rag.node[n]['pixel count']) rag.node[n]['alpha'] = np.sum(rag.node[n]['mean color']**2) for x, y, d in rag.edges(data=True): # TODO: might be wrong, check later #d['weight'] = 1 / (fudge + (rag.node[x]['alpha'] - rag.node[y]['alpha']) ** 2) d['weight'] = -((rag.node[x]['alpha'] - rag.node[y]['alpha'])**2.) #d['weight'] = np.log((rag.node[x]['alpha'] - rag.node[y]['alpha']) ** 2.) return rag
def prepare_graphs(self): self.centroids = [] self.graphs = [] print('preparing graphs...') pbar = tqdm(total=len(self.imgs) - (self.depth - 1)) for i in range(self.labels.shape[-1] - self.depth + 1): labels = self.labels[..., i:i + self.depth].copy() for d in range(1, self.depth): labels[..., d] += labels[..., d - 1].max() + 1 graph = skg.RAG(label_image=labels) self.graphs.append(graph) labels = self.labels[..., i:i + self.depth].copy() centroids = [] for d in range(self.depth): regions = measure.regionprops(labels[..., d] + 1) centroids_ = np.array([ (p['centroid'][1] / labels[..., d].shape[1], p['centroid'][0] / labels[..., d].shape[0]) for p in regions ]) centroids.append(centroids_) self.centroids.append(np.concatenate(centroids)) pbar.update(1) pbar.close()
def _contiguous(self): """ (Private) Procedure that produce a contiguous set of segments. By default clustering on embeddings may provide segments that are far apart within the image. """ Gr = graph.RAG(self._presegmentation, connectivity=1) new_labels = copy(self._clustering) for _label in unique(self._clustering): labelmax = amax(new_labels) # getting regions with this label vertices = 1 + argwhere(new_labels == _label).flatten() Gc = Gr.subgraph(vertices) if (not (is_connected(Gc))): connected_component = sorted(connected_components(Gc), key=len, reverse=True) to_relabel = connected_component[1:] labelcpt = 1 for cc in to_relabel: for vertex in cc: new_labels[vertex - 1] = labelmax + labelcpt labelcpt += 1 self._clustering = new_labels for l, line in enumerate(self._presegmentation): for j, value in enumerate(line): self._segmentation[l][j] = new_labels[value - 1] + 1
def segment_map_to_rag_edges(segment_map): # rag = graph.rag_mean_color(np.zeros_like(segment_map), segment_map) rag = graph.RAG(segment_map, connectivity=2) rag_edges = np.array(rag.edges()) rag_edges = _remove_rows_with_negative( rag_edges ) #remove all edges connection to segment labels < 0 as they represent no segment return rag_edges
def prepare_graphs(self): self.graphs = [] print('preparing graphs...') pbar = tqdm(total=len(self.imgs)) for idx, (im, truth) in enumerate(zip(self.imgs, self.truths)): labels = self.labels[..., idx] graph = skg.RAG(label_image=labels) self.graphs.append(graph) pbar.update(1) pbar.close()
def build_rag(labels, weight_mode, imtitle, beta): ''' Constructs region adjacency graph for segmented image. ''' print("Constructing graph...") g = graph.RAG(labels, connectivity=2) for n in g: g.nodes[n].update({'labels': [n]}) mean_colors = np.load('../tmp/regions/regions_%s.npy' % imtitle) for n in g: g.nodes[n]['mean color'] = mean_colors[n] assign_weights(g, weight_mode, imtitle, beta) return g
def color_labels_by_graph(labels): label_graph = graph.RAG(label_image=labels) graph_dict = nx.coloring.greedy_color(label_graph, strategy='largest_first') label_outline = find_boundaries(labels.astype('int'), connectivity=1) output_labels = copy.copy(labels) output_labels[label_outline > 0] = 0 for idx in np.unique(output_labels): mask = output_labels == idx if idx == 0: output_labels[mask] = 0 else: val = graph_dict[idx] output_labels[mask] = val + 1 return output_labels
def topology_analyze(lith_block, fault_block, n_faults, areas_bool=False, return_block=False): """ Function to analyze the geological model topology. :param lith_block: :param fault_block: :param n_faults: Return: G, centroids, labels_unique, lith_to_labels_lot, labels_to_lith_lot """ lith_block = lith_block.astype(int) lithologies = np.unique(lith_block.astype(int)) # store a safe copy of the lith block for reference block_original = lith_block.astype(int) fault_block = fault_block.astype(int) # label the fault block for normalization (comparability of e.g. pynoddy and gempy models) fault_block = label(fault_block, neighbors=8, background=9999) if 0 in lith_block: # then this is a gempy model, numpy starts with 1 lith_block[lith_block == 0] = int(np.max(lith_block) + 1) # set the 0 to highest value + 1 lith_block -= n_faults # lower by n_faults to equal with pynoddy models # so the block starts at 1 and goes continuously to max # make sure that faults seperate lithologies in labeling, YUGE clever algorithm of the narcisist ublock = (lith_block.max() + 1) * fault_block + lith_block # label the block for unique regions labels_block, labels_n = label(ublock, neighbors=8, return_num=True, background=9999) if 0 in np.unique(labels_block): labels_block += 1 labels_unique = np.unique(labels_block) # create adjacency graph from labeled block G = graph.RAG(labels_block) # get the centroids from the labeled block centroids = get_centroids(labels_block) # create look-up-tables in both directions # TODO: change dict to pandas df you lazy dict fan lith_to_labels_lot = lithology_labels_lot(lithologies, labels_block, block_original, labels_unique) labels_to_lith_lot = labels_lithology_lot(labels_unique, labels_block, block_original) # classify the edges (stratigraphic, across-fault) # TODO: Across-unconformity edge identification classify_edges(G, centroids, block_original, fault_block) # compute the adjacency areas for each edge if areas_bool: # TODO: 2d option (if slice only), right now it only works for 3d compute_areas(G, labels_block) if not return_block: return G, centroids, labels_unique, lith_to_labels_lot, labels_to_lith_lot else: return G, centroids, labels_unique, lith_to_labels_lot, labels_to_lith_lot, labels_block
def run_watershed_seg(radio, segmented_regions, cluster_regions, label_image, image_grey, patch, threshold, num_split=1): """ Run watershed segmentation recursively. :param radio: An integer value. Expected cell radio. :param segmented_regions: A list of unicell scikit-image regions. :param cluster_regions: A list of cells clusters scikit-image regions. :param region_image: A numpy matrix. Labeled image. :param image_grey: A numpy matrix. Original image. :param patch: An integer value. Leght (in pixels) of the side of the sqare patch. """ from random import shuffle from skimage.future import graph if cluster_regions == [] or num_split >= 10: segmented_regions += cluster_regions segmented_image = np.zeros( (label_image.shape[0], label_image.shape[1])) for h, i in enumerate(segmented_regions): i.label = h + 1 for j in range(i.coords.shape[0]): segmented_image[i.coords[j, 0], i.coords[j, 1]] = i.label rag = graph.RAG(segmented_image) try: rag.remove_node(0) except networkx.exception.NetworkXError: print("Not node 0") for n, d in rag.nodes_iter(data=True): d['labels'] = [n] for x, y, d in rag.edges_iter(data=True): d['weight'] = 1 regions_to_evaluate = [n for n in rag if len(rag[n]) >= 1] final_proposes = get_optimized_cells(rag, regions_to_evaluate, segmented_regions, patch, segmented_image, image_grey) lb = label_final_cell_proposed(final_proposes, segmented_image) del (rag, final_proposes, regions_to_evaluate) final_regions, instances_image = sort_instance_image(lb, image_grey) instances_image[:, :] = 0 shuffle(final_regions) for h, i in enumerate(final_regions): i.label = h + 1 for j in range(i.coords.shape[0]): instances_image[i.coords[j, 0], i.coords[j, 1]] = i.label return (instances_image, final_regions) else: region_image = np.zeros((label_image.shape[0], label_image.shape[1])) image_leftovers = region_image.copy() image_labels = region_image.copy() for h, j in enumerate(cluster_regions): region_image[label_image == j.label] = j.label image_labels[label_image == j.label] = j.label image_leftovers[region_image != 0] = image_grey[region_image != 0] region_image[:, :] = 0 bw = closing(image_leftovers > threshold, square(3)) regions_splitted, label_image = apply_watershed( bw, image_labels, radio / num_split, image_leftovers, cluster_regions) generate_patches(regions_splitted, patch, label_image, image_grey, CELL_NUM=1000 * num_split) predictions = CNN_inference(192, len(regions_splitted)) segmented_regions += [ j for i, j in enumerate(regions_splitted) if predictions[i] <= 1 ] cluster_regions = [ j for i, j in enumerate(regions_splitted) if predictions[i] == 2 ] rm_tmp_files() num_split += 1 return (run_watershed_seg(radio, segmented_regions, cluster_regions, label_image, image_grey, patch, threshold, num_split))
def searchCrownTile(inpath, raster, clump, ram, grid, outpath, nbcore = 4, ngrid = -1, logger=logger): """ in : inpath : working directory with datas raster : name of raster ram : ram for otb application grid : grid name for serialisation out : output path ngrid : tile number out : raster with normelized name (tile_ngrid.tif) """ begintime = time.time() if os.path.exists(os.path.join(outpath, "tile_%s.tif"%(ngrid))): logger.error("Output file '%s' already exists"%(os.path.join(outpath, \ "tile_%s.tif"%(ngrid)))) sys.exit() rasterfile = gdal.Open(clump, 0) clumpBand = rasterfile.GetRasterBand(1) xsize = rasterfile.RasterXSize ysize = rasterfile.RasterYSize clumpArray = clumpBand.ReadAsArray() clumpProps = regionprops(clumpArray) rasterfile = clumpBand = clumpArray = None # Get extent of all image clumps params = {x.label:x.bbox for x in clumpProps} timeextents = time.time() logger.info(" ".join([" : ".join(["Get extents of all entities", str(round(timeextents - begintime, 2))]), "seconds"])) # Open Grid file driver = ogr.GetDriverByName("ESRI Shapefile") shape = driver.Open(grid, 0) grid_layer = shape.GetLayer() allTile = False # for each tile for feature in grid_layer : if ngrid is None: ngrid = int(feature.GetField("FID")) allTile = True # get feature FID idtile = int(feature.GetField("FID")) # feature ID vs. requested tile (ngrid) if idtile == int(ngrid): logger.info("Tile : %s"%(idtile)) # manage environment if not os.path.exists(os.path.join(inpath, str(ngrid))): os.mkdir(os.path.join(inpath, str(ngrid))) # entities ID list of tile listTileId = listTileEntities(raster, outpath, feature) # if no entities in tile if len(listTileId) != 0 : timentities = time.time() logger.info(" ".join([" : ".join(["Entities ID list of tile", str(round(timentities - timeextents, 2))]), "seconds"])) logger.info(" : ".join(["Entities number", str(len(listTileId))])) # tile entities bounding box listExtent = ExtentEntitiesTile(listTileId, params, xsize, ysize, False) timeextent = time.time() logger.info(" ".join([" : ".join(["Compute geographical extent of entities", str(round(timeextent - timentities, 2))]), "seconds"])) # Extract classification raster on tile entities extent tifRasterExtract = os.path.join(inpath, str(ngrid), "tile_%s.tif"%(ngrid)) if os.path.exists(tifRasterExtract):os.remove(tifRasterExtract) xmin, ymax = pixToGeo(raster, listExtent[1], listExtent[0]) xmax, ymin = pixToGeo(raster, listExtent[3], listExtent[2]) command = "gdalwarp -q -multi -wo NUM_THREADS={} -te {} {} {} {} -ot UInt32 {} {}".format(nbcore,\ xmin, \ ymin, \ xmax, \ ymax, \ raster, \ tifRasterExtract) Utils.run(command) timeextract = time.time() logger.info(" ".join([" : ".join(["Extract classification raster on tile entities extent", str(round(timeextract - timeextent, 2))]), "seconds"])) # Crown entities research ds = gdal.Open(tifRasterExtract) idx = ds.ReadAsArray()[1] g = graph.RAG(idx.astype(int), connectivity = 2) # Create connection duplicates listelt = [] for elt in g.edges(): if elt[0] > 301 and elt[1] > 301: listelt.append(elt) listelt.append((elt[1], elt[0])) # group by tile entities id topo = dict(fu.sortByFirstElem(listelt)) # Flat list and remove tile entities flatneighbors = set(chain(*list(dict((key,value) for key, value in list(topo.items()) if key in listTileId).values()))) timecrownentities = time.time() logger.info(" ".join([" : ".join(["List crown entities", str(round(timecrownentities - timeextract, 2))]), "seconds"])) # Crown raster extraction listExtentneighbors = ExtentEntitiesTile(flatneighbors, params, xsize, ysize, False) xmin, ymax = pixToGeo(raster, listExtentneighbors[1], listExtentneighbors[0]) xmax, ymin = pixToGeo(raster, listExtentneighbors[3], listExtentneighbors[2]) rastEntitiesNeighbors = os.path.join(inpath, str(ngrid), "crown_%s.tif"%(ngrid)) if os.path.exists(rastEntitiesNeighbors):os.remove(rastEntitiesNeighbors) command = "gdalwarp -q -multi -wo NUM_THREADS={} -te {} {} {} {} -ot UInt32 {} {}".format(nbcore,\ xmin, \ ymin, \ xmax, \ ymax, \ raster, \ rastEntitiesNeighbors) Utils.run(command) timeextractcrown = time.time() logger.info(" ".join([" : ".join(["Extract classification raster on crown entities extent", str(round(timeextractcrown - timecrownentities, 2))]), "seconds"])) shutil.copy(rastEntitiesNeighbors, os.path.join(outpath, "crown_%s.tif"%(ngrid))) with open(os.path.join(inpath, str(ngrid), "listid_%s"%(ngrid)), 'wb') as fp: pickle.dump([listTileId + list(flatneighbors)], fp) shutil.copy(os.path.join(inpath, str(ngrid), "listid_%s"%(ngrid)), os.path.join(outpath, "listid_%s"%(ngrid))) shutil.rmtree(os.path.join(inpath, str(ngrid)), ignore_errors=True) if allTile: ngrid += 1
def test_generic_rag_2d(): labels = np.array([[1, 2], [3, 4]], dtype=np.uint8) g = graph.RAG(labels) assert g.has_edge(1, 2) and g.has_edge(2, 4) and not g.has_edge(1, 4) h = graph.RAG(labels, connectivity=2) assert h.has_edge(1, 2) and h.has_edge(1, 4) and h.has_edge(2, 3)
def _topology_analyze(lith_block, fault_block, n_faults, areas_bool=False, return_block=False, return_rprops=False, filter_rogue=False, noddy=False, filter_threshold_area=10, neighbors=8, enhanced_labels=True): """ Analyses the block models adjacency topology. Every lithological entity is described by a uniquely labeled node (centroid) and its connections to other entities by edges. Args: lith_block (np.ndarray): Lithology block model fault_block (np.ndarray): Fault block model n_faults (int): Number of df. Keyword Args: areas_bool (bool): If True computes adjacency areas for connected nodes in voxel number. Default False. return_block (bool): If True additionally returns the uniquely labeled block model as np.ndarray. n_faults (int): Number of faults. areas_bool (bool, optional): If True computes adjacency areas for connected nodes in voxel number. Default False. return_block (bool, optional): If True additionally returns the uniquely labeled block model as np.ndarray. return_rprops (bool, optional): If True additionally returns region properties of the unique regions (see skimage.measure.regionprops). filter_rogue (bool, optional): If True filters nodes with region areas below threshold (default: 1) from topology graph. filter_threshold_area (int, optional): Specifies the threshold area value (number of pixels) for filtering small regions that may thow off topology analysis. neighbors (int, optional): Specifies the neighbor voxel connectivity taken into account for the topology analysis. Must be either 4 or 8 (default: 8). enhanced_labels (bool, optional): If True enhances the topology graph node labeling with fb_id, lb_id and instance id (e.g. 1_6_b), if False reverses to just numeric labeling (default: True). Return: tuple: G: Region adjacency graph object (skimage.future.graph.rag.RAG) containing the adjacency topology graph (G.adj). centroids (dict): Centroid node coordinates as a dictionary with node id's (int) as keys and (x,y,z) coordinates as values. labels_unique (np.array): List of all labels used. lith_to_labels_lot (dict): Dictionary look-up-table to go from lithology id to node id. labels_to_lith_lot (dict): Dictionary look-up-table to go from node id to lithology id. """ block_original = lith_block.astype(int) # do we really still need this? # generate unique labels block by combining lith and fault blocks labels_block = get_unique_regions(lith_block, fault_block, n_faults, neighbors=neighbors, noddy=noddy) # create adjacency graph from labeled block G = graph.RAG(labels_block) rprops = regionprops( labels_block) # get properties of uniquely labeles regions centroids = get_centroids(rprops) # unique region centroids coordinates # classify edges (stratigraphic, fault) classify_edges(G, centroids, fault_block) # filter rogue pixel nodes from graph if wanted if filter_rogue: G, centroids = filter_region_areas( G, centroids, rprops, area_threshold=filter_threshold_area) G = convert_node_labels_to_integers(G, first_label=1) centroids = { i + 1: coords for i, coords in enumerate(centroids.values()) } # enhanced node labeling containing fault block and lith id if enhanced_labels: labels = enhanced_labeling(G, rprops, lith_block, fault_block) G = relabel_nodes(G, labels) # relabel graph centroids = get_centroids( rprops) # redo centroids for updated labeling # compute the adjacency areas for each edge if areas_bool: compute_areas( G, labels_block ) # TODO: 2d option (if slice only), right now it only works for 3d # prep returned objects topo = [G, centroids] # keep option for old labeling for legacy support if not enhanced_labels: # create look-up-tables in both directions topo.append(lithology_labels_lot(labels_block, block_original)) topo.append(labels_lithology_lot(labels_block, block_original)) if return_block: # append the labeled block to return topo.append(labels_block) if return_rprops: # append rprops to return topo.append(rprops) return tuple(topo)
def topology_analyze(lith_block, fault_block, n_faults, areas_bool=False, return_block=False): """ Analyses the block models adjacency topology. Every lithological entity is described by a uniquely labeled node (centroid) and its connections to other entities by edges. Args: lith_block (np.ndarray): Lithology block model fault_block (np.ndarray): Fault block model n_faults (int): Number of faults. Keyword Args: areas_bool (bool): If True computes adjacency areas for connected nodes in voxel number. Default False. return_block (bool): If True additionally returns the uniquely labeled block model as np.ndarray. Return: tuple: G: Region adjacency graph object (skimage.future.graph.rag.RAG) containing the adjacency topology graph (G.adj). centroids (dict): Centroid node coordinates as a dictionary with node id's (int) as keys and (x,y,z) coordinates as values. labels_unique (np.array): List of all labels used. lith_to_labels_lot (dict): Dictionary look-up-table to go from lithology id to node id. labels_to_lith_lot (dict): Dictionary look-up-table to go from node id to lithology id. """ lith_block = lith_block.astype(int) lithologies = np.unique(lith_block.astype(int)) # store a safe copy of the lith block for reference block_original = lith_block.astype(int) fault_block = fault_block.astype(int) # label the fault block for normalization (comparability of e.g. pynoddy and gempy models) fault_block = label(fault_block, neighbors=8, background=9999) if 0 in lith_block: # then this is a gempy model, numpy starts with 1 lith_block[lith_block == 0] = int(np.max(lith_block) + 1) # set the 0 to highest value + 1 lith_block -= n_faults # lower by n_faults to equal with pynoddy models # so the block starts at 1 and goes continuously to max # make sure that faults seperate lithologies in labeling, YUGE clever algorithm of the narcisist ublock = (lith_block.max() + 1) * fault_block + lith_block # label the block for unique regions labels_block, labels_n = label(ublock, neighbors=8, return_num=True, background=9999) if 0 in np.unique(labels_block): labels_block += 1 labels_unique = np.unique(labels_block) # create adjacency graph from labeled block G = graph.RAG(labels_block) # get the centroids from the labeled block centroids = get_centroids(labels_block) # create look-up-tables in both directions lith_to_labels_lot = lithology_labels_lot(lithologies, labels_block, block_original, labels_unique) labels_to_lith_lot = labels_lithology_lot(labels_unique, labels_block, block_original) # classify the edges (stratigraphic, across-fault) classify_edges(G, centroids, block_original, fault_block) # compute the adjacency areas for each edge if areas_bool: # TODO: 2d option (if slice only), right now it only works for 3d compute_areas(G, labels_block) if not return_block: return G, centroids, labels_unique, lith_to_labels_lot, labels_to_lith_lot else: return G, centroids, labels_unique, lith_to_labels_lot, labels_to_lith_lot, labels_block
def quantify(self, tile, img_seg, channel_names=None, include_cell_intensity=True, include_nucleus_intensity=False, include_cell_graph=False, spot_count_channels=None, spot_count_params=None): ncyc, nz, _, nh, nw = tile.shape # Move cycles and channels to last axes (in that order) tile = np.moveaxis(tile, 0, -1) tile = np.moveaxis(tile, 1, -1) # Collapse tile to ZHWC (instead of cycles and channels being separate) tile = np.reshape(tile, (nz, nh, nw, -1)) nch = tile.shape[-1] # Generate default channel names list if necessary if channel_names is None: channel_names = ['{:03d}'.format(i) for i in range(nch)] if nch != len(channel_names): raise ValueError( 'Tile has {} channels but given channel name list has {} (they should be equal); ' 'channel names given = {}, tile shape = {}' .format(nch, len(channel_names), channel_names, tile.shape) ) # Configure features to be calculated based on provided flags feature_calculators = [BasicCellFeatures()] if include_cell_intensity: feature_calculators.append(IntensityFeatures(nch, channel_names, COMP_CELL)) if include_nucleus_intensity: feature_calculators.append(IntensityFeatures(nch, channel_names, COMP_NUCLEUS)) if include_cell_graph: feature_calculators.append(GraphFeatures()) if spot_count_channels is not None: indexes = [channel_names.index(c) for c in spot_count_channels] params = spot_count_params or {} feature_calculators.append(SpotFeatures(indexes, spot_count_channels, **params)) # Compute list of resulting feature names (values will be added in this order) feature_names = [v for fc in feature_calculators for v in fc.get_feature_names()] feature_values = [] for z in range(nz): # Calculate properties of masked+labeled cell components cell_props = measure.regionprops(img_seg[z][CELL_CHANNEL], cache=False) nucleus_props = measure.regionprops(img_seg[z][NUCLEUS_CHANNEL], cache=False) if len(cell_props) != len(nucleus_props): raise ValueError( 'Expecting cell and nucleus properties to have same length (nucleus props = {}, cell props = {})' .format(len(nucleus_props), len(cell_props)) ) # Compute RAG for cells if necessary graph = None if include_cell_graph: labels = img_seg[z][CELL_CHANNEL] # rag_boundary fails on all zero label matrices so default to empty graph if that is the case # see: https://github.com/scikit-image/scikit-image/blob/master/skimage/future/graph/rag.py#L386 if np.count_nonzero(labels) > 0: graph = label_graph.rag_boundary(labels, np.ones(labels.shape)) else: graph = label_graph.RAG() # Loop through each detected cell and compute features for i in range(len(cell_props)): props = ObjectProperties(cell=cell_props[i], nucleus=nucleus_props[i]) # Run each feature calculator and add results in order feature_values.append([ v for fc in feature_calculators for v in fc.get_feature_values(tile, img_seg, graph, props, z) ]) return pd.DataFrame(feature_values, columns=feature_names)
def _generate_graph(self, input_image: np.ndarray, superpixels: np.ndarray) -> graph: """Construct RAG graph using initial superpixel instance map Args: input_image (np.ndarray): Input image superpixels (np.ndarray): Initial superpixel instance map Returns: graph: Constructed graph """ g = graph.RAG(superpixels, connectivity=self.connectivity) if 0 in g.nodes: g.remove_node(n=0) # remove background node for n in g: g.nodes[n].update({ "labels": [n], "N": 0, "x": np.array([0, 0, 0]), "y": np.array([0, 0, 0]), "r": np.array([]), "g": np.array([]), "b": np.array([]), }) for index in np.ndindex(superpixels.shape): current = superpixels[index] if current == 0: continue g.nodes[current]["N"] += 1 g.nodes[current]["x"] += input_image[index] g.nodes[current]["y"] = np.vstack( (g.nodes[current]["y"], input_image[index])) for n in g: g.nodes[n]["mean"] = g.nodes[n]["x"] / g.nodes[n]["N"] g.nodes[n]["mean"] = g.nodes[n]["mean"] / \ np.linalg.norm(g.nodes[n]["mean"]) g.nodes[n]["y"] = np.delete(g.nodes[n]["y"], 0, axis=0) g.nodes[n]["r"] = self._color_features_per_channel( g.nodes[n]["y"][:, 0]) g.nodes[n]["g"] = self._color_features_per_channel( g.nodes[n]["y"][:, 1]) g.nodes[n]["b"] = self._color_features_per_channel( g.nodes[n]["y"][:, 2]) g.nodes[n]["r"] = g.nodes[n]["r"] / np.linalg.norm(g.nodes[n]["r"]) g.nodes[n]["g"] = g.nodes[n]["r"] / np.linalg.norm(g.nodes[n]["g"]) g.nodes[n]["b"] = g.nodes[n]["r"] / np.linalg.norm(g.nodes[n]["b"]) for x, y, d in g.edges(data=True): diff_mean = np.linalg.norm(g.nodes[x]["mean"] - g.nodes[y]["mean"]) / 2 diff_r = np.linalg.norm(g.nodes[x]["r"] - g.nodes[y]["r"]) / 2 diff_g = np.linalg.norm(g.nodes[x]["g"] - g.nodes[y]["g"]) / 2 diff_b = np.linalg.norm(g.nodes[x]["b"] - g.nodes[y]["b"]) / 2 diff_hist = (diff_r + diff_g + diff_b) / 3 diff = self.w_hist * diff_hist + self.w_mean * diff_mean d["weight"] = diff return g