458 """Find streaks in a masked image.
462 maskedImage : `lsst.afw.image.maskedImage`
463 The image in which to search for streaks.
467 result : `lsst.pipe.base.Struct`
468 Results as a struct with attributes:
471 Lines identified by kernel hough transform.
473 Lines grouped into clusters in rho-theta space.
475 Final result for lines after line-profile fit.
477 2-d boolean mask where detected lines are True.
479 mask = maskedImage.mask
480 detectionMask = (mask.array & mask.getPlaneBitMask(self.config.detectedMaskPlane))
485 if len(self.
lines) == 0:
486 lineMask = np.zeros(detectionMask.shape, dtype=bool)
491 fitLines, lineMask = self.
_fitProfile(clusters, maskedImage)
494 outputMask = lineMask & detectionMask.astype(bool)
496 return pipeBase.Struct(
498 lineClusters=clusters,
499 originalLines=self.
lines,
504 def run(self, maskedImage):
505 """Find and mask streaks in a masked image.
507 Finds streaks in the image and modifies maskedImage in place by adding a
508 mask plane with any identified streaks.
512 maskedImage : `lsst.afw.image.Exposure` or `lsst.afw.image.maskedImage`
513 The image in which to search for streaks. The mask detection plane
514 corresponding to `config.detectedMaskPlane` must be set with the
515 detected pixels. The mask will have a plane added with any detected
516 streaks, and with the mask plane name set by
517 self.config.streaksMaskPlane.
521 result : `lsst.pipe.base.Struct`
522 Results as a struct with attributes:
525 Lines identified by kernel hough transform.
527 Lines grouped into clusters in rho-theta space.
529 Final result for lines after line-profile fit.
531 streaks = self.
find(maskedImage)
533 maskedImage.mask.addMaskPlane(self.config.streaksMaskPlane)
534 maskedImage.mask.array[streaks.mask] |= maskedImage.mask.getPlaneBitMask(self.config.streaksMaskPlane)
536 return pipeBase.Struct(
538 lineClusters=streaks.lineClusters,
539 originalLines=streaks.originalLines,
561 """Run Kernel Hough Transform on image.
566 2-d image data on which to detect lines.
570 result : `LineCollection`
571 Collection of detected lines, with their detected rho and theta
574 lines = lsst.kht.find_lines(image, self.config.clusterMinimumSize,
575 self.config.clusterMinimumDeviation, self.config.delta,
576 self.config.minimumKernelHeight, self.config.nSigma,
577 self.config.absMinimumKernelHeight)
578 self.log.info(
"The Kernel Hough Transform detected %s line(s)", len(lines))
583 """Group lines that are close in parameter space and likely describe
588 lines : `LineCollection`
589 Collection of lines to group into clusters.
593 result : `LineCollection`
594 Average `Line` for each cluster of `Line`s in the input
601 x = lines.rhos / self.config.rhoBinSize
602 y = lines.thetas / self.config.thetaBinSize
603 X = np.array([x, y]).T
612 kmeans = KMeans(n_clusters=nClusters, n_init=
'auto').fit(X)
613 clusterStandardDeviations = np.zeros((nClusters, 2))
614 for c
in range(nClusters):
615 inCluster = X[kmeans.labels_ == c]
616 clusterStandardDeviations[c] = np.std(inCluster, axis=0)
618 if (clusterStandardDeviations <= 1).all():
623 finalClusters = kmeans.cluster_centers_.T
626 finalRhos = finalClusters[0] * self.config.rhoBinSize
627 finalThetas = finalClusters[1] * self.config.thetaBinSize
629 self.log.info(
"Lines were grouped into %s potential streak(s)", len(finalRhos))
634 """Fit the profile of the streak.
636 Given the initial parameters of detected lines, fit a model for the
637 streak to the original (non-binary image). The assumed model is a
638 straight line with a Moffat profile.
642 lines : `LineCollection`
643 Collection of guesses for `Line`s detected in the image.
644 maskedImage : `lsst.afw.image.maskedImage`
645 Original image to be used to fit profile of streak.
649 lineFits : `LineCollection`
650 Collection of `Line` profiles fit to the data.
651 finalMask : `np.ndarray`
652 2d mask array with detected streaks=1.
654 data = maskedImage.image.array
655 weights = maskedImage.variance.array**-1
657 weights[~np.isfinite(weights) | ~np.isfinite(data)] = 0
660 finalLineMasks = [np.zeros(data.shape, dtype=bool)]
664 line.sigma = self.config.invSigma**-1
667 if lineModel.lineMaskSize == 0:
670 fit, chi2, fitFailure = lineModel.fit(dChi2Tol=self.config.dChi2Tolerance, log=self.log)
674 if ((abs(fit.rho - line.rho) > 2 * self.config.rhoBinSize)
675 or (abs(fit.theta - line.theta) > 2 * self.config.thetaBinSize)):
677 self.log.debug(
"Streak fit moved too far from initial estimate. Line will be dropped.")
684 lineModel.setLineMask(fit)
685 finalModel = lineModel.makeProfile(fit)
687 finalModelMax = abs(finalModel).max()
688 finalLineMask = abs(finalModel) > self.config.footprintThreshold
690 if not finalLineMask.any():
693 fit.finalModelMax = finalModelMax
695 finalLineMasks.append(finalLineMask)
699 self.log.info(
"Streak profile could not be fit for %d out of %d detected lines.", nFitFailures,
701 finalMask = np.array(finalLineMasks).any(axis=0)
703 return lineFits, finalMask