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)
예제 #2
0
    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
예제 #3
0
    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
예제 #6
0
파일: loader.py 프로젝트: AmmieQi/ksptrack
    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()
예제 #7
0
파일: gest.py 프로젝트: anthonimes/GeST
    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
예제 #9
0
파일: loader.py 프로젝트: AmmieQi/ksptrack
    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()
예제 #10
0
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
예제 #11
0
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
예제 #12
0
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
예제 #13
0
파일: SegNu_def.py 프로젝트: davco6/SegNu
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))
예제 #14
0
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)
예제 #16
0
파일: topology.py 프로젝트: prisae/gempy
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)
예제 #17
0
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
예제 #18
0
    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)
예제 #19
0
    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