def solve_component(component_id): # extract the nodes in this component sub_nodes = np.where(cc_labels == component_id)[0].astype('uint64') # if we only have a single node, return trivial labeling if len(sub_nodes) == 1: return sub_nodes, np.array([0], dtype='uint64'), 1 # extract the subgraph corresponding to this component inner_edges, _ = graph.extractSubgraphFromNodes(sub_nodes) sub_uvs = uv_ids[inner_edges] assert len(inner_edges) == len(sub_uvs), "%i, %i" % (len(inner_edges), len(sub_uvs)) # relabel sub-nodes and associated uv-ids sub_nodes_relabeled, max_local, node_mapping = relabelConsecutive( sub_nodes, start_label=0, keep_zeros=False) sub_uvs = nifty.tools.takeDict(node_mapping, sub_uvs) # build the graph sub_graph = nifty.graph.undirectedGraph(max_local + 1) sub_graph.insertEdges(sub_uvs) # solve local multicut sub_costs = costs[inner_edges] assert len(sub_costs) == sub_graph.numberOfEdges, "%i, %i" % ( len(sub_costs), sub_graph.numberOfEdges) sub_labels = solver(sub_graph, sub_costs, time_limit=time_limit) # relabel the solution sub_labels, max_seg_local, _ = relabelConsecutive(sub_labels, start_label=0, keep_zeros=False) assert len(sub_labels) == len(sub_nodes), "%i, %i" % (len(sub_labels), len(sub_nodes)) return sub_nodes, sub_labels, max_seg_local + 1
def mutex_watershed(affs, offsets, strides, randomize_strides=False, mask=None, noise_level=0): """ Compute mutex watershed segmentation. Introduced in "The Mutex Watershed and its Objective: Efficient, Parameter-Free Image Partitioning": https://arxiv.org/pdf/1904.12654.pdf Arguments: affs [np.ndarray] - input affinity map offsets [list[list[int]]] - pixel offsets corresponding to affinity channels strides [list[int]] - strides used to sub-sample long range edges randomize_strides [bool] - randomize the strides? (default: False) mask [np.ndarray] - mask to exclude from segmentation (default: None) noise_level [float] - sigma of noise added to affinities (default: 0) """ ndim = len(offsets[0]) if noise_level > 0: affs += noise_level * np.random.rand(*affs.shape) affs[:ndim] *= -1 affs[:ndim] += 1 seg = compute_mws_segmentation(affs, offsets, number_of_attractive_channels=ndim, strides=strides, mask=mask, randomize_strides=randomize_strides) relabelConsecutive(seg, out=seg, start_label=1, keep_zeros=mask is not None) return seg
def mutex_watershed_with_seeds(affs, offsets, seeds, strides, randomize_strides=False, mask=None, noise_level=0, return_graph=False, seed_state=None): """ Compute mutex watershed segmentation with seeds. Introduced in "The Mutex Watershed and its Objective: Efficient, Parameter-Free Image Partitioning": https://arxiv.org/pdf/1904.12654.pdf Arguments: affs [np.ndarray] - input affinity map offsets [list[list[int]]] - pixel offsets corresponding to affinity channels seeds [np.ndarray] - array with seed points strides [list[int]] - strides used to sub-sample long range edges randomize_strides [bool] - randomize the strides? (default: False) mask [np.ndarray] - mask to exclude from segmentation (default: None) noise_level [float] - sigma of noise added to affinities (default: 0) seed_state [dict] - seed state (default: None) """ ndim = len(offsets[0]) if noise_level > 0: affs += noise_level * np.random.rand(*affs.shape) affs[:ndim] *= -1 affs[:ndim] += 1 # compute grid graph with seeds and optional mask shape = affs.shape[1:] grid_graph = compute_grid_graph(shape, mask, seeds) # compute nn and mutex nh grid_graph.intra_seed_weight = 1 # set intra-seed weight to maximal attractive if seed_state is not None: attractive_edges, attractive_weights = seed_state['attractive'] grid_graph.set_seed_state(attractive_edges, attractive_weights) uvs, weights = grid_graph.compute_nh_and_weights(np.require(affs[:ndim], requirements='C'), offsets[:ndim]) grid_graph.intra_seed_weight = 0 # set intral-seed weight to minimal repulsive if seed_state is not None: repulsive_edges, repulsive_weights = seed_state['repulsive'] grid_graph.clear_seed_state() grid_graph.set_seed_state(repulsive_edges, repulsive_weights) mutex_uvs, mutex_weights = grid_graph.compute_nh_and_weights(np.require(affs[ndim:], requirements='C'), offsets[ndim:], strides, randomize_strides) # compute the segmentation n_nodes = grid_graph.n_nodes seg = compute_mws_clustering(n_nodes, uvs, mutex_uvs, weights, mutex_weights) relabelConsecutive(seg, out=seg, start_label=1, keep_zeros=mask is not None) seg = seg.reshape(shape) if mask is not None: seg[np.logical_not(mask)] = 0 if return_graph: return seg, grid_graph else: return seg
def mutex_watershed(affs, offsets, strides, randomize_strides=False, mask=None, noise_level=0): assert compute_mws_segmentation is not None, "Need affogato for mutex watershed" ndim = len(offsets[0]) if noise_level > 0: affs += noise_level * np.random.rand(*affs.shape) affs[:ndim] *= -1 affs[:ndim] += 1 seg = compute_mws_segmentation(affs, offsets, number_of_attractive_channels=ndim, strides=strides, mask=mask, randomize_strides=randomize_strides) relabelConsecutive(seg, out=seg, start_label=1, keep_zeros=mask is not None) return seg
def solve_subproblem(block_id): # extract nodes from this block block = blocking.getBlock(block_id) if halo is None else\ blocking.getBlockWithHalo(block_id, halo).outerBlock bb = tuple(slice(beg, end) for beg, end in zip(block.begin, block.end)) node_ids = np.unique(segmentation[bb]) # get the sub-graph corresponding to the nodes inner_edges, outer_edges = graph.extractSubgraphFromNodes(node_ids) sub_uvs = uv_ids[inner_edges] # relabel the sub-nodes and associated uv-ids for more efficient processing nodes_relabeled, max_id, mapping = relabelConsecutive(node_ids, start_label=0, keep_zeros=False) sub_uvs = nifty.tools.takeDict(mapping, sub_uvs) n_local_nodes = max_id + 1 sub_graph = nifty.graph.undirectedGraph(n_local_nodes) sub_graph.insertEdges(sub_uvs) sub_costs = costs[inner_edges] assert len(sub_costs) == sub_graph.numberOfEdges # solve multicut for the sub-graph sub_result = solver(sub_graph, sub_costs) assert len(sub_result) == len(node_ids), "%i, %i" % (len(sub_result), len(node_ids)) sub_edgeresult = sub_result[sub_uvs[:, 0]] != sub_result[sub_uvs[:, 1]] assert len(sub_edgeresult) == len(inner_edges) cut_edge_ids = inner_edges[sub_edgeresult] cut_edge_ids = np.concatenate([cut_edge_ids, outer_edges]) return cut_edge_ids
def mws_block(block_id): block = blocking.getBlock(block_id) bb = tuple(slice(beg, end) for beg, end in zip(block.begin, block.end)) bb_affs = (slice(None), ) + bb affs_ = affs[bb_affs].copy( ) # we need to copy here to leave the original affs unchanged mask_ = None if mask is None else mask[bb] if noise_level > 0: affs_ += noise_level * np.random.rand(*affs_.shape) affs_[:ndim] *= -1 affs_[:ndim] += 1 seg = compute_mws_segmentation(affs_, offsets, number_of_attractive_channels=ndim, strides=strides, mask=mask_, randomize_strides=randomize_strides) max_id = relabelConsecutive(seg, out=seg, start_label=1, keep_zeros=mask is not None)[1] segmentation[bb] = seg return max_id
def _merge_nodes(problem_path, scale, blocking, block_list, nodes, uv_ids, initial_node_labeling, n_threads): # load the cut edge ids n_edges = len(uv_ids) cut_edge_ids = _load_cut_edges(problem_path, scale, blocking, block_list, n_threads) assert len(cut_edge_ids) < n_edges, "%i = %i, does not reduce problem" % ( len(cut_edge_ids), n_edges) merge_edges = np.ones(n_edges, dtype='bool') merge_edges[cut_edge_ids] = False fu.log('merging %i / %i edges' % (np.sum(merge_edges), n_edges)) # merge node pairs with ufd ufd = nufd.boost_ufd(nodes) ufd.merge(uv_ids[merge_edges]) # get the node results and label them consecutively node_labeling = ufd.find(nodes) node_labeling, max_new_id, _ = relabelConsecutive(node_labeling, start_label=0, keep_zeros=False) assert node_labeling[0] == 0 # FIXME this looks fishy, redo !!! # # make sure that zero is still mapped to zero # if node_labeling[0] != 0: # # if it isn't, swap labels accordingly # zero_label = node_labeling[0] # to_relabel = node_labeling == 0 # node_labeling[node_labeling == zero_label] = 0 # node_labeling[to_relabel] = zero_laebl n_new_nodes = max_new_id + 1 fu.log("have %i nodes in new node labeling" % n_new_nodes) # get the labeling of initial nodes if initial_node_labeling is None: # if we don't have an initial node labeling, we are in the first scale. # here, the graph nodes might not be consecutive / not start at zero. # to keep the node labeling valid, we must make the labeling consecutive by inserting zeros fu.log("don't have an initial node labeling") # check if `nodes` are consecutive and start at zero node_max_id = int(nodes.max()) if node_max_id + 1 != len(nodes): fu.log("nodes are not consecutve and/or don't start at zero") fu.log("inflating node labels accordingly") node_labeling = nt.inflateLabeling(nodes, node_labeling, node_max_id) new_initial_node_labeling = node_labeling else: fu.log( "mapping new node labeling to labeling of inital (= scale 0) nodes" ) # NOTE access like this is ok because all node labelings will be consecutive new_initial_node_labeling = node_labeling[initial_node_labeling] assert len(new_initial_node_labeling) == len(initial_node_labeling) return n_new_nodes, node_labeling, new_initial_node_labeling
def merge_nodes(tmp_folder, scale, block_list, n_nodes, uv_ids, initial_node_labeling, n_threads): n_edges = len(uv_ids) # load the cut-edge ids from the prev. blocks and make merge edge ids with futures.ThreadPoolExecutor(n_threads) as tp: tasks = [tp.submit(np.load, os.path.join(tmp_folder, 'subproblem_s%i_%i.npy' % (scale, block_id))) for block_id in block_list] res = [t.result() for t in tasks] cut_edge_ids = np.concatenate([re for re in res if re.size]) cut_edge_ids = np.unique(cut_edge_ids).astype('uint64') # print("Number of cut edges:", len(cut_edge_ids)) # print(" /", n_edges) assert len(cut_edge_ids) < n_edges, "%i = %i, does not reduce problem" % (len(cut_edge_ids), n_edges) merge_edges = np.ones(n_edges, dtype='bool') merge_edges[cut_edge_ids] = False # TODO make sure that zero stayes mapped to zero # additionally, we make sure that all edges are cut ignore_edges = (uv_ids == 0).any(axis=1) merge_edges[ignore_edges] = False # merge node pairs with ufd # FIXME if we process roi's, the number of nodes is underestimated gravely n_nodes = int(uv_ids.max()) + 1 # TODO try relabeling consecutively here to increase processing speed # the issue is that we have to do awkward things to get out the complete node # labeling again ... # uv_ids, n_nodes, mapping = vigra.analysis.relabelConsecutive(uv_ids) # inv_mappin = {val: key for key, val in mapping.items()} ufd = nifty.ufd.ufd(n_nodes) merge_pairs = uv_ids[merge_edges] ufd.merge(merge_pairs) # get the node results and label them consecutively node_labeling = ufd.elementLabeling() node_labeling, max_new_id, _ = relabelConsecutive(node_labeling, keep_zeros=True, start_label=1) n_new_nodes = max_new_id + 1 # TODO this is not all we need to do # full_node_labeling = np.arange(n_nodes_total, dtype='uint64') # node_ids = list(mapping.keys()) # full_node_labeling[node_ids] = node_labeling # get the labeling of initial nodes if initial_node_labeling is None: new_initial_node_labeling = node_labeling else: # should this ever become a bottleneck, we can parallelize this in nifty # but for now this would really be premature optimization new_initial_node_labeling = node_labeling[initial_node_labeling] return n_new_nodes, node_labeling, new_initial_node_labeling
def mutex_watershed_with_seeds(affs, offsets, seeds, strides, randomize_strides=False, mask=None, noise_level=0, return_graph=False, seed_state=None): assert compute_mws_segmentation is not None, "Need affogato for mutex watershed" ndim = len(offsets[0]) if noise_level > 0: affs += noise_level * np.random.rand(*affs.shape) affs[:ndim] *= -1 affs[:ndim] += 1 # compute grid graph with seeds and optional mask shape = affs.shape[1:] grid_graph = compute_grid_graph(shape, mask, seeds) # compute nn and mutex nh grid_graph.intra_seed_weight = 1 # set intra-seed weight to maximal attractive if seed_state is not None: attractive_edges, attractive_weights = seed_state['attractive'] grid_graph.set_seed_state(attractive_edges, attractive_weights) uvs, weights = grid_graph.compute_nh_and_weights(np.require(affs[:ndim], requirements='C'), offsets[:ndim]) grid_graph.intra_seed_weight = 0 # set intral-seed weight to minimal repulsive if seed_state is not None: repulsive_edges, repulsive_weights = seed_state['repulsive'] grid_graph.clear_seed_state() grid_graph.set_seed_state(repulsive_edges, repulsive_weights) mutex_uvs, mutex_weights = grid_graph.compute_nh_and_weights(np.require(affs[ndim:], requirements='C'), offsets[ndim:], strides, randomize_strides) # compute the segmentation n_nodes = grid_graph.n_nodes seg = compute_mws_clustering(n_nodes, uvs, mutex_uvs, weights, mutex_weights) relabelConsecutive(seg, out=seg, start_label=1, keep_zeros=mask is not None) seg = seg.reshape(shape) if mask is not None: seg[np.logical_not(mask)] = 0 if return_graph: return seg, grid_graph else: return seg
def _cluster_segmentation_impl(segmentation, input_map, cluster_function, offsets=None, n_threads=None, min_segment_size=0, **cluster_kwargs): graph, edge_weights, edge_sizes = compute_graph_and_features( segmentation, input_map, offsets=offsets, n_threads=n_threads) clusters = cluster_function(graph=graph, edge_features=edge_weights, edge_sizes=edge_sizes, **cluster_kwargs) clusters = relabelConsecutive(clusters, start_label=1, keep_zeros=False)[0].astype('uint32') seg = project_node_labels_to_pixels(graph, clusters) if min_segment_size > 0: inp = input_map if offsets is None else input_map[0] seg = apply_size_filter(seg, inp, min_segment_size)[0] seg = relabelConsecutive(seg, start_label=1, keep_zeros=False)[0] return seg
def analyse_multicut_problem(graph, costs, verbose=True, cost_threshold=0, topk=5): # problem size and cost summary n_nodes, n_edges = graph.numberOfNodes, graph.numberOfEdges min_cost, max_cost = costs.min(), costs.max() mean_cost, std_cost = costs.mean(), costs.std() # component analysis merge_edges = costs > cost_threshold ufd = nufd.ufd(n_nodes) uv_ids = graph.uvIds() ufd.merge(uv_ids[merge_edges]) cc_labels = ufd.elementLabeling() cc_labels, max_id, _ = relabelConsecutive(cc_labels, start_label=0, keep_zeros=False) n_components = max_id + 1 _, component_sizes = np.unique(cc_labels, return_counts=True) component_sizes = np.sort(component_sizes)[::-1] topk_rel_sizes = component_sizes[:topk].astype("float32") / n_nodes # TODO add partial optimality analysis from # http://proceedings.mlr.press/v80/lange18a.html if verbose: print("Analysis of multicut problem:") print("The graph has", n_nodes, "nodes and", n_edges, "edges") print("The costs are in range", min_cost, "to", max_cost) print("The mean cost is", mean_cost, "+-", std_cost) print("The problem decomposes into", n_components, "components at threshold", cost_threshold) print("The 5 largest components have the following sizes:", topk_rel_sizes) data = [ n_nodes, n_edges, max_cost, min_cost, mean_cost, std_cost, n_components, cost_threshold ] data.extend(topk_rel_sizes.tolist()) columns = [ "n_nodes", "n_edges", "max_cost", "min_cost", "mean_cost", "std_cost", "n_components", "cost_threshold" ] columns.extend([ f"relative_size_top{i+1}_component" for i in range(len(topk_rel_sizes)) ]) df = pd.DataFrame(data=[data], columns=columns) return df
def mws_with_seeds(affs, offsets, seeds, strides, randomize_strides=False, mask=None): ndim = len(offsets[0]) # compute grid graph with seeds and optional mask shape = affs.shape[1:] grid_graph = compute_grid_graph(shape, mask, seeds) # compute nn and mutex nh grid_graph.add_attractive_seed_edges = True uvs, weights = grid_graph.compute_nh_and_weights( 1. - np.require(affs[:ndim], requirements='C'), offsets[:ndim]) grid_graph.add_attractive_seed_edges = False mutex_uvs, mutex_weights = grid_graph.compute_nh_and_weights( np.require(affs[ndim:], requirements='C'), offsets[ndim:], strides, randomize_strides) # compute the segmentation n_nodes = grid_graph.n_nodes seg = compute_mws_clustering(n_nodes, uvs, mutex_uvs, weights, mutex_weights) relabelConsecutive(seg, out=seg, start_label=1, keep_zeros=mask is not None) grid_graph.relabel_to_seeds(seg) seg = seg.reshape(shape) if mask is not None: seg[np.logical_not(mask)] = 0 return seg
def merge_nodes(tmp_folder, scale, n_jobs, n_nodes, uv_ids, initial_node_labeling): n_edges = len(uv_ids) # load the cut-edge ids from the prev. jobs and make merge edge ids # TODO we could parallelize this cut_edge_ids = np.concatenate([ np.load( os.path.join(tmp_folder, '1_output_s%i_%i.npy' % (scale, job_id))) for job_id in range(n_jobs) ]) cut_edge_ids = np.unique(cut_edge_ids).astype('uint64') # print("Number of cut edges:", len(cut_edge_ids)) # print(" /", n_edges) assert len(cut_edge_ids) < n_edges, "%i = %i, does not reduce problem" % ( len(cut_edge_ids), n_edges) merge_edges = np.ones(n_edges, dtype='bool') merge_edges[cut_edge_ids] = False # TODO make sure that zero stayes mapped to zero # additionally, we make sure that all edges are cut ignore_edges = (uv_ids == 0).any(axis=1) merge_edges[ignore_edges] = False # merge node pairs with ufd ufd = nufd.ufd(n_nodes) merge_pairs = uv_ids[merge_edges] ufd.merge(merge_pairs) # get the node results and label them consecutively node_labeling = ufd.elementLabeling() node_labeling, max_new_id, _ = relabelConsecutive(node_labeling) n_new_nodes = max_new_id + 1 # get the labeling of initial nodes if initial_node_labeling is None: new_initial_node_labeling = node_labeling else: # should this ever become a bottleneck, we can parallelize this in nifty # but for now this would really be premature optimization new_initial_node_labeling = node_labeling[initial_node_labeling] return n_new_nodes, node_labeling, new_initial_node_labeling
def update_boutons(): with h5py.File('./test_data.h5', 'r') as f: raw = f['raw'][:] boutons = f['boutons'][:] with napari.gui_qt(): viewer = napari.Viewer() viewer.add_image(raw) viewer.add_labels(boutons) boutons = viewer.layers['boutons'].data boutons = relabelConsecutive(boutons.astype('uint32'), start_label=1, keep_zeros=True)[0] with h5py.File('./test_data.h5', 'a') as f: ds = f.require_dataset('boutons_corrected', shape=boutons.shape, dtype=boutons.dtype, compression='gzip') ds[:] = boutons
def multicut_decomposition(graph, costs, time_limit=None, n_threads=1, internal_solver='kernighan-lin', **kwargs): """ Solve multicut problem with decomposition solver. Arguments: graph [nifty.graph] - graph of multicut problem costs [np.ndarray] - edge costs of multicut problem time_limit [float] - time limit for inference (default: None) n_threads [int] - number of threads (default: 1) internal_solver [str] - name of solver used for connected components (default: 'kernighan-lin') """ # get the agglomerator solver = get_multicut_solver(internal_solver) # merge attractive edges with ufd to # obtain natural connected components merge_edges = costs > 0 ufd = nufd.ufd(graph.numberOfNodes) uv_ids = graph.uvIds() ufd.merge(uv_ids[merge_edges]) cc_labels = ufd.elementLabeling() # relabel component ids consecutively cc_labels, max_id, _ = relabelConsecutive(cc_labels, start_label=0, keep_zeros=False) # solve a component sub-problem def solve_component(component_id): # extract the nodes in this component sub_nodes = np.where(cc_labels == component_id)[0].astype('uint64') # if we only have a single node, return trivial labeling if len(sub_nodes) == 1: return sub_nodes, np.array([0], dtype='uint64'), 1 # extract the subgraph corresponding to this component inner_edges, _ = graph.extractSubgraphFromNodes(sub_nodes) sub_uvs = uv_ids[inner_edges] assert len(inner_edges) == len(sub_uvs), "%i, %i" % (len(inner_edges), len(sub_uvs)) # relabel sub-nodes and associated uv-ids sub_nodes_relabeled, max_local, node_mapping = relabelConsecutive( sub_nodes, start_label=0, keep_zeros=False) sub_uvs = nifty.tools.takeDict(node_mapping, sub_uvs) # build the graph sub_graph = nifty.graph.undirectedGraph(max_local + 1) sub_graph.insertEdges(sub_uvs) # solve local multicut sub_costs = costs[inner_edges] assert len(sub_costs) == sub_graph.numberOfEdges, "%i, %i" % ( len(sub_costs), sub_graph.numberOfEdges) sub_labels = solver(sub_graph, sub_costs, time_limit=time_limit) # relabel the solution sub_labels, max_seg_local, _ = relabelConsecutive(sub_labels, start_label=0, keep_zeros=False) assert len(sub_labels) == len(sub_nodes), "%i, %i" % (len(sub_labels), len(sub_nodes)) return sub_nodes, sub_labels, max_seg_local + 1 # solve all components in parallel with futures.ThreadPoolExecutor(n_threads) as tp: tasks = [ tp.submit(solve_component, component_id) for component_id in range(max_id + 1) ] results = [t.result() for t in tasks] sub_nodes = [res[0] for res in results] sub_results = [res[1] for res in results] offsets = np.array([res[2] for res in results], dtype='uint64') # make proper offsets for the component results offsets = np.roll(offsets, 1) offsets[0] = 0 offsets = np.cumsum(offsets) # insert sub-results into the components node_labels = np.zeros_like(cc_labels, dtype='uint64') def insert_solution(component_id): nodes = sub_nodes[component_id] node_labels[nodes] = (sub_results[component_id] + offsets[component_id]) with futures.ThreadPoolExecutor(n_threads) as tp: tasks = [ tp.submit(insert_solution, component_id) for component_id in range(max_id + 1) ] [t.result() for t in tasks] return node_labels
def _agglomerate_block(blocking, block_id, ds_in, ds_out, config): fu.log("start processing block %i" % block_id) have_ignore_label = config['have_ignore_label'] use_mala_agglomeration = config.get('use_mala_agglomeration', True) threshold = config.get('threshold', 0.9) size_regularizer = config.get('size_regularizer', .5) invert_inputs = config.get('invert_inputs', False) offsets = config.get('offsets', None) bb = vu.block_to_bb(blocking.getBlock(block_id)) # load the segmentation / output seg = ds_out[bb] # check if this block is empty if np.sum(seg) == 0: fu.log_block_success(block_id) return # load the input data ndim_in = ds_in.ndim if ndim_in == 4: assert offsets is not None assert len(offsets) <= ds_in.shape[0] bb_in = (slice(0, len(offsets)),) + bb input_ = vu.normalize(ds_in[bb_in]) else: assert offsets is None input_ = vu.normalize(ds_in[bb]) if invert_inputs: input_ = 1. - input_ id_offset = int(seg[seg != 0].min()) # relabel the segmentation _, max_id, _ = relabelConsecutive(seg, out=seg, keep_zeros=True, start_label=1) seg = seg.astype('uint32') # construct rag rag = nrag.gridRag(seg, numberOfLabels=max_id + 1, numberOfThreads=1) # extract edge features if offsets is None: edge_features = nrag.accumulateEdgeMeanAndLength(rag, input_, numberOfThreads=1) else: edge_features = nrag.accumulateAffinityStandartFeatures(rag, input_, offsets, numberOfThreads=1) edge_features, edge_sizes = edge_features[:, 0], edge_features[:, -1] uv_ids = rag.uvIds() # set edges to ignore label to be maximally repulsive if have_ignore_label: ignore_mask = (uv_ids == 0).any(axis=1) edge_features[ignore_mask] = 1 # build undirected graph n_nodes = rag.numberOfNodes graph = nifty.graph.undirectedGraph(n_nodes) graph.insertEdges(uv_ids) if use_mala_agglomeration: node_labels = mala_clustering(graph, edge_features, edge_sizes, threshold) else: node_ids, node_sizes = np.unique(seg, return_counts=True) if node_ids[0] != 0: node_sizes = np.concatenate([np.array([0]), node_sizes]) n_stop = int(threshold * n_nodes) node_labels = agglomerative_clustering(graph, edge_features, node_sizes, edge_sizes, n_stop, size_regularizer) # run clusteting node_labels, max_id, _ = relabelConsecutive(node_labels, start_label=1, keep_zeros=True) fu.log("reduced number of labels from %i to %i" % (n_nodes, max_id + 1)) # project node labels back to segmentation seg = nrag.projectScalarNodeDataToPixels(rag, node_labels, numberOfThreads=1) seg = seg.astype('uint64') # add offset back to segmentation seg[seg != 0] += id_offset ds_out[bb] = seg # log block success fu.log_block_success(block_id)