def find_bright_columns(imarr, threshold): """Find bright columns in an image array. Parameters ---------- imarr : `numpy.ndarrawy`, (Nx, Ny) An array representing an image to analyze. threshold : `float` Pixel value threshold defining a bright column. Returns ------- bright_cols : `list` List of column indices corresponding to bright columns. """ image = afwImage.ImageF(imarr) fp_set = FootprintSet(image, Threshold(threshold)) columns = dict([(x, []) for x in range(0, image.getWidth())]) for footprint in fp_set.getFootprints(): for span in footprint.getSpans(): y = span.getY() for x in range(span.getX0(), span.getX1() + 1): columns[x].append(y) bright_cols = [] x0 = image.getX0() y0 = image.getY0() for x in columns: if bad_column(columns[x], 20): bright_cols.append(x - x0) # # Sort the output. # bright_cols.sort() return bright_cols
def calculateThreshold(self, exposure, seed, sigma=None): """Calculate new threshold This is the main functional addition to the vanilla `SourceDetectionTask`. We identify sky objects and perform forced PSF photometry on them. Using those PSF flux measurements and estimated errors, we set the threshold so that the stdev of the measurements matches the median estimated error. Parameters ---------- exposure : `lsst.afw.image.Exposure` Exposure on which we're detecting sources. seed : `int` RNG seed to use for finding sky objects. sigma : `float`, optional Gaussian sigma of smoothing kernel; if not provided, will be deduced from the exposure's PSF. Returns ------- result : `lsst.pipe.base.Struct` Result struct with components: - ``multiplicative``: multiplicative factor to be applied to the configured detection threshold (`float`). - ``additive``: additive factor to be applied to the background level (`float`). """ # Make a catalog of sky objects fp = self.skyObjects.run(exposure.maskedImage.mask, seed) skyFootprints = FootprintSet(exposure.getBBox()) skyFootprints.setFootprints(fp) table = SourceTable.make(self.skyMeasurement.schema) catalog = SourceCatalog(table) catalog.reserve(len(skyFootprints.getFootprints())) skyFootprints.makeSources(catalog) key = catalog.getCentroidKey() for source in catalog: peaks = source.getFootprint().getPeaks() assert len(peaks) == 1 source.set(key, peaks[0].getF()) source.updateCoord(exposure.getWcs()) # Forced photometry on sky objects self.skyMeasurement.run(catalog, exposure, catalog, exposure.getWcs()) # Calculate new threshold fluxes = catalog["base_PsfFlux_instFlux"] area = catalog["base_PsfFlux_area"] bg = catalog["base_LocalBackground_instFlux"] good = (~catalog["base_PsfFlux_flag"] & ~catalog["base_LocalBackground_flag"] & np.isfinite(fluxes) & np.isfinite(area) & np.isfinite(bg)) if good.sum() < self.config.minNumSources: self.log.warn("Insufficient good flux measurements (%d < %d) for dynamic threshold calculation", good.sum(), self.config.minNumSources) return Struct(multiplicative=1.0, additive=0.0) bgMedian = np.median((fluxes/area)[good]) lq, uq = np.percentile((fluxes - bg*area)[good], [25.0, 75.0]) stdevMeas = 0.741*(uq - lq) medianError = np.median(catalog["base_PsfFlux_instFluxErr"][good]) return Struct(multiplicative=medianError/stdevMeas, additive=bgMedian)