1058 def measureDipoles(self, measRecord, exposure, posExp=None, negExp=None):
1059 """Perform the non-linear least squares minimization on the putative dipole source.
1060
1061 Parameters
1062 ----------
1063 measRecord : `lsst.afw.table.SourceRecord`
1064 diaSources that will be measured using dipole measurement
1065 exposure : `lsst.afw.image.Exposure`
1066 Difference exposure on which the diaSources were detected; `exposure = posExp-negExp`
1067 If both `posExp` and `negExp` are `None`, will attempt to fit the
1068 dipole to just the `exposure` with no constraint.
1069 posExp : `lsst.afw.image.Exposure`, optional
1070 "Positive" exposure, typically a science exposure, or None if unavailable
1071 When `posExp` is `None`, will compute `posImage = exposure + negExp`.
1072 negExp : `lsst.afw.image.Exposure`, optional
1073 "Negative" exposure, typically a template exposure, or None if unavailable
1074 When `negExp` is `None`, will compute `negImage = posExp - exposure`.
1075
1076 Notes
1077 -----
1078 The main functionality of this routine was placed outside of
1079 this plugin (into `DipoleFitAlgorithm.fitDipole()`) so that
1080 `DipoleFitAlgorithm.fitDipole()` can be called separately for
1081 testing (@see `tests/testDipoleFitter.py`)
1082 """
1083 result = None
1084 pks = measRecord.getFootprint().getPeaks()
1085
1086
1087 if (
1088
1089 ((nPeaks := len(pks)) <= 1)
1090
1091
1092 or (nPeaks > 1 and (np.sign(pks[0].getPeakValue())
1093 == np.sign(pks[-1].getPeakValue())))
1094 ):
1095 if not self.config.fitAllDiaSources:
1096
1097
1098 measRecord[self.centroidKey.getX()] = measRecord.getX()
1099 measRecord[self.centroidKey.getY()] = measRecord.getY()
1100 self.centroidKey.getCentroidErr().
set(measRecord, measRecord.getCentroidErr())
1101 measRecord[self.flagKey] = measRecord.getCentroidFlag()
1102 return
1103
1104
1105 if ((area := measRecord.getFootprint().getArea()) > self.config.maxFootprintArea):
1106 self.fail(measRecord, measBase.MeasurementError(f"{area} > {self.config.maxFootprintArea}",
1107 self.FAILURE_TOO_LARGE))
1108
1109 try:
1110 alg = self.DipoleFitAlgorithmClass(exposure, posImage=posExp, negImage=negExp)
1111 result, _ = alg.fitDipole(
1112 measRecord, rel_weight=self.config.relWeight,
1113 tol=self.config.tolerance,
1114 maxSepInSigma=self.config.maxSeparation,
1115 fitBackground=self.config.fitBackground,
1116 separateNegParams=self.config.fitSeparateNegParams,
1117 verbose=False, display=False)
1119 self.fail(measRecord, measBase.MeasurementError('edge failure', self.FAILURE_EDGE))
1120 except Exception as e:
1121 errorMessage = f"Exception in dipole fit. {e.__class__.__name__}: {e}"
1122 self.fail(measRecord, measBase.MeasurementError(errorMessage, self.FAILURE_FIT))
1123
1124 self.log.debug("Dipole fit result: %d %s", measRecord.getId(), str(result))
1125
1126 if result is None:
1127 self.fail(measRecord, measBase.MeasurementError("bad dipole fit", self.FAILURE_FIT))
1128 return
1129
1130
1131
1132 measRecord[self.posFluxKey.getInstFlux()] = result.posFlux
1133 measRecord[self.posFluxKey.getInstFluxErr()] = result.signalToNoise
1134 measRecord[self.posCentroidKey.getX()] = result.posCentroidX
1135 measRecord[self.posCentroidKey.getY()] = result.posCentroidY
1136
1137 measRecord[self.negFluxKey.getInstFlux()] = result.negFlux
1138 measRecord[self.negFluxKey.getInstFluxErr()] = result.signalToNoise
1139 measRecord[self.negCentroidKey.getX()] = result.negCentroidX
1140 measRecord[self.negCentroidKey.getY()] = result.negCentroidY
1141
1142
1143 measRecord[self.fluxKey.getInstFlux()] = (abs(result.posFlux) + abs(result.negFlux))/2.
1144 measRecord[self.orientationKey] = result.orientation
1145 measRecord[self.separationKey] = np.sqrt((result.posCentroidX - result.negCentroidX)**2.
1146 + (result.posCentroidY - result.negCentroidY)**2.)
1147 measRecord[self.centroidKey.getX()] = result.centroidX
1148 measRecord[self.centroidKey.getY()] = result.centroidY
1149
1150 measRecord[self.signalToNoiseKey] = result.signalToNoise
1151 measRecord[self.chi2dofKey] = result.redChi2
1152
1153 if result.nData >= 1:
1154 measRecord[self.nDataKey] = result.nData
1155 else:
1156 measRecord[self.nDataKey] = 0
1157
1158 self.doClassify(measRecord, result.chi2)
1159
Reports attempts to exceed implementation-defined length limits for some classes.