def __call__(self, img, prev_detected, **params): if "tiles" in params: params["tiles_v"] = params["tiles"] params["tiles_h"] = params["tiles"] elif not ("tiles_v" in params and "tiles_h" in params): logger.error( ( "Invalid tiling information given to " "TiledFCDProcessor. Provide either 'tiles' or both 'tiles_h' " "and 'tiles_v'." ) ) return [] # Copy given parameters and check for completeness self.p.update(params) if not self.all_params_set(params): logger.critical("First FCD parameter set must be complete") return [] if self.img is None: self.img = np.copy(img) if self.neighbourhood_max_img is None and self.p["mincenterlevel"] is not None: self.neighbourhood_max_img = self.generic_filter(self.img, np.max, 3) if self.prev_detected is None: self.prev_detected = list(prev_detected) # copy # Process firstnewcircle = len(self.allcircles) logger.debug("Preprocessing image...") # try: # import pickle # gradcircles = pickle.load(open("testdata/circles.pickle_5tiles", 'r')) # except IOError: if True: img_preproc = self.preprocess(self.img, self.p["gaussian"], self.p["sobel"]) logger.debug("Computing gradient...") grads = self.tiled_gradients( img_preproc, prev_detected, self.p["tiles_v"], self.p["tiles_h"], self.p["maxr"] + 3 ) gradcircles = [] for i, grad in enumerate(grads): logger.debug("Tile {} / {}".format(i + 1, len(grads))) logger.debug("Finding circle candidates...") candidates = self.findcandidates( grad, self.p["alpha"], self.p["beta"], self.p["gamma"], self.p["minnorm"], self.p["maxr"], self.p["mincenterlevel"], ) logger.debug("Number of candidates: {}".format(len(candidates))) logger.debug("Clustering...") circles = self.cluster( candidates, self.p["minr"], self.p["maxr"], self.p["radiusscaler"], self.p["minmembers"], self.p["epsilon"], self.p["minsamples"], self.p["maxangspread"], ) logger.debug("Number of detected circles: {}".format(len(circles))) # Attach index of this tile to circles circles = np.hstack((circles, i * np.ones((circles.shape[0], 1)))) gradcircles.append(circles) # import pickle # pickle.dump(gradcircles, open("testdata/circles.pickle", 'w'), -1) logger.debug("Cleaning circle list...") self.totmergers = 0 self.totoverl = 0 for i in range(self.p["tiles_v"] - 1): for j in range(self.p["tiles_h"] - 1): logger.debug( "Circle group {} / {}".format( i * (self.p["tiles_h"] - 1) + j + 1, (self.p["tiles_v"] - 1) * (self.p["tiles_h"] - 1) ) ) tileindices = ( i * self.p["tiles_h"] + j, i * self.p["tiles_h"] + j + 1, (i + 1) * self.p["tiles_h"] + j, (i + 1) * self.p["tiles_h"] + j + 1, ) # Put circles of four-tile-groups into same list and clean groupcircles = np.concatenate(tuple(gradcircles[ti] for ti in tileindices)) groupcircles = self.cleancirclelist(groupcircles) # Split returned list back into corresponding gradcircles for x in tileindices: gradcircles[x] = groupcircles[groupcircles[:, -1] == x] logger.debug("Total mergers: {}".format(self.totmergers)) logger.debug("Total overlaps: {}".format(self.totoverl)) newcircles = np.concatenate(gradcircles) logger.debug("Number of new circles: {}".format(len(newcircles))) self.allcircles = np.concatenate((self.allcircles, newcircles)) return [DiskInfo(c[0], c[1], c[2]) for c in self.allcircles[firstnewcircle:]]
def __call__(self, img, prev_detected, **params): """Get list of circles in image using FCD. Args: img: Image to process (as nparray) gaussian: standard deviation for Gaussian kernel sobel: boolean value specifying whether to apply sobel filter minnorm: Gradient norm cutoff alpha: FCD gradient angle difference tolerance beta: FCD connecting line angle difference tolerance gamma: FCD relative gradient norm difference tolerance minr: Lower radius cutoff maxr: Upper radius cutoff radiusscaler: DBSCAN preprocessing radius coefficient minmembers: Minimum number of cluster members PER UNIT RADIUS for a cluster to be considered a detected circle epsilon: DBSCAN neighbourhood size minsamples: DBSCAN minimum cluster density in neighbourhood maxangspread: TODO DOCUMENT THIS Returns: A list of DiskInfos """ # Copy given parameters and check for completeness self.p.update(params) if not self.all_params_set(params): logger.critical("First FCD parameter set must be complete") return [] # Figure out if we can use some previous calculations startat = self.where_to_start(params) logger.debug("Starting at {}".format(startat)) if self.img is None: self.img = np.copy(img) if self.neighbourhood_max_img is None and self.p["mincenterlevel"] is not None: self.neighbourhood_max_img = self.generic_filter(self.img, np.max, 3) if self.prev_detected is None: self.prev_detected = list(prev_detected) # copy # Process firstnewcircle = len(self.allcircles) if startat <= self.PREPROCESS: logger.debug("Preprocessing image...") self.img_preproc = self.preprocess(self.img, self.p["gaussian"], self.p["sobel"]) if startat <= self.GRADIENT: logger.debug("Computing gradient...") self.grad = self.gradient(self.img_preproc, prev_detected) if startat <= self.FINDCANDIDATES: logger.debug("Finding circle candidates...") self.candidates = self.findcandidates( self.grad, self.p["alpha"], self.p["beta"], self.p["gamma"], self.p["minnorm"], self.p["maxr"], self.p["mincenterlevel"], ) logger.debug("Number of candidates: {}".format(len(self.candidates))) if startat <= self.CLUSTER: logger.debug("Clustering...") circles = self.cluster( self.candidates, self.p["minr"], self.p["maxr"], self.p["radiusscaler"], self.p["minmembers"], self.p["epsilon"], self.p["minsamples"], self.p["maxangspread"], ) logger.debug("Number of detected circles: {}".format(len(circles))) # Shift circles by current offset and append index of this # parameter set to all circles if len(circles) > 0: newcircles = np.hstack((circles, self.paramindex * np.ones((circles.shape[0], 1)))) self.allcircles = np.append(self.allcircles, newcircles, axis=0) # OLD: # All detected circles, EXCEPT THE ONES JUST DETECTED IN THIS # RUN, should be removed for future calls to findcandidates # NOW: # All detected circles are removed from the gradient map for # future calls to findcandidates # return allcircles logger.debug("Cleaning circle list...") self.allcircles = self.cleancirclelist(self.allcircles) logger.debug("Number of new circles: {}".format(len(self.allcircles) - firstnewcircle)) self.paramindex += 1 return [DiskInfo(c[0], c[1], c[2]) for c in self.allcircles[firstnewcircle:]]
def loadconfig(self, configfile): logger.debug("Loading configuration from '{}'".format(configfile)) # Lazy load yaml so we don't require it if configfile.endswith('.yml') or configfile.endswith('.yaml'): import yaml with open(configfile, 'r') as f: cfg = yaml.load(f) else: # FIXME: Assume JSON? logger.critical("Unknown config file type") raise NotImplementedError # Set default modules if none given: if not 'imgloaders' in cfg: cfg['imgloaders'] = [ 'fire.imageloaders.MahotasImageLoader', ] if not 'preprocessors' in cfg: cfg['preprocessors'] = { 'crop': 'fire.preprocessors.CropPreprocessor', 'makefloat': 'fire.preprocessors.MakeFloatPreprocessor', 'greyscale': 'fire.preprocessors.GreyscalePreprocessor', 'removebg': 'fire.preprocessors.RemoveBackgroundPreprocessor', } if not 'processors' in cfg: cfg['processors'] = { 'threshold': 'fire.processors.ThresholdProcessor', 'watershed': 'fire.processors.WatershedProcessor', 'fcd': 'fire.processors.FCDProcessor', 'tiledfcd': 'fire.processors.TiledFCDProcessor', 'diskextend': 'fire.processors.DiskExtendProcessor', 'removeouter': 'fire.processors.RemoveOuterProcessor', 'dirtyremoveedges': 'fire.processors.DirtyRemoveEdgesProcessor', } if not 'exporters' in cfg: cfg['exporters'] = { 'string': 'fire.exporters.StringExporter', 'save': 'fire.exporters.SaveExporter', } # Load and set config for what in ('imgloaders', ): instances = [] for modclass in cfg[what]: modulename, classname = modclass.rsplit('.', 1) mod = import_module(modulename) instances.append(getattr(mod, classname)(manager = self)) setattr(self, what, instances) for what in ('preprocessors', 'processors', 'exporters'): instances = {} for modname, modclass in cfg[what].items(): modulename, objectname = modclass.rsplit('.', 1) mod = import_module(modulename) obj = getattr(mod, objectname) if isinstance(obj, type): # obj is a class and should be initialised instances[modname] = obj(manager = self) else: # Assume that obj is a function instances[modname] = obj setattr(self, what, instances) for what in ('preprocsteps', 'procsteps', 'exportsteps'): steplist = [] for rawstep in cfg[what]: if isinstance(rawstep, dict): stepname = rawstep.keys()[0] steplist.append( (stepname, {} if rawstep[stepname] is None else rawstep[stepname]) ) else: steplist.append( (rawstep, {}) ) setattr(self, what, steplist)