77 """Calculate time of midpoint of travel for this profile.
79 Derived from Shuang Liang's CTN-002 (https://ctn-002.lsst.io).
80 Equation numbers listed are from this document. As the fits
81 have already been done, we can ignore the raw position/Hall
87 Fit model to use to calculate the midpoint.
89 If true, only the acceleration based calculation will be
90 done. If false, both the acceleration and position based
91 calculations are done.
96 The time of the midpoint from the start of motion in
97 seconds, as derived from the point where the acceleration
98 on the shutter is zero.
100 The time of the midpoint from the start of motion in
101 seconds, as derived from the point where the shutter
102 position is midway between its starting and ending
103 locations. This will be NaN if ``skipPosition`` is true.
108 Raised if the requested ``modelName`` is not found in the
112 for idx, name
in enumerate(self.
fit_name):
113 if name == modelName:
116 raise RuntimeError(f
"Unknown model {modelName} requested.")
139 tm_accel = newton(acc, 0.5*(t2 + t1))
140 except Exception
as e:
141 self.
log.warn(f
"Midpoint calculation (from acceleration) failed to converge: {e}")
149 V1 = t1**2 * (j0 - j1)/2. - t1*A1
150 S1 = t1**3 * (j0 - j1)/6. - t1**2 * A1/2. - t1*V1
154 return j1*(t**3)/6. + A1*(t**2)/2. + V1*t + S1 - Smid
157 tm_position = newton(pos, tm_accel)
158 except Exception
as e:
159 self.
log.warn(f
"Midpoint calculation (from position) failed to converge: {e}")
163 return tm_accel + t0, tm_position + t0
167 """Construct a ShutterMotionProfile from an exposure.
171 exposure : `lsst.afw.image.Exposuref`
172 Exposure to read header information from.
173 direction : `str`, optional
174 Direction of shutter to construcxt. Should be one of
179 calib : `lsst.ip.isr.ShutterMotionProfile`
183 if direction
not in (
'open',
'close'):
184 raise ValueError(f
"Unknown shutter direction {direction} suppled.")
186 keywords = [f
'SHUTTER {direction.upper()} STARTTIME TAI ISOT',
187 f
'SHUTTER {direction.upper()} STARTTIME TAI MJD',
188 f
'SHUTTER {direction.upper()} SIDE',
189 f
'SHUTTER {direction.upper()} MODEL',
190 f
'SHUTTER {direction.upper()} HALLSENSORFIT MODELSTARTTIME',
191 f
'SHUTTER {direction.upper()} HALLSENSORFIT PIVOTPOINT1',
192 f
'SHUTTER {direction.upper()} HALLSENSORFIT PIVOTPOINT2',
193 f
'SHUTTER {direction.upper()} HALLSENSORFIT JERK0',
194 f
'SHUTTER {direction.upper()} HALLSENSORFIT JERK1',
195 f
'SHUTTER {direction.upper()} HALLSENSORFIT JERK2']
198 if kw
not in exposure.metadata:
199 raise RuntimeError(f
"Expected header keyword not found: {kw}.")
202 calib.time_tai.append(exposure.metadata[f
'SHUTTER {direction.upper()} STARTTIME TAI ISOT'])
203 calib.time_mjd.append(exposure.metadata[f
'SHUTTER {direction.upper()} STARTTIME TAI MJD'])
204 calib.metadata[
'SIDE'] = exposure.metadata[f
'SHUTTER {direction.upper()} SIDE']
206 calib.fit_name.append(
"hallSensorFit")
208 calib.fit_start_time.append(
209 exposure.metadata[f
'SHUTTER {direction.upper()} HALLSENSORFIT MODELSTARTTIME']
211 calib.fit_pivot1.append(exposure.metadata[f
'SHUTTER {direction.upper()} HALLSENSORFIT PIVOTPOINT1'])
212 calib.fit_pivot2.append(exposure.metadata[f
'SHUTTER {direction.upper()} HALLSENSORFIT PIVOTPOINT2'])
213 calib.fit_jerk0.append(exposure.metadata[f
'SHUTTER {direction.upper()} HALLSENSORFIT JERK0'])
214 calib.fit_jerk1.append(exposure.metadata[f
'SHUTTER {direction.upper()} HALLSENSORFIT JERK1'])
215 calib.fit_jerk2.append(exposure.metadata[f
'SHUTTER {direction.upper()} HALLSENSORFIT JERK2'])
221 """Construct a ShutterMotionProfile from a dictionary of properties.
226 Dictionary of properties.
230 calib : `lsst.ip.isr.ShutterMotionProfile
231 Constructed calibration.
236 Raised if the supplied dictionary is for a different
241 if calib._OBSTYPE != dictionary[
"fileType"]:
242 raise RuntimeError(f
"Incorrect calibration supplied. Expected {calib._OBSTYPE}, "
243 f
"found {dictionary['OBSTYPE']}")
244 motionProfile = dictionary.pop(
"motionProfile")
246 encodeSamples = motionProfile.pop(
"encodeSamples")
247 hallTransitions = motionProfile.pop(
"hallTransitions")
248 fitResults = motionProfile.pop(
"fitResults")
250 if "metadata" in dictionary:
251 metadata = dictionary.pop(
"metadata")
252 for key, value
in metadata.items():
253 dictionary[key] = value
254 calib.setMetadata(dictionary)
256 formatVersion = calib.metadata[
"version"]
258 startTime = motionProfile.pop(
"startTime")
259 if formatVersion == 1.0:
261 motionProfile[
"startTime_tai"] = startTime[
"tai"]
262 motionProfile[
"startTime_mjd"] = startTime[
"mjd"]
265 motionProfile[
"startTime_tai"] = startTime[
"tai"][
"isot"]
266 motionProfile[
"startTime_mjd"] = startTime[
"tai"][
"mjd"]
268 calib.readEncodeSamples(encodeSamples, formatVersion)
269 calib.readHallTransitions(hallTransitions, formatVersion)
270 calib.readFitResults(fitResults)
272 calib.updateMetadata(**motionProfile)
351 """Construct calibration from a list of tables.
353 This method uses the `fromDict` method to create the
354 calibration, after constructing an appropriate dictionary from
359 tableList : `list` [`lsst.afw.table.Table`]
360 List of tables to use to construct the crosstalk
361 calibration. For shutter motion profiles, the first table
362 contains the samples, the second the Hall transition data,
363 and the third the model fits.
367 calib : `lsst.ip.isr.ShutterMotionProfile`
368 The calibration defined in the tables.
370 samples = tableList[0]
371 transitions = tableList[1]
372 modelFits = tableList[2]
374 metadata = samples.meta
377 calib.time_tai = np.squeeze(samples[
"TIME_TAI"].data).tolist()
378 if hasattr(calib.time_tai[0],
"decode"):
379 calib.time_tai = [time.decode(
"utf-8")
for time
in calib.time_tai]
380 calib.time_mjd = np.squeeze(samples[
"TIME_MJD"].data).tolist()
381 calib.position = np.squeeze(samples[
"POSITION"].data).tolist()
383 calib.hall_time_tai = np.squeeze(transitions[
"HALL_TIME_TAI"].data).tolist()
384 if hasattr(calib.hall_time_tai[0],
"decode"):
385 calib.hall_time_tai = [time.decode(
"utf-8")
for time
in calib.hall_time_tai]
386 calib.hall_time_mjd = np.squeeze(transitions[
"HALL_TIME_MJD"].data).tolist()
387 calib.hall_position = np.squeeze(transitions[
"HALL_POSITION"].data).tolist()
388 calib.hall_sensorId = np.squeeze(transitions[
"HALL_SENSORID"].data).tolist()
389 calib.hall_isOn = np.squeeze(transitions[
"HALL_ISON"].data).tolist()
391 calib.fit_model = modelFits.meta[
"FIT_MODEL"]
393 calib.fit_name = np.squeeze(modelFits[
"FIT_NAME"].data).tolist()
394 if hasattr(calib.fit_name[0],
"decode"):
395 calib.fit_name = [fit.decode(
"utf-8")
for fit
in calib.fit_name]
396 calib.fit_start_time = np.squeeze(modelFits[
"FIT_START_TIME"].data).tolist()
397 calib.fit_pivot1 = np.squeeze(modelFits[
"FIT_PIVOT1"].data).tolist()
398 calib.fit_pivot2 = np.squeeze(modelFits[
"FIT_PIVOT2"].data).tolist()
399 calib.fit_jerk0 = np.squeeze(modelFits[
"FIT_JERK0"].data).tolist()
400 calib.fit_jerk1 = np.squeeze(modelFits[
"FIT_JERK1"].data).tolist()
401 calib.fit_jerk2 = np.squeeze(modelFits[
"FIT_JERK2"].data).tolist()
403 if "OBSTYPE" not in metadata:
408 for key
in (
"fileName",
"fileType",
"obsId",
"version",
"side",
"isOpen"):
409 if key.upper()
in metadata:
410 value = metadata.pop(key.upper())
411 metadata[key] = value
412 for key
in (
"CALIB_ID",
"DETECTOR",
"DET_NAME",
"DET_SER",
"FILTER",
"INSTRUME",
413 "RAFTNAME",
"SEQCKSUM",
"SEQFILE",
"SEQNAME",
"SLOTNAME"):
415 if metadata[key] ==
"":
418 calib.updateMetadata(**metadata)