def compare_lineage_trees_to_gt(gt_dir, proposal_dir, lineage_trees): import compare_tracking from multiprocessing import Pool, cpu_count import itertools print("Parallelizing over {} cores".format(cpu_count())) processing_pool = Pool(cpu_count()) gt_filenames, proposal_filenames = compare_tracking.get_tracking_filenames(gt_dir, proposal_dir) first_timestep = int(os.path.splitext(os.path.basename(gt_filenames[0]))[0]) timesteps = min(len(gt_filenames), len(proposal_filenames)) associations = compare_tracking.construct_associations(gt_filenames, proposal_filenames, timesteps) filename_pairs = zip(gt_filenames[0:timesteps], proposal_filenames[0:timesteps]) lineage_tree_traxels = [lt.get_all_traxels() for lt in lineage_trees] pb = ProgressBar(0, len(lineage_trees)) lineage_tree_measures = [] for measure in processing_pool.imap(compute_traxel_set_measures, itertools.izip(lineage_tree_traxels, itertools.repeat(associations), itertools.repeat(filename_pairs), itertools.repeat(first_timestep))): lineage_tree_measures.append(measure) pb.show() return lineage_tree_measures
def compare_lineage_trees_to_gt(gt_dir, proposal_dir, lineage_trees): import compare_tracking from multiprocessing import Pool, cpu_count import itertools print("Parallelizing over {} cores".format(cpu_count())) processing_pool = Pool(cpu_count()) gt_filenames, proposal_filenames = compare_tracking.get_tracking_filenames( gt_dir, proposal_dir) first_timestep = int( os.path.splitext(os.path.basename(gt_filenames[0]))[0]) timesteps = min(len(gt_filenames), len(proposal_filenames)) associations = compare_tracking.construct_associations( gt_filenames, proposal_filenames, timesteps) filename_pairs = zip(gt_filenames[0:timesteps], proposal_filenames[0:timesteps]) lineage_tree_traxels = [lt.get_all_traxels() for lt in lineage_trees] pb = ProgressBar(0, len(lineage_trees)) lineage_tree_measures = [] for measure in processing_pool.imap( compute_traxel_set_measures, itertools.izip(lineage_tree_traxels, itertools.repeat(associations), itertools.repeat(filename_pairs), itertools.repeat(first_timestep))): lineage_tree_measures.append(measure) pb.show() return lineage_tree_measures
def create_and_link_tracks_and_divisions(track_features_h5, ts, region_features): # storage for all tracks and divisions tracks = {} divisions = {} # mapping of traxelID at front and back of track to track_id track_starts_with_traxel_id = {} track_ends_with_traxel_id = {} pb = ProgressBar(0, len(track_features_h5['tracks'].keys()) + len(track_features_h5['divisions'].keys())) print("Extracting Tracks and Divisions") for track_id in track_features_h5['tracks'].keys(): pb.show() track_id_int = int(track_id) t = Track(track_id_int) t.extract(track_features_h5) t.extract_region_features(ts, region_features) # store in container tracks[track_id_int] = t # create mappings track_starts_with_traxel_id[t.start_traxel_id] = track_id_int track_ends_with_traxel_id[t.end_traxel_id] = track_id_int max_length = max([t.length for t in tracks.values()]) for t in tracks.values(): t.features['track_length'] = float(t.length) / max_length for division_id in track_features_h5['divisions'].keys(): pb.show() division_id_int = int(division_id) d = Division(division_id_int) d.extract(track_features_h5) # find the tracks that are connected by this division # and update information in the tracks try: d.parent_track_id = track_ends_with_traxel_id[d.parent_traxel_id] tracks[d.parent_track_id].end_division_id = division_id_int except KeyError as e: print("Could not find parent track of division {}: ".format(division_id), e.message) for i in [0, 1]: try: d.children_track_ids[i] = track_starts_with_traxel_id[d.children_traxel_ids[i]] tracks[d.children_track_ids[i]].start_division_id = division_id_int except KeyError as e: print("Could not find child track of division {}: ".format(division_id), e.message) # store in container divisions[division_id_int] = d return tracks, divisions
def _findOverlaps(self): """ Check which objects are overlapping between the different segmentation hypotheses, and store that information in every traxel. """ getLogger().info("Checking for overlapping segmentation hypotheses...") t0 = time.time() # find exclusion constraints if self._useMultiprocessing: # use ProcessPoolExecutor, which instanciates as many processes as there CPU cores by default ExecutorType = concurrent.futures.ProcessPoolExecutor getLogger().info('Parallelizing via multiprocessing on all cores!') else: ExecutorType = DummyExecutor getLogger().info('Running on single core!') jobs = [] progressBar = ProgressBar(stop=self.timeRange[1] - self.timeRange[0]) progressBar.show(increase=0) with ExecutorType() as executor: for frame in range(self.timeRange[0], self.timeRange[1]): jobs.append( executor.submit(findConflictingHypothesesInSeparateProcess, frame, self._labelImageFilenames, self._labelImagePaths, self._labelImageFrameIdToGlobalId, self._pluginPaths)) for job in concurrent.futures.as_completed(jobs): progressBar.show() frame, overlaps = job.result() for objectId, overlapIds in overlaps.iteritems(): if self.TraxelsPerFrame[frame][ objectId].conflictingTraxelIds is None: self.TraxelsPerFrame[frame][ objectId].conflictingTraxelIds = [] self.TraxelsPerFrame[frame][ objectId].conflictingTraxelIds.extend(overlapIds) t1 = time.time() getLogger().info("Finding overlaps took {} secs".format(t1 - t0))
def _findOverlaps(self): """ Check which objects are overlapping between the different segmentation hypotheses, and store that information in every traxel. """ getLogger().info("Checking for overlapping segmentation hypotheses...") t0 = time.time() # find exclusion constraints if self._useMultiprocessing: # use ProcessPoolExecutor, which instanciates as many processes as there CPU cores by default ExecutorType = concurrent.futures.ProcessPoolExecutor getLogger().info('Parallelizing via multiprocessing on all cores!') else: ExecutorType = DummyExecutor getLogger().info('Running on single core!') jobs = [] progressBar = ProgressBar(stop=self.timeRange[1] - self.timeRange[0]) progressBar.show(increase=0) with ExecutorType() as executor: for frame in range(self.timeRange[0], self.timeRange[1]): jobs.append(executor.submit(findConflictingHypothesesInSeparateProcess, frame, self._labelImageFilenames, self._labelImagePaths, self._labelImageFrameIdToGlobalId, self._pluginPaths )) for job in concurrent.futures.as_completed(jobs): progressBar.show() frame, overlaps = job.result() for objectId, overlapIds in overlaps.iteritems(): if self.TraxelsPerFrame[frame][objectId].conflictingTraxelIds is None: self.TraxelsPerFrame[frame][objectId].conflictingTraxelIds = [] self.TraxelsPerFrame[frame][objectId].conflictingTraxelIds.extend(overlapIds) t1 = time.time() getLogger().info("Finding overlaps took {} secs".format(t1 - t0))
def convexifyCosts(self, epsilon=0.000001): ''' Convexify all cost vectors in this model (in place!). If two values are equal, the specified `epsilon` will be added to make sure the gradient does not stay at 0. Needed to run the flow solver afterwards ''' if not self.model['settings']['statesShareWeights']: raise ValueError( 'This script can only convexify feature vectors with shared weights!' ) if 'segmentationHypotheses' in self.model: segmentationHypotheses = self.model['segmentationHypotheses'] else: segmentationHypotheses = [] if 'linkingHypotheses' in self.model: linkingHypotheses = self.model['linkingHypotheses'] else: linkingHypotheses = [] if 'divisionHypotheses' in self.model: divisionHypotheses = self.model['divisionHypotheses'] else: divisionHypotheses = [] progressBar = ProgressBar(stop=(len(segmentationHypotheses) + len(linkingHypotheses) + len(divisionHypotheses))) for seg in segmentationHypotheses: for f in [ 'features', 'appearanceFeatures', 'disappearanceFeatures' ]: if f in seg: try: seg[f] = convexify(seg[f], epsilon) except: getLogger().warning( "Convexification failed for feature {} of :{}". format(f, seg)) exit(0) # division features are always convex (2 values defines just a line) progressBar.show() for link in linkingHypotheses: link['features'] = convexify(link['features'], epsilon) progressBar.show() for division in divisionHypotheses: division['features'] = convexify(division['features'], epsilon) progressBar.show()
def convexifyCosts(self, epsilon=0.000001): ''' Convexify all cost vectors in this model (in place!). If two values are equal, the specified `epsilon` will be added to make sure the gradient does not stay at 0. Needed to run the flow solver afterwards ''' if not self.model['settings']['statesShareWeights']: raise ValueError('This script can only convexify feature vectors with shared weights!') if 'segmentationHypotheses' in self.model: segmentationHypotheses = self.model['segmentationHypotheses'] else: segmentationHypotheses = [] if 'linkingHypotheses' in self.model: linkingHypotheses = self.model['linkingHypotheses'] else: linkingHypotheses = [] if 'divisionHypotheses' in self.model: divisionHypotheses = self.model['divisionHypotheses'] else: divisionHypotheses = [] progressBar = ProgressBar(stop=(len(segmentationHypotheses) + len(linkingHypotheses) + len(divisionHypotheses))) for seg in segmentationHypotheses: for f in ['features', 'appearanceFeatures', 'disappearanceFeatures']: if f in seg: try: seg[f] = convexify(seg[f], epsilon) except: getLogger().warning("Convexification failed for feature {} of :{}".format(f, seg)) exit(0) # division features are always convex (2 values defines just a line) progressBar.show() for link in linkingHypotheses: link['features'] = convexify(link['features'], epsilon) progressBar.show() for division in divisionHypotheses: division['features'] = convexify(division['features'], epsilon) progressBar.show()
def convertLegacyHypothesesGraphToJsonGraph( hypothesesGraph, nodeIterator, arcIterator, withTracklets, maxNumObjects, numElements, traxelMap, detectionProbabilityFunc, transitionProbabilityFunc, boundaryCostMultiplierFunc, divisionProbabilityFunc): ''' Build a json representation of this hypotheses graph, by transforming the probabilities for certain events (given by the `*ProbabilityFunc`-functions per traxel) into energies. If the given graph contained tracklets (`withTracklets`), then also the probabilities over all contained traxels will be accumulated for those nodes in the graph. The `hypothesesGraph` as well as `nodeIterator` and `arcIterator` are needed as parameters to support the legacy pgmlink-style hypotheses graph as well. ** Parameters: ** * `hypothesesGraph`: graph whose nodes and edges we are about to traverse. * `nodeIterator`: node iterator * `arcIterator`: arc iterator * `withTracklets`: whether tracklets are used * `maxNumObjects`: the max number of objects per detections * `numElements`: number of nodes + number of edges (for progress bar) * `traxelMap`: mapping from graph-node to list of traxels (in a tracklet) * `detectionProbabilityFunc`: should take a traxel and return its detection probabilities ([prob0objects, prob1object,...]) * `transitionProbabilityFunc`: should take two traxels and return this link's probabilities ([prob0objectsInTransition, prob1objectsInTransition,...]) * `boundaryCostMultiplierFunc`: should take a traxel and a boolean that is true if we are seeking for an appearance cost multiplier, false for disappearance, and return a scalar multiplier between 0 and 1 for the appearance/disappearance cost that depends on the traxel's distance to the spacial and time boundary * `divisionProbabilityFunc`: should take a traxel and return its division probabilities ([probNoDiv, probDiv]) ''' getLogger().info("Creating JSON graph from legacy hypotheses graph") progressBar = ProgressBar(stop=numElements) trackingGraph = hytra.core.jsongraph.JsonTrackingGraph() # add all detections to JSON for n in nodeIterator: if not withTracklets: # only one traxel, but make it a list so everything below works the same traxels = [traxelMap[n]] else: traxels = traxelMap[n] # accumulate features over all contained traxels previousTraxel = None detectionFeatures = np.zeros(maxNumObjects + 1) for t in traxels: detectionFeatures += np.array(negLog(detectionProbabilityFunc(t))) if previousTraxel is not None: detectionFeatures += np.array( negLog(transitionProbabilityFunc(previousTraxel, t))) previousTraxel = t detectionFeatures = listify(list(detectionFeatures)) # division only if probability is big enough divisionFeatures = divisionProbabilityFunc(traxels[-1]) if divisionFeatures is not None: divisionFeatures = listify(negLog(divisionFeatures)) # appearance/disappearance appearanceFeatures = listify( [0.0] + [boundaryCostMultiplierFunc(traxels[0], True)] * maxNumObjects) disappearanceFeatures = listify( [0.0] + [boundaryCostMultiplierFunc(traxels[-1], False)] * maxNumObjects) trackingGraph.addDetectionHypothesesFromTracklet( traxels, detectionFeatures, divisionFeatures, appearanceFeatures, disappearanceFeatures, timestep=[traxels[0].Timestep, traxels[-1].Timestep]) progressBar.show() # add all links for a in arcIterator: if not withTracklets: srcTraxel = traxelMap[hypothesesGraph.source(a)] destTraxel = traxelMap[hypothesesGraph.target(a)] else: srcTraxel = traxelMap[hypothesesGraph.source(a)][ -1] # src is last of the traxels in source tracklet destTraxel = traxelMap[hypothesesGraph.target(a)][ 0] # dest is first of traxels in destination tracklet src = trackingGraph.traxelIdPerTimestepToUniqueIdMap[str( srcTraxel.Timestep)][str(srcTraxel.Id)] dest = trackingGraph.traxelIdPerTimestepToUniqueIdMap[str( destTraxel.Timestep)][str(destTraxel.Id)] features = listify( negLog(transitionProbabilityFunc(srcTraxel, destTraxel))) trackingGraph.addLinkingHypotheses(src, dest, features) progressBar.show() return trackingGraph
def fillTraxels(self, usePgmlink=True, ts=None, fs=None, dispyNodeIps=[], turnOffFeatures=[]): """ Compute all the features and predict object count as well as division probabilities. Store the resulting information (and all other features) in the given pgmlink::TraxelStore, or create a new one if ts=None. usePgmlink: boolean whether pgmlink should be used and a pgmlink.TraxelStore and pgmlink.FeatureStore returned ts: an initial pgmlink.TraxelStore (only used if usePgmlink=True) fs: an initial pgmlink.FeatureStore (only used if usePgmlink=True) returns (ts, fs) but only if usePgmlink=True, otherwise it fills self.TraxelsPerFrame """ if usePgmlink: import pgmlink if ts is None: ts = pgmlink.TraxelStore() fs = pgmlink.FeatureStore() else: assert (fs is not None) getLogger().info("Extracting features...") self._featuresPerFrame = self._extractAllFeatures( dispyNodeIps=dispyNodeIps, turnOffFeatures=turnOffFeatures) getLogger().info("Creating traxels...") progressBar = ProgressBar(stop=len(self._featuresPerFrame)) progressBar.show(increase=0) for frame, features in self._featuresPerFrame.iteritems(): # predict random forests if self._countClassifier is not None: objectCountProbabilities = self._countClassifier.predictProbabilities( features=None, featureDict=features) if self._divisionClassifier is not None and frame + 1 < self.timeRange[ 1]: divisionProbabilities = self._divisionClassifier.predictProbabilities( features=None, featureDict=features) # create traxels for all objects for objectId in range(1, features.values()[0].shape[0]): # print("Frame {} Object {}".format(frame, objectId)) pixelSize = features['Count'][objectId] if pixelSize == 0 or (self._options.sizeFilter is not None \ and (pixelSize < self._options.sizeFilter[0] \ or pixelSize > self._options.sizeFilter[1])): continue # create traxel if usePgmlink: traxel = pgmlink.Traxel() else: traxel = Traxel() traxel.Id = objectId traxel.Timestep = frame # add raw features for key, val in features.iteritems(): if key == 'id': traxel.idInSegmentation = val[objectId] elif key == 'filename': traxel.segmentationFilename = val[objectId] else: try: if isinstance( val, list): # polygon feature returns a list! featureValues = val[objectId] else: featureValues = val[objectId, ...] except: getLogger().error( "Could not get feature values of {} for key {} from matrix with shape {}" .format(objectId, key, val.shape)) raise AssertionError() try: self._setTraxelFeatureArray( traxel, featureValues, key) if key == 'RegionCenter': self._setTraxelFeatureArray( traxel, featureValues, 'com') except: getLogger().error( "Could not add feature array {} for {}".format( featureValues, key)) raise AssertionError() # add random forest predictions if self._countClassifier is not None: self._setTraxelFeatureArray( traxel, objectCountProbabilities[objectId, :], self.detectionProbabilityFeatureName) if self._divisionClassifier is not None and frame + 1 < self.timeRange[ 1]: self._setTraxelFeatureArray( traxel, divisionProbabilities[objectId, :], self.divisionProbabilityFeatureName) # set other parameters traxel.set_x_scale(self.x_scale) traxel.set_y_scale(self.y_scale) traxel.set_z_scale(self.z_scale) if usePgmlink: # add to pgmlink's traxelstore ts.add(fs, traxel) else: self.TraxelsPerFrame.setdefault(frame, {})[objectId] = traxel progressBar.show() if usePgmlink: return ts, fs
def _extractAllFeatures(self, dispyNodeIps=[], turnOffFeatures=[]): """ Extract the features of all frames. If a list of IP addresses is given e.g. as `dispyNodeIps = ["104.197.178.206","104.196.46.138"]`, then the computation will be distributed across these nodes. Otherwise, multiprocessing will be used if `self._useMultiprocessing=True`, which it is by default. If `dispyNodeIps` is an empty list, then the feature extraction will be parallelized via multiprocessing. **TODO:** fix division feature computation for distributed mode """ import logging # configure progress bar numSteps = self.timeRange[1] - self.timeRange[0] if self._divisionClassifier is not None: numSteps *= 2 t0 = time.time() if (len(dispyNodeIps) == 0): # no dispy node IDs given, parallelize object feature computation via processes if self._useMultiprocessing: # use ProcessPoolExecutor, which instanciates as many processes as there CPU cores by default ExecutorType = concurrent.futures.ProcessPoolExecutor logging.getLogger('Traxelstore').info( 'Parallelizing feature extraction via multiprocessing on all cores!' ) else: ExecutorType = DummyExecutor logging.getLogger('Traxelstore').info( 'Running feature extraction on single core!') featuresPerFrame = {} progressBar = ProgressBar(stop=numSteps) progressBar.show(increase=0) with ExecutorType() as executor: # 1st pass for region features jobs = [] for frame in range(self.timeRange[0], self.timeRange[1]): jobs.append( executor.submit(computeRegionFeaturesOnCloud, frame, self._options.rawImageFilename, self._options.rawImagePath, self._options.rawImageAxes, self._options.labelImageFilename, self._options.labelImagePath, turnOffFeatures, self._pluginPaths)) for job in concurrent.futures.as_completed(jobs): progressBar.show() frame, feats = job.result() featuresPerFrame[frame] = feats # 2nd pass for division features if self._divisionClassifier is not None: jobs = [] for frame in range(self.timeRange[0], self.timeRange[1] - 1): jobs.append( executor.submit( computeDivisionFeaturesOnCloud, frame, featuresPerFrame[frame], featuresPerFrame[frame + 1], self._pluginManager.getImageProvider(), self._options.labelImageFilename, self._options.labelImagePath, self.getNumDimensions(), self._divisionFeatureNames)) for job in concurrent.futures.as_completed(jobs): progressBar.show() frame, feats = job.result() featuresPerFrame[frame].update(feats) # # serialize features?? # for frame in range(self.timeRange[0], self.timeRange[1]): # featureSerializer.storeFeaturesForFrame(featuresPerFrame[frame], frame) else: import logging logging.getLogger('Traxelstore').warning( 'Parallelization with dispy is WORK IN PROGRESS!') import random import dispy cluster = dispy.JobCluster(computeRegionFeaturesOnCloud, nodes=dispyNodeIps, loglevel=logging.DEBUG, depends=[self._pluginManager], secret="teamtracking") jobs = [] for frame in range(self.timeRange[0], self.timeRange[1]): job = cluster.submit( frame, self._options.rawImageFilename, self._options.rawImagePath, self._options.rawImageAxes, self._options.labelImageFilename, self._options.labelImagePath, turnOffFeatures, pluginPaths=['/home/carstenhaubold/embryonic/plugins']) job.id = frame jobs.append(job) for job in jobs: job() # wait for job to finish print job.exception print job.stdout print job.stderr print job.id logging.getLogger('Traxelstore').warning( 'Using dispy we cannot compute division features yet!') # # 2nd pass for division features # if self._divisionClassifier is not None: # for frame in range(self.timeRange[0], self.timeRange[1]): # progressBar.show() # featuresPerFrame[frame].update(self._extractDivisionFeaturesForFrame(frame, featuresPerFrame)[1]) t1 = time.time() getLogger().info("Feature computation took {} secs".format(t1 - t0)) return featuresPerFrame
def _extractAllFeatures(self, dispyNodeIps=[], turnOffFeatures=[]): """ Extract the features of all frames of all segmentation hypotheses. Feature extraction will be parallelized via multiprocessing. WARNING: distributed computation via Dispy is not supported here, so dispyNodeIps must be an empty list! """ # configure progress bar numSteps = (self.timeRange[1] - self.timeRange[0]) * len( self._labelImageFilenames) if self._divisionClassifier is not None: numSteps *= 2 t0 = time.time() if self._useMultiprocessing: # use ProcessPoolExecutor, which instanciates as many processes as there CPU cores by default ExecutorType = concurrent.futures.ProcessPoolExecutor logging.getLogger('Traxelstore').info( 'Parallelizing feature extraction via multiprocessing on all cores!' ) else: ExecutorType = DummyExecutor logging.getLogger('Traxelstore').info( 'Running feature extraction on single core!') featuresPerFrame = {} progressBar = ProgressBar(stop=numSteps) progressBar.show(increase=0) with ExecutorType() as executor: # 1st pass for region features, once per segmentation hypotheses for filename, path in zip(self._labelImageFilenames, self._labelImagePaths): jobs = [] for frame in range(self.timeRange[0], self.timeRange[1]): jobs.append( executor.submit(computeRegionFeaturesOnCloud, frame, self._options.rawImageFilename, self._options.rawImagePath, self._options.rawImageAxes, filename, path, turnOffFeatures, self._pluginPaths)) for job in concurrent.futures.as_completed(jobs): progressBar.show() frame, feats = job.result() self._insertFilenameAndIdToFeatures(feats, filename) if frame not in featuresPerFrame: featuresPerFrame[frame] = feats else: self._mergeFrameFeatures(featuresPerFrame[frame], feats) # 2nd pass for division features # TODO: the division feature manager should also see the child candidates in all segmentation hypotheses for filename, path in zip(self._labelImageFilenames, self._labelImagePaths): if self._divisionClassifier is not None: jobs = [] for frame in range(self.timeRange[0], self.timeRange[1] - 1): jobs.append( executor.submit( computeDivisionFeaturesOnCloud, frame, featuresPerFrame[frame], featuresPerFrame[frame + 1], self._pluginManager.getImageProvider(), filename, path, self.getNumDimensions(), self._divisionFeatureNames)) for job in concurrent.futures.as_completed(jobs): progressBar.show() frame, feats = job.result() # add division features to the dictionary for the first set, and then merge the new features in if feats.keys()[0] not in featuresPerFrame[frame]: featuresPerFrame[frame].update(feats) else: self._mergeFrameFeatures(featuresPerFrame[frame], feats) self._storeBackwardMapping(featuresPerFrame) t1 = time.time() getLogger().info("Feature computation took {} secs".format(t1 - t0)) return featuresPerFrame
def insertEnergies(self, maxNumObjects, detectionProbabilityFunc, transitionProbabilityFunc, boundaryCostMultiplierFunc, divisionProbabilityFunc, skipLinksBias): ''' Insert energies for detections, divisions and links into the hypotheses graph, by transforming the probabilities for certain events (given by the `*ProbabilityFunc`-functions per traxel) into energies. If the given graph contained tracklets (`self.withTracklets is True`), then also the probabilities over all contained traxels will be accumulated for those nodes in the graph. The energies are stored in the networkx graph under the following attribute names (to match the format for solvers): * detection energies: `self._graph.node[n]['features']` * division energies: `self._graph.node[n]['divisionFeatures']` * appearance energies: `self._graph.node[n]['appearanceFeatures']` * disappearance energies: `self._graph.node[n]['disappearanceFeatures']` * transition energies: `self._graph.edge[src][dest]['features']` * additionally we also store the timestep (range for traxels) per node as `timestep` attribute ** Parameters: ** * `maxNumObjects`: the max number of objects per detections * `detectionProbabilityFunc`: should take a traxel and return its detection probabilities ([prob0objects, prob1object,...]) * `transitionProbabilityFunc`: should take two traxels and return this link's probabilities ([prob0objectsInTransition, prob1objectsInTransition,...]) * `boundaryCostMultiplierFunc`: should take a traxel and a boolean that is true if we are seeking for an appearance cost multiplier, false for disappearance, and return a scalar multiplier between 0 and 1 for the appearance/disappearance cost that depends on the traxel's distance to the spacial and time boundary * `divisionProbabilityFunc`: should take a traxel and return its division probabilities ([probNoDiv, probDiv]) ''' numElements = self._graph.number_of_nodes( ) + self._graph.number_of_edges() progressBar = ProgressBar(stop=numElements) # insert detection probabilities for all detections (and some also get a div probability) for n in self._graph.nodes_iter(): if not self.withTracklets: # only one traxel, but make it a list so everything below works the same traxels = [self._graph.node[n]['traxel']] else: traxels = self._graph.node[n]['tracklet'] # accumulate features over all contained traxels previousTraxel = None detectionFeatures = np.zeros(maxNumObjects + 1) for t in traxels: detectionFeatures += np.array( negLog(detectionProbabilityFunc(t))) if previousTraxel is not None: detectionFeatures += np.array( negLog(transitionProbabilityFunc(previousTraxel, t))) previousTraxel = t detectionFeatures = listify(list(detectionFeatures)) # division only if probability is big enough divisionFeatures = divisionProbabilityFunc(traxels[-1]) if divisionFeatures is not None: divisionFeatures = listify(negLog(divisionFeatures)) # appearance/disappearance appearanceFeatures = listify( [0.0] + [boundaryCostMultiplierFunc(traxels[0], True)] * maxNumObjects) disappearanceFeatures = listify( [0.0] + [boundaryCostMultiplierFunc(traxels[-1], False)] * maxNumObjects) self._graph.node[n]['features'] = detectionFeatures if divisionFeatures is not None: self._graph.node[n]['divisionFeatures'] = divisionFeatures self._graph.node[n]['appearanceFeatures'] = appearanceFeatures self._graph.node[n][ 'disappearanceFeatures'] = disappearanceFeatures self._graph.node[n]['timestep'] = [ traxels[0].Timestep, traxels[-1].Timestep ] progressBar.show() # insert transition probabilities for all links for a in self._graph.edges_iter(): if not self.withTracklets: srcTraxel = self._graph.node[self.source(a)]['traxel'] destTraxel = self._graph.node[self.target(a)]['traxel'] else: srcTraxel = self._graph.node[self.source(a)]['tracklet'][ -1] # src is last of the traxels in source tracklet destTraxel = self._graph.node[self.target(a)]['tracklet'][ 0] # dest is first of traxels in destination tracklet features = listify( negLog(transitionProbabilityFunc(srcTraxel, destTraxel))) # add feature for additional Frames. Since we do not want these edges to be primarily taken, we add a bias to the edge. Now: hard coded, future: parameter frame_gap = destTraxel.Timestep - srcTraxel.Timestep # 1. method if frame_gap > 1: features[1][0] = features[1][0] + skipLinksBias * frame_gap # # 2. method # # introduce a new energies like: [[6], [15]] -> [[6, 23], [15, 23]] for first links and # # [[6], [15]] -> [[23, 6], [23, 15]] for second links, and so on for 3rd order links # # !!! this will introduce a new weight in the weight.json file. For the 2nd link, comes in 2nd row and so on. # # drawback: did not manage to adjust parameter to get sensible results. # for feat in features: # for i in range(frame_gap): # feat.append(23) # if frame_gap > 1: # feat[frame_gap-1], feat[0] = feat[0], feat[frame_gap-1] self._graph.edge[a[0]][a[1]]['src'] = self._graph.node[a[0]]['id'] self._graph.edge[a[0]][a[1]]['dest'] = self._graph.node[a[1]]['id'] self._graph.edge[a[0]][a[1]]['features'] = features progressBar.show()
def buildFromProbabilityGenerator(self, probabilityGenerator, maxNeighborDist=200, numNearestNeighbors=1, forwardBackwardCheck=True, withDivisions=True, divisionThreshold=0.1, skipLinks=1): """ Takes a python probabilityGenerator containing traxel features and finds probable links between frames. Builds a kdTree with the 'numNearestneighbors' for each frame and adds the nodes. In the same iteration, it adds a number of 'skipLinks' between the nodes separated by 'skipLinks' frames. """ assert (probabilityGenerator is not None) assert (len(probabilityGenerator.TraxelsPerFrame) > 0) assert (skipLinks > 0) def checkNodeWhileAddingLinks(frame, obj): if (frame, obj) not in self._graph: getLogger().warning( "Adding node ({}, {}) when setting up links".format( frame, obj)) kdTreeFrames = [None] * (skipLinks + 1) # len(probabilityGenerator.TraxelsPerFrame.keys()) is NOT an indicator for the total number of frames, # because an empty frame does not create a key in the dictionary. E.g. for one frame in the middle of the # dataset, we won't access the last one. # Idea: take the max key in the dict. Remember, frame numbering starts with 0. frameMax = max(probabilityGenerator.TraxelsPerFrame.keys()) frameMin = min(probabilityGenerator.TraxelsPerFrame.keys()) numFrames = frameMax - frameMin + 1 progressBar = ProgressBar(stop=numFrames * skipLinks) progressBar.show(0) for frame in range(numFrames): if frame > 0: del kdTreeFrames[0] # this is the current frame if frame + skipLinks < numFrames and frameMin + frame + skipLinks in probabilityGenerator.TraxelsPerFrame.keys( ): kdTreeFrames.append( self._buildFrameKdTree( probabilityGenerator.TraxelsPerFrame[frameMin + frame + skipLinks])) self._addNodesForFrame( frameMin + frame + skipLinks, probabilityGenerator.TraxelsPerFrame[frameMin + frame + skipLinks]) else: for i in range(0, skipLinks + 1): if frameMin + frame + i in probabilityGenerator.TraxelsPerFrame.keys( ): # empty frame kdTreeFrames[i] = self._buildFrameKdTree( probabilityGenerator.TraxelsPerFrame[frameMin + frame + i]) self._addNodesForFrame( frameMin + frame + i, probabilityGenerator.TraxelsPerFrame[frameMin + frame + i]) # find forward links if frameMin + frame in probabilityGenerator.TraxelsPerFrame.keys( ): # 'frame' could be empty for obj, traxel in probabilityGenerator.TraxelsPerFrame[ frameMin + frame].iteritems(): divisionPreservingNumNearestNeighbors = numNearestNeighbors if divisionPreservingNumNearestNeighbors < 2 \ and withDivisions \ and self._traxelMightDivide(traxel, divisionThreshold): divisionPreservingNumNearestNeighbors = 2 for i in range(1, skipLinks + 1): if frame + i < numFrames and frameMin + frame + i in probabilityGenerator.TraxelsPerFrame.keys( ): neighbors = (self._findNearestNeighbors( kdTreeFrames[i], traxel, divisionPreservingNumNearestNeighbors, maxNeighborDist)) # type(neighbors) is list for n in neighbors: checkNodeWhileAddingLinks( frameMin + frame, obj) checkNodeWhileAddingLinks( frameMin + frame + i, n) self._graph.add_edge((frameMin + frame, obj), (frameMin + frame + i, n)) self._graph.edge[frameMin + frame, obj][ frameMin + frame + i, n]['src'] = self._graph.node[(frameMin + frame, obj)]['id'] self._graph.edge[frameMin + frame, obj][ frameMin + frame + i, n]['dest'] = self._graph.node[(frameMin + frame + i, n)]['id'] # find backward links if forwardBackwardCheck: for i in range(1, skipLinks + 1): if frame + i < numFrames: if frameMin + frame + i in probabilityGenerator.TraxelsPerFrame.keys( ): # empty frame for obj, traxel in probabilityGenerator.TraxelsPerFrame[ frameMin + frame + i].iteritems(): if kdTreeFrames[0] is not None: neighbors = (self._findNearestNeighbors( kdTreeFrames[0], traxel, numNearestNeighbors, maxNeighborDist)) for n in neighbors: checkNodeWhileAddingLinks( frameMin + frame, n) checkNodeWhileAddingLinks( frameMin + frame + i, obj) self._graph.add_edge( (frameMin + frame, n), (frameMin + frame + i, obj)) self._graph.edge[frameMin + frame, n][ frameMin + frame + i, obj]['src'] = self._graph.node[( frameMin + frame, n)]['id'] self._graph.edge[frameMin + frame, n][ frameMin + frame + i, obj]['dest'] = self._graph.node[( frameMin + frame + i, obj)]['id'] progressBar.show() progressBar.show()
if prev is not None: links.append((prev, timestepIdTuple)) prev = timestepIdTuple # group by timestep # timesteps = [t for t in traxelIdPerTimestepToUniqueIdMap.keys()] # there might be empty frames. We want them as output too. timesteps = [str(t).decode("utf-8") for t in range(int(min(traxelIdPerTimestepToUniqueIdMap.keys())) , int(max(traxelIdPerTimestepToUniqueIdMap.keys()))+1 )] linksPerTimestep = dict([(t, [(a[1], b[1]) for a, b in links if b[0] == int(t)]) for t in timesteps]) assert(len(linksPerTimestep['0']) == 0) # create output array resultVolume = np.zeros((len(timesteps),) + shape, dtype='uint32') print("resulting volume shape: {}".format(resultVolume.shape)) progressBar = ProgressBar(stop=len(timesteps)) progressBar.show(0) # iterate over timesteps and label tracks from front to back in a distinct color nextUnusedColor = 1 lastFrameColorMap = {} lastFrameLabelImage = getLabelImageForFrame(args.labelImageFilename, args.labelImagePath, 0, shape) for t in range(1,len(timesteps)): progressBar.show() thisFrameColorMap = {} thisFrameLabelImage = getLabelImageForFrame(args.labelImageFilename, args.labelImagePath, t, shape) for a, b in linksPerTimestep[str(t)]: # propagate color if possible, otherwise assign a new one if a in lastFrameColorMap: thisFrameColorMap[b] = lastFrameColorMap[a] else: thisFrameColorMap[b] = nextUnusedColor
def convertLegacyHypothesesGraphToJsonGraph(hypothesesGraph, nodeIterator, arcIterator, withTracklets, maxNumObjects, numElements, traxelMap, detectionProbabilityFunc, transitionProbabilityFunc, boundaryCostMultiplierFunc, divisionProbabilityFunc): ''' Build a json representation of this hypotheses graph, by transforming the probabilities for certain events (given by the `*ProbabilityFunc`-functions per traxel) into energies. If the given graph contained tracklets (`withTracklets`), then also the probabilities over all contained traxels will be accumulated for those nodes in the graph. The `hypothesesGraph` as well as `nodeIterator` and `arcIterator` are needed as parameters to support the legacy pgmlink-style hypotheses graph as well. ** Parameters: ** * `hypothesesGraph`: graph whose nodes and edges we are about to traverse. * `nodeIterator`: node iterator * `arcIterator`: arc iterator * `withTracklets`: whether tracklets are used * `maxNumObjects`: the max number of objects per detections * `numElements`: number of nodes + number of edges (for progress bar) * `traxelMap`: mapping from graph-node to list of traxels (in a tracklet) * `detectionProbabilityFunc`: should take a traxel and return its detection probabilities ([prob0objects, prob1object,...]) * `transitionProbabilityFunc`: should take two traxels and return this link's probabilities ([prob0objectsInTransition, prob1objectsInTransition,...]) * `boundaryCostMultiplierFunc`: should take a traxel and a boolean that is true if we are seeking for an appearance cost multiplier, false for disappearance, and return a scalar multiplier between 0 and 1 for the appearance/disappearance cost that depends on the traxel's distance to the spacial and time boundary * `divisionProbabilityFunc`: should take a traxel and return its division probabilities ([probNoDiv, probDiv]) ''' getLogger().info("Creating JSON graph from legacy hypotheses graph") progressBar = ProgressBar(stop=numElements) trackingGraph = hytra.core.jsongraph.JsonTrackingGraph() # add all detections to JSON for n in nodeIterator: if not withTracklets: # only one traxel, but make it a list so everything below works the same traxels = [traxelMap[n]] else: traxels = traxelMap[n] # accumulate features over all contained traxels previousTraxel = None detectionFeatures = np.zeros(maxNumObjects + 1) for t in traxels: detectionFeatures += np.array(negLog(detectionProbabilityFunc(t))) if previousTraxel is not None: detectionFeatures += np.array(negLog(transitionProbabilityFunc(previousTraxel, t))) previousTraxel = t detectionFeatures = listify(list(detectionFeatures)) # division only if probability is big enough divisionFeatures = divisionProbabilityFunc(traxels[-1]) if divisionFeatures is not None: divisionFeatures = listify(negLog(divisionFeatures)) # appearance/disappearance appearanceFeatures = listify([0.0] + [boundaryCostMultiplierFunc(traxels[0], True)] * maxNumObjects) disappearanceFeatures = listify([0.0] + [boundaryCostMultiplierFunc(traxels[-1], False)] * maxNumObjects) trackingGraph.addDetectionHypothesesFromTracklet(traxels, detectionFeatures, divisionFeatures, appearanceFeatures, disappearanceFeatures, timestep=[traxels[0].Timestep, traxels[-1].Timestep]) progressBar.show() # add all links for a in arcIterator: if not withTracklets: srcTraxel = traxelMap[hypothesesGraph.source(a)] destTraxel = traxelMap[hypothesesGraph.target(a)] else: srcTraxel = traxelMap[hypothesesGraph.source(a)][-1] # src is last of the traxels in source tracklet destTraxel = traxelMap[hypothesesGraph.target(a)][0] # dest is first of traxels in destination tracklet src = trackingGraph.traxelIdPerTimestepToUniqueIdMap[str(srcTraxel.Timestep)][str(srcTraxel.Id)] dest = trackingGraph.traxelIdPerTimestepToUniqueIdMap[str(destTraxel.Timestep)][str(destTraxel.Id)] features = listify(negLog(transitionProbabilityFunc(srcTraxel, destTraxel))) trackingGraph.addLinkingHypotheses(src, dest, features) progressBar.show() return trackingGraph
def buildFromProbabilityGenerator(self, probabilityGenerator, maxNeighborDist=200, numNearestNeighbors=1, forwardBackwardCheck=True, withDivisions=True, divisionThreshold=0.1): """ Takes a python traxelstore containing traxel features and finds probable links between frames. """ assert (probabilityGenerator is not None) assert (len(probabilityGenerator.TraxelsPerFrame) > 0) def checkNodeWhileAddingLinks(frame, obj): if (frame, obj) not in self._graph: getLogger().warning( "Adding node ({}, {}) when setting up links".format( frame, obj)) kdTreeNextFrame = None numFrames = len(probabilityGenerator.TraxelsPerFrame.keys()) progressBar = ProgressBar(stop=numFrames) progressBar.show(0) for frame in range(numFrames - 1): if frame > 0: kdTreeThisFrame = kdTreeNextFrame else: kdTreeThisFrame = self._buildFrameKdTree( probabilityGenerator.TraxelsPerFrame[frame]) self._addNodesForFrame( frame, probabilityGenerator.TraxelsPerFrame[frame]) kdTreeNextFrame = self._buildFrameKdTree( probabilityGenerator.TraxelsPerFrame[frame + 1]) self._addNodesForFrame( frame + 1, probabilityGenerator.TraxelsPerFrame[frame + 1]) # find forward links for obj, traxel in probabilityGenerator.TraxelsPerFrame[ frame].iteritems(): divisionPreservingNumNearestNeighbors = numNearestNeighbors if divisionPreservingNumNearestNeighbors < 2 \ and withDivisions \ and self._traxelMightDivide(traxel, divisionThreshold): divisionPreservingNumNearestNeighbors = 2 neighbors = self._findNearestNeighbors( kdTreeNextFrame, traxel, divisionPreservingNumNearestNeighbors, maxNeighborDist) for n in neighbors: checkNodeWhileAddingLinks(frame, obj) checkNodeWhileAddingLinks(frame + 1, n) self._graph.add_edge((frame, obj), (frame + 1, n)) self._graph.edge[frame, obj][frame + 1, n]['src'] = self._graph.node[( frame, obj)]['id'] self._graph.edge[frame, obj][frame + 1, n]['dest'] = self._graph.node[( frame + 1, n)]['id'] # find backward links if forwardBackwardCheck: for obj, traxel in probabilityGenerator.TraxelsPerFrame[ frame + 1].iteritems(): neighbors = self._findNearestNeighbors( kdTreeThisFrame, traxel, numNearestNeighbors, maxNeighborDist) for n in neighbors: checkNodeWhileAddingLinks(frame, n) checkNodeWhileAddingLinks(frame + 1, obj) self._graph.add_edge((frame, n), (frame + 1, obj)) self._graph.edge[frame, n][frame + 1, obj]['src'] = self._graph.node[( frame, n)]['id'] self._graph.edge[frame, n][frame + 1, obj]['dest'] = self._graph.node[( frame + 1, obj)]['id'] progressBar.show() progressBar.show()
def insertEnergies(self, maxNumObjects, detectionProbabilityFunc, transitionProbabilityFunc, boundaryCostMultiplierFunc, divisionProbabilityFunc): ''' Insert energies for detections, divisions and links into the hypotheses graph, by transforming the probabilities for certain events (given by the `*ProbabilityFunc`-functions per traxel) into energies. If the given graph contained tracklets (`self.withTracklets is True`), then also the probabilities over all contained traxels will be accumulated for those nodes in the graph. The energies are stored in the networkx graph under the following attribute names (to match the format for solvers): * detection energies: `self._graph.node[n]['features']` * division energies: `self._graph.node[n]['divisionFeatures']` * appearance energies: `self._graph.node[n]['appearanceFeatures']` * disappearance energies: `self._graph.node[n]['disappearanceFeatures']` * transition energies: `self._graph.edge[src][dest]['features']` * additionally we also store the timestep (range for traxels) per node as `timestep` attribute ** Parameters: ** * `maxNumObjects`: the max number of objects per detections * `detectionProbabilityFunc`: should take a traxel and return its detection probabilities ([prob0objects, prob1object,...]) * `transitionProbabilityFunc`: should take two traxels and return this link's probabilities ([prob0objectsInTransition, prob1objectsInTransition,...]) * `boundaryCostMultiplierFunc`: should take a traxel and return a scalar multiplier between 0 and 1 for the appearance/disappearance cost that depends on the traxel's distance to the spacial and time boundary * `divisionProbabilityFunc`: should take a traxel and return its division probabilities ([probNoDiv, probDiv]) ''' numElements = self._graph.number_of_nodes( ) + self._graph.number_of_edges() progressBar = ProgressBar(stop=numElements) # insert detection probabilities for all detections (and some also get a div probability) for n in self._graph.nodes_iter(): if not self.withTracklets: # only one traxel, but make it a list so everything below works the same traxels = [self._graph.node[n]['traxel']] else: traxels = self._graph.node[n]['tracklet'] # accumulate features over all contained traxels previousTraxel = None detectionFeatures = np.zeros(maxNumObjects + 1) for t in traxels: detectionFeatures += np.array( negLog(detectionProbabilityFunc(t))) if previousTraxel is not None: detectionFeatures += np.array( negLog(transitionProbabilityFunc(previousTraxel, t))) previousTraxel = t detectionFeatures = listify(list(detectionFeatures)) # division only if probability is big enough divisionFeatures = divisionProbabilityFunc(traxels[-1]) if divisionFeatures is not None: divisionFeatures = listify(negLog(divisionFeatures)) # appearance/disappearance appearanceFeatures = listify( [0.0] + [boundaryCostMultiplierFunc(traxels[0])] * maxNumObjects) disappearanceFeatures = listify( [0.0] + [boundaryCostMultiplierFunc(traxels[-1])] * maxNumObjects) self._graph.node[n]['features'] = detectionFeatures if divisionFeatures is not None: self._graph.node[n]['divisionFeatures'] = divisionFeatures self._graph.node[n]['appearanceFeatures'] = appearanceFeatures self._graph.node[n][ 'disappearanceFeatures'] = disappearanceFeatures self._graph.node[n]['timestep'] = [ traxels[0].Timestep, traxels[-1].Timestep ] progressBar.show() # insert transition probabilities for all links for a in self._graph.edges_iter(): if not self.withTracklets: srcTraxel = self._graph.node[self.source(a)]['traxel'] destTraxel = self._graph.node[self.target(a)]['traxel'] else: srcTraxel = self._graph.node[self.source(a)]['tracklet'][ -1] # src is last of the traxels in source tracklet destTraxel = self._graph.node[self.target(a)]['tracklet'][ 0] # dest is first of traxels in destination tracklet features = listify( negLog(transitionProbabilityFunc(srcTraxel, destTraxel))) self._graph.edge[a[0]][a[1]]['src'] = self._graph.node[a[0]]['id'] self._graph.edge[a[0]][a[1]]['dest'] = self._graph.node[a[1]]['id'] self._graph.edge[a[0]][a[1]]['features'] = features progressBar.show()
def findGroundTruthJaccardScoreAndMapping(self, hypothesesGraph, groundTruthSegmentationFilename=None, groundTruthSegmentationPath=None, groundTruthTextFilename=None, groundTruthMinJaccardScore=0.5): """ Find the overlap between all objects in the given segmentations with the groundtruth, and store that jaccard score in each traxel's features. **Returns** a solution dictionary in our JSON format, which fits to the given hypotheses graph. TODO: simplify this method! Currently there are 4 different sets of IDs to reference nodes: * the ground truth trackId * a corresponding globalId (which is unique within a frame across different segmentation hypotheses) * an objectId (which equals the labelId within one segmentation hypotheses) * a globally unique UUID as used in the JSON files The nodes in the hypotheses graph are indexed by (frame, globalId), the resulting dict must use UUIDs. """ getLogger().info("Computing Jaccard scores w.r.t. GroundTruth ...") t0 = time.time() # find exclusion constraints if self._useMultiprocessing: # use ProcessPoolExecutor, which instanciates as many processes as there CPU cores by default ExecutorType = concurrent.futures.ProcessPoolExecutor getLogger().info('Parallelizing via multiprocessing on all cores!') else: ExecutorType = DummyExecutor getLogger().info('Running on single core!') jobs = [] progressBar = ProgressBar(stop=self.timeRange[1] - self.timeRange[0]) progressBar.show(increase=0) gtFrameIdToGlobalIdsWithScoresMap = {} with ExecutorType() as executor: for frame in range(self.timeRange[0], self.timeRange[1]): jobs.append(executor.submit(computeJaccardScoresOnCloud, frame, self._labelImageFilenames, self._labelImagePaths, self._labelImageFrameIdToGlobalId, groundTruthSegmentationFilename, groundTruthSegmentationPath, groundTruthMinJaccardScore, self._pluginPaths )) for job in concurrent.futures.as_completed(jobs): progressBar.show() frame, scores, frameGtToGlobalIdMap = job.result() for objectId, individualScores in scores.iteritems(): self.TraxelsPerFrame[frame][objectId].Features['JaccardScores'] = individualScores gtFrameIdToGlobalIdsWithScoresMap.update(frameGtToGlobalIdMap) t1 = time.time() getLogger().info("Finding jaccard scores took {} secs".format(t1 - t0)) # create JSON result by mapping it to the hypotheses graph traxelIdPerTimestepToUniqueIdMap, _ = hypothesesGraph.getMappingsBetweenUUIDsAndTraxels() detectionResults = [] for gtFrameAndId, globalIdsAndScores in gtFrameIdToGlobalIdsWithScoresMap.iteritems(): detectionResults.append({"id": traxelIdPerTimestepToUniqueIdMap[str(gtFrameAndId[0])][str(globalIdsAndScores[-1][0])], "value":1}) # read tracks from textfile with open(groundTruthTextFilename, 'r') as tracksFile: lines = tracksFile.readlines() tracks = [[int(x) for x in line.strip().split(" ")] for line in lines] # order them by track start time and process track by track tracks.sort(key=lambda x: x[1]) linkingResults = [] descendants = {} missingLinks = 0 def checkLinkExists(gtSrc, gtDest): # first check that both GT nodes have been mapped to a hypotheses if gtSrc in gtFrameIdToGlobalIdsWithScoresMap: src = (gtSrc[0], gtFrameIdToGlobalIdsWithScoresMap[gtSrc][-1][0]) else: getLogger().warning("GT link's source node {} has no match in the segmentation hypotheses".format(gtSrc)) return False if gtDest in gtFrameIdToGlobalIdsWithScoresMap: dest = (gtDest[0], gtFrameIdToGlobalIdsWithScoresMap[gtDest][-1][0]) else: getLogger().warning("GT link's destination node {} has no match in the segmentation hypotheses".format(gtDest)) return False # then map them to the hypotheses graph if not hypothesesGraph.hasNode(src): getLogger().warning("Source node of GT link {} was not found in graph".format((gtSrc, gtDest))) return False if not hypothesesGraph.hasNode(dest): getLogger().warning("Destination node of GTlink {} was not found in graph".format((gtSrc, gtDest))) return False if not hypothesesGraph.hasEdge(src, dest): getLogger().warning("Nodes are present, but GT link {} was not found in graph".format((gtSrc, gtDest))) return False return True def gtIdPerFrameToUuid(frame, gtId): return traxelIdPerTimestepToUniqueIdMap[str(frame)][str(gtFrameIdToGlobalIdsWithScoresMap[(frame, gtId)][-1][0])] # add links of all tracks for track in tracks: trackId, startFrame, endFrame, parent = track if parent != 0: descendants.setdefault(parent, []).append((startFrame, trackId)) # add transitions along track for frame in range(startFrame, min(endFrame, self.timeRange[1])): if not checkLinkExists((frame, trackId), (frame + 1, trackId)): getLogger().warning("Ignoring GT link from {} to {}".format((frame, trackId), (frame + 1, trackId))) missingLinks += 1 continue link = { "src":gtIdPerFrameToUuid(frame, trackId), "dest":gtIdPerFrameToUuid(frame + 1, trackId), "value":1 } linkingResults.append(link) # construct divisions divisionResults = [] for parent, childrenFrameIds in descendants.iteritems(): if len(childrenFrameIds) != 2: getLogger().warning("Found track {} that had descendants, but not exactly two. Ignoring it".format(parent)) continue if childrenFrameIds[0][0] != childrenFrameIds[1][0]: getLogger().warning("Track {} divided, but children are not in same timeframe. Ignoring it".format(parent)) continue # all good, found a proper division. Make sure the mother-daughter-links are available in the hypotheses graph foundAllLinks = True divisionFrame = childrenFrameIds[0][0] - 1 if divisionFrame >= self.timeRange[1]: continue for i in [0, 1]: foundAllLinks = foundAllLinks and checkLinkExists((divisionFrame, parent), (childrenFrameIds[i][0], childrenFrameIds[i][1])) if foundAllLinks: divisionResults.append({"id": gtIdPerFrameToUuid(divisionFrame, parent), "value": 1}) for i in [0, 1]: if not checkLinkExists((divisionFrame, parent), (childrenFrameIds[i][0], childrenFrameIds[i][1])): getLogger().warning("Ignoring GT link from {} to {}".format((frame, trackId), (frame + 1, trackId))) continue link = { "src":gtIdPerFrameToUuid(divisionFrame, parent), "dest":gtIdPerFrameToUuid(childrenFrameIds[i][0], childrenFrameIds[i][1]), "value":1 } linkingResults.append(link) else: getLogger().warning("Division of {} ignored, could not find the links to the children, or not all participating GT nodes found a mapping".format(parent)) missingLinks += 1 getLogger().info("Ground Truth mapping could not find an equivalent for {} links, {} links projected.".format(missingLinks, len(linkingResults))) result = {} result['detectionResults'] = detectionResults result['linkingResults'] = linkingResults result['divisionResults'] = divisionResults return result
def findGroundTruthJaccardScoreAndMapping( self, hypothesesGraph, groundTruthSegmentationFilename=None, groundTruthSegmentationPath=None, groundTruthTextFilename=None, groundTruthMinJaccardScore=0.5): """ Find the overlap between all objects in the given segmentations with the groundtruth, and store that jaccard score in each traxel's features. **Returns** a solution dictionary in our JSON format, which fits to the given hypotheses graph. TODO: simplify this method! Currently there are 4 different sets of IDs to reference nodes: * the ground truth trackId * a corresponding globalId (which is unique within a frame across different segmentation hypotheses) * an objectId (which equals the labelId within one segmentation hypotheses) * a globally unique UUID as used in the JSON files The nodes in the hypotheses graph are indexed by (frame, globalId), the resulting dict must use UUIDs. """ getLogger().info("Computing Jaccard scores w.r.t. GroundTruth ...") t0 = time.time() # find exclusion constraints if self._useMultiprocessing: # use ProcessPoolExecutor, which instanciates as many processes as there CPU cores by default ExecutorType = concurrent.futures.ProcessPoolExecutor getLogger().info('Parallelizing via multiprocessing on all cores!') else: ExecutorType = DummyExecutor getLogger().info('Running on single core!') jobs = [] progressBar = ProgressBar(stop=self.timeRange[1] - self.timeRange[0]) progressBar.show(increase=0) gtFrameIdToGlobalIdsWithScoresMap = {} with ExecutorType() as executor: for frame in range(self.timeRange[0], self.timeRange[1]): jobs.append( executor.submit(computeJaccardScoresOnCloud, frame, self._labelImageFilenames, self._labelImagePaths, self._labelImageFrameIdToGlobalId, groundTruthSegmentationFilename, groundTruthSegmentationPath, groundTruthMinJaccardScore, self._pluginPaths)) for job in concurrent.futures.as_completed(jobs): progressBar.show() frame, scores, frameGtToGlobalIdMap = job.result() for objectId, individualScores in scores.iteritems(): self.TraxelsPerFrame[frame][objectId].Features[ 'JaccardScores'] = individualScores gtFrameIdToGlobalIdsWithScoresMap.update(frameGtToGlobalIdMap) t1 = time.time() getLogger().info("Finding jaccard scores took {} secs".format(t1 - t0)) # create JSON result by mapping it to the hypotheses graph traxelIdPerTimestepToUniqueIdMap, _ = hypothesesGraph.getMappingsBetweenUUIDsAndTraxels( ) detectionResults = [] for gtFrameAndId, globalIdsAndScores in gtFrameIdToGlobalIdsWithScoresMap.iteritems( ): detectionResults.append({ "id": traxelIdPerTimestepToUniqueIdMap[str(gtFrameAndId[0])][str( globalIdsAndScores[-1][0])], "value": 1 }) # read tracks from textfile with open(groundTruthTextFilename, 'r') as tracksFile: lines = tracksFile.readlines() tracks = [[int(x) for x in line.strip().split(" ")] for line in lines] # order them by track start time and process track by track tracks.sort(key=lambda x: x[1]) linkingResults = [] descendants = {} missingLinks = 0 def checkLinkExists(gtSrc, gtDest): # first check that both GT nodes have been mapped to a hypotheses if gtSrc in gtFrameIdToGlobalIdsWithScoresMap: src = (gtSrc[0], gtFrameIdToGlobalIdsWithScoresMap[gtSrc][-1][0]) else: getLogger().warning( "GT link's source node {} has no match in the segmentation hypotheses" .format(gtSrc)) return False if gtDest in gtFrameIdToGlobalIdsWithScoresMap: dest = (gtDest[0], gtFrameIdToGlobalIdsWithScoresMap[gtDest][-1][0]) else: getLogger().warning( "GT link's destination node {} has no match in the segmentation hypotheses" .format(gtDest)) return False # then map them to the hypotheses graph if not hypothesesGraph.hasNode(src): getLogger().warning( "Source node of GT link {} was not found in graph".format( (gtSrc, gtDest))) return False if not hypothesesGraph.hasNode(dest): getLogger().warning( "Destination node of GTlink {} was not found in graph". format((gtSrc, gtDest))) return False if not hypothesesGraph.hasEdge(src, dest): getLogger().warning( "Nodes are present, but GT link {} was not found in graph". format((gtSrc, gtDest))) return False return True def gtIdPerFrameToUuid(frame, gtId): return traxelIdPerTimestepToUniqueIdMap[str(frame)][str( gtFrameIdToGlobalIdsWithScoresMap[(frame, gtId)][-1][0])] # add links of all tracks for track in tracks: trackId, startFrame, endFrame, parent = track if parent != 0: descendants.setdefault(parent, []).append( (startFrame, trackId)) # add transitions along track for frame in range(startFrame, min(endFrame, self.timeRange[1])): if not checkLinkExists((frame, trackId), (frame + 1, trackId)): getLogger().warning( "Ignoring GT link from {} to {}".format( (frame, trackId), (frame + 1, trackId))) missingLinks += 1 continue link = { "src": gtIdPerFrameToUuid(frame, trackId), "dest": gtIdPerFrameToUuid(frame + 1, trackId), "value": 1 } linkingResults.append(link) # construct divisions divisionResults = [] for parent, childrenFrameIds in descendants.iteritems(): if len(childrenFrameIds) != 2: getLogger().warning( "Found track {} that had descendants, but not exactly two. Ignoring it" .format(parent)) continue if childrenFrameIds[0][0] != childrenFrameIds[1][0]: getLogger().warning( "Track {} divided, but children are not in same timeframe. Ignoring it" .format(parent)) continue # all good, found a proper division. Make sure the mother-daughter-links are available in the hypotheses graph foundAllLinks = True divisionFrame = childrenFrameIds[0][0] - 1 if divisionFrame >= self.timeRange[1]: continue for i in [0, 1]: foundAllLinks = foundAllLinks and checkLinkExists( (divisionFrame, parent), (childrenFrameIds[i][0], childrenFrameIds[i][1])) if foundAllLinks: divisionResults.append({ "id": gtIdPerFrameToUuid(divisionFrame, parent), "value": 1 }) for i in [0, 1]: if not checkLinkExists( (divisionFrame, parent), (childrenFrameIds[i][0], childrenFrameIds[i][1])): getLogger().warning( "Ignoring GT link from {} to {}".format( (frame, trackId), (frame + 1, trackId))) continue link = { "src": gtIdPerFrameToUuid(divisionFrame, parent), "dest": gtIdPerFrameToUuid(childrenFrameIds[i][0], childrenFrameIds[i][1]), "value": 1 } linkingResults.append(link) else: getLogger().warning( "Division of {} ignored, could not find the links to the children, or not all participating GT nodes found a mapping" .format(parent)) missingLinks += 1 getLogger().info( "Ground Truth mapping could not find an equivalent for {} links, {} links projected." .format(missingLinks, len(linkingResults))) result = {} result['detectionResults'] = detectionResults result['linkingResults'] = linkingResults result['divisionResults'] = divisionResults return result
def _extractAllFeatures(self, dispyNodeIps=[], turnOffFeatures=[]): """ Extract the features of all frames of all segmentation hypotheses. Feature extraction will be parallelized via multiprocessing. WARNING: distributed computation via Dispy is not supported here, so dispyNodeIps must be an empty list! """ # configure progress bar numSteps = (self.timeRange[1] - self.timeRange[0]) * len(self._labelImageFilenames) if self._divisionClassifier is not None: numSteps *= 2 t0 = time.time() if self._useMultiprocessing: # use ProcessPoolExecutor, which instanciates as many processes as there CPU cores by default ExecutorType = concurrent.futures.ProcessPoolExecutor logging.getLogger('Traxelstore').info('Parallelizing feature extraction via multiprocessing on all cores!') else: ExecutorType = DummyExecutor logging.getLogger('Traxelstore').info('Running feature extraction on single core!') featuresPerFrame = {} progressBar = ProgressBar(stop=numSteps) progressBar.show(increase=0) with ExecutorType() as executor: # 1st pass for region features, once per segmentation hypotheses for filename, path in zip(self._labelImageFilenames, self._labelImagePaths): jobs = [] for frame in range(self.timeRange[0], self.timeRange[1]): jobs.append(executor.submit(computeRegionFeaturesOnCloud, frame, self._options.rawImageFilename, self._options.rawImagePath, self._options.rawImageAxes, filename, path, turnOffFeatures, self._pluginPaths )) for job in concurrent.futures.as_completed(jobs): progressBar.show() frame, feats = job.result() self._insertFilenameAndIdToFeatures(feats, filename) if frame not in featuresPerFrame: featuresPerFrame[frame] = feats else: self._mergeFrameFeatures(featuresPerFrame[frame], feats) # 2nd pass for division features # TODO: the division feature manager should also see the child candidates in all segmentation hypotheses for filename, path in zip(self._labelImageFilenames, self._labelImagePaths): if self._divisionClassifier is not None: jobs = [] for frame in range(self.timeRange[0], self.timeRange[1] - 1): jobs.append(executor.submit(computeDivisionFeaturesOnCloud, frame, featuresPerFrame[frame], featuresPerFrame[frame + 1], self._pluginManager.getImageProvider(), filename, path, self.getNumDimensions(), self._divisionFeatureNames )) for job in concurrent.futures.as_completed(jobs): progressBar.show() frame, feats = job.result() # add division features to the dictionary for the first set, and then merge the new features in if feats.keys()[0] not in featuresPerFrame[frame]: featuresPerFrame[frame].update(feats) else: self._mergeFrameFeatures(featuresPerFrame[frame], feats) self._storeBackwardMapping(featuresPerFrame) t1 = time.time() getLogger().info("Feature computation took {} secs".format(t1 - t0)) return featuresPerFrame
def getPredictedCount(uuid): segHyps = model['segmentationHypotheses'] for i, s in enumerate(segHyps): if s['id'] == uuid: break if i == len(segHyps) and s['id'] != uuid: raise InvalidArgumentException feats = segHyps[i]['features'] feats = np.array(feats) return np.argmin(feats) objectCounts = {} print("Extracting GT mergers...") progressBar = ProgressBar(stop=num_frames) progressBar.show(0) duplicates = 0 # handle all frames by remapping their indices for frame in range(0, num_frames): # find moves, but only store in temporary list because # we don't know the number of cells in that move yet moves = get_frame_dataset(frame, "Moves", args) for src, dest in moves: if src == 0 or dest == 0: continue assert (str(src) in traxelIdPerTimestepToUniqueIdMap[str(frame - 1)]) assert (str(dest) in traxelIdPerTimestepToUniqueIdMap[str(frame)]) s = traxelIdPerTimestepToUniqueIdMap[str(frame - 1)][str(src)] t = traxelIdPerTimestepToUniqueIdMap[str(frame)][str(dest)]
def buildFromProbabilityGenerator(self, probabilityGenerator, maxNeighborDist=200, numNearestNeighbors=1, forwardBackwardCheck=True, withDivisions=True, divisionThreshold=0.1, skipLinks=1): """ Takes a python probabilityGenerator containing traxel features and finds probable links between frames. Builds a kdTree with the 'numNearestneighbors' for each frame and adds the nodes. In the same iteration, it adds a number of 'skipLinks' between the nodes separated by 'skipLinks' frames. """ assert (probabilityGenerator is not None) assert (len(probabilityGenerator.TraxelsPerFrame) > 0) assert (skipLinks > 0) def checkNodeWhileAddingLinks(frame, obj): if (frame, obj) not in self._graph: getLogger().warning("Adding node ({}, {}) when setting up links".format(frame, obj)) kdTreeFrames = [None]*(skipLinks + 1) # len(probabilityGenerator.TraxelsPerFrame.keys()) is NOT an indicator for the total number of frames, # because an empty frame does not create a key in the dictionary. E.g. for one frame in the middle of the # dataset, we won't access the last one. # Idea: take the max key in the dict. Remember, frame numbering starts with 0. frameMax = max(probabilityGenerator.TraxelsPerFrame.keys()) frameMin = min(probabilityGenerator.TraxelsPerFrame.keys()) numFrames = frameMax - frameMin + 1 progressBar = ProgressBar(stop=numFrames*skipLinks) progressBar.show(0) for frame in range(numFrames): if frame > 0: del kdTreeFrames[0] # this is the current frame if frame + skipLinks < numFrames and frameMin + frame + skipLinks in probabilityGenerator.TraxelsPerFrame.keys(): kdTreeFrames.append(self._buildFrameKdTree(probabilityGenerator.TraxelsPerFrame[frameMin + frame + skipLinks])) self._addNodesForFrame(frameMin + frame + skipLinks, probabilityGenerator.TraxelsPerFrame[frameMin + frame + skipLinks]) else: for i in range(0, skipLinks+1): if frameMin + frame + i in probabilityGenerator.TraxelsPerFrame.keys(): # empty frame kdTreeFrames[i] = self._buildFrameKdTree(probabilityGenerator.TraxelsPerFrame[frameMin + frame + i]) self._addNodesForFrame(frameMin + frame + i, probabilityGenerator.TraxelsPerFrame[frameMin + frame + i]) # find forward links if frameMin + frame in probabilityGenerator.TraxelsPerFrame.keys(): # 'frame' could be empty for obj, traxel in probabilityGenerator.TraxelsPerFrame[frameMin + frame].iteritems(): divisionPreservingNumNearestNeighbors = numNearestNeighbors if divisionPreservingNumNearestNeighbors < 2 \ and withDivisions \ and self._traxelMightDivide(traxel, divisionThreshold): divisionPreservingNumNearestNeighbors = 2 for i in range(1, skipLinks+1): if frame + i < numFrames and frameMin + frame + i in probabilityGenerator.TraxelsPerFrame.keys(): neighbors = (self._findNearestNeighbors(kdTreeFrames[i], traxel, divisionPreservingNumNearestNeighbors, maxNeighborDist)) # type(neighbors) is list for n in neighbors: checkNodeWhileAddingLinks(frameMin + frame, obj) checkNodeWhileAddingLinks(frameMin + frame + i, n) self._graph.add_edge((frameMin + frame, obj), (frameMin + frame + i, n)) self._graph.edge[frameMin + frame, obj][frameMin + frame + i, n]['src'] = self._graph.node[(frameMin + frame, obj)]['id'] self._graph.edge[frameMin + frame, obj][frameMin + frame + i, n]['dest'] = self._graph.node[(frameMin + frame + i, n)]['id'] # find backward links if forwardBackwardCheck: for i in range(1, skipLinks+1): if frame + i < numFrames: if frameMin + frame + i in probabilityGenerator.TraxelsPerFrame.keys(): # empty frame for obj, traxel in probabilityGenerator.TraxelsPerFrame[frameMin + frame + i].iteritems(): if kdTreeFrames[0] is not None: neighbors = (self._findNearestNeighbors(kdTreeFrames[0], traxel, numNearestNeighbors, maxNeighborDist)) for n in neighbors: checkNodeWhileAddingLinks(frameMin + frame, n) checkNodeWhileAddingLinks(frameMin + frame + i, obj) self._graph.add_edge((frameMin + frame, n), (frameMin + frame + i, obj)) self._graph.edge[frameMin + frame, n][frameMin + frame + i, obj]['src'] = self._graph.node[(frameMin + frame, n)]['id'] self._graph.edge[frameMin + frame, n][frameMin + frame + i, obj]['dest'] = self._graph.node[(frameMin + frame + i, obj)]['id'] progressBar.show() progressBar.show()
def insertEnergies(self, maxNumObjects, detectionProbabilityFunc, transitionProbabilityFunc, boundaryCostMultiplierFunc, divisionProbabilityFunc, skipLinksBias): ''' Insert energies for detections, divisions and links into the hypotheses graph, by transforming the probabilities for certain events (given by the `*ProbabilityFunc`-functions per traxel) into energies. If the given graph contained tracklets (`self.withTracklets is True`), then also the probabilities over all contained traxels will be accumulated for those nodes in the graph. The energies are stored in the networkx graph under the following attribute names (to match the format for solvers): * detection energies: `self._graph.node[n]['features']` * division energies: `self._graph.node[n]['divisionFeatures']` * appearance energies: `self._graph.node[n]['appearanceFeatures']` * disappearance energies: `self._graph.node[n]['disappearanceFeatures']` * transition energies: `self._graph.edge[src][dest]['features']` * additionally we also store the timestep (range for traxels) per node as `timestep` attribute ** Parameters: ** * `maxNumObjects`: the max number of objects per detections * `detectionProbabilityFunc`: should take a traxel and return its detection probabilities ([prob0objects, prob1object,...]) * `transitionProbabilityFunc`: should take two traxels and return this link's probabilities ([prob0objectsInTransition, prob1objectsInTransition,...]) * `boundaryCostMultiplierFunc`: should take a traxel and a boolean that is true if we are seeking for an appearance cost multiplier, false for disappearance, and return a scalar multiplier between 0 and 1 for the appearance/disappearance cost that depends on the traxel's distance to the spacial and time boundary * `divisionProbabilityFunc`: should take a traxel and return its division probabilities ([probNoDiv, probDiv]) ''' numElements = self._graph.number_of_nodes() + self._graph.number_of_edges() progressBar = ProgressBar(stop=numElements) # insert detection probabilities for all detections (and some also get a div probability) for n in self._graph.nodes_iter(): if not self.withTracklets: # only one traxel, but make it a list so everything below works the same traxels = [self._graph.node[n]['traxel']] else: traxels = self._graph.node[n]['tracklet'] # accumulate features over all contained traxels previousTraxel = None detectionFeatures = np.zeros(maxNumObjects + 1) for t in traxels: detectionFeatures += np.array(negLog(detectionProbabilityFunc(t))) if previousTraxel is not None: detectionFeatures += np.array(negLog(transitionProbabilityFunc(previousTraxel, t))) previousTraxel = t detectionFeatures = listify(list(detectionFeatures)) # division only if probability is big enough divisionFeatures = divisionProbabilityFunc(traxels[-1]) if divisionFeatures is not None: divisionFeatures = listify(negLog(divisionFeatures)) # appearance/disappearance appearanceFeatures = listify([0.0] + [boundaryCostMultiplierFunc(traxels[0], True)] * maxNumObjects) disappearanceFeatures = listify([0.0] + [boundaryCostMultiplierFunc(traxels[-1], False)] * maxNumObjects) self._graph.node[n]['features'] = detectionFeatures if divisionFeatures is not None: self._graph.node[n]['divisionFeatures'] = divisionFeatures self._graph.node[n]['appearanceFeatures'] = appearanceFeatures self._graph.node[n]['disappearanceFeatures'] = disappearanceFeatures self._graph.node[n]['timestep'] = [traxels[0].Timestep, traxels[-1].Timestep] progressBar.show() # insert transition probabilities for all links for a in self._graph.edges_iter(): if not self.withTracklets: srcTraxel = self._graph.node[self.source(a)]['traxel'] destTraxel = self._graph.node[self.target(a)]['traxel'] else: srcTraxel = self._graph.node[self.source(a)]['tracklet'][-1] # src is last of the traxels in source tracklet destTraxel = self._graph.node[self.target(a)]['tracklet'][0] # dest is first of traxels in destination tracklet features = listify(negLog(transitionProbabilityFunc(srcTraxel, destTraxel))) # add feature for additional Frames. Since we do not want these edges to be primarily taken, we add a bias to the edge. Now: hard coded, future: parameter frame_gap = destTraxel.Timestep - srcTraxel.Timestep # 1. method if frame_gap > 1: features[1][0] = features[1][0] + skipLinksBias*frame_gap # # 2. method # # introduce a new energies like: [[6], [15]] -> [[6, 23], [15, 23]] for first links and # # [[6], [15]] -> [[23, 6], [23, 15]] for second links, and so on for 3rd order links # # !!! this will introduce a new weight in the weight.json file. For the 2nd link, comes in 2nd row and so on. # # drawback: did not manage to adjust parameter to get sensible results. # for feat in features: # for i in range(frame_gap): # feat.append(23) # if frame_gap > 1: # feat[frame_gap-1], feat[0] = feat[0], feat[frame_gap-1] self._graph.edge[a[0]][a[1]]['src'] = self._graph.node[a[0]]['id'] self._graph.edge[a[0]][a[1]]['dest'] = self._graph.node[a[1]]['id'] self._graph.edge[a[0]][a[1]]['features'] = features progressBar.show()
def create_and_link_tracks_and_divisions(track_features_h5, ts, region_features): # storage for all tracks and divisions tracks = {} divisions = {} # mapping of traxelID at front and back of track to track_id track_starts_with_traxel_id = {} track_ends_with_traxel_id = {} pb = ProgressBar( 0, len(track_features_h5['tracks'].keys()) + len(track_features_h5['divisions'].keys())) print("Extracting Tracks and Divisions") for track_id in track_features_h5['tracks'].keys(): pb.show() track_id_int = int(track_id) t = Track(track_id_int) t.extract(track_features_h5) t.extract_region_features(ts, region_features) # store in container tracks[track_id_int] = t # create mappings track_starts_with_traxel_id[t.start_traxel_id] = track_id_int track_ends_with_traxel_id[t.end_traxel_id] = track_id_int max_length = max([t.length for t in tracks.values()]) for t in tracks.values(): t.features['track_length'] = float(t.length) / max_length for division_id in track_features_h5['divisions'].keys(): pb.show() division_id_int = int(division_id) d = Division(division_id_int) d.extract(track_features_h5) # find the tracks that are connected by this division # and update information in the tracks try: d.parent_track_id = track_ends_with_traxel_id[d.parent_traxel_id] tracks[d.parent_track_id].end_division_id = division_id_int except KeyError as e: print( "Could not find parent track of division {}: ".format( division_id), e.message) for i in [0, 1]: try: d.children_track_ids[i] = track_starts_with_traxel_id[ d.children_traxel_ids[i]] tracks[d.children_track_ids[ i]].start_division_id = division_id_int except KeyError as e: print( "Could not find child track of division {}: ".format( division_id), e.message) # store in container divisions[division_id_int] = d return tracks, divisions