Coverage for tests / test_shutterMotion.py: 24%
99 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-05-07 08:28 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-05-07 08:28 +0000
1# This file is part of ip_isr.
2#
3# Developed for the LSST Data Management System.
4# This product includes software developed by the LSST Project
5# (https://www.lsst.org).
6# See the COPYRIGHT file at the top-level directory of this distribution
7# for details of code ownership.
8#
9# This program is free software: you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation, either version 3 of the License, or
12# (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with this program. If not, see <https://www.gnu.org/licenses/>.
22import os
23import tempfile
24import unittest
26import lsst.utils.tests
28from lsst.afw.image import ExposureF
29from lsst.ip.isr import ShutterMotionProfile, ShutterMotionProfileFull
31TESTDIR = os.path.abspath(os.path.dirname(__file__))
34class ShutterMotionV1TestCase(lsst.utils.tests.TestCase):
35 def setUp(self):
36 filename = os.path.join(TESTDIR, "data", "MC_O_20250526_000095_shutterMotionProfileOpen.json")
37 self.calib = ShutterMotionProfile.readText(filename)
39 def testRoundTrip(self):
40 outPath = tempfile.mktemp() + '.yaml'
41 self.calib.writeText(outPath)
42 newCalib = ShutterMotionProfile.readText(outPath)
43 self.assertEqual(self.calib, newCalib)
45 outPath = tempfile.mktemp() + '.fits'
46 self.calib.writeFits(outPath)
47 newCalib = ShutterMotionProfile.readFits(outPath)
48 self.assertEqual(self.calib, newCalib)
50 def testMidpointCalculation(self):
51 mid_accel, mid_position = self.calib.calculateMidpoint(modelName="hallSensorFit")
52 self.assertFloatsAlmostEqual(mid_accel, 0.45052044442394185)
53 self.assertFloatsAlmostEqual(mid_position, 0.44985220648315977)
55 mid_accel, mid_position = self.calib.calculateMidpoint(modelName="motorEncoderFit")
56 self.assertFloatsAlmostEqual(mid_accel, 0.45040944835186697)
57 self.assertFloatsAlmostEqual(mid_position, 0.4497698788310154)
60class ShutterMotionV2TestCase(lsst.utils.tests.TestCase):
61 def setUp(self):
62 # We do not yet have true v2 shutter motion profiles, so this
63 # is a v1 file modified to match the expected changes.
64 filename = os.path.join(TESTDIR, "data", "MC_O_20250720_000554_shutterMotionProfileClose.json")
65 self.calib = ShutterMotionProfile.readText(filename)
67 def testRoundTrip(self):
68 outPath = tempfile.mktemp() + '.yaml'
69 self.calib.writeText(outPath)
70 newCalib = ShutterMotionProfile.readText(outPath)
71 self.assertEqual(self.calib, newCalib)
73 outPath = tempfile.mktemp() + '.fits'
74 self.calib.writeFits(outPath)
75 newCalib = ShutterMotionProfile.readFits(outPath)
76 self.assertEqual(self.calib, newCalib)
78 def testMidpointCalculation(self):
79 mid_accel, mid_position = self.calib.calculateMidpoint(modelName="hallSensorFit")
80 self.assertFloatsAlmostEqual(mid_accel, 0.4503675064864023)
81 self.assertFloatsAlmostEqual(mid_position, 0.45007949121581464)
83 mid_accel, mid_position = self.calib.calculateMidpoint(modelName="motorEncoderFit")
84 self.assertFloatsAlmostEqual(mid_accel, 0.4503689163377142)
85 self.assertFloatsAlmostEqual(mid_position, 0.45008433680071347)
88class ShutterMotionFullTestCase(lsst.utils.tests.TestCase):
89 def setUp(self):
90 # Get the profiles from the text files.
91 file_open = os.path.join(TESTDIR, "data",
92 "MC_O_20251203_000317_shutterMotionProfileOpen.json")
93 file_close = os.path.join(TESTDIR, "data",
94 "MC_O_20251203_000317_shutterMotionProfileClose.json")
95 self.profile_open = ShutterMotionProfile.readText(file_open)
96 self.profile_close = ShutterMotionProfile.readText(file_close)
98 # Simulate the image that would match these profiles, using
99 # the as-written header of this exposure.
100 self.exposure = ExposureF(1, 1) # Image size doesn't matter.
102 # Fill the exposure header with the important information
103 self.exposure.metadata['SHUTTER OPEN STARTTIME TAI ISOT'] = '2025-12-04T08:46:56.592'
104 self.exposure.metadata['SHUTTER OPEN STARTTIME TAI MJD'] = 61013.365932771936
105 self.exposure.metadata['SHUTTER OPEN SIDE'] = 'MINUSX'
106 self.exposure.metadata['SHUTTER OPEN MODEL'] = 'ThreeJerksModelv1'
107 self.exposure.metadata['SHUTTER OPEN HALLSENSORFIT MODELSTARTTIME'] = 0.0005325114037250868
108 self.exposure.metadata['SHUTTER OPEN HALLSENSORFIT PIVOTPOINT1'] = 0.2249632907921461
109 self.exposure.metadata['SHUTTER OPEN HALLSENSORFIT PIVOTPOINT2'] = 0.6764472494529934
110 self.exposure.metadata['SHUTTER OPEN HALLSENSORFIT JERK0'] = 32998.841790483784
111 self.exposure.metadata['SHUTTER OPEN HALLSENSORFIT JERK1'] = -32961.436183162106
112 self.exposure.metadata['SHUTTER OPEN HALLSENSORFIT JERK2'] = 33455.024219797444
113 self.exposure.metadata['SHUTTER CLOSE STARTTIME TAI ISOT'] = '2025-12-04T08:47:06.593'
114 self.exposure.metadata['SHUTTER CLOSE STARTTIME TAI MJD'] = 61013.366048524156
115 self.exposure.metadata['SHUTTER CLOSE SIDE'] = 'PLUSX'
116 self.exposure.metadata['SHUTTER CLOSE MODEL'] = 'ThreeJerksModelv1'
117 self.exposure.metadata['SHUTTER CLOSE HALLSENSORFIT MODELSTARTTIME'] = 0.000900220338163209
118 self.exposure.metadata['SHUTTER CLOSE HALLSENSORFIT PIVOTPOINT1'] = 0.2239073050036253
119 self.exposure.metadata['SHUTTER CLOSE HALLSENSORFIT PIVOTPOINT2'] = 0.6804015164010819
120 self.exposure.metadata['SHUTTER CLOSE HALLSENSORFIT JERK0'] = 33134.34774228922
121 self.exposure.metadata['SHUTTER CLOSE HALLSENSORFIT JERK1'] = -32799.5059339621
122 self.exposure.metadata['SHUTTER CLOSE HALLSENSORFIT JERK2'] = 36183.41782883381
124 def testMidpointCalculations(self):
125 # A "full" profile should yield the same results as the json
126 # profiles.
127 profile_full = ShutterMotionProfileFull.fromExposure(self.exposure)
129 open_mid, close_mid = profile_full.calculateMidpoints()
131 open_acc, _ = self.profile_open.calculateMidpoint()
132 close_acc, _ = self.profile_close.calculateMidpoint()
134 self.assertFloatsAlmostEqual(open_mid, open_acc)
135 self.assertFloatsAlmostEqual(close_mid, close_acc)
137 def testEdgeCases(self):
138 # These are options that will not work with the exposure based
139 # profiles, as they only have the Hall sensor fit, and no
140 # position data.
141 profile_full = ShutterMotionProfileFull.fromExposure(self.exposure)
142 with self.assertRaises(KeyError):
143 profile_full.profile_open.calculateMidpoint(skipPosition=False)
145 with self.assertRaises(KeyError):
146 profile_full.profile_close.calculateMidpoint(skipPosition=False)
148 with self.assertRaises(RuntimeError):
149 profile_full.profile_open.calculateMidpoint(modelName="motorEncoderFit", skipPosition=True)
151 with self.assertRaises(RuntimeError):
152 profile_full.profile_close.calculateMidpoint(modelName="motorEncoderFit", skipPosition=True)
155class MemoryTester(lsst.utils.tests.MemoryTestCase):
156 pass
159def setup_module(module):
160 lsst.utils.tests.init()
163if __name__ == "__main__": 163 ↛ 164line 163 didn't jump to line 164 because the condition on line 163 was never true
164 import sys
165 setup_module(sys.modules[__name__])
166 unittest.main()