def _threshold_block(block_id, blocking, ds_in, ds_out, threshold, threshold_mode, channel, sigma): fu.log("start processing block %i" % block_id) block = blocking.getBlock(block_id) bb = vu.block_to_bb(block) bb = vu.block_to_bb(block) if channel is None: input_ = ds_in[bb] else: channel_ = [channel] if isinstance(channel, int) else channel in_shape = (len(channel_), ) + tuple(b.stop - b.start for b in bb) input_ = np.zeros(in_shape, dtype=ds_in.dtype) for chan_id, chan in enumerate(channel_): bb_inp = (slice(chan, chan + 1), ) + bb input_[chan_id] = ds_in[bb_inp].squeeze() input_ = np.mean(input_, axis=0) input_ = vu.normalize(input_) if sigma > 0: input_ = vu.apply_filter(input_, 'gaussianSmoothing', sigma) input_ = vu.normalize(input_) if threshold_mode == 'greater': input_ = input_ > threshold elif threshold_mode == 'less': input_ = input_ < threshold elif threshold_mode == 'equal': input_ = input_ == threshold else: raise RuntimeError("Thresholding Mode %s not supported" % threshold_mode) ds_out[bb] = input_.astype('uint8') fu.log_block_success(block_id)
def _read_data(ds_in, input_bb, config): # read the input data if ds_in.ndim == 4: channel_begin = config.get('channel_begin', 0) channel_end = config.get('channel_end', None) input_bb = (slice(channel_begin, channel_end), ) + input_bb input_ = vu.normalize(ds_in[input_bb]) agglomerate = config.get('agglomerate_channels', 'mean') assert agglomerate in ('mean', 'max', 'min') input_ = getattr(np, agglomerate)(input_, axis=0) else: input_ = vu.normalize(ds_in[input_bb]) return input_
def _cc_block_with_mask(block_id, blocking, ds_in, ds_out, threshold, threshold_mode, mask, channel, sigma): fu.log("start processing block %i" % block_id) block = blocking.getBlock(block_id) bb = vu.block_to_bb(block) # get the mask and check if we have any pixels in_mask = mask[bb].astype('bool') if np.sum(in_mask) == 0: fu.log_block_success(block_id) return 0 bb = vu.block_to_bb(block) if channel is None: input_ = ds_in[bb] else: channel_ = [channel] if isinstance(channel, int) else channel in_shape = (len(channel_),) + tuple(b.stop - b.start for b in bb) input_ = np.zeros(in_shape, dtype=ds_in.dtype) for chan_id, chan in enumerate(channel_): bb_inp = (slice(chan, chan + 1),) + bb input_[chan_id] = ds_in[bb_inp].squeeze() input_ = np.mean(input_, axis=0) input_ = vu.normalize(input_) if sigma > 0: input_ = vu.apply_filter(input_, 'gaussianSmoothing', sigma) input_ = vu.normalize(input_) if threshold_mode == 'greater': input_ = input_ > threshold elif threshold_mode == 'less': input_ = input_ < threshold elif threshold_mode == 'equal': input_ = input_ == threshold else: raise RuntimeError("Thresholding Mode %s not supported" % threshold_mode) input_[np.logical_not(in_mask)] = 0 if np.sum(input_) == 0: fu.log_block_success(block_id) return 0 components = label(input_) ds_out[bb] = components fu.log_block_success(block_id) return int(components.max()) + 1
def _make_hmap(input_, distances, alpha, sigma_weights): distances = 1. - vu.normalize(distances) hmap = alpha * input_ + (1. - alpha) * distances # smooth input if sigma is given if sigma_weights != 0: hmap = vu.apply_filter(hmap, 'gaussianSmoothing', sigma_weights) return hmap
def _mws_block_pass1(block_id, blocking, ds_in, ds_out, mask, offsets, strides, randomize_strides, halo, noise_level, max_block_id, tmp_folder): fu.log("(Pass1) start processing block %i" % block_id) block = blocking.getBlockWithHalo(block_id, halo) in_bb = vu.block_to_bb(block.outerBlock) if mask is None: bb_mask = None else: bb_mask = mask[in_bb].astype('bool') if np.sum(bb_mask) == 0: fu.log_block_success(block_id) return aff_bb = (slice(None), ) + in_bb affs = vu.normalize(ds_in[aff_bb]) seg = mutex_watershed(affs, offsets, strides=strides, mask=bb_mask, randomize_strides=randomize_strides, noise_level=noise_level) out_bb = vu.block_to_bb(block.innerBlock) local_bb = vu.block_to_bb(block.innerBlockLocal) seg = seg[local_bb] # FIXME once vigra supports uint64 or we implement our own ... # seg = vigra.analysis.labelVolumeWithBackground(seg) # offset with lowest block coordinate offset_id = block_id * np.prod(blocking.blockShape) vigra.analysis.relabelConsecutive(seg, start_label=offset_id, keep_zeros=True, out=seg) ds_out[out_bb] = seg # get the state of the segmentation of this block grid_graph = compute_grid_graph(seg.shape, mask=bb_mask) affs = affs[(slice(None), ) + local_bb] # FIXME this function yields incorrect uv-ids ! state_uvs, state_weights, state_attractive = grid_graph.compute_state_for_segmentation( affs, seg, offsets, n_attractive_channels=3, ignore_label=True) # serialize the states save_path = os.path.join(tmp_folder, 'seg_state_block%i.h5' % block_id) with vu.file_reader(save_path) as f: f.create_dataset('edges', data=state_uvs) f.create_dataset('weights', data=state_weights) f.create_dataset('attractive_edge_mask', data=state_attractive) # write max-id for the last block if block_id == max_block_id: _write_nlabels(ds_out, seg) # log block success fu.log_block_success(block_id)
def check_results(self, in_key, feat_func): f = z5py.File(self.input_path) ds_inp = f[in_key] ds_inp.n_threads = 8 ds_ws = f[self.ws_key] ds_ws.n_threads = 8 # load features features = z5py.File(self.output_path)[self.output_key][:] # load seg and input, make rag seg = ds_ws[:] inp = normalize(ds_inp[:]) if ds_inp.ndim == 3 else normalize(ds_inp[:3]) rag = nrag.gridRag(seg, numberOfLabels=int(seg.max()) + 1) # compute nifty lens if inp.ndim == 3: len_nifty = nrag.accumulateEdgeMeanAndLength(rag, inp)[:, 1] else: len_nifty = nrag.accumulateEdgeMeanAndLength(rag, inp[0])[:, 1] self.assertTrue(np.allclose(len_nifty, features[:, -1])) # compute nifty features features_nifty = feat_func(rag, inp) # check feature shape self.assertEqual(len(features_nifty), len(features)) self.assertEqual(features_nifty.shape[1], features.shape[1] - 1) # debugging: check closeness of the min values # close = np.isclose(features_nifty[:, 2], features[:, 2]) # print(close.sum(), '/', len(close)) # not_close = ~close # print(np.where(not_close)[:10]) # print(features[:, 2][not_close][:10]) # print(features_nifty[:, 2][not_close][:10]) # we can only assert equality for mean, std, min, max and len # -> mean self.assertTrue(np.allclose(features_nifty[:, 0], features[:, 0])) # -> std self.assertTrue(np.allclose(features_nifty[:, 1], features[:, 1])) # -> min self.assertTrue(np.allclose(features_nifty[:, 2], features[:, 2])) # -> max self.assertTrue(np.allclose(features_nifty[:, 8], features[:, 8]))
def cast_type(data, dtype): if np.dtype(data.dtype) == np.dtype(dtype): return data # special casting for uint8 elif np.dtype(dtype) == 'uint8': data = vu.normalize(data) data *= 255 return data.astype('uint8') else: return data.astype(dtype)
def _apply_filter(blocking, block_id, ds_in, ds_out, halo, filter_name, sigma, apply_in_2d): fu.log("start processing block %i" % block_id) block = blocking.getBlockWithHalo(block_id, halo) bb_in = vu.block_to_bb(block.outerBlock) input_ = vu.normalize(ds_in[bb_in]) response = vu.apply_filter(input_, filter_name, sigma, apply_in_2d) bb_out = vu.block_to_bb(block.innerBlock) inner_bb = vu.block_to_bb(block.innerBlockLocal) ds_out[bb_out] = response[inner_bb] fu.log_block_success(block_id)
def _block_features(block_id, blocking, ds_in, ds_labels, ds_out, ignore_label, channel, feature_names): fu.log("start processing block %i" % block_id) block = blocking.getBlock(block_id) bb = vu.block_to_bb(block) labels = ds_labels[bb] # check if we have an ignore label and return # if this block is purely ignore label if ignore_label is not None: if np.sum(labels != ignore_label) == 0: fu.log_block_success(block_id) return # get global normalization values min_val = 0 max_val = 255. if ds_in.dtype == np.dtype('uint8') else 1. bb_in = bb if channel is None else (channel, ) + bb input_ = ds_in[bb_in] input_ = vu.normalize(input_, min_val, max_val) ids = np.unique(labels) if ids[0] == 0: feat_slice = np.s_[:] exp_len = len(ids) else: feat_slice = np.s_[1:] exp_len = len(ids) + 1 # relabel consecutive in order to save memory labels = relabel_sequential(labels, ids) feats = vigra.analysis.extractRegionFeatures(input_, labels.astype('uint32'), features=feature_names, ignoreLabel=ignore_label) assert len( feats['count']) == exp_len, "%i, %i" % (len(feats['count']), exp_len) # make serialization n_cols = len(feature_names) + 1 data = np.zeros(n_cols * len(ids), dtype='float32') # write the ids data[::n_cols] = ids.astype('float32') # write all the features for feat_id, feat_name in enumerate(feature_names, 1): data[feat_id::n_cols] = feats[feat_name][feat_slice] chunks = blocking.blockShape chunk_id = tuple(b.start // ch for b, ch in zip(bb, chunks)) ds_out.write_chunk(chunk_id, data, True) fu.log_block_success(block_id)
def filter_block(block_id): block = blocking.getBlock(block_id) bb = tuple(slice(beg, end) for beg, end in zip(block.begin, block.end)) seg = ds_seg[bb].astype('uint32') if seg.sum() == 0: return inp = normalize(ds_fg[bb]) mean_fg = vigra.analysis.extractRegionFeatures(inp, seg, features=['mean'])['mean'] fg_ids = np.where(mean_fg > self.threshold)[0] filtered = np.isin(seg, fg_ids) ds_seg[bb] = filtered.astype(ds_seg.dtype)
def check_subresults(self): f_feat = z5py.File( os.path.join(self.tmp_folder, 'region_features_tmp.n5')) ds_feat = f_feat['block_feats'] feature_names = ds_feat.attrs['feature_names'] n_cols = len(feature_names) + 1 with z5py.File(self.input_path) as f: data = f[self.input_key] data.n_threads = self.max_jobs data = normalize(data[:], 0, 255) segmentation = f[self.seg_key] segmentation.max_jobs = self.max_jobs segmentation = segmentation[:].astype('uint32') blocking = nt.blocking([0, 0, 0], data.shape, self.block_shape) n_blocks = blocking.numberOfBlocks for block_id in range(n_blocks): block = blocking.getBlock(block_id) bb = tuple( slice(beg, end) for beg, end in zip(block.begin, block.end)) inp = data[bb] seg = segmentation[bb].astype('uint32') # load the sub-result chunk_id = tuple(beg // bs for beg, bs in zip(block.begin, self.block_shape)) res = ds_feat.read_chunk(chunk_id) if res is None: self.assertEqual(seg.sum(), 0) continue # check that ids are correct ids = res[::n_cols].astype('uint32') expected_ids = np.unique(seg) self.assertEqual(ids.shape, expected_ids.shape) self.assertTrue(np.array_equal(ids, expected_ids)) # check that the features are correct expected = vigra.analysis.extractRegionFeatures( inp, seg, features=feature_names, ignoreLabel=0) for feat_id, feat_name in enumerate(feature_names, 1): feat_res = res[feat_id::n_cols] self.check_features(feat_res, expected, feat_name, ids) return feature_names
def _block_features(block_id, blocking, ds_in, ds_labels, ds_out, ignore_label): fu.log("start processing block %i" % block_id) block = blocking.getBlock(block_id) bb = vu.block_to_bb(block) labels = ds_labels[bb] # check if we have an ignore label and return # if this block is purely ignore label if ignore_label is not None: if np.sum(labels != ignore_label) == 0: fu.log_block_success(block_id) return # TODO support multichannel # get global normalization values min_val = 0 max_val = 255. if ds_in.dtype == np.dtype('uint8') else 1. input_ = vu.normalize(ds_in[bb], min_val, max_val) # TODO support more features # TODO we might want to check for overflows and in general allow vigra to # work with uint64s ... labels = labels.astype('uint32') feats = vigra.analysis.extractRegionFeatures(input_, labels, features=['mean', 'count'], ignoreLabel=ignore_label) counts = feats['count'] feats = feats['mean'] # make serialization ids = np.unique(labels) data = np.zeros(3 * len(ids), dtype='float32') # write the ids data[::3] = ids.astype('float32') # write the counts data[1::3] = counts[ids] # write the features data[2::3] = feats[ids] chunks = blocking.blockShape chunk_id = tuple(b.start // ch for b, ch in zip(bb, chunks)) ds_out.write_chunk(chunk_id, data, True) fu.log_block_success(block_id)
def _insert_affinities(affs, objs, offsets, dilate_by): dtype = affs.dtype # compute affinities to objs and bring them to our aff convention affs_insert, mask = compute_affinities(objs, offsets) mask = mask == 0 affs_insert = 1. - affs_insert affs_insert[mask] = 0 # dilate affinity channels for c in range(affs_insert.shape[0]): affs_insert[c] = dilate(affs_insert[c], iterations=dilate_by, dilate_2d=True) # dirty hack: z affinities look pretty weird, so we add the averaged xy affinities affs_insert[0] += np.mean(affs_insert[1:3], axis=0) # insert affinities affs = vu.normalize(affs) affs += affs_insert affs = np.clip(affs, 0., 1.) affs = cast(affs, dtype) return affs
def check_result(self, feature_names): # load the result with z5py.File(self.output_path) as f: res = f[self.output_key][:] # compute the vigra result with z5py.File(self.input_path) as f: inp = f[self.input_key] inp.n_threads = self.max_jobs inp = normalize(inp[:], 0, 255) seg = f[self.seg_key] seg.max_jobs = self.max_jobs seg = seg[:].astype('uint32') expected = vigra.analysis.extractRegionFeatures(inp, seg, features=feature_names, ignoreLabel=0) for feat_id, feat_name in enumerate(feature_names): self.check_features(res[:, feat_id], expected, feat_name)
def _mws_block(block_id, blocking, ds_in, ds_out, mask, offsets, strides, randomize_strides, halo, noise_level): fu.log("start processing block %i" % block_id) in_bb, out_bb, local_bb = _get_bbs(blocking, block_id, halo) if mask is None: bb_mask = None else: bb_mask = mask[in_bb].astype('bool') if np.sum(bb_mask) == 0: fu.log_block_success(block_id) return aff_bb = (slice(None), ) + in_bb affs = ds_in[aff_bb] if affs.sum() == 0: fu.log_block_success(block_id) return affs = vu.normalize(affs) seg = su.mutex_watershed(affs, offsets, strides=strides, mask=bb_mask, randomize_strides=randomize_strides, noise_level=noise_level) seg = seg[local_bb] # FIXME once vigra supports uint64 or we implement our own ... # seg = vigra.analysis.labelVolumeWithBackground(seg) # offset with lowest block coordinate offset_id = block_id * np.prod(blocking.blockShape) vigra.analysis.relabelConsecutive(seg, start_label=offset_id, keep_zeros=True, out=seg) ds_out[out_bb] = seg # log block success fu.log_block_success(block_id)
def _mws_block(block_id, blocking, ds_in, ds_out, mask, offsets, strides, randomize_strides, halo, noise_level): fu.log("start processing block %i" % block_id) in_bb, out_bb, local_bb = _get_bbs(blocking, block_id, halo) if mask is None: bb_mask = None else: bb_mask = mask[in_bb].astype('bool') if np.sum(bb_mask) == 0: fu.log_block_success(block_id) return aff_bb = (slice(None), ) + in_bb affs = ds_in[aff_bb] if affs.sum() == 0: fu.log_block_success(block_id) return affs = vu.normalize(affs) seg = mutex_watershed(affs, offsets, strides=strides, mask=bb_mask, randomize_strides=randomize_strides, noise_level=noise_level) seg = seg[local_bb] # offset with lowest block coordinate offset_id = max(block_id * int(np.prod(blocking.blockShape)), 1) assert offset_id < np.iinfo('uint64').max, "Id overflow" vigra.analysis.relabelConsecutive(seg, start_label=offset_id, keep_zeros=True, out=seg) ds_out[out_bb] = seg # log block success fu.log_block_success(block_id)
def dt_watershed(input_, threshold=.25, sigma=2., alpha=.9, min_seg_size=100, suppression=True): # make distance transform threshd = (input_ > threshold).astype('uint32') dt = vigra.filters.distanceTransform(threshd) dt = vigra.filters.gaussianSmoothing(dt, sigma) # make seeds seeds = vigra.analysis.localMaxima3D(dt, allowPlateaus=True, allowAtBorder=True, marker=np.nan) seeds = np.isnan(seeds).astype('uint32') seeds = vigra.analysis.labelVolumeWithBackground(seeds) # non-max suppression if suppression: seeds = np.array(np.where(seeds)).transpose() seeds = nonMaximumDistanceSuppression(dt, seeds) seeds = _points_to_vol(seeds, dt.shape) # make hmap hmap = alpha * input_ + (1. - alpha) * (1. - normalize(dt)) ws, max_id = watershed(hmap, seeds, min_seg_size) return ws, max_id
def _mws_block_pass2(block_id, blocking, ds_in, ds_out, mask, offsets, strides, randomize_strides, halo, noise_level, max_block_id, tmp_folder): fu.log("(Pass2) start processing block %i" % block_id) block = blocking.getBlockWithHalo(block_id, halo) in_bb = vu.block_to_bb(block.outerBlock) if mask is None: # if we don't have a mask, initialize with fully 'in-mask' volume # bb_mask = np.ones(tuple(b.stop - b.begin for b in in_bb), # dtype='bool') bb_mask = None else: bb_mask = mask[in_bb].astype('bool') if np.sum(bb_mask) == 0: fu.log_block_success(block_id) return # TODO does this make sense ? # set the mask for parts of indirect neighbor blocks # (which are also part of pass 2) to 0 # bb_mask = mask_corners(bb_mask, halo) aff_bb = (slice(None),) + in_bb affs = vu.normalize(ds_in[aff_bb]) # load seeds seeds = ds_out[in_bb] seed_ids = np.unique(seeds) if seed_ids[0] == 0: seed_ids = seed_ids[1:] # load the serialized state for the neighboring (pass1) blocks # and find relevant edges between seed ids seed_edges = [] seed_edge_weights = [] attractive_mask = [] for axis in range(3): for to_lower in (False, True): ngb_id = blocking.getNeighborId(block_id, axis, to_lower) # get path to state serialization and check if it exists save_path = os.path.join(tmp_folder, 'seg_state_block%i.h5' % ngb_id) if not os.path.exists(save_path): continue with vu.file_reader(save_path) as f: # first, load the edges and see if they have overlap with our seed ids ngb_edges = f['edges'][:] ngb_edge_mask = np.in1d(ngb_edges, seed_ids).reshape(ngb_edges.shape) ngb_edge_mask = ngb_edge_mask.all(axis=1) # if we have edges, load the corresponding weights # and attractive / repulsive state if ngb_edge_mask.sum() > 0: ngb_edges = ngb_edges[ngb_edge_mask] ngb_weights = f['weights'][:][ngb_edge_mask] ngb_attractive_edges = f['attractive_edge_mask'][:][ngb_edge_mask] seed_edges.append(ngb_edges) seed_edge_weights.append(ngb_weights) attractive_mask.append(ngb_attractive_edges) seed_edges = np.concatenate(seed_edges, axis=0) seed_edge_weights = np.concatenate(seed_edge_weights) attractive_mask = np.concatenate(attractive_mask) assert len(seed_edges) == len(seed_edge_weights) == len(attractive_mask) repulsive_mask = np.logical_not(attractive_mask) attractive_edges, repulsive_edges = seed_edges[attractive_mask], seed_edges[repulsive_mask] attractive_weights, repulsive_weights = seed_edge_weights[attractive_mask], seed_edge_weights[repulsive_mask] # run mws segmentation with seeds seg, grid_graph = su.mutex_watershed_with_seeds(affs, offsets, seeds, strides=strides, mask=bb_mask, randomize_strides=randomize_strides, noise_level=noise_level, return_graph=True, seed_state={'attractive': (attractive_edges, attractive_weights), 'repulsive': (repulsive_edges, repulsive_weights)}) # offset with lowest block coordinate offset_id = block_id * np.prod(blocking.blockShape) vigra.analysis.relabelConsecutive(seg, start_label=offset_id, keep_zeros=True, out=seg) # find assignment of seed ids to segmentation ids assignments = grid_graph.get_seed_assignments_from_node_labels(seg.flatten()) # get the cropped segmentation local_bb = vu.block_to_bb(block.innerBlockLocal) seg_crop = seg[local_bb] # filter the assignments from ids that are not in the crop crop_ids = np.unique(seg_crop) filter_mask = np.in1d(assignments[:, 1], crop_ids) assignments = assignments[filter_mask] # store assignments to tmp folder save_path = os.path.join(tmp_folder, 'mws_two_pass_assignments_block_%i.npy' % block_id) np.save(save_path, assignments) out_bb = vu.block_to_bb(block.innerBlock) ds_out[out_bb] = seg_crop # write max-id for the last block if block_id == max_block_id: _write_nlabels(ds_out, seg) # log block success fu.log_block_success(block_id)
def _accumulate_block(block_id, blocking, ds_in, ds_labels, ds_edges, ds_out, filters, sigmas, halo, ignore_label, apply_in_2d, channel_agglomeration): fu.log("start processing block %i" % block_id) chunk_pos = blocking.blockGridPosition(block_id) # load edges and construct the graph if this block has edges edges = ds_edges.read_chunk(chunk_pos) if edges is None: fu.log("block %i has no edges" % block_id) fu.log_block_success(block_id) return edges = edges.reshape((edges.size, 2)) graph = ndist.Graph(edges) shape = ds_labels.shape # get the bounding if sum(halo) > 0: block = blocking.getBlockWithHalo(block_id, halo) block_shape = block.outerBlock.shape bb_in = vu.block_to_bb(block.outerBlock) bb = vu.block_to_bb(block.innerBlock) bb_local = vu.block_to_bb(block.innerBlockLocal) # increase inner bounding box by 1 in posirive direction # in accordance with the graph extraction bb = tuple( slice(b.start, min(b.stop + 1, sh)) for b, sh in zip(bb, shape)) bb_local = tuple( slice(b.start, min(b.stop + 1, bsh)) for b, bsh in zip(bb_local, block_shape)) else: block = blocking.getBlock(block_id) bb = vu.block_to_bb(block) bb = tuple( slice(b.start, min(b.stop + 1, sh)) for b, sh in zip(bb, shape)) bb_in = bb bb_local = slice(None) input_dim = ds_in.ndim # TODO make choice of channels optional if input_dim == 4: bb_in = (slice(0, 3), ) + bb_in input_ = vu.normalize(ds_in[bb_in]) if input_dim == 4: assert channel_agglomeration is not None input_ = getattr(np, channel_agglomeration)(input_, axis=0) # load labels labels = ds_labels[bb] # TODO pre-smoothing ?! # accumulate the edge features edge_features = [ _accumulate_filter(input_, graph, labels, bb_local, filter_name, sigma, ignore_label, filter_name == filters[-1] and sigma == sigmas[-1], apply_in_2d) for filter_name in filters for sigma in sigmas ] edge_features = np.concatenate(edge_features, axis=1) # save the features fu.log("saving feature result of shape %s" % str(edge_features.shape)) ds_out.write_chunk(chunk_pos, edge_features.flatten(), True) fu.log_block_success(block_id) return edge_features.shape[1]
def _block_features(block_id, blocking, ds_in, ds_labels, ds_out, ignore_label, feature_list): fu.log("start processing block %i" % block_id) block = blocking.getBlock(block_id) bb = vu.block_to_bb(block) labels = ds_labels[bb] # check if we have an ignore label and return # if this block is purely ignore label if ignore_label is not None: if np.sum(labels != ignore_label) == 0: fu.log_block_success(block_id) return # TODO support multichannel # get global normalization values min_val = 0 max_val = 255. if ds_in.dtype == np.dtype('uint8') else 1. input_ = vu.normalize(ds_in[bb], min_val, max_val) # TODO support more features # TODO we might want to check for overflows and in general allow vigra to # work with uint64s ... labels = labels.astype('uint32') feats = vigra.analysis.extractRegionFeatures(input_, labels, features=feature_list, ignoreLabel=ignore_label) # make serialization num_features = len(feature_list) ids = np.unique(labels) # Number of actual feature values, since some features (like Histogram) compute multiple values num_feature_vals = 1 # start with 1 for ids for i in range(0, num_features): feature_vals = feats[feature_list[i]] if (len(feature_vals.shape) == 2): num_feature_vals += feature_vals.shape[1] else: num_feature_vals += 1 data = np.zeros((len(ids), num_feature_vals), dtype='float32') # write the ids data[:, 0] = ids.astype('float32') feature_indices = [''] * num_feature_vals # write features feature_i = 1 for i in range(num_features): feature_vals = feats[feature_list[i]][ids] if len(feature_vals.shape) == 2: for j in range(feature_vals.shape[1]): data[:, feature_i] = feature_vals[:, j] feature_indices[feature_i] = feature_list[i] feature_i += 1 else: data[:, feature_i] = feature_vals feature_indices[feature_i] = feature_list[i] feature_i += 1 data = data.reshape((-1)) # z5py cant handle nan, so we need to replace it data[np.isnan(data)] = 0 chunks = blocking.blockShape chunk_id = tuple(b.start // ch for b, ch in zip(bb, chunks)) ds_out.write_chunk(chunk_id, data, True) ds_out.attrs['feature_indices'] = feature_indices fu.log_block_success(block_id)
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)
def _accumulate_block(block_id, blocking, ds_in, ds_labels, out_prefix, graph_block_prefix, filters, sigmas, halo, ignore_label, apply_in_2d, channel_agglomeration): fu.log("start processing block %i" % block_id) # load graph and check if this block has edges graph = ndist.Graph(graph_block_prefix + str(block_id)) if graph.numberOfEdges == 0: fu.log("block %i has no edges" % block_id) fu.log_block_success(block_id) return shape = ds_labels.shape # get the bounding if sum(halo) > 0: block = blocking.getBlockWithHalo(block_id, halo) block_shape = block.outerBlock.shape bb_in = vu.block_to_bb(block.outerBlock) bb = vu.block_to_bb(block.innerBlock) bb_local = vu.block_to_bb(block.innerBlockLocal) # increase inner bounding box by 1 in posirive direction # in accordance with the graph extraction bb = tuple( slice(b.start, min(b.stop + 1, sh)) for b, sh in zip(bb, shape)) bb_local = tuple( slice(b.start, min(b.stop + 1, bsh)) for b, bsh in zip(bb_local, block_shape)) else: block = blocking.getBlock(block_id) bb = vu.block_to_bb(block) bb = tuple( slice(b.start, min(b.stop + 1, sh)) for b, sh in zip(bb, shape)) bb_in = bb bb_local = slice(None) input_dim = ds_in.ndim # TODO make choice of channels optional if input_dim == 4: bb_in = (slice(0, 3), ) + bb_in input_ = vu.normalize(ds_in[bb_in]) if input_dim == 4: assert channel_agglomeration is not None input_ = getattr(np, channel_agglomeration)(input_, axis=0) # load labels labels = ds_labels[bb] # TODO pre-smoothing ?! # accumulate the edge features edge_features = [ _accumulate_filter(input_, graph, labels, bb_local, filter_name, sigma, ignore_label, filter_name == filters[-1] and sigma == sigmas[-1], apply_in_2d) for filter_name in filters for sigma in sigmas ] edge_features = np.concatenate(edge_features, axis=1) # save the features save_path = out_prefix + str(block_id) fu.log("saving feature result of shape %s to %s" % (str(edge_features.shape), save_path)) save_root, save_key = os.path.split(save_path) with z5py.N5File(save_root) as f: f.create_dataset(save_key, data=edge_features, chunks=edge_features.shape) fu.log_block_success(block_id)