From f35182cd8d24f45efe0202d2b71153d824cde6e4 Mon Sep 17 00:00:00 2001 From: Dan Weatherill Date: Mon, 3 Apr 2023 13:59:41 +0100 Subject: [PATCH 01/17] start on base_calsys derived classes for AT and MT structure for methods start fleshing out a few more details start on monochromator setup functions start on various calculations helper function for LFA retrieval stub method for take_data --- .../ts/observatory/control/auxtel/atcalsys.py | 81 ++++++++ .../ts/observatory/control/base_calsys.py | 177 ++++++++++++++++++ .../observatory/control/maintel/mtcalsys.py | 17 ++ 3 files changed, 275 insertions(+) create mode 100644 python/lsst/ts/observatory/control/auxtel/atcalsys.py create mode 100644 python/lsst/ts/observatory/control/base_calsys.py create mode 100644 python/lsst/ts/observatory/control/maintel/mtcalsys.py diff --git a/python/lsst/ts/observatory/control/auxtel/atcalsys.py b/python/lsst/ts/observatory/control/auxtel/atcalsys.py new file mode 100644 index 00000000..66011062 --- /dev/null +++ b/python/lsst/ts/observatory/control/auxtel/atcalsys.py @@ -0,0 +1,81 @@ +from typing import List, Optional, NamedTuple +from ..base_calsys import BaseCalsys, HardcodeCalsysThroughput +from lsst.ts import salobj +from lsst.ts.idl.enums import ATMonochromator +import asyncio + +class ATSpectrographSlits(NamedTuple): + FRONTENTRANCE: float + FRONTEXIT: float + + +class ATCalsys(BaseCalsys, HardcodeCalsysThroughput): + """ class which specifically handles the calibration system for auxtel""" + _AT_SAL_COMPONENTS: List[str] = ["ATMonochromator", "FiberSpectrograph", "Electrometer"] + CHANGE_GRATING_TIME: int = 60 + + #these below numbers should be able to be loaded from a (fairly static) config! + GRATING_CHANGEOVER_WL: float = 532.0 #WARNING: PLACEHOLDER VALUE!!! + GRATING_CHANGEOVER_BW: float = 55.0 #WARNING! PLACEHOLDER VALUE!!! + + + def __init__(self, **kwargs:) + super().__init__(self._AT_SAL_COMPONENTS, **kwargs) + self._specsposure_time: Optional[float] = None + self._elecsposure_time: Optional[float] = None + + + + async def setup_for_wavelength(self, wavelen: float, nelec: float, spectral_res: float) -> None: + # to be copied basically from existing SAL script mechanisms + + grating = self.calculate_grating_type(wavelen, spectral_res) + slit_widths = self.calculate_slit_widths(spectral_res, grating) + + self.log.debug(f"setting up monochromtor with wavlength {wavelen} nm and spectral resolution {spectral_res}") + self.log.debug(f"calculated slit widthsare {slit_widths}") + self.log.debug(f"calculated grating is {grating}") + + monoch_fut = self._sal_cmd_helper("monochromator", "updateMonochromatorSetup", + gratingType = grating, + frontExitSlitWidth = slit_widths.FRONTEXIT, + frontEntranceSlitWdth = slit_widths.FRONTENTRACE, + wavelength = wavelen) + + elect_fut = self._sal_cmd_helper("electrometer", "performZeroCalib") + elect_fut2 = self._sal_cmd_helper("electrometer", "setDigitalFilter", + activateFilter=False, + activateAvgFilter=False, + activateMedFilter=False) + + + + + #TODO: electrometer + #TODO: fibre spectrograph + + asyncio.wait([monoch_fut, elect_fut, elect_fut2], return_when=asyncio.ALL_COMPLETED) + self.log.debug("all SAL setup commands returned") + + specsposure_time = self.spectrograph_exposure_time_for_nelectrons(nelec) + + + return + + + def calculate_slit_width(self, spectral_res: float, grating) -> ATSpectrographSlits: + #NOTE: this will either need to be derived by doing calculations on the Grating equation, or by loading in calibration data (which I couldn't find yet!) + pass + + def calculate_grating_type(self, wavelen: float, spectral_res: float): + #TODO: placeholder logic, in particular the exact numbers will be WRONG! + #likely something like the below + if spectral_res > self.GRATING_CHANGEOVER_BW: + return ATMonochromator.Grating.MIRROR + elif wavelen < self.GRATING_CHANGEOVER_WL: + return ATMonochromator.Grating.BLUE + return ATMonochromator.Grating.RED + + + async def _setup_spectrograph(self, int_time: float) -> None: + pass diff --git a/python/lsst/ts/observatory/control/base_calsys.py b/python/lsst/ts/observatory/control/base_calsys.py new file mode 100644 index 00000000..f9916611 --- /dev/null +++ b/python/lsst/ts/observatory/control/base_calsys.py @@ -0,0 +1,177 @@ +from abc import ABCMeta, abstractmethod +from .remote_group import RemoteGroup +from typing import Iterable, Optional, Tuple, List, Union, Callable +from functools import reduce +from operator import mul +from lsst.ts import salobj +import logging +import asyncio + +class CalsysThroughputCalculationMixin: + """mixin class to allow pluggable source for calculation of throughputs""" + + @abstractmethod + @property + def detector_throughput(self, wavelen: float) -> float: + """the throughput value will return (in appropriate units TBD) the detector throughput of the particular + calibration system specified in the class for which this mixin class is specified as a base. + + Parameters + ---------- + + wavelen: float - wavelength of the calibration to be performed in nm + + + Returns + ------- + + A value (units TBD, likely electrons per pixel per second), which can be used to + either determine how long to integrate the sensor for to achieve a desired level of calibration + field in electrons, or determine how many electrons will be obtained for a specific integration time + + """ + + @abstractmethod + @property + def spectrograph_throughput(self, wavelen: float, calsys_power: float) -> float: + """ the throughput expected of the fiber spectrograph of the calibration system. + To aid calculations of total throughput + """ + + @abstractmethod + @property + def radiometer_throughput(self, wavelen: float, calsys_power: float) -> float: + """ same as spectrograph_throughput but for radiometer """ + + def end_to_end_throughput(self, wavelen: float, calsys_power: float) -> float: + #intended to be SOMETHING LIKE + return reduce(lambda t, f: t*f(wavelen, calsys_power), + [self.detector_throughput, self.spectrograph_throughput, + self.radiometer_throughput]) + + +class ButlerCalsysThroughput(CalsysThroughpu_etCalculationMixin): + """Mixin class for calculating throughput of the calibration system backed by measurements stored + in a DM butler""" + + +class HardcodeCalsysThroughput(CalsysThroughputCalculationMixin): + """Mixin class for calculating throughput of the calibration system with hardcoded values, + i.e. which can be directly imported from python code """ + + + +class BaseCalsys(RemoteGroup, metaclass=ABCMeta): + """Base class for calibration systems""" + + + def __init__(self, + components: Iterable[str], + domain: Optional[salobj.domain] = None, + cmd_timeout: Optional[int] = 10, + log: Optional[logging.Logger] = None): + + super().__init__(components, domain, log=log, + intended_usage=salobj.BaseUsages.StateTransition, + concurrent_operation = False) #QUESTION: is this last one true???? + + self._cmd_timeout = cmd_timeout + + + def _sal_cmd_helper(self, salobj, cmdname: str, run_immediate: bool= True, **setargs): + if isinstance(salobj, str): + salobj = getattr(self, salobj) + cmdfun = getattr(salobj, f"cmd_{cmdname}") + cmdfun.set(**setargs) + pkgtask = lambda : cmdfun.start(timeout = self._cmd_timeout) + if run_immediate: + return asyncio.createtask(pkgtask()) + return pkgtask() + + def _lfa_event_helper(self, salobj, run_immediate: bool=True, **evtargs): + if isinstance(salobj, str): + salobj = getattr(self, salobj) + cmdfun = getattr(salobj, "evt_largeFileObjectAvailable") + pkgtask = lambda: cmdfun.start(timeout = self._cmd_timeout) + if run_immediate: + return asyncio.create_task(pkgtask()) + return pkgtask() + + + def detector_exposure_time_for_nelectrons(self, wavelen: float, nelec: float) -> float: + """ using the appropriate mixin for obtaining calibration data on throughput, + will calculate and return the exposure time needed to obtain a flat field calibration of n + electrons at the imager specified in the class definition + + Parameters + ---------- + + wavelen: float - wavelength (in nm??) of the intended calibration + nelec: float - number of electrons (probably measured in ke-??) desired in calibration field + + Returns + ------- + + exposure time: float - time (in seconds??) needed for the imager to obtain desired calibration field + + """ + + #must have a way to access the calibration data + assert issubclass(type(self), CalsysThroughputCalculationMixin) + + + def spectrograph_exposure_time_for_nelectrons(self, nelec: float) -> float: + pass + + def pd_exposure_time_for_nelectrons(self, nelec: float) -> float: + pass + + @abstractmethod + async def turn_on_light(self) -> None: + """awaitable command which turns on the calibration light, having + already set up the appropriate wavelength and (if applicable) time delays for stabilization etc + + """ + + @abstractmethod + async def turn_off_light(self) -> None: + """ awaitable which turns off the calibration light""" + + + @abstractmethod + async def setup_for_wavelength(self, wavelen: float, **extra_params) -> None: + """ awaitable which sets up the various remote components of a calibration system + to perform a calibration at a particular wavelength. + + Intended to be a 'high level' setup function, such that user doesn't have to worry about e.g. setting up integration times for spectrographs etc + + Parameters + ---------- + + wavelen: float - desired wavelength (in nm) + + extra_params: to handle things which are specific to individual calibration systems + (e.g that the auxtel can also adjust spectral bandwidth) + + """ + pass + + @abstractmethod + async def take_calibration_instr_exposures(self) -> None: + """ awaitable which starts the exposures for the calibration instruments (i.e the spectrographs, electrometers etc) according to the setup. It does not take images with the instrument under test, it is intended that script components which use this class do that themselves""" + pass + + + @property + @abstractmethod + def wavelen(self) -> float: + """ returns the currently configured wavelength""" + + @abstractmethod + async def take_data(self): + """This will fire off all async tasks to take calibration data in sequence, and return locations and metadata about the files supplied etc""" + + + + + diff --git a/python/lsst/ts/observatory/control/maintel/mtcalsys.py b/python/lsst/ts/observatory/control/maintel/mtcalsys.py new file mode 100644 index 00000000..02eb4ac4 --- /dev/null +++ b/python/lsst/ts/observatory/control/maintel/mtcalsys.py @@ -0,0 +1,17 @@ +from typing import List + +from ..base_calsys import BaseCalsys + +class MTCalsys(BaseCalsys): + """ class which specifically handles the calibration system for maintel """ + _MT_SAL_COMPONENTS: List[str] = [] #TODO, what do we actually need here???! + + def __init__(self, + domain: Optional[salobj.Domain] = None): + super().__init__(self._MT_SAL_COMPONENTS, domain) + + async def turn_on_light(self) -> None: ... + + async def turn_off_light(self) -> None: ... + + async def setup_for_wavelength(self, wavelen: float) -> None: ... From b9787ac8264a6ea41f6cb8a152e4383369b14b94 Mon Sep 17 00:00:00 2001 From: Dan Weatherill Date: Wed, 5 Jul 2023 14:19:30 +0100 Subject: [PATCH 02/17] B# This is a combination of 6 commits. start bringing in calibration curves radiometer throughput stubs cal curves dunder init start annotating unit types start on test suite skeleton start on calculations of maximum electrometer time add maintel throughput calibration curves --- .../ts/observatory/control/base_calsys.py | 60 +- .../control/cal_curves/__init__.py | 1 + .../cal_curves/calibration_tput_init_g.csv | 807 ++++++++++++++++++ .../cal_curves/calibration_tput_init_i.csv | 807 ++++++++++++++++++ .../cal_curves/calibration_tput_init_r.csv | 807 ++++++++++++++++++ .../cal_curves/calibration_tput_init_u.csv | 807 ++++++++++++++++++ .../cal_curves/calibration_tput_init_y4.csv | 807 ++++++++++++++++++ .../cal_curves/calibration_tput_init_z.csv | 807 ++++++++++++++++++ .../cal_curves/hamamatsu_responsivity.csv | 162 ++++ tests/test_calsys_logic.py | 31 + 10 files changed, 5082 insertions(+), 14 deletions(-) create mode 100644 python/lsst/ts/observatory/control/cal_curves/__init__.py create mode 100644 python/lsst/ts/observatory/control/cal_curves/calibration_tput_init_g.csv create mode 100644 python/lsst/ts/observatory/control/cal_curves/calibration_tput_init_i.csv create mode 100644 python/lsst/ts/observatory/control/cal_curves/calibration_tput_init_r.csv create mode 100644 python/lsst/ts/observatory/control/cal_curves/calibration_tput_init_u.csv create mode 100644 python/lsst/ts/observatory/control/cal_curves/calibration_tput_init_y4.csv create mode 100644 python/lsst/ts/observatory/control/cal_curves/calibration_tput_init_z.csv create mode 100644 python/lsst/ts/observatory/control/cal_curves/hamamatsu_responsivity.csv create mode 100644 tests/test_calsys_logic.py diff --git a/python/lsst/ts/observatory/control/base_calsys.py b/python/lsst/ts/observatory/control/base_calsys.py index f9916611..c5c77656 100644 --- a/python/lsst/ts/observatory/control/base_calsys.py +++ b/python/lsst/ts/observatory/control/base_calsys.py @@ -1,11 +1,20 @@ from abc import ABCMeta, abstractmethod from .remote_group import RemoteGroup -from typing import Iterable, Optional, Tuple, List, Union, Callable +from typing import Iterable, Optional, Tuple, List, Union +from typing import Sequence, Callable, Mapping, TypeAlias from functools import reduce from operator import mul from lsst.ts import salobj import logging import asyncio +from importlib.resources import files +import csv +from scipy.interpolate import InterpolatedUnivariateSpline +from astropy.units import ampere, watt, nm, Quantity +import astropy.units as un + +Responsivity: TypeAlias = Quantity[ampere/watt] + class CalsysThroughputCalculationMixin: """mixin class to allow pluggable source for calculation of throughputs""" @@ -32,16 +41,14 @@ def detector_throughput(self, wavelen: float) -> float: """ @abstractmethod - @property def spectrograph_throughput(self, wavelen: float, calsys_power: float) -> float: """ the throughput expected of the fiber spectrograph of the calibration system. To aid calculations of total throughput """ @abstractmethod - @property - def radiometer_throughput(self, wavelen: float, calsys_power: float) -> float: - """ same as spectrograph_throughput but for radiometer """ + def radiometer_responsivity(self, wavelen: Quantity["length"]) -> Responsivity: + """ return the responsivity of the radiometer """ def end_to_end_throughput(self, wavelen: float, calsys_power: float) -> float: #intended to be SOMETHING LIKE @@ -49,8 +56,16 @@ def end_to_end_throughput(self, wavelen: float, calsys_power: float) -> float: [self.detector_throughput, self.spectrograph_throughput, self.radiometer_throughput]) + def total_radiometer_exposure_time(self, cam_integration_time: Quantity["time"], + nplc: Quantity["time"]) -> Quantity["time"]: + # Note: magic numbers from communication with Parker F. To be added to electrometer CSC docs + rad_int_time = nplc / (60 * un.s) + + # FIXME: need to ask about units of nplc and what the units of the 3.07 magic number are + + -class ButlerCalsysThroughput(CalsysThroughpu_etCalculationMixin): +class ButlerCalsysThroughput(CalsysThroughputCalculationMixin): """Mixin class for calculating throughput of the calibration system backed by measurements stored in a DM butler""" @@ -58,7 +73,28 @@ class ButlerCalsysThroughput(CalsysThroughpu_etCalculationMixin): class HardcodeCalsysThroughput(CalsysThroughputCalculationMixin): """Mixin class for calculating throughput of the calibration system with hardcoded values, i.e. which can be directly imported from python code """ + BASERES: str = "lsst.ts.observatory.control.cal_curves" + RADIOMETER_CALFILE: str = "hamamatsu_responsivity.csv" + @classmethod + def load_calibration_csv(cls, fname: str) -> Mapping[str, Sequence[float]]: + res = files(cls.BASERES).joinpath(fname) + with res.open("r") as f: + rdr = csv.DictReader(f) + out = { k : [] for k in rdr.fieldnames} + for row in rdr: + for k,v in row.items(): + out[k].append(float(v)) + return out + + def radiometer_responsivity(self, wavelen: Quantity["length"]) -> Responsivity: + calres = self.load_calibration_csv(self.RADIOMETER_CALFILE) + itp = InterpolatedUnivariateSpline(calres["wavelength"], calres["responsivity"]) + + wlin : float = wavelen.to(nm).value + Rawout: float = itp(wlin) + + return (Rawout << un.ampere / un.watt) class BaseCalsys(RemoteGroup, metaclass=ABCMeta): @@ -98,7 +134,7 @@ def _lfa_event_helper(self, salobj, run_immediate: bool=True, **evtargs): return pkgtask() - def detector_exposure_time_for_nelectrons(self, wavelen: float, nelec: float) -> float: + def detector_exposure_time_for_nelectrons(self, wavelen: Quantity["length"], nelec: float) -> float: """ using the appropriate mixin for obtaining calibration data on throughput, will calculate and return the exposure time needed to obtain a flat field calibration of n electrons at the imager specified in the class definition @@ -115,7 +151,7 @@ def detector_exposure_time_for_nelectrons(self, wavelen: float, nelec: float) -> exposure time: float - time (in seconds??) needed for the imager to obtain desired calibration field """ - + #must have a way to access the calibration data assert issubclass(type(self), CalsysThroughputCalculationMixin) @@ -161,17 +197,13 @@ async def take_calibration_instr_exposures(self) -> None: """ awaitable which starts the exposures for the calibration instruments (i.e the spectrographs, electrometers etc) according to the setup. It does not take images with the instrument under test, it is intended that script components which use this class do that themselves""" pass - + @property @abstractmethod - def wavelen(self) -> float: + def wavelen(self) -> Quantity[nm]: """ returns the currently configured wavelength""" @abstractmethod async def take_data(self): """This will fire off all async tasks to take calibration data in sequence, and return locations and metadata about the files supplied etc""" - - - - diff --git a/python/lsst/ts/observatory/control/cal_curves/__init__.py b/python/lsst/ts/observatory/control/cal_curves/__init__.py new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/python/lsst/ts/observatory/control/cal_curves/__init__.py @@ -0,0 +1 @@ + diff --git a/python/lsst/ts/observatory/control/cal_curves/calibration_tput_init_g.csv b/python/lsst/ts/observatory/control/cal_curves/calibration_tput_init_g.csv new file mode 100644 index 00000000..6cbcbed4 --- /dev/null +++ b/python/lsst/ts/observatory/control/cal_curves/calibration_tput_init_g.csv @@ -0,0 +1,807 @@ +Wavelength[nm],Throughput +320.0,0.0 +321.0,0.0 +322.0,0.0 +323.0,0.0 +324.0,0.0 +325.0,0.0 +326.0,0.0 +327.0,0.0 +328.0,0.0 +329.0,0.0 +330.0,0.0 +331.0,0.0 +332.0,0.0 +333.0,0.0 +334.0,0.0 +335.0,0.0 +336.0,0.0 +337.0,0.0 +338.0,0.0 +339.0,0.0 +340.0,0.0 +341.0,0.0 +342.0,0.0 +343.0,0.0 +344.0,0.0 +345.0,0.0 +346.0,0.0 +347.0,0.0 +348.0,0.0 +349.0,0.0 +350.0,0.0 +351.0,0.0 +352.0,0.0 +353.0,0.0 +354.0,0.0 +355.0,0.0 +356.0,0.0 +357.0,0.0 +358.0,0.0 +359.0,0.0 +360.0,0.0 +361.0,0.0 +362.0,0.0 +363.0,0.0 +364.0,0.0 +365.0,0.0 +366.0,0.0 +367.0,0.0 +368.0,0.0 +369.0,0.0 +370.0,0.0 +371.0,0.0 +372.0,0.0 +373.0,0.0 +374.0,0.0 +375.0,0.0 +376.0,0.0 +377.0,0.0 +378.0,0.0 +379.0,0.0 +380.0,0.0 +381.0,0.0 +382.0,0.0 +383.0,0.0 +384.0,0.0 +385.0,2.6225638654362886e-07 +386.0,3.972972450813916e-07 +387.0,6.726011531125758e-07 +388.0,1.3238530035743078e-06 +389.0,1.818185723053796e-06 +390.0,1.8279187248393178e-06 +391.0,2.160421843539651e-06 +392.0,2.993632373155921e-06 +393.0,3.9267493472688715e-06 +394.0,4.838594877052303e-06 +395.0,6.032905660624018e-06 +396.0,6.968240690986617e-06 +397.0,7.211664222288916e-06 +398.0,8.260831510318081e-06 +399.0,1.0120900799538927e-05 +400.0,1.1788142370573031e-05 +401.0,1.3133919747869306e-05 +402.0,1.4137221383078861e-05 +403.0,1.435855757736236e-05 +404.0,1.4961559087992903e-05 +405.0,1.6468322135945427e-05 +406.0,1.792199249104346e-05 +407.0,1.887515271666764e-05 +408.0,1.991218743780877e-05 +409.0,2.0798040246003907e-05 +410.0,2.1149353998586196e-05 +411.0,2.1724501004055066e-05 +412.0,2.2774586596798326e-05 +413.0,2.344941461258037e-05 +414.0,2.3537981380604953e-05 +415.0,2.3542882326978582e-05 +416.0,2.3557231690850853e-05 +417.0,2.355847007909992e-05 +418.0,2.3653290004256034e-05 +419.0,2.382406742078397e-05 +420.0,2.4026675809391782e-05 +421.0,2.4220404096564223e-05 +422.0,2.4232269409510672e-05 +423.0,2.4240861248881384e-05 +424.0,2.440731426715892e-05 +425.0,2.4605696917799368e-05 +426.0,2.4800935220252443e-05 +427.0,2.506085716083515e-05 +428.0,2.5296065387134607e-05 +429.0,2.5419793286069726e-05 +430.0,2.5467609051671373e-05 +431.0,2.541535432841786e-05 +432.0,2.5448940850857493e-05 +433.0,2.544019767933216e-05 +434.0,2.5366599957655527e-05 +435.0,2.538026746650519e-05 +436.0,2.546822248524284e-05 +437.0,2.5425001875373146e-05 +438.0,2.5375101528847406e-05 +439.0,2.545026747586448e-05 +440.0,2.549626779649399e-05 +441.0,2.5458507766691184e-05 +442.0,2.544770371492481e-05 +443.0,2.5497389502106888e-05 +444.0,2.5547105321134028e-05 +445.0,2.5512256096352484e-05 +446.0,2.5435556976696023e-05 +447.0,2.549810696857544e-05 +448.0,2.5665498447296295e-05 +449.0,2.572671276998536e-05 +450.0,2.573197190357158e-05 +451.0,2.5917508340108966e-05 +452.0,2.6099770115794176e-05 +453.0,2.6202173311432738e-05 +454.0,2.625293363262473e-05 +455.0,2.6304748959789496e-05 +456.0,2.6443830605583608e-05 +457.0,2.6606459328872964e-05 +458.0,2.6683613497935964e-05 +459.0,2.6750916169184926e-05 +460.0,2.686330510585885e-05 +461.0,2.687805217059422e-05 +462.0,2.689243496126631e-05 +463.0,2.6912871647282363e-05 +464.0,2.6919237825040024e-05 +465.0,2.6980350523016733e-05 +466.0,2.7089635708474886e-05 +467.0,2.7163483132717644e-05 +468.0,2.7198278933223364e-05 +469.0,2.71828343353376e-05 +470.0,2.710790537624732e-05 +471.0,2.7108168717562116e-05 +472.0,2.717375701352291e-05 +473.0,2.7195637164458828e-05 +474.0,2.718260874082676e-05 +475.0,2.716886763255757e-05 +476.0,2.7168096277936945e-05 +477.0,2.720157791311631e-05 +478.0,2.7227668983574437e-05 +479.0,2.720445003573187e-05 +480.0,2.723871670388167e-05 +481.0,2.7373646318304635e-05 +482.0,2.746305803220094e-05 +483.0,2.7470916887662985e-05 +484.0,2.7432192669836182e-05 +485.0,2.738711449743638e-05 +486.0,2.7457357815308753e-05 +487.0,2.7586800701846823e-05 +488.0,2.7650235354969862e-05 +489.0,2.7686886064401754e-05 +490.0,2.7753257314358484e-05 +491.0,2.7831882022359113e-05 +492.0,2.791727760419743e-05 +493.0,2.7970059321707294e-05 +494.0,2.796094445008659e-05 +495.0,2.798159030068015e-05 +496.0,2.8071217417429854e-05 +497.0,2.8159386087372192e-05 +498.0,2.823276494944542e-05 +499.0,2.8256546743003517e-05 +500.0,2.821330602404791e-05 +501.0,2.82020932381356e-05 +502.0,2.8224841937519566e-05 +503.0,2.8222920016510964e-05 +504.0,2.8256395117335387e-05 +505.0,2.8303837074080197e-05 +506.0,2.833625968395768e-05 +507.0,2.8384296329513958e-05 +508.0,2.8398772113361465e-05 +509.0,2.835974117152094e-05 +510.0,2.832780714395456e-05 +511.0,2.828215816700458e-05 +512.0,2.819877189408158e-05 +513.0,2.8149456332888416e-05 +514.0,2.811673371150923e-05 +515.0,2.8081990133719407e-05 +516.0,2.8087576916867415e-05 +517.0,2.8106897076436055e-05 +518.0,2.8099862905012272e-05 +519.0,2.8105235759810816e-05 +520.0,2.810376830737528e-05 +521.0,2.8022244675873028e-05 +522.0,2.7880485656789748e-05 +523.0,2.767498629207746e-05 +524.0,2.745908064192945e-05 +525.0,2.7386234546001363e-05 +526.0,2.747701808728696e-05 +527.0,2.761657474390964e-05 +528.0,2.7752823615878265e-05 +529.0,2.7808276458073785e-05 +530.0,2.7779871673828515e-05 +531.0,2.7823916892147423e-05 +532.0,2.7917154079577534e-05 +533.0,2.798431957782323e-05 +534.0,2.802967458119679e-05 +535.0,2.7997008024351088e-05 +536.0,2.7859197506654317e-05 +537.0,2.7651424220262895e-05 +538.0,2.7247351987448928e-05 +539.0,2.652354267990042e-05 +540.0,2.5606959025097585e-05 +541.0,2.4617324287273976e-05 +542.0,2.3661269987564446e-05 +543.0,2.2797116990338226e-05 +544.0,2.197156126275995e-05 +545.0,2.122861495356934e-05 +546.0,2.058755354517298e-05 +547.0,1.9686977062561782e-05 +548.0,1.816690093481562e-05 +549.0,1.6161568011248232e-05 +550.0,1.407890136608678e-05 +551.0,1.2303480442768084e-05 +552.0,1.0990542030610653e-05 +553.0,1.0039209663625154e-05 +554.0,9.149461398641314e-06 +555.0,7.967129770555648e-06 +556.0,6.302406078903022e-06 +557.0,4.394176819998003e-06 +558.0,2.7401588820822958e-06 +559.0,1.599938583627618e-06 +560.0,9.187886771384184e-07 +561.0,5.354940608384359e-07 +562.0,3.2126786320022323e-07 +563.0,1.9968378131738513e-07 +564.0,1.287409131134401e-07 +565.0,0.0 +566.0,0.0 +567.0,0.0 +568.0,0.0 +569.0,0.0 +570.0,0.0 +571.0,0.0 +572.0,0.0 +573.0,0.0 +574.0,0.0 +575.0,0.0 +576.0,0.0 +577.0,0.0 +578.0,0.0 +579.0,0.0 +580.0,0.0 +581.0,0.0 +582.0,0.0 +583.0,0.0 +584.0,0.0 +585.0,0.0 +586.0,0.0 +587.0,0.0 +588.0,0.0 +589.0,0.0 +590.0,0.0 +591.0,0.0 +592.0,0.0 +593.0,0.0 +594.0,0.0 +595.0,0.0 +596.0,0.0 +597.0,0.0 +598.0,0.0 +599.0,0.0 +600.0,0.0 +601.0,0.0 +602.0,0.0 +603.0,0.0 +604.0,0.0 +605.0,0.0 +606.0,0.0 +607.0,0.0 +608.0,0.0 +609.0,0.0 +610.0,0.0 +611.0,0.0 +612.0,0.0 +613.0,0.0 +614.0,0.0 +615.0,0.0 +616.0,0.0 +617.0,0.0 +618.0,0.0 +619.0,0.0 +620.0,0.0 +621.0,0.0 +622.0,0.0 +623.0,0.0 +624.0,0.0 +625.0,0.0 +626.0,0.0 +627.0,0.0 +628.0,0.0 +629.0,0.0 +630.0,0.0 +631.0,0.0 +632.0,0.0 +633.0,0.0 +634.0,0.0 +635.0,0.0 +636.0,0.0 +637.0,0.0 +638.0,0.0 +639.0,0.0 +640.0,0.0 +641.0,0.0 +642.0,0.0 +643.0,0.0 +644.0,0.0 +645.0,0.0 +646.0,0.0 +647.0,0.0 +648.0,0.0 +649.0,0.0 +650.0,0.0 +651.0,0.0 +652.0,0.0 +653.0,0.0 +654.0,0.0 +655.0,0.0 +656.0,0.0 +657.0,0.0 +658.0,0.0 +659.0,0.0 +660.0,0.0 +661.0,0.0 +662.0,0.0 +663.0,0.0 +664.0,0.0 +665.0,0.0 +666.0,0.0 +667.0,0.0 +668.0,0.0 +669.0,0.0 +670.0,0.0 +671.0,0.0 +672.0,0.0 +673.0,0.0 +674.0,0.0 +675.0,0.0 +676.0,0.0 +677.0,0.0 +678.0,0.0 +679.0,0.0 +680.0,0.0 +681.0,0.0 +682.0,0.0 +683.0,0.0 +684.0,0.0 +685.0,0.0 +686.0,0.0 +687.0,0.0 +688.0,0.0 +689.0,0.0 +690.0,0.0 +691.0,0.0 +692.0,0.0 +693.0,0.0 +694.0,0.0 +695.0,0.0 +696.0,0.0 +697.0,0.0 +698.0,0.0 +699.0,0.0 +700.0,0.0 +701.0,0.0 +702.0,0.0 +703.0,0.0 +704.0,0.0 +705.0,0.0 +706.0,0.0 +707.0,0.0 +708.0,0.0 +709.0,0.0 +710.0,0.0 +711.0,0.0 +712.0,0.0 +713.0,0.0 +714.0,0.0 +715.0,0.0 +716.0,0.0 +717.0,0.0 +718.0,0.0 +719.0,0.0 +720.0,0.0 +721.0,0.0 +722.0,0.0 +723.0,0.0 +724.0,0.0 +725.0,0.0 +726.0,0.0 +727.0,0.0 +728.0,0.0 +729.0,0.0 +730.0,0.0 +731.0,0.0 +732.0,0.0 +733.0,0.0 +734.0,0.0 +735.0,0.0 +736.0,0.0 +737.0,0.0 +738.0,0.0 +739.0,0.0 +740.0,0.0 +741.0,0.0 +742.0,0.0 +743.0,0.0 +744.0,0.0 +745.0,0.0 +746.0,0.0 +747.0,0.0 +748.0,0.0 +749.0,0.0 +750.0,0.0 +751.0,0.0 +752.0,0.0 +753.0,0.0 +754.0,0.0 +755.0,0.0 +756.0,0.0 +757.0,0.0 +758.0,0.0 +759.0,0.0 +760.0,0.0 +761.0,0.0 +762.0,0.0 +763.0,0.0 +764.0,0.0 +765.0,0.0 +766.0,0.0 +767.0,0.0 +768.0,0.0 +769.0,0.0 +770.0,0.0 +771.0,0.0 +772.0,0.0 +773.0,0.0 +774.0,0.0 +775.0,0.0 +776.0,0.0 +777.0,0.0 +778.0,0.0 +779.0,0.0 +780.0,0.0 +781.0,0.0 +782.0,0.0 +783.0,0.0 +784.0,0.0 +785.0,0.0 +786.0,0.0 +787.0,0.0 +788.0,0.0 +789.0,0.0 +790.0,0.0 +791.0,0.0 +792.0,0.0 +793.0,0.0 +794.0,0.0 +795.0,0.0 +796.0,0.0 +797.0,0.0 +798.0,0.0 +799.0,0.0 +800.0,0.0 +801.0,0.0 +802.0,0.0 +803.0,0.0 +804.0,0.0 +805.0,0.0 +806.0,0.0 +807.0,0.0 +808.0,0.0 +809.0,0.0 +810.0,0.0 +811.0,0.0 +812.0,0.0 +813.0,0.0 +814.0,0.0 +815.0,0.0 +816.0,0.0 +817.0,0.0 +818.0,0.0 +819.0,0.0 +820.0,0.0 +821.0,0.0 +822.0,0.0 +823.0,0.0 +824.0,0.0 +825.0,0.0 +826.0,0.0 +827.0,0.0 +828.0,0.0 +829.0,0.0 +830.0,0.0 +831.0,0.0 +832.0,0.0 +833.0,0.0 +834.0,0.0 +835.0,0.0 +836.0,0.0 +837.0,0.0 +838.0,0.0 +839.0,0.0 +840.0,0.0 +841.0,0.0 +842.0,0.0 +843.0,0.0 +844.0,0.0 +845.0,0.0 +846.0,0.0 +847.0,0.0 +848.0,0.0 +849.0,0.0 +850.0,0.0 +851.0,0.0 +852.0,0.0 +853.0,0.0 +854.0,0.0 +855.0,0.0 +856.0,0.0 +857.0,0.0 +858.0,0.0 +859.0,0.0 +860.0,0.0 +861.0,0.0 +862.0,0.0 +863.0,0.0 +864.0,0.0 +865.0,0.0 +866.0,0.0 +867.0,0.0 +868.0,0.0 +869.0,0.0 +870.0,0.0 +871.0,0.0 +872.0,0.0 +873.0,0.0 +874.0,0.0 +875.0,0.0 +876.0,0.0 +877.0,0.0 +878.0,0.0 +879.0,0.0 +880.0,0.0 +881.0,0.0 +882.0,0.0 +883.0,0.0 +884.0,0.0 +885.0,0.0 +886.0,0.0 +887.0,0.0 +888.0,0.0 +889.0,0.0 +890.0,0.0 +891.0,0.0 +892.0,0.0 +893.0,0.0 +894.0,0.0 +895.0,0.0 +896.0,0.0 +897.0,0.0 +898.0,0.0 +899.0,0.0 +900.0,0.0 +901.0,0.0 +902.0,0.0 +903.0,0.0 +904.0,0.0 +905.0,0.0 +906.0,0.0 +907.0,0.0 +908.0,0.0 +909.0,0.0 +910.0,0.0 +911.0,0.0 +912.0,0.0 +913.0,0.0 +914.0,0.0 +915.0,0.0 +916.0,0.0 +917.0,0.0 +918.0,0.0 +919.0,0.0 +920.0,0.0 +921.0,0.0 +922.0,0.0 +923.0,0.0 +924.0,0.0 +925.0,0.0 +926.0,0.0 +927.0,0.0 +928.0,0.0 +929.0,0.0 +930.0,0.0 +931.0,0.0 +932.0,0.0 +933.0,0.0 +934.0,0.0 +935.0,0.0 +936.0,0.0 +937.0,0.0 +938.0,0.0 +939.0,0.0 +940.0,0.0 +941.0,0.0 +942.0,0.0 +943.0,0.0 +944.0,0.0 +945.0,0.0 +946.0,0.0 +947.0,0.0 +948.0,0.0 +949.0,0.0 +950.0,0.0 +951.0,0.0 +952.0,0.0 +953.0,0.0 +954.0,0.0 +955.0,0.0 +956.0,0.0 +957.0,0.0 +958.0,0.0 +959.0,0.0 +960.0,0.0 +961.0,0.0 +962.0,0.0 +963.0,0.0 +964.0,0.0 +965.0,0.0 +966.0,0.0 +967.0,0.0 +968.0,0.0 +969.0,0.0 +970.0,0.0 +971.0,0.0 +972.0,0.0 +973.0,0.0 +974.0,0.0 +975.0,0.0 +976.0,0.0 +977.0,0.0 +978.0,0.0 +979.0,0.0 +980.0,0.0 +981.0,0.0 +982.0,0.0 +983.0,0.0 +984.0,0.0 +985.0,0.0 +986.0,0.0 +987.0,0.0 +988.0,0.0 +989.0,0.0 +990.0,0.0 +991.0,0.0 +992.0,0.0 +993.0,0.0 +994.0,0.0 +995.0,0.0 +996.0,0.0 +997.0,0.0 +998.0,0.0 +999.0,0.0 +1000.0,0.0 +1001.0, +1002.0, +1003.0, +1004.0, +1005.0, +1006.0, +1007.0, +1008.0, +1009.0, +1010.0, +1011.0, +1012.0, +1013.0, +1014.0, +1015.0, +1016.0, +1017.0, +1018.0, +1019.0, +1020.0, +1021.0, +1022.0, +1023.0, +1024.0, +1025.0, +1026.0, +1027.0, +1028.0, +1029.0, +1030.0, +1031.0, +1032.0, +1033.0, +1034.0, +1035.0, +1036.0, +1037.0, +1038.0, +1039.0, +1040.0, +1041.0, +1042.0, +1043.0, +1044.0, +1045.0, +1046.0, +1047.0, +1048.0, +1049.0, +1050.0, +1051.0, +1052.0, +1053.0, +1054.0, +1055.0, +1056.0, +1057.0, +1058.0, +1059.0, +1060.0, +1061.0, +1062.0, +1063.0, +1064.0, +1065.0, +1066.0, +1067.0, +1068.0, +1069.0, +1070.0, +1071.0, +1072.0, +1073.0, +1074.0, +1075.0, +1076.0, +1077.0, +1078.0, +1079.0, +1080.0, +1081.0, +1082.0, +1083.0, +1084.0, +1085.0, +1086.0, +1087.0, +1088.0, +1089.0, +1090.0, +1091.0, +1092.0, +1093.0, +1094.0, +1095.0, +1096.0, +1097.0, +1098.0, +1099.0, +1100.0, +1101.0, +1102.0, +1103.0, +1104.0, +1105.0, +1106.0, +1107.0, +1108.0, +1109.0, +1110.0, +1111.0, +1112.0, +1113.0, +1114.0, +1115.0, +1116.0, +1117.0, +1118.0, +1119.0, +1120.0, +1121.0, +1122.0, +1123.0, +1124.0, +1125.0, diff --git a/python/lsst/ts/observatory/control/cal_curves/calibration_tput_init_i.csv b/python/lsst/ts/observatory/control/cal_curves/calibration_tput_init_i.csv new file mode 100644 index 00000000..93cf1e6d --- /dev/null +++ b/python/lsst/ts/observatory/control/cal_curves/calibration_tput_init_i.csv @@ -0,0 +1,807 @@ +Wavelength[nm],Throughput +320.0,0.0 +321.0,0.0 +322.0,0.0 +323.0,0.0 +324.0,0.0 +325.0,0.0 +326.0,0.0 +327.0,0.0 +328.0,0.0 +329.0,0.0 +330.0,0.0 +331.0,0.0 +332.0,0.0 +333.0,0.0 +334.0,0.0 +335.0,0.0 +336.0,0.0 +337.0,0.0 +338.0,0.0 +339.0,0.0 +340.0,0.0 +341.0,0.0 +342.0,0.0 +343.0,0.0 +344.0,0.0 +345.0,0.0 +346.0,0.0 +347.0,0.0 +348.0,0.0 +349.0,0.0 +350.0,0.0 +351.0,0.0 +352.0,0.0 +353.0,0.0 +354.0,0.0 +355.0,0.0 +356.0,0.0 +357.0,0.0 +358.0,0.0 +359.0,0.0 +360.0,0.0 +361.0,0.0 +362.0,0.0 +363.0,0.0 +364.0,0.0 +365.0,0.0 +366.0,0.0 +367.0,0.0 +368.0,0.0 +369.0,0.0 +370.0,0.0 +371.0,0.0 +372.0,0.0 +373.0,0.0 +374.0,0.0 +375.0,0.0 +376.0,0.0 +377.0,0.0 +378.0,0.0 +379.0,0.0 +380.0,0.0 +381.0,0.0 +382.0,0.0 +383.0,0.0 +384.0,0.0 +385.0,0.0 +386.0,0.0 +387.0,0.0 +388.0,0.0 +389.0,0.0 +390.0,0.0 +391.0,0.0 +392.0,0.0 +393.0,0.0 +394.0,0.0 +395.0,0.0 +396.0,0.0 +397.0,0.0 +398.0,0.0 +399.0,0.0 +400.0,0.0 +401.0,0.0 +402.0,0.0 +403.0,0.0 +404.0,0.0 +405.0,0.0 +406.0,0.0 +407.0,0.0 +408.0,0.0 +409.0,0.0 +410.0,0.0 +411.0,0.0 +412.0,0.0 +413.0,0.0 +414.0,0.0 +415.0,0.0 +416.0,0.0 +417.0,0.0 +418.0,0.0 +419.0,0.0 +420.0,0.0 +421.0,0.0 +422.0,0.0 +423.0,0.0 +424.0,0.0 +425.0,0.0 +426.0,0.0 +427.0,0.0 +428.0,0.0 +429.0,0.0 +430.0,0.0 +431.0,0.0 +432.0,0.0 +433.0,0.0 +434.0,0.0 +435.0,0.0 +436.0,0.0 +437.0,0.0 +438.0,0.0 +439.0,0.0 +440.0,0.0 +441.0,0.0 +442.0,0.0 +443.0,0.0 +444.0,0.0 +445.0,0.0 +446.0,0.0 +447.0,0.0 +448.0,0.0 +449.0,0.0 +450.0,0.0 +451.0,0.0 +452.0,0.0 +453.0,0.0 +454.0,0.0 +455.0,0.0 +456.0,0.0 +457.0,0.0 +458.0,0.0 +459.0,0.0 +460.0,0.0 +461.0,0.0 +462.0,0.0 +463.0,0.0 +464.0,0.0 +465.0,0.0 +466.0,0.0 +467.0,0.0 +468.0,0.0 +469.0,0.0 +470.0,0.0 +471.0,0.0 +472.0,0.0 +473.0,0.0 +474.0,0.0 +475.0,0.0 +476.0,0.0 +477.0,0.0 +478.0,0.0 +479.0,0.0 +480.0,0.0 +481.0,0.0 +482.0,0.0 +483.0,0.0 +484.0,0.0 +485.0,0.0 +486.0,0.0 +487.0,0.0 +488.0,0.0 +489.0,0.0 +490.0,0.0 +491.0,0.0 +492.0,0.0 +493.0,0.0 +494.0,0.0 +495.0,0.0 +496.0,0.0 +497.0,0.0 +498.0,0.0 +499.0,0.0 +500.0,0.0 +501.0,0.0 +502.0,0.0 +503.0,0.0 +504.0,0.0 +505.0,0.0 +506.0,0.0 +507.0,0.0 +508.0,0.0 +509.0,0.0 +510.0,0.0 +511.0,0.0 +512.0,0.0 +513.0,0.0 +514.0,0.0 +515.0,0.0 +516.0,0.0 +517.0,0.0 +518.0,0.0 +519.0,0.0 +520.0,0.0 +521.0,0.0 +522.0,0.0 +523.0,0.0 +524.0,0.0 +525.0,0.0 +526.0,0.0 +527.0,0.0 +528.0,0.0 +529.0,0.0 +530.0,0.0 +531.0,0.0 +532.0,0.0 +533.0,0.0 +534.0,0.0 +535.0,0.0 +536.0,0.0 +537.0,0.0 +538.0,0.0 +539.0,0.0 +540.0,0.0 +541.0,0.0 +542.0,0.0 +543.0,0.0 +544.0,0.0 +545.0,0.0 +546.0,0.0 +547.0,0.0 +548.0,0.0 +549.0,0.0 +550.0,0.0 +551.0,0.0 +552.0,0.0 +553.0,0.0 +554.0,0.0 +555.0,0.0 +556.0,0.0 +557.0,0.0 +558.0,0.0 +559.0,0.0 +560.0,0.0 +561.0,0.0 +562.0,0.0 +563.0,0.0 +564.0,0.0 +565.0,0.0 +566.0,0.0 +567.0,0.0 +568.0,0.0 +569.0,0.0 +570.0,0.0 +571.0,0.0 +572.0,0.0 +573.0,0.0 +574.0,0.0 +575.0,0.0 +576.0,0.0 +577.0,0.0 +578.0,0.0 +579.0,0.0 +580.0,0.0 +581.0,0.0 +582.0,0.0 +583.0,0.0 +584.0,0.0 +585.0,0.0 +586.0,0.0 +587.0,0.0 +588.0,0.0 +589.0,0.0 +590.0,0.0 +591.0,0.0 +592.0,0.0 +593.0,0.0 +594.0,0.0 +595.0,0.0 +596.0,0.0 +597.0,0.0 +598.0,0.0 +599.0,0.0 +600.0,0.0 +601.0,0.0 +602.0,0.0 +603.0,0.0 +604.0,0.0 +605.0,0.0 +606.0,0.0 +607.0,0.0 +608.0,0.0 +609.0,0.0 +610.0,0.0 +611.0,0.0 +612.0,0.0 +613.0,0.0 +614.0,0.0 +615.0,0.0 +616.0,0.0 +617.0,0.0 +618.0,0.0 +619.0,0.0 +620.0,0.0 +621.0,0.0 +622.0,0.0 +623.0,0.0 +624.0,0.0 +625.0,0.0 +626.0,0.0 +627.0,0.0 +628.0,0.0 +629.0,0.0 +630.0,0.0 +631.0,0.0 +632.0,0.0 +633.0,0.0 +634.0,0.0 +635.0,0.0 +636.0,0.0 +637.0,0.0 +638.0,0.0 +639.0,0.0 +640.0,0.0 +641.0,0.0 +642.0,0.0 +643.0,0.0 +644.0,0.0 +645.0,0.0 +646.0,0.0 +647.0,0.0 +648.0,0.0 +649.0,0.0 +650.0,0.0 +651.0,0.0 +652.0,0.0 +653.0,0.0 +654.0,0.0 +655.0,9.933280430483123e-08 +656.0,1.1263073758318209e-07 +657.0,1.2516562175634902e-07 +658.0,1.3765930809756968e-07 +659.0,1.5091456712114894e-07 +660.0,1.662463945893274e-07 +661.0,1.8393280378232726e-07 +662.0,2.058705559182558e-07 +663.0,2.344922452269479e-07 +664.0,2.740278364151367e-07 +665.0,3.3075840317701137e-07 +666.0,4.1236474671655925e-07 +667.0,5.325990994023723e-07 +668.0,7.152624407726889e-07 +669.0,9.962248203850016e-07 +670.0,1.4279617848565072e-06 +671.0,2.068623679648106e-06 +672.0,2.8803354133949696e-06 +673.0,3.6458125776751226e-06 +674.0,4.131241637006448e-06 +675.0,4.3534099680852025e-06 +676.0,4.551765264495017e-06 +677.0,4.942029020973247e-06 +678.0,5.524624023279614e-06 +679.0,6.126102653029211e-06 +680.0,6.664999391618532e-06 +681.0,7.23919748683286e-06 +682.0,7.943005416218176e-06 +683.0,8.78837199938411e-06 +684.0,9.711768114152048e-06 +685.0,1.0740183193497861e-05 +686.0,1.2003412406901445e-05 +687.0,1.3484890018405163e-05 +688.0,1.5078920497249594e-05 +689.0,1.6659365599980538e-05 +690.0,1.7949615090232976e-05 +691.0,1.879097400880589e-05 +692.0,1.9262724219689836e-05 +693.0,1.9617596922823022e-05 +694.0,2.0047336180843003e-05 +695.0,2.0747002788515092e-05 +696.0,2.1766295312604094e-05 +697.0,2.305637398793548e-05 +698.0,2.456932902051757e-05 +699.0,2.6041298858519946e-05 +700.0,2.7262440471828517e-05 +701.0,2.814379558524078e-05 +702.0,2.8537105977467685e-05 +703.0,2.8518903556215868e-05 +704.0,2.837956895646215e-05 +705.0,2.8191541540741905e-05 +706.0,2.806101360482517e-05 +707.0,2.8139151718368025e-05 +708.0,2.8309990778160218e-05 +709.0,2.8525473853632953e-05 +710.0,2.8814701651751856e-05 +711.0,2.902334303385923e-05 +712.0,2.9125280172941064e-05 +713.0,2.9200768237895623e-05 +714.0,2.9155104181556297e-05 +715.0,2.9084986167031565e-05 +716.0,2.9030590324730818e-05 +717.0,2.8963916939631873e-05 +718.0,2.887502552800687e-05 +719.0,2.8922220843217207e-05 +720.0,2.8930225555542464e-05 +721.0,2.896140398112042e-05 +722.0,2.9058662417518325e-05 +723.0,2.910880558378127e-05 +724.0,2.9113970922674996e-05 +725.0,2.916493985218555e-05 +726.0,2.9167522260629014e-05 +727.0,2.914413671692509e-05 +728.0,2.9138177049248697e-05 +729.0,2.9098424133115624e-05 +730.0,2.9064540958134114e-05 +731.0,2.9085420578619037e-05 +732.0,2.9099079624463134e-05 +733.0,2.9119547698336645e-05 +734.0,2.915163722619439e-05 +735.0,2.9165598168688917e-05 +736.0,2.9156396502024715e-05 +737.0,2.921291217204029e-05 +738.0,2.9252295376098455e-05 +739.0,2.9273010098576908e-05 +740.0,2.9338606764456976e-05 +741.0,2.936732899680062e-05 +742.0,2.935715943153276e-05 +743.0,2.9386608631362285e-05 +744.0,2.9360021966648058e-05 +745.0,2.932924157061169e-05 +746.0,2.933052265395656e-05 +747.0,2.9282497641252826e-05 +748.0,2.919759881854704e-05 +749.0,2.9170468253916707e-05 +750.0,2.912834855153549e-05 +751.0,2.908167707429422e-05 +752.0,2.906253614442504e-05 +753.0,2.9049338802371313e-05 +754.0,2.9041560846738214e-05 +755.0,2.910644133931573e-05 +756.0,2.915308002376863e-05 +757.0,2.918412782458186e-05 +758.0,2.9249379371045257e-05 +759.0,2.9290034179095314e-05 +760.0,2.9259018213295786e-05 +761.0,2.924026123952071e-05 +762.0,2.9202152632922857e-05 +763.0,2.911742644779901e-05 +764.0,2.9079272080310678e-05 +765.0,2.8985885725868844e-05 +766.0,2.888552581431088e-05 +767.0,2.8846551657629077e-05 +768.0,2.8789317502226884e-05 +769.0,2.872834148037889e-05 +770.0,2.8721428758632858e-05 +771.0,2.8721925977103878e-05 +772.0,2.8717261903873365e-05 +773.0,2.8750776853854995e-05 +774.0,2.8779655664981062e-05 +775.0,2.8784562251648374e-05 +776.0,2.881267540774212e-05 +777.0,2.881151688704591e-05 +778.0,2.8796087899673268e-05 +779.0,2.8790835744330654e-05 +780.0,2.8763125588665565e-05 +781.0,2.8713304720321758e-05 +782.0,2.8685080633822397e-05 +783.0,2.8625457620947543e-05 +784.0,2.857856217460312e-05 +785.0,2.8538684313486448e-05 +786.0,2.849204502919563e-05 +787.0,2.8458316907480754e-05 +788.0,2.843472010080862e-05 +789.0,2.8395205161391783e-05 +790.0,2.8354240494774245e-05 +791.0,2.833870882455387e-05 +792.0,2.8301120741522373e-05 +793.0,2.8278195799861062e-05 +794.0,2.8297528681578066e-05 +795.0,2.8293699904025363e-05 +796.0,2.8282900554610472e-05 +797.0,2.8274784757290825e-05 +798.0,2.822004225916752e-05 +799.0,2.8097113193862316e-05 +800.0,2.7924127891190882e-05 +801.0,2.7634776363156655e-05 +802.0,2.723653936559096e-05 +803.0,2.6750985191517152e-05 +804.0,2.618435244478721e-05 +805.0,2.5542306212396453e-05 +806.0,2.4890007585749625e-05 +807.0,2.4195519180864737e-05 +808.0,2.349311759533162e-05 +809.0,2.2821117381726183e-05 +810.0,2.213969653219752e-05 +811.0,2.149134935342564e-05 +812.0,2.088292250155276e-05 +813.0,2.0275721289997085e-05 +814.0,1.9668171125256874e-05 +815.0,1.9083019250433943e-05 +816.0,1.842122215960297e-05 +817.0,1.769174968996551e-05 +818.0,1.689759346105273e-05 +819.0,1.597264537296089e-05 +820.0,1.494546635977974e-05 +821.0,1.3851738553391297e-05 +822.0,1.2696981161892457e-05 +823.0,1.1525724960735355e-05 +824.0,1.0376397987711995e-05 +825.0,9.270000477531994e-06 +826.0,8.227373608143935e-06 +827.0,7.265038902527831e-06 +828.0,6.377220363955191e-06 +829.0,5.567885067772665e-06 +830.0,4.833014683437098e-06 +831.0,4.167537995306492e-06 +832.0,3.5645230166432626e-06 +833.0,3.0268781232834284e-06 +834.0,2.5512018051853112e-06 +835.0,2.1378145601845973e-06 +836.0,1.7829665337009595e-06 +837.0,1.482779430425011e-06 +838.0,1.2339430952008443e-06 +839.0,1.0274946889490002e-06 +840.0,8.584880859321698e-07 +841.0,7.21887575775059e-07 +842.0,6.118115892522961e-07 +843.0,5.22093686312583e-07 +844.0,4.491777295617182e-07 +845.0,3.891457080691304e-07 +846.0,3.390888755276928e-07 +847.0,2.9826799586790804e-07 +848.0,2.641978834383166e-07 +849.0,2.3493403180977584e-07 +850.0,2.100174497918022e-07 +851.0,1.8850962492785744e-07 +852.0,1.7030108870478955e-07 +853.0,1.5452946613287699e-07 +854.0,1.4077925213964633e-07 +855.0,1.2861899405780531e-07 +856.0,1.1797640000527505e-07 +857.0,1.0890055208153157e-07 +858.0,1.0035312889900328e-07 +859.0,0.0 +860.0,0.0 +861.0,0.0 +862.0,0.0 +863.0,0.0 +864.0,0.0 +865.0,0.0 +866.0,0.0 +867.0,0.0 +868.0,0.0 +869.0,0.0 +870.0,0.0 +871.0,0.0 +872.0,0.0 +873.0,0.0 +874.0,0.0 +875.0,0.0 +876.0,0.0 +877.0,0.0 +878.0,0.0 +879.0,0.0 +880.0,0.0 +881.0,0.0 +882.0,0.0 +883.0,0.0 +884.0,0.0 +885.0,0.0 +886.0,0.0 +887.0,0.0 +888.0,0.0 +889.0,0.0 +890.0,0.0 +891.0,0.0 +892.0,0.0 +893.0,0.0 +894.0,0.0 +895.0,0.0 +896.0,0.0 +897.0,0.0 +898.0,0.0 +899.0,0.0 +900.0,0.0 +901.0,0.0 +902.0,0.0 +903.0,0.0 +904.0,0.0 +905.0,0.0 +906.0,0.0 +907.0,0.0 +908.0,0.0 +909.0,0.0 +910.0,0.0 +911.0,0.0 +912.0,0.0 +913.0,0.0 +914.0,0.0 +915.0,0.0 +916.0,0.0 +917.0,0.0 +918.0,0.0 +919.0,0.0 +920.0,0.0 +921.0,0.0 +922.0,0.0 +923.0,0.0 +924.0,0.0 +925.0,0.0 +926.0,0.0 +927.0,0.0 +928.0,0.0 +929.0,0.0 +930.0,0.0 +931.0,0.0 +932.0,0.0 +933.0,0.0 +934.0,0.0 +935.0,0.0 +936.0,0.0 +937.0,0.0 +938.0,0.0 +939.0,0.0 +940.0,0.0 +941.0,0.0 +942.0,0.0 +943.0,0.0 +944.0,0.0 +945.0,0.0 +946.0,0.0 +947.0,0.0 +948.0,0.0 +949.0,0.0 +950.0,0.0 +951.0,0.0 +952.0,0.0 +953.0,0.0 +954.0,0.0 +955.0,0.0 +956.0,0.0 +957.0,0.0 +958.0,0.0 +959.0,0.0 +960.0,0.0 +961.0,0.0 +962.0,0.0 +963.0,0.0 +964.0,0.0 +965.0,0.0 +966.0,0.0 +967.0,0.0 +968.0,0.0 +969.0,0.0 +970.0,0.0 +971.0,0.0 +972.0,0.0 +973.0,0.0 +974.0,0.0 +975.0,0.0 +976.0,0.0 +977.0,0.0 +978.0,0.0 +979.0,0.0 +980.0,0.0 +981.0,0.0 +982.0,0.0 +983.0,0.0 +984.0,0.0 +985.0,0.0 +986.0,0.0 +987.0,0.0 +988.0,0.0 +989.0,0.0 +990.0,0.0 +991.0,0.0 +992.0,0.0 +993.0,0.0 +994.0,0.0 +995.0,0.0 +996.0,0.0 +997.0,0.0 +998.0,0.0 +999.0,0.0 +1000.0,0.0 +1001.0, +1002.0, +1003.0, +1004.0, +1005.0, +1006.0, +1007.0, +1008.0, +1009.0, +1010.0, +1011.0, +1012.0, +1013.0, +1014.0, +1015.0, +1016.0, +1017.0, +1018.0, +1019.0, +1020.0, +1021.0, +1022.0, +1023.0, +1024.0, +1025.0, +1026.0, +1027.0, +1028.0, +1029.0, +1030.0, +1031.0, +1032.0, +1033.0, +1034.0, +1035.0, +1036.0, +1037.0, +1038.0, +1039.0, +1040.0, +1041.0, +1042.0, +1043.0, +1044.0, +1045.0, +1046.0, +1047.0, +1048.0, +1049.0, +1050.0, +1051.0, +1052.0, +1053.0, +1054.0, +1055.0, +1056.0, +1057.0, +1058.0, +1059.0, +1060.0, +1061.0, +1062.0, +1063.0, +1064.0, +1065.0, +1066.0, +1067.0, +1068.0, +1069.0, +1070.0, +1071.0, +1072.0, +1073.0, +1074.0, +1075.0, +1076.0, +1077.0, +1078.0, +1079.0, +1080.0, +1081.0, +1082.0, +1083.0, +1084.0, +1085.0, +1086.0, +1087.0, +1088.0, +1089.0, +1090.0, +1091.0, +1092.0, +1093.0, +1094.0, +1095.0, +1096.0, +1097.0, +1098.0, +1099.0, +1100.0, +1101.0, +1102.0, +1103.0, +1104.0, +1105.0, +1106.0, +1107.0, +1108.0, +1109.0, +1110.0, +1111.0, +1112.0, +1113.0, +1114.0, +1115.0, +1116.0, +1117.0, +1118.0, +1119.0, +1120.0, +1121.0, +1122.0, +1123.0, +1124.0, +1125.0, diff --git a/python/lsst/ts/observatory/control/cal_curves/calibration_tput_init_r.csv b/python/lsst/ts/observatory/control/cal_curves/calibration_tput_init_r.csv new file mode 100644 index 00000000..2ec0e372 --- /dev/null +++ b/python/lsst/ts/observatory/control/cal_curves/calibration_tput_init_r.csv @@ -0,0 +1,807 @@ +Wavelength[nm],Throughput +320.0,0.0 +321.0,0.0 +322.0,0.0 +323.0,0.0 +324.0,0.0 +325.0,0.0 +326.0,0.0 +327.0,0.0 +328.0,0.0 +329.0,0.0 +330.0,0.0 +331.0,0.0 +332.0,0.0 +333.0,0.0 +334.0,0.0 +335.0,0.0 +336.0,0.0 +337.0,0.0 +338.0,0.0 +339.0,0.0 +340.0,0.0 +341.0,0.0 +342.0,0.0 +343.0,0.0 +344.0,0.0 +345.0,0.0 +346.0,0.0 +347.0,0.0 +348.0,0.0 +349.0,0.0 +350.0,0.0 +351.0,0.0 +352.0,0.0 +353.0,0.0 +354.0,0.0 +355.0,0.0 +356.0,0.0 +357.0,0.0 +358.0,0.0 +359.0,0.0 +360.0,0.0 +361.0,0.0 +362.0,0.0 +363.0,0.0 +364.0,0.0 +365.0,0.0 +366.0,0.0 +367.0,0.0 +368.0,0.0 +369.0,0.0 +370.0,0.0 +371.0,0.0 +372.0,0.0 +373.0,0.0 +374.0,0.0 +375.0,0.0 +376.0,0.0 +377.0,0.0 +378.0,0.0 +379.0,0.0 +380.0,0.0 +381.0,0.0 +382.0,0.0 +383.0,0.0 +384.0,0.0 +385.0,0.0 +386.0,0.0 +387.0,0.0 +388.0,0.0 +389.0,0.0 +390.0,0.0 +391.0,0.0 +392.0,0.0 +393.0,0.0 +394.0,0.0 +395.0,0.0 +396.0,0.0 +397.0,0.0 +398.0,0.0 +399.0,0.0 +400.0,0.0 +401.0,0.0 +402.0,0.0 +403.0,0.0 +404.0,0.0 +405.0,0.0 +406.0,0.0 +407.0,0.0 +408.0,0.0 +409.0,0.0 +410.0,0.0 +411.0,0.0 +412.0,0.0 +413.0,0.0 +414.0,0.0 +415.0,0.0 +416.0,0.0 +417.0,0.0 +418.0,0.0 +419.0,0.0 +420.0,0.0 +421.0,0.0 +422.0,0.0 +423.0,0.0 +424.0,0.0 +425.0,0.0 +426.0,0.0 +427.0,0.0 +428.0,0.0 +429.0,0.0 +430.0,0.0 +431.0,0.0 +432.0,0.0 +433.0,0.0 +434.0,0.0 +435.0,0.0 +436.0,0.0 +437.0,0.0 +438.0,0.0 +439.0,0.0 +440.0,0.0 +441.0,0.0 +442.0,0.0 +443.0,0.0 +444.0,0.0 +445.0,0.0 +446.0,0.0 +447.0,0.0 +448.0,0.0 +449.0,0.0 +450.0,0.0 +451.0,0.0 +452.0,0.0 +453.0,0.0 +454.0,0.0 +455.0,0.0 +456.0,0.0 +457.0,0.0 +458.0,0.0 +459.0,0.0 +460.0,0.0 +461.0,0.0 +462.0,0.0 +463.0,0.0 +464.0,0.0 +465.0,0.0 +466.0,0.0 +467.0,0.0 +468.0,0.0 +469.0,0.0 +470.0,0.0 +471.0,0.0 +472.0,0.0 +473.0,0.0 +474.0,0.0 +475.0,0.0 +476.0,0.0 +477.0,0.0 +478.0,0.0 +479.0,0.0 +480.0,0.0 +481.0,0.0 +482.0,0.0 +483.0,0.0 +484.0,0.0 +485.0,0.0 +486.0,0.0 +487.0,0.0 +488.0,0.0 +489.0,0.0 +490.0,0.0 +491.0,0.0 +492.0,0.0 +493.0,0.0 +494.0,0.0 +495.0,0.0 +496.0,0.0 +497.0,0.0 +498.0,0.0 +499.0,0.0 +500.0,0.0 +501.0,0.0 +502.0,0.0 +503.0,0.0 +504.0,0.0 +505.0,0.0 +506.0,0.0 +507.0,0.0 +508.0,0.0 +509.0,0.0 +510.0,0.0 +511.0,0.0 +512.0,0.0 +513.0,0.0 +514.0,0.0 +515.0,0.0 +516.0,0.0 +517.0,0.0 +518.0,0.0 +519.0,0.0 +520.0,0.0 +521.0,1.0252737299876089e-07 +522.0,1.2505404673453327e-07 +523.0,1.5186587353411323e-07 +524.0,1.8355076636775825e-07 +525.0,2.2125630879700396e-07 +526.0,2.6615639218048306e-07 +527.0,3.195198099018459e-07 +528.0,3.8362458921850074e-07 +529.0,4.6248173005898634e-07 +530.0,5.600894930052812e-07 +531.0,6.866496380898341e-07 +532.0,8.530257289557631e-07 +533.0,1.0738212025878297e-06 +534.0,1.3691922625078497e-06 +535.0,1.7567062257898075e-06 +536.0,2.2453579825868655e-06 +537.0,2.837871946329046e-06 +538.0,3.503462454675896e-06 +539.0,4.170039484912881e-06 +540.0,4.763575986793821e-06 +541.0,5.226348390181365e-06 +542.0,5.5896587472873055e-06 +543.0,5.975193863285298e-06 +544.0,6.503545818553598e-06 +545.0,7.284245297694626e-06 +546.0,8.444591072290544e-06 +547.0,1.006570325121306e-05 +548.0,1.2144186031673684e-05 +549.0,1.4502107406022494e-05 +550.0,1.666083445252459e-05 +551.0,1.8158861932174768e-05 +552.0,1.8965536591384955e-05 +553.0,1.935581578524713e-05 +554.0,1.9666353344581965e-05 +555.0,2.01810393950331e-05 +556.0,2.0973135748615887e-05 +557.0,2.2044110956017524e-05 +558.0,2.3432956023820546e-05 +559.0,2.500968540706111e-05 +560.0,2.6493578333761115e-05 +561.0,2.751828771985479e-05 +562.0,2.7926851826963292e-05 +563.0,2.7940477723626086e-05 +564.0,2.7830623357610694e-05 +565.0,2.7725303112296557e-05 +566.0,2.770325519373857e-05 +567.0,2.769504473352832e-05 +568.0,2.7676629264229456e-05 +569.0,2.7706473979774465e-05 +570.0,2.774738515138291e-05 +571.0,2.7789147617339394e-05 +572.0,2.7883209843677754e-05 +573.0,2.7947678327440325e-05 +574.0,2.7914310477044774e-05 +575.0,2.783996381910628e-05 +576.0,2.7702962470622037e-05 +577.0,2.7529924609177572e-05 +578.0,2.7455247083909147e-05 +579.0,2.7475928054087393e-05 +580.0,2.7555388394749467e-05 +581.0,2.772779200838104e-05 +582.0,2.790547222604529e-05 +583.0,2.799177405959787e-05 +584.0,2.7989617227491833e-05 +585.0,2.786428740054274e-05 +586.0,2.7692020958909188e-05 +587.0,2.7575965684321576e-05 +588.0,2.7490471027927833e-05 +589.0,2.745289145486602e-05 +590.0,2.7523676466397167e-05 +591.0,2.764146203530471e-05 +592.0,2.7721345706167216e-05 +593.0,2.7842303132673465e-05 +594.0,2.792352287903623e-05 +595.0,2.792191261816193e-05 +596.0,2.7903252701154246e-05 +597.0,2.7827962524735577e-05 +598.0,2.7730712256294228e-05 +599.0,2.769379106871747e-05 +600.0,2.7691886870064996e-05 +601.0,2.7715859289458752e-05 +602.0,2.779780443841548e-05 +603.0,2.7920946685189618e-05 +604.0,2.8007719746613405e-05 +605.0,2.808120199545225e-05 +606.0,2.811049581165469e-05 +607.0,2.804980422448403e-05 +608.0,2.8024610263754195e-05 +609.0,2.797515588181518e-05 +610.0,2.7925405346415654e-05 +611.0,2.7974832018202134e-05 +612.0,2.8045583530921168e-05 +613.0,2.8105854049597122e-05 +614.0,2.81981687020968e-05 +615.0,2.8216401984615048e-05 +616.0,2.8143420454456744e-05 +617.0,2.809808901695794e-05 +618.0,2.8023119970716422e-05 +619.0,2.794471883941316e-05 +620.0,2.7974944614114292e-05 +621.0,2.803336208544337e-05 +622.0,2.8089515219326104e-05 +623.0,2.8201219396607283e-05 +624.0,2.8272666800459818e-05 +625.0,2.8278013753401913e-05 +626.0,2.8255623538052984e-05 +627.0,2.8163514908664176e-05 +628.0,2.8083015508204294e-05 +629.0,2.8105603629898354e-05 +630.0,2.8155062676334596e-05 +631.0,2.8192323401355338e-05 +632.0,2.8305495999062498e-05 +633.0,2.8366890753123025e-05 +634.0,2.8354165484164335e-05 +635.0,2.8368007928104737e-05 +636.0,2.8297587196465368e-05 +637.0,2.818292019926794e-05 +638.0,2.815795204648578e-05 +639.0,2.814664436008744e-05 +640.0,2.817359222777104e-05 +641.0,2.828698456334239e-05 +642.0,2.8413167895367545e-05 +643.0,2.8489006768995383e-05 +644.0,2.8546819016555833e-05 +645.0,2.852761849621979e-05 +646.0,2.84555673555524e-05 +647.0,2.842478077195965e-05 +648.0,2.838881583959627e-05 +649.0,2.8354069381001713e-05 +650.0,2.8433788589322606e-05 +651.0,2.8531304315798555e-05 +652.0,2.8631793462236406e-05 +653.0,2.8765482003633712e-05 +654.0,2.884599474068041e-05 +655.0,2.8817460597273974e-05 +656.0,2.8837700286375206e-05 +657.0,2.8778877768164697e-05 +658.0,2.870943449903455e-05 +659.0,2.869504884100305e-05 +660.0,2.871250148804455e-05 +661.0,2.8711973870875544e-05 +662.0,2.8799874281432024e-05 +663.0,2.8842827305471494e-05 +664.0,2.8857624503649692e-05 +665.0,2.893229363027797e-05 +666.0,2.8943012945141406e-05 +667.0,2.89072547876583e-05 +668.0,2.889803350452231e-05 +669.0,2.8864308461516007e-05 +670.0,2.8797647187115896e-05 +671.0,2.882086603352063e-05 +672.0,2.881531154339944e-05 +673.0,2.8755307708667095e-05 +674.0,2.8693582807339926e-05 +675.0,2.8446474308119078e-05 +676.0,2.7974970433283557e-05 +677.0,2.739949523284922e-05 +678.0,2.6654410884541434e-05 +679.0,2.5812010798907395e-05 +680.0,2.4999144057053696e-05 +681.0,2.4181242765836308e-05 +682.0,2.3373253279382637e-05 +683.0,2.264187731468806e-05 +684.0,2.18943923764379e-05 +685.0,2.1065305881775627e-05 +686.0,2.0102715100017834e-05 +687.0,1.8884334902015653e-05 +688.0,1.7559039929395652e-05 +689.0,1.6318368784795028e-05 +690.0,1.5145988706241517e-05 +691.0,1.403027026773912e-05 +692.0,1.294767846100234e-05 +693.0,1.1881349383955749e-05 +694.0,1.075459441717483e-05 +695.0,9.517994834717098e-06 +696.0,8.100963632489058e-06 +697.0,6.509383411836922e-06 +698.0,4.893362052327149e-06 +699.0,3.4373780906684147e-06 +700.0,2.299049735075077e-06 +701.0,1.5156513561444715e-06 +702.0,9.985195047837025e-07 +703.0,6.66481604600759e-07 +704.0,4.552479274712152e-07 +705.0,3.1760146454418034e-07 +706.0,2.2614994036254793e-07 +707.0,1.646621597077562e-07 +708.0,1.21936315114152e-07 +709.0,0.0 +710.0,0.0 +711.0,0.0 +712.0,0.0 +713.0,0.0 +714.0,0.0 +715.0,0.0 +716.0,0.0 +717.0,0.0 +718.0,0.0 +719.0,0.0 +720.0,0.0 +721.0,0.0 +722.0,0.0 +723.0,0.0 +724.0,0.0 +725.0,0.0 +726.0,0.0 +727.0,0.0 +728.0,0.0 +729.0,0.0 +730.0,0.0 +731.0,0.0 +732.0,0.0 +733.0,0.0 +734.0,0.0 +735.0,0.0 +736.0,0.0 +737.0,0.0 +738.0,0.0 +739.0,0.0 +740.0,0.0 +741.0,0.0 +742.0,0.0 +743.0,0.0 +744.0,0.0 +745.0,0.0 +746.0,0.0 +747.0,0.0 +748.0,0.0 +749.0,0.0 +750.0,0.0 +751.0,0.0 +752.0,0.0 +753.0,0.0 +754.0,0.0 +755.0,0.0 +756.0,0.0 +757.0,0.0 +758.0,0.0 +759.0,0.0 +760.0,0.0 +761.0,0.0 +762.0,0.0 +763.0,0.0 +764.0,0.0 +765.0,0.0 +766.0,0.0 +767.0,0.0 +768.0,0.0 +769.0,0.0 +770.0,0.0 +771.0,0.0 +772.0,0.0 +773.0,0.0 +774.0,0.0 +775.0,0.0 +776.0,0.0 +777.0,0.0 +778.0,0.0 +779.0,0.0 +780.0,0.0 +781.0,0.0 +782.0,0.0 +783.0,0.0 +784.0,0.0 +785.0,0.0 +786.0,0.0 +787.0,0.0 +788.0,0.0 +789.0,0.0 +790.0,0.0 +791.0,0.0 +792.0,0.0 +793.0,0.0 +794.0,0.0 +795.0,0.0 +796.0,0.0 +797.0,0.0 +798.0,0.0 +799.0,0.0 +800.0,0.0 +801.0,0.0 +802.0,0.0 +803.0,0.0 +804.0,0.0 +805.0,0.0 +806.0,0.0 +807.0,0.0 +808.0,0.0 +809.0,0.0 +810.0,0.0 +811.0,0.0 +812.0,0.0 +813.0,0.0 +814.0,0.0 +815.0,0.0 +816.0,0.0 +817.0,0.0 +818.0,0.0 +819.0,0.0 +820.0,0.0 +821.0,0.0 +822.0,0.0 +823.0,0.0 +824.0,0.0 +825.0,0.0 +826.0,0.0 +827.0,0.0 +828.0,0.0 +829.0,0.0 +830.0,0.0 +831.0,0.0 +832.0,0.0 +833.0,0.0 +834.0,0.0 +835.0,0.0 +836.0,0.0 +837.0,0.0 +838.0,0.0 +839.0,0.0 +840.0,0.0 +841.0,0.0 +842.0,0.0 +843.0,0.0 +844.0,0.0 +845.0,0.0 +846.0,0.0 +847.0,0.0 +848.0,0.0 +849.0,0.0 +850.0,0.0 +851.0,0.0 +852.0,0.0 +853.0,0.0 +854.0,0.0 +855.0,0.0 +856.0,0.0 +857.0,0.0 +858.0,0.0 +859.0,0.0 +860.0,0.0 +861.0,0.0 +862.0,0.0 +863.0,0.0 +864.0,0.0 +865.0,0.0 +866.0,0.0 +867.0,0.0 +868.0,0.0 +869.0,0.0 +870.0,0.0 +871.0,0.0 +872.0,0.0 +873.0,0.0 +874.0,0.0 +875.0,0.0 +876.0,0.0 +877.0,0.0 +878.0,0.0 +879.0,0.0 +880.0,0.0 +881.0,0.0 +882.0,0.0 +883.0,0.0 +884.0,0.0 +885.0,0.0 +886.0,0.0 +887.0,0.0 +888.0,0.0 +889.0,0.0 +890.0,0.0 +891.0,0.0 +892.0,0.0 +893.0,0.0 +894.0,0.0 +895.0,0.0 +896.0,0.0 +897.0,0.0 +898.0,0.0 +899.0,0.0 +900.0,0.0 +901.0,0.0 +902.0,0.0 +903.0,0.0 +904.0,0.0 +905.0,0.0 +906.0,0.0 +907.0,0.0 +908.0,0.0 +909.0,0.0 +910.0,0.0 +911.0,0.0 +912.0,0.0 +913.0,0.0 +914.0,0.0 +915.0,0.0 +916.0,0.0 +917.0,0.0 +918.0,0.0 +919.0,0.0 +920.0,0.0 +921.0,0.0 +922.0,0.0 +923.0,0.0 +924.0,0.0 +925.0,0.0 +926.0,0.0 +927.0,0.0 +928.0,0.0 +929.0,0.0 +930.0,0.0 +931.0,0.0 +932.0,0.0 +933.0,0.0 +934.0,0.0 +935.0,0.0 +936.0,0.0 +937.0,0.0 +938.0,0.0 +939.0,0.0 +940.0,0.0 +941.0,0.0 +942.0,0.0 +943.0,0.0 +944.0,0.0 +945.0,0.0 +946.0,0.0 +947.0,0.0 +948.0,0.0 +949.0,0.0 +950.0,0.0 +951.0,0.0 +952.0,0.0 +953.0,0.0 +954.0,0.0 +955.0,0.0 +956.0,0.0 +957.0,0.0 +958.0,0.0 +959.0,0.0 +960.0,0.0 +961.0,0.0 +962.0,0.0 +963.0,0.0 +964.0,0.0 +965.0,0.0 +966.0,0.0 +967.0,0.0 +968.0,0.0 +969.0,0.0 +970.0,0.0 +971.0,0.0 +972.0,0.0 +973.0,0.0 +974.0,0.0 +975.0,0.0 +976.0,0.0 +977.0,0.0 +978.0,0.0 +979.0,0.0 +980.0,0.0 +981.0,0.0 +982.0,0.0 +983.0,0.0 +984.0,0.0 +985.0,0.0 +986.0,0.0 +987.0,0.0 +988.0,0.0 +989.0,0.0 +990.0,0.0 +991.0,0.0 +992.0,0.0 +993.0,0.0 +994.0,0.0 +995.0,0.0 +996.0,0.0 +997.0,0.0 +998.0,0.0 +999.0,0.0 +1000.0,0.0 +1001.0, +1002.0, +1003.0, +1004.0, +1005.0, +1006.0, +1007.0, +1008.0, +1009.0, +1010.0, +1011.0, +1012.0, +1013.0, +1014.0, +1015.0, +1016.0, +1017.0, +1018.0, +1019.0, +1020.0, +1021.0, +1022.0, +1023.0, +1024.0, +1025.0, +1026.0, +1027.0, +1028.0, +1029.0, +1030.0, +1031.0, +1032.0, +1033.0, +1034.0, +1035.0, +1036.0, +1037.0, +1038.0, +1039.0, +1040.0, +1041.0, +1042.0, +1043.0, +1044.0, +1045.0, +1046.0, +1047.0, +1048.0, +1049.0, +1050.0, +1051.0, +1052.0, +1053.0, +1054.0, +1055.0, +1056.0, +1057.0, +1058.0, +1059.0, +1060.0, +1061.0, +1062.0, +1063.0, +1064.0, +1065.0, +1066.0, +1067.0, +1068.0, +1069.0, +1070.0, +1071.0, +1072.0, +1073.0, +1074.0, +1075.0, +1076.0, +1077.0, +1078.0, +1079.0, +1080.0, +1081.0, +1082.0, +1083.0, +1084.0, +1085.0, +1086.0, +1087.0, +1088.0, +1089.0, +1090.0, +1091.0, +1092.0, +1093.0, +1094.0, +1095.0, +1096.0, +1097.0, +1098.0, +1099.0, +1100.0, +1101.0, +1102.0, +1103.0, +1104.0, +1105.0, +1106.0, +1107.0, +1108.0, +1109.0, +1110.0, +1111.0, +1112.0, +1113.0, +1114.0, +1115.0, +1116.0, +1117.0, +1118.0, +1119.0, +1120.0, +1121.0, +1122.0, +1123.0, +1124.0, +1125.0, diff --git a/python/lsst/ts/observatory/control/cal_curves/calibration_tput_init_u.csv b/python/lsst/ts/observatory/control/cal_curves/calibration_tput_init_u.csv new file mode 100644 index 00000000..aebb04f2 --- /dev/null +++ b/python/lsst/ts/observatory/control/cal_curves/calibration_tput_init_u.csv @@ -0,0 +1,807 @@ +Wavelength[nm],Throughput +320.0,1.3064060821583233e-06 +321.0,1.5411647848767224e-06 +322.0,2.043861512653295e-06 +323.0,3.1110654520427915e-06 +324.0,4.213180572804155e-06 +325.0,4.937001476600012e-06 +326.0,5.334517248590325e-06 +327.0,6.030918883309032e-06 +328.0,6.973537609972514e-06 +329.0,7.854628544258087e-06 +330.0,8.940291022183392e-06 +331.0,9.376418374992355e-06 +332.0,9.787272234892374e-06 +333.0,1.0211407306468835e-05 +334.0,1.0611495111956468e-05 +335.0,1.1129819885468922e-05 +336.0,1.1582808309076177e-05 +337.0,1.2036272612785535e-05 +338.0,1.2493096004954702e-05 +339.0,1.300199293571253e-05 +340.0,1.3577308551874923e-05 +341.0,1.3817887665565732e-05 +342.0,1.404561011579204e-05 +343.0,1.4226094951609832e-05 +344.0,1.435273837747875e-05 +345.0,1.4527064296199151e-05 +346.0,1.4599452589258485e-05 +347.0,1.4692039906657488e-05 +348.0,1.4869347505745646e-05 +349.0,1.5079779897541386e-05 +350.0,1.535716613958027e-05 +351.0,1.5574004423153928e-05 +352.0,1.5723735614877106e-05 +353.0,1.581351359017009e-05 +354.0,1.587546849251505e-05 +355.0,1.5915945500744592e-05 +356.0,1.5934231643555696e-05 +357.0,1.6018342543322894e-05 +358.0,1.6116944808969376e-05 +359.0,1.6245369370292973e-05 +360.0,1.6448098973940106e-05 +361.0,1.6402482174502894e-05 +362.0,1.633534585690011e-05 +363.0,1.6253733789223945e-05 +364.0,1.6131613201238984e-05 +365.0,1.5962795290400755e-05 +366.0,1.5818345349795297e-05 +367.0,1.565261893303396e-05 +368.0,1.5550277382789066e-05 +369.0,1.5481936438230175e-05 +370.0,1.5363968634630833e-05 +371.0,1.5452663137152602e-05 +372.0,1.553579494660731e-05 +373.0,1.5588448710823286e-05 +374.0,1.5628763910918846e-05 +375.0,1.5689519637464655e-05 +376.0,1.577314577399887e-05 +377.0,1.5887767547734462e-05 +378.0,1.6053490434757235e-05 +379.0,1.621209354378608e-05 +380.0,1.629620647783549e-05 +381.0,1.664531677937116e-05 +382.0,1.6720636335694796e-05 +383.0,1.6019685313834178e-05 +384.0,1.5458845113671918e-05 +385.0,1.4764085548342261e-05 +386.0,1.4006618450318102e-05 +387.0,1.3404302940914085e-05 +388.0,1.3177073751149e-05 +389.0,1.3072961465298319e-05 +390.0,1.2510259585470493e-05 +391.0,1.105658709949602e-05 +392.0,9.12256895018024e-06 +393.0,7.2975468403504375e-06 +394.0,5.941364073438035e-06 +395.0,5.045679583823637e-06 +396.0,4.488129440613109e-06 +397.0,4.072686945919676e-06 +398.0,3.576277007913236e-06 +399.0,2.925235483914582e-06 +400.0,2.2433422662966517e-06 +401.0,1.6811871934814829e-06 +402.0,1.3095584262631711e-06 +403.0,1.0912999522544289e-06 +404.0,9.64939508651549e-07 +405.0,8.70690824097266e-07 +406.0,7.498009598326968e-07 +407.0,5.872114498012886e-07 +408.0,4.3601283483479026e-07 +409.0,3.364889430637831e-07 +410.0,2.838374841054121e-07 +411.0,2.56168123499725e-07 +412.0,2.3381627846500313e-07 +413.0,2.1068705514635334e-07 +414.0,1.9335586841464902e-07 +415.0,1.8823889291293486e-07 +416.0,1.188455695039216e-07 +417.0,7.68664719581215e-08 +418.0,0.0 +419.0,0.0 +420.0,0.0 +421.0,0.0 +422.0,0.0 +423.0,0.0 +424.0,0.0 +425.0,0.0 +426.0,0.0 +427.0,0.0 +428.0,0.0 +429.0,0.0 +430.0,0.0 +431.0,0.0 +432.0,0.0 +433.0,0.0 +434.0,0.0 +435.0,0.0 +436.0,0.0 +437.0,0.0 +438.0,0.0 +439.0,0.0 +440.0,0.0 +441.0,0.0 +442.0,0.0 +443.0,0.0 +444.0,0.0 +445.0,0.0 +446.0,0.0 +447.0,0.0 +448.0,0.0 +449.0,0.0 +450.0,0.0 +451.0,0.0 +452.0,0.0 +453.0,0.0 +454.0,0.0 +455.0,0.0 +456.0,0.0 +457.0,0.0 +458.0,0.0 +459.0,0.0 +460.0,0.0 +461.0,0.0 +462.0,0.0 +463.0,0.0 +464.0,0.0 +465.0,0.0 +466.0,0.0 +467.0,0.0 +468.0,0.0 +469.0,0.0 +470.0,0.0 +471.0,0.0 +472.0,0.0 +473.0,0.0 +474.0,0.0 +475.0,0.0 +476.0,0.0 +477.0,0.0 +478.0,0.0 +479.0,0.0 +480.0,0.0 +481.0,0.0 +482.0,0.0 +483.0,0.0 +484.0,0.0 +485.0,0.0 +486.0,0.0 +487.0,0.0 +488.0,0.0 +489.0,0.0 +490.0,0.0 +491.0,0.0 +492.0,0.0 +493.0,0.0 +494.0,0.0 +495.0,0.0 +496.0,0.0 +497.0,0.0 +498.0,0.0 +499.0,0.0 +500.0,0.0 +501.0,0.0 +502.0,0.0 +503.0,0.0 +504.0,0.0 +505.0,0.0 +506.0,0.0 +507.0,0.0 +508.0,0.0 +509.0,0.0 +510.0,0.0 +511.0,0.0 +512.0,0.0 +513.0,0.0 +514.0,0.0 +515.0,0.0 +516.0,0.0 +517.0,0.0 +518.0,0.0 +519.0,0.0 +520.0,0.0 +521.0,0.0 +522.0,0.0 +523.0,0.0 +524.0,0.0 +525.0,0.0 +526.0,0.0 +527.0,0.0 +528.0,0.0 +529.0,0.0 +530.0,0.0 +531.0,0.0 +532.0,0.0 +533.0,0.0 +534.0,0.0 +535.0,0.0 +536.0,0.0 +537.0,0.0 +538.0,0.0 +539.0,0.0 +540.0,0.0 +541.0,0.0 +542.0,0.0 +543.0,0.0 +544.0,0.0 +545.0,0.0 +546.0,0.0 +547.0,0.0 +548.0,0.0 +549.0,0.0 +550.0,0.0 +551.0,0.0 +552.0,0.0 +553.0,0.0 +554.0,0.0 +555.0,0.0 +556.0,0.0 +557.0,0.0 +558.0,0.0 +559.0,0.0 +560.0,0.0 +561.0,0.0 +562.0,0.0 +563.0,0.0 +564.0,0.0 +565.0,0.0 +566.0,0.0 +567.0,0.0 +568.0,0.0 +569.0,0.0 +570.0,0.0 +571.0,0.0 +572.0,0.0 +573.0,0.0 +574.0,0.0 +575.0,0.0 +576.0,0.0 +577.0,0.0 +578.0,0.0 +579.0,0.0 +580.0,0.0 +581.0,0.0 +582.0,0.0 +583.0,0.0 +584.0,0.0 +585.0,0.0 +586.0,0.0 +587.0,0.0 +588.0,0.0 +589.0,0.0 +590.0,0.0 +591.0,0.0 +592.0,0.0 +593.0,0.0 +594.0,0.0 +595.0,0.0 +596.0,0.0 +597.0,0.0 +598.0,0.0 +599.0,0.0 +600.0,0.0 +601.0,0.0 +602.0,0.0 +603.0,0.0 +604.0,0.0 +605.0,0.0 +606.0,0.0 +607.0,0.0 +608.0,0.0 +609.0,0.0 +610.0,0.0 +611.0,0.0 +612.0,0.0 +613.0,0.0 +614.0,0.0 +615.0,0.0 +616.0,0.0 +617.0,0.0 +618.0,0.0 +619.0,0.0 +620.0,0.0 +621.0,0.0 +622.0,0.0 +623.0,0.0 +624.0,0.0 +625.0,0.0 +626.0,0.0 +627.0,0.0 +628.0,0.0 +629.0,0.0 +630.0,0.0 +631.0,0.0 +632.0,0.0 +633.0,0.0 +634.0,0.0 +635.0,0.0 +636.0,0.0 +637.0,0.0 +638.0,0.0 +639.0,0.0 +640.0,0.0 +641.0,0.0 +642.0,0.0 +643.0,0.0 +644.0,0.0 +645.0,0.0 +646.0,0.0 +647.0,0.0 +648.0,0.0 +649.0,0.0 +650.0,0.0 +651.0,0.0 +652.0,0.0 +653.0,0.0 +654.0,0.0 +655.0,0.0 +656.0,0.0 +657.0,0.0 +658.0,0.0 +659.0,0.0 +660.0,0.0 +661.0,0.0 +662.0,0.0 +663.0,0.0 +664.0,0.0 +665.0,0.0 +666.0,0.0 +667.0,0.0 +668.0,0.0 +669.0,0.0 +670.0,0.0 +671.0,0.0 +672.0,0.0 +673.0,0.0 +674.0,0.0 +675.0,0.0 +676.0,0.0 +677.0,0.0 +678.0,0.0 +679.0,0.0 +680.0,0.0 +681.0,0.0 +682.0,0.0 +683.0,0.0 +684.0,0.0 +685.0,0.0 +686.0,0.0 +687.0,0.0 +688.0,0.0 +689.0,0.0 +690.0,0.0 +691.0,0.0 +692.0,0.0 +693.0,0.0 +694.0,0.0 +695.0,0.0 +696.0,0.0 +697.0,0.0 +698.0,0.0 +699.0,0.0 +700.0,0.0 +701.0,0.0 +702.0,0.0 +703.0,0.0 +704.0,0.0 +705.0,0.0 +706.0,0.0 +707.0,0.0 +708.0,0.0 +709.0,0.0 +710.0,0.0 +711.0,0.0 +712.0,0.0 +713.0,0.0 +714.0,0.0 +715.0,0.0 +716.0,0.0 +717.0,0.0 +718.0,0.0 +719.0,0.0 +720.0,0.0 +721.0,0.0 +722.0,0.0 +723.0,0.0 +724.0,0.0 +725.0,0.0 +726.0,0.0 +727.0,0.0 +728.0,0.0 +729.0,0.0 +730.0,0.0 +731.0,0.0 +732.0,0.0 +733.0,0.0 +734.0,0.0 +735.0,0.0 +736.0,0.0 +737.0,0.0 +738.0,0.0 +739.0,0.0 +740.0,0.0 +741.0,0.0 +742.0,0.0 +743.0,0.0 +744.0,0.0 +745.0,0.0 +746.0,0.0 +747.0,0.0 +748.0,0.0 +749.0,0.0 +750.0,0.0 +751.0,0.0 +752.0,0.0 +753.0,0.0 +754.0,0.0 +755.0,0.0 +756.0,0.0 +757.0,0.0 +758.0,0.0 +759.0,0.0 +760.0,0.0 +761.0,0.0 +762.0,0.0 +763.0,0.0 +764.0,0.0 +765.0,0.0 +766.0,0.0 +767.0,0.0 +768.0,0.0 +769.0,0.0 +770.0,0.0 +771.0,0.0 +772.0,0.0 +773.0,0.0 +774.0,0.0 +775.0,0.0 +776.0,0.0 +777.0,0.0 +778.0,0.0 +779.0,0.0 +780.0,0.0 +781.0,0.0 +782.0,0.0 +783.0,0.0 +784.0,0.0 +785.0,0.0 +786.0,0.0 +787.0,0.0 +788.0,0.0 +789.0,0.0 +790.0,0.0 +791.0,0.0 +792.0,0.0 +793.0,0.0 +794.0,0.0 +795.0,0.0 +796.0,0.0 +797.0,0.0 +798.0,0.0 +799.0,0.0 +800.0,0.0 +801.0,0.0 +802.0,0.0 +803.0,0.0 +804.0,0.0 +805.0,0.0 +806.0,0.0 +807.0,0.0 +808.0,0.0 +809.0,0.0 +810.0,0.0 +811.0,0.0 +812.0,0.0 +813.0,0.0 +814.0,0.0 +815.0,0.0 +816.0,0.0 +817.0,0.0 +818.0,0.0 +819.0,0.0 +820.0,0.0 +821.0,0.0 +822.0,0.0 +823.0,0.0 +824.0,0.0 +825.0,0.0 +826.0,0.0 +827.0,0.0 +828.0,0.0 +829.0,0.0 +830.0,0.0 +831.0,0.0 +832.0,0.0 +833.0,0.0 +834.0,0.0 +835.0,0.0 +836.0,0.0 +837.0,0.0 +838.0,0.0 +839.0,0.0 +840.0,0.0 +841.0,0.0 +842.0,0.0 +843.0,0.0 +844.0,0.0 +845.0,0.0 +846.0,0.0 +847.0,0.0 +848.0,0.0 +849.0,0.0 +850.0,0.0 +851.0,0.0 +852.0,0.0 +853.0,0.0 +854.0,0.0 +855.0,0.0 +856.0,0.0 +857.0,0.0 +858.0,0.0 +859.0,0.0 +860.0,0.0 +861.0,0.0 +862.0,0.0 +863.0,0.0 +864.0,0.0 +865.0,0.0 +866.0,0.0 +867.0,0.0 +868.0,0.0 +869.0,0.0 +870.0,0.0 +871.0,0.0 +872.0,0.0 +873.0,0.0 +874.0,0.0 +875.0,0.0 +876.0,0.0 +877.0,0.0 +878.0,0.0 +879.0,0.0 +880.0,0.0 +881.0,0.0 +882.0,0.0 +883.0,0.0 +884.0,0.0 +885.0,0.0 +886.0,0.0 +887.0,0.0 +888.0,0.0 +889.0,0.0 +890.0,0.0 +891.0,0.0 +892.0,0.0 +893.0,0.0 +894.0,0.0 +895.0,0.0 +896.0,0.0 +897.0,0.0 +898.0,0.0 +899.0,0.0 +900.0,0.0 +901.0,0.0 +902.0,0.0 +903.0,0.0 +904.0,0.0 +905.0,0.0 +906.0,0.0 +907.0,0.0 +908.0,0.0 +909.0,0.0 +910.0,0.0 +911.0,0.0 +912.0,0.0 +913.0,0.0 +914.0,0.0 +915.0,0.0 +916.0,0.0 +917.0,0.0 +918.0,0.0 +919.0,0.0 +920.0,0.0 +921.0,0.0 +922.0,0.0 +923.0,0.0 +924.0,0.0 +925.0,0.0 +926.0,0.0 +927.0,0.0 +928.0,0.0 +929.0,0.0 +930.0,0.0 +931.0,0.0 +932.0,0.0 +933.0,0.0 +934.0,0.0 +935.0,0.0 +936.0,0.0 +937.0,0.0 +938.0,0.0 +939.0,0.0 +940.0,0.0 +941.0,0.0 +942.0,0.0 +943.0,0.0 +944.0,0.0 +945.0,0.0 +946.0,0.0 +947.0,0.0 +948.0,0.0 +949.0,0.0 +950.0,0.0 +951.0,0.0 +952.0,0.0 +953.0,0.0 +954.0,0.0 +955.0,0.0 +956.0,0.0 +957.0,0.0 +958.0,0.0 +959.0,0.0 +960.0,0.0 +961.0,0.0 +962.0,0.0 +963.0,0.0 +964.0,0.0 +965.0,0.0 +966.0,0.0 +967.0,0.0 +968.0,0.0 +969.0,0.0 +970.0,0.0 +971.0,0.0 +972.0,0.0 +973.0,0.0 +974.0,0.0 +975.0,0.0 +976.0,0.0 +977.0,0.0 +978.0,0.0 +979.0,0.0 +980.0,0.0 +981.0,0.0 +982.0,0.0 +983.0,0.0 +984.0,0.0 +985.0,0.0 +986.0,0.0 +987.0,0.0 +988.0,0.0 +989.0,0.0 +990.0,0.0 +991.0,0.0 +992.0,0.0 +993.0,0.0 +994.0,0.0 +995.0,0.0 +996.0,0.0 +997.0,0.0 +998.0,0.0 +999.0,0.0 +1000.0,0.0 +1001.0, +1002.0, +1003.0, +1004.0, +1005.0, +1006.0, +1007.0, +1008.0, +1009.0, +1010.0, +1011.0, +1012.0, +1013.0, +1014.0, +1015.0, +1016.0, +1017.0, +1018.0, +1019.0, +1020.0, +1021.0, +1022.0, +1023.0, +1024.0, +1025.0, +1026.0, +1027.0, +1028.0, +1029.0, +1030.0, +1031.0, +1032.0, +1033.0, +1034.0, +1035.0, +1036.0, +1037.0, +1038.0, +1039.0, +1040.0, +1041.0, +1042.0, +1043.0, +1044.0, +1045.0, +1046.0, +1047.0, +1048.0, +1049.0, +1050.0, +1051.0, +1052.0, +1053.0, +1054.0, +1055.0, +1056.0, +1057.0, +1058.0, +1059.0, +1060.0, +1061.0, +1062.0, +1063.0, +1064.0, +1065.0, +1066.0, +1067.0, +1068.0, +1069.0, +1070.0, +1071.0, +1072.0, +1073.0, +1074.0, +1075.0, +1076.0, +1077.0, +1078.0, +1079.0, +1080.0, +1081.0, +1082.0, +1083.0, +1084.0, +1085.0, +1086.0, +1087.0, +1088.0, +1089.0, +1090.0, +1091.0, +1092.0, +1093.0, +1094.0, +1095.0, +1096.0, +1097.0, +1098.0, +1099.0, +1100.0, +1101.0, +1102.0, +1103.0, +1104.0, +1105.0, +1106.0, +1107.0, +1108.0, +1109.0, +1110.0, +1111.0, +1112.0, +1113.0, +1114.0, +1115.0, +1116.0, +1117.0, +1118.0, +1119.0, +1120.0, +1121.0, +1122.0, +1123.0, +1124.0, +1125.0, diff --git a/python/lsst/ts/observatory/control/cal_curves/calibration_tput_init_y4.csv b/python/lsst/ts/observatory/control/cal_curves/calibration_tput_init_y4.csv new file mode 100644 index 00000000..d34ed26a --- /dev/null +++ b/python/lsst/ts/observatory/control/cal_curves/calibration_tput_init_y4.csv @@ -0,0 +1,807 @@ +Wavelength[nm],Throughput +320.0,0.0 +321.0,0.0 +322.0,0.0 +323.0,0.0 +324.0,0.0 +325.0,0.0 +326.0,0.0 +327.0,0.0 +328.0,0.0 +329.0,0.0 +330.0,0.0 +331.0,0.0 +332.0,0.0 +333.0,0.0 +334.0,0.0 +335.0,0.0 +336.0,0.0 +337.0,0.0 +338.0,0.0 +339.0,0.0 +340.0,0.0 +341.0,0.0 +342.0,0.0 +343.0,0.0 +344.0,0.0 +345.0,0.0 +346.0,0.0 +347.0,0.0 +348.0,0.0 +349.0,0.0 +350.0,0.0 +351.0,0.0 +352.0,0.0 +353.0,0.0 +354.0,0.0 +355.0,0.0 +356.0,0.0 +357.0,0.0 +358.0,0.0 +359.0,0.0 +360.0,0.0 +361.0,0.0 +362.0,0.0 +363.0,0.0 +364.0,0.0 +365.0,0.0 +366.0,0.0 +367.0,0.0 +368.0,0.0 +369.0,0.0 +370.0,0.0 +371.0,0.0 +372.0,0.0 +373.0,0.0 +374.0,0.0 +375.0,0.0 +376.0,0.0 +377.0,0.0 +378.0,0.0 +379.0,0.0 +380.0,0.0 +381.0,0.0 +382.0,0.0 +383.0,0.0 +384.0,0.0 +385.0,0.0 +386.0,0.0 +387.0,0.0 +388.0,0.0 +389.0,0.0 +390.0,0.0 +391.0,0.0 +392.0,0.0 +393.0,0.0 +394.0,0.0 +395.0,0.0 +396.0,0.0 +397.0,0.0 +398.0,0.0 +399.0,0.0 +400.0,0.0 +401.0,0.0 +402.0,0.0 +403.0,0.0 +404.0,0.0 +405.0,0.0 +406.0,0.0 +407.0,0.0 +408.0,0.0 +409.0,0.0 +410.0,0.0 +411.0,0.0 +412.0,0.0 +413.0,0.0 +414.0,0.0 +415.0,0.0 +416.0,0.0 +417.0,0.0 +418.0,0.0 +419.0,0.0 +420.0,0.0 +421.0,0.0 +422.0,0.0 +423.0,0.0 +424.0,0.0 +425.0,0.0 +426.0,0.0 +427.0,0.0 +428.0,0.0 +429.0,0.0 +430.0,0.0 +431.0,0.0 +432.0,0.0 +433.0,0.0 +434.0,0.0 +435.0,0.0 +436.0,0.0 +437.0,0.0 +438.0,0.0 +439.0,0.0 +440.0,0.0 +441.0,0.0 +442.0,0.0 +443.0,0.0 +444.0,0.0 +445.0,0.0 +446.0,0.0 +447.0,0.0 +448.0,0.0 +449.0,0.0 +450.0,0.0 +451.0,0.0 +452.0,0.0 +453.0,0.0 +454.0,0.0 +455.0,0.0 +456.0,0.0 +457.0,0.0 +458.0,0.0 +459.0,0.0 +460.0,0.0 +461.0,0.0 +462.0,0.0 +463.0,0.0 +464.0,0.0 +465.0,0.0 +466.0,0.0 +467.0,0.0 +468.0,0.0 +469.0,0.0 +470.0,0.0 +471.0,0.0 +472.0,0.0 +473.0,0.0 +474.0,0.0 +475.0,0.0 +476.0,0.0 +477.0,0.0 +478.0,0.0 +479.0,0.0 +480.0,0.0 +481.0,0.0 +482.0,0.0 +483.0,0.0 +484.0,0.0 +485.0,0.0 +486.0,0.0 +487.0,0.0 +488.0,0.0 +489.0,0.0 +490.0,0.0 +491.0,0.0 +492.0,0.0 +493.0,0.0 +494.0,0.0 +495.0,0.0 +496.0,0.0 +497.0,0.0 +498.0,0.0 +499.0,0.0 +500.0,0.0 +501.0,0.0 +502.0,0.0 +503.0,0.0 +504.0,0.0 +505.0,0.0 +506.0,0.0 +507.0,0.0 +508.0,0.0 +509.0,0.0 +510.0,0.0 +511.0,0.0 +512.0,0.0 +513.0,0.0 +514.0,0.0 +515.0,0.0 +516.0,0.0 +517.0,0.0 +518.0,0.0 +519.0,0.0 +520.0,0.0 +521.0,0.0 +522.0,0.0 +523.0,0.0 +524.0,0.0 +525.0,0.0 +526.0,0.0 +527.0,0.0 +528.0,0.0 +529.0,0.0 +530.0,0.0 +531.0,0.0 +532.0,0.0 +533.0,0.0 +534.0,0.0 +535.0,0.0 +536.0,0.0 +537.0,0.0 +538.0,0.0 +539.0,0.0 +540.0,0.0 +541.0,0.0 +542.0,0.0 +543.0,0.0 +544.0,0.0 +545.0,0.0 +546.0,0.0 +547.0,0.0 +548.0,0.0 +549.0,0.0 +550.0,0.0 +551.0,0.0 +552.0,0.0 +553.0,0.0 +554.0,0.0 +555.0,0.0 +556.0,0.0 +557.0,0.0 +558.0,0.0 +559.0,0.0 +560.0,0.0 +561.0,0.0 +562.0,0.0 +563.0,0.0 +564.0,0.0 +565.0,0.0 +566.0,0.0 +567.0,0.0 +568.0,0.0 +569.0,0.0 +570.0,0.0 +571.0,0.0 +572.0,0.0 +573.0,0.0 +574.0,0.0 +575.0,0.0 +576.0,0.0 +577.0,0.0 +578.0,0.0 +579.0,0.0 +580.0,0.0 +581.0,0.0 +582.0,0.0 +583.0,0.0 +584.0,0.0 +585.0,0.0 +586.0,0.0 +587.0,0.0 +588.0,0.0 +589.0,0.0 +590.0,0.0 +591.0,0.0 +592.0,0.0 +593.0,0.0 +594.0,0.0 +595.0,0.0 +596.0,0.0 +597.0,0.0 +598.0,0.0 +599.0,0.0 +600.0,0.0 +601.0,0.0 +602.0,0.0 +603.0,0.0 +604.0,0.0 +605.0,0.0 +606.0,0.0 +607.0,0.0 +608.0,0.0 +609.0,0.0 +610.0,0.0 +611.0,0.0 +612.0,0.0 +613.0,0.0 +614.0,0.0 +615.0,0.0 +616.0,0.0 +617.0,0.0 +618.0,0.0 +619.0,0.0 +620.0,0.0 +621.0,0.0 +622.0,0.0 +623.0,0.0 +624.0,0.0 +625.0,0.0 +626.0,0.0 +627.0,0.0 +628.0,0.0 +629.0,0.0 +630.0,0.0 +631.0,0.0 +632.0,0.0 +633.0,0.0 +634.0,0.0 +635.0,0.0 +636.0,0.0 +637.0,0.0 +638.0,0.0 +639.0,0.0 +640.0,0.0 +641.0,0.0 +642.0,0.0 +643.0,0.0 +644.0,0.0 +645.0,0.0 +646.0,0.0 +647.0,0.0 +648.0,0.0 +649.0,0.0 +650.0,0.0 +651.0,0.0 +652.0,0.0 +653.0,0.0 +654.0,0.0 +655.0,0.0 +656.0,0.0 +657.0,0.0 +658.0,0.0 +659.0,0.0 +660.0,0.0 +661.0,0.0 +662.0,0.0 +663.0,0.0 +664.0,0.0 +665.0,0.0 +666.0,0.0 +667.0,0.0 +668.0,0.0 +669.0,0.0 +670.0,0.0 +671.0,0.0 +672.0,0.0 +673.0,0.0 +674.0,0.0 +675.0,0.0 +676.0,0.0 +677.0,0.0 +678.0,0.0 +679.0,0.0 +680.0,0.0 +681.0,0.0 +682.0,0.0 +683.0,0.0 +684.0,0.0 +685.0,0.0 +686.0,0.0 +687.0,0.0 +688.0,0.0 +689.0,0.0 +690.0,0.0 +691.0,0.0 +692.0,0.0 +693.0,0.0 +694.0,0.0 +695.0,0.0 +696.0,0.0 +697.0,0.0 +698.0,0.0 +699.0,0.0 +700.0,0.0 +701.0,0.0 +702.0,0.0 +703.0,0.0 +704.0,0.0 +705.0,0.0 +706.0,0.0 +707.0,0.0 +708.0,0.0 +709.0,0.0 +710.0,0.0 +711.0,0.0 +712.0,0.0 +713.0,0.0 +714.0,0.0 +715.0,0.0 +716.0,0.0 +717.0,0.0 +718.0,0.0 +719.0,0.0 +720.0,0.0 +721.0,0.0 +722.0,0.0 +723.0,0.0 +724.0,0.0 +725.0,0.0 +726.0,0.0 +727.0,0.0 +728.0,0.0 +729.0,0.0 +730.0,0.0 +731.0,0.0 +732.0,0.0 +733.0,0.0 +734.0,0.0 +735.0,0.0 +736.0,0.0 +737.0,0.0 +738.0,0.0 +739.0,0.0 +740.0,0.0 +741.0,0.0 +742.0,0.0 +743.0,0.0 +744.0,0.0 +745.0,0.0 +746.0,0.0 +747.0,0.0 +748.0,0.0 +749.0,0.0 +750.0,0.0 +751.0,0.0 +752.0,0.0 +753.0,0.0 +754.0,0.0 +755.0,0.0 +756.0,0.0 +757.0,0.0 +758.0,0.0 +759.0,0.0 +760.0,0.0 +761.0,0.0 +762.0,0.0 +763.0,0.0 +764.0,0.0 +765.0,0.0 +766.0,0.0 +767.0,0.0 +768.0,0.0 +769.0,0.0 +770.0,0.0 +771.0,0.0 +772.0,0.0 +773.0,0.0 +774.0,0.0 +775.0,0.0 +776.0,0.0 +777.0,0.0 +778.0,0.0 +779.0,0.0 +780.0,0.0 +781.0,0.0 +782.0,0.0 +783.0,0.0 +784.0,0.0 +785.0,0.0 +786.0,0.0 +787.0,0.0 +788.0,0.0 +789.0,0.0 +790.0,0.0 +791.0,0.0 +792.0,0.0 +793.0,0.0 +794.0,0.0 +795.0,0.0 +796.0,0.0 +797.0,0.0 +798.0,0.0 +799.0,0.0 +800.0,0.0 +801.0,0.0 +802.0,0.0 +803.0,0.0 +804.0,0.0 +805.0,0.0 +806.0,0.0 +807.0,0.0 +808.0,0.0 +809.0,0.0 +810.0,0.0 +811.0,0.0 +812.0,0.0 +813.0,0.0 +814.0,0.0 +815.0,0.0 +816.0,0.0 +817.0,0.0 +818.0,0.0 +819.0,0.0 +820.0,0.0 +821.0,0.0 +822.0,0.0 +823.0,0.0 +824.0,0.0 +825.0,0.0 +826.0,0.0 +827.0,0.0 +828.0,0.0 +829.0,0.0 +830.0,0.0 +831.0,0.0 +832.0,0.0 +833.0,0.0 +834.0,0.0 +835.0,0.0 +836.0,0.0 +837.0,0.0 +838.0,0.0 +839.0,0.0 +840.0,0.0 +841.0,0.0 +842.0,0.0 +843.0,0.0 +844.0,0.0 +845.0,0.0 +846.0,0.0 +847.0,0.0 +848.0,0.0 +849.0,0.0 +850.0,0.0 +851.0,0.0 +852.0,0.0 +853.0,0.0 +854.0,0.0 +855.0,0.0 +856.0,0.0 +857.0,0.0 +858.0,0.0 +859.0,0.0 +860.0,0.0 +861.0,0.0 +862.0,0.0 +863.0,0.0 +864.0,0.0 +865.0,0.0 +866.0,0.0 +867.0,0.0 +868.0,0.0 +869.0,0.0 +870.0,0.0 +871.0,0.0 +872.0,0.0 +873.0,0.0 +874.0,1.1020016181941213e-07 +875.0,1.2236123693675476e-07 +876.0,1.319784153450368e-07 +877.0,1.4038026946874895e-07 +878.0,1.4894694860921922e-07 +879.0,1.5872865808274355e-07 +880.0,1.7035483282834546e-07 +881.0,1.8442783701209868e-07 +882.0,2.0152211723596726e-07 +883.0,2.2242432058784164e-07 +884.0,2.47867422888994e-07 +885.0,2.7825047289781306e-07 +886.0,3.136902544176224e-07 +887.0,3.542263096861582e-07 +888.0,4.000791072906008e-07 +889.0,4.5151578279049975e-07 +890.0,5.089977871978955e-07 +891.0,5.726862252701649e-07 +892.0,6.42636395778505e-07 +893.0,7.200464237973587e-07 +894.0,8.064275404908843e-07 +895.0,9.049696827339625e-07 +896.0,1.0198287453859744e-06 +897.0,1.1559819312702198e-06 +898.0,1.3180396858550791e-06 +899.0,1.5070993393571294e-06 +900.0,1.7239001509909039e-06 +901.0,1.9672797332833476e-06 +902.0,2.237059300066932e-06 +903.0,2.535019583103752e-06 +904.0,2.8656999196314162e-06 +905.0,3.233471132618951e-06 +906.0,3.6451646689442825e-06 +907.0,4.114588295517597e-06 +908.0,4.648209895928916e-06 +909.0,5.246944178578426e-06 +910.0,5.908446239630127e-06 +911.0,6.604541440501929e-06 +912.0,7.284433937438564e-06 +913.0,7.890563522108509e-06 +914.0,8.375269349252958e-06 +915.0,8.747255471081424e-06 +916.0,9.065332599281774e-06 +917.0,9.402863658836441e-06 +918.0,9.84810840300006e-06 +919.0,1.0491441687294419e-05 +920.0,1.1368508674627754e-05 +921.0,1.2447200899882101e-05 +922.0,1.365373971803495e-05 +923.0,1.4796632374394742e-05 +924.0,1.5674668825810472e-05 +925.0,1.6274913726656675e-05 +926.0,1.667298864856432e-05 +927.0,1.7017265747778827e-05 +928.0,1.7419833614067153e-05 +929.0,1.7942848758576485e-05 +930.0,1.8642862910758417e-05 +931.0,1.9497249924153757e-05 +932.0,2.046985854677455e-05 +933.0,2.150527116243159e-05 +934.0,2.2498724360688233e-05 +935.0,2.3353444341692758e-05 +936.0,2.4036609852433213e-05 +937.0,2.4511736089859804e-05 +938.0,2.4736565891410447e-05 +939.0,2.4716650592270014e-05 +940.0,2.4508315777910297e-05 +941.0,2.425820970381708e-05 +942.0,2.4020043719594456e-05 +943.0,2.3873723174294723e-05 +944.0,2.3764166594397726e-05 +945.0,2.368890687523874e-05 +946.0,2.360841651739761e-05 +947.0,2.3508548987216387e-05 +948.0,2.3379129751847576e-05 +949.0,2.3164728689343773e-05 +950.0,2.2900355902421687e-05 +951.0,2.262092184054147e-05 +952.0,2.2341021623361022e-05 +953.0,2.2062951348648808e-05 +954.0,2.1816835119661656e-05 +955.0,2.158945360922262e-05 +956.0,2.1418379125643305e-05 +957.0,2.1277382490588542e-05 +958.0,2.1133701801015077e-05 +959.0,2.1004559743889332e-05 +960.0,2.0871967257235394e-05 +961.0,2.0754013302390363e-05 +962.0,2.058820160106351e-05 +963.0,2.0386093767332936e-05 +964.0,2.0147547714184278e-05 +965.0,1.9897559228360664e-05 +966.0,1.9637886853199217e-05 +967.0,1.9349966514872348e-05 +968.0,1.9073625617927965e-05 +969.0,1.8817052854294142e-05 +970.0,1.8577330910571106e-05 +971.0,1.8363019090702967e-05 +972.0,1.8153552692819964e-05 +973.0,1.795300168006777e-05 +974.0,1.7746154352373093e-05 +975.0,1.7566109331089393e-05 +976.0,1.7359838701616147e-05 +977.0,1.7141108530443453e-05 +978.0,1.6909112739981606e-05 +979.0,1.667183569763827e-05 +980.0,1.6412738904975785e-05 +981.0,1.6220622809475688e-05 +982.0,1.602315360563085e-05 +983.0,1.583519702103979e-05 +984.0,1.563668461772591e-05 +985.0,1.5445852871252275e-05 +986.0,1.5236449928957628e-05 +987.0,1.50424956780145e-05 +988.0,1.4842356061617117e-05 +989.0,1.4634937804817078e-05 +990.0,1.4413564667137e-05 +991.0,1.409832023997959e-05 +992.0,1.3785373199212948e-05 +993.0,1.3470422524188055e-05 +994.0,1.3129199667549475e-05 +995.0,1.2800933052609684e-05 +996.0,1.2471005561455845e-05 +997.0,1.2148712207390068e-05 +998.0,1.1826756590667509e-05 +999.0,1.1518146675677225e-05 +1000.0,1.1213952215282243e-05 +1001.0, +1002.0, +1003.0, +1004.0, +1005.0, +1006.0, +1007.0, +1008.0, +1009.0, +1010.0, +1011.0, +1012.0, +1013.0, +1014.0, +1015.0, +1016.0, +1017.0, +1018.0, +1019.0, +1020.0, +1021.0, +1022.0, +1023.0, +1024.0, +1025.0, +1026.0, +1027.0, +1028.0, +1029.0, +1030.0, +1031.0, +1032.0, +1033.0, +1034.0, +1035.0, +1036.0, +1037.0, +1038.0, +1039.0, +1040.0, +1041.0, +1042.0, +1043.0, +1044.0, +1045.0, +1046.0, +1047.0, +1048.0, +1049.0, +1050.0, +1051.0, +1052.0, +1053.0, +1054.0, +1055.0, +1056.0, +1057.0, +1058.0, +1059.0, +1060.0, +1061.0, +1062.0, +1063.0, +1064.0, +1065.0, +1066.0, +1067.0, +1068.0, +1069.0, +1070.0, +1071.0, +1072.0, +1073.0, +1074.0, +1075.0, +1076.0, +1077.0, +1078.0, +1079.0, +1080.0, +1081.0, +1082.0, +1083.0, +1084.0, +1085.0, +1086.0, +1087.0, +1088.0, +1089.0, +1090.0, +1091.0, +1092.0, +1093.0, +1094.0, +1095.0, +1096.0, +1097.0, +1098.0, +1099.0, +1100.0, +1101.0, +1102.0, +1103.0, +1104.0, +1105.0, +1106.0, +1107.0, +1108.0, +1109.0, +1110.0, +1111.0, +1112.0, +1113.0, +1114.0, +1115.0, +1116.0, +1117.0, +1118.0, +1119.0, +1120.0, +1121.0, +1122.0, +1123.0, +1124.0, +1125.0, diff --git a/python/lsst/ts/observatory/control/cal_curves/calibration_tput_init_z.csv b/python/lsst/ts/observatory/control/cal_curves/calibration_tput_init_z.csv new file mode 100644 index 00000000..abe47ab3 --- /dev/null +++ b/python/lsst/ts/observatory/control/cal_curves/calibration_tput_init_z.csv @@ -0,0 +1,807 @@ +Wavelength[nm],Throughput +320.0,0.0 +321.0,0.0 +322.0,0.0 +323.0,0.0 +324.0,0.0 +325.0,0.0 +326.0,0.0 +327.0,0.0 +328.0,0.0 +329.0,0.0 +330.0,0.0 +331.0,0.0 +332.0,0.0 +333.0,0.0 +334.0,0.0 +335.0,0.0 +336.0,0.0 +337.0,0.0 +338.0,0.0 +339.0,0.0 +340.0,0.0 +341.0,0.0 +342.0,0.0 +343.0,0.0 +344.0,0.0 +345.0,0.0 +346.0,0.0 +347.0,0.0 +348.0,0.0 +349.0,0.0 +350.0,0.0 +351.0,0.0 +352.0,0.0 +353.0,0.0 +354.0,0.0 +355.0,0.0 +356.0,0.0 +357.0,0.0 +358.0,0.0 +359.0,0.0 +360.0,0.0 +361.0,0.0 +362.0,0.0 +363.0,0.0 +364.0,0.0 +365.0,0.0 +366.0,0.0 +367.0,0.0 +368.0,0.0 +369.0,0.0 +370.0,0.0 +371.0,0.0 +372.0,0.0 +373.0,0.0 +374.0,0.0 +375.0,0.0 +376.0,0.0 +377.0,0.0 +378.0,0.0 +379.0,0.0 +380.0,0.0 +381.0,0.0 +382.0,0.0 +383.0,0.0 +384.0,0.0 +385.0,0.0 +386.0,0.0 +387.0,0.0 +388.0,0.0 +389.0,0.0 +390.0,0.0 +391.0,0.0 +392.0,0.0 +393.0,0.0 +394.0,0.0 +395.0,0.0 +396.0,0.0 +397.0,0.0 +398.0,0.0 +399.0,0.0 +400.0,0.0 +401.0,0.0 +402.0,0.0 +403.0,0.0 +404.0,0.0 +405.0,0.0 +406.0,0.0 +407.0,0.0 +408.0,0.0 +409.0,0.0 +410.0,0.0 +411.0,0.0 +412.0,0.0 +413.0,0.0 +414.0,0.0 +415.0,0.0 +416.0,0.0 +417.0,0.0 +418.0,0.0 +419.0,0.0 +420.0,0.0 +421.0,0.0 +422.0,0.0 +423.0,0.0 +424.0,0.0 +425.0,0.0 +426.0,0.0 +427.0,0.0 +428.0,0.0 +429.0,0.0 +430.0,0.0 +431.0,0.0 +432.0,0.0 +433.0,0.0 +434.0,0.0 +435.0,0.0 +436.0,0.0 +437.0,0.0 +438.0,0.0 +439.0,0.0 +440.0,0.0 +441.0,0.0 +442.0,0.0 +443.0,0.0 +444.0,0.0 +445.0,0.0 +446.0,0.0 +447.0,0.0 +448.0,0.0 +449.0,0.0 +450.0,0.0 +451.0,0.0 +452.0,0.0 +453.0,0.0 +454.0,0.0 +455.0,0.0 +456.0,0.0 +457.0,0.0 +458.0,0.0 +459.0,0.0 +460.0,0.0 +461.0,0.0 +462.0,0.0 +463.0,0.0 +464.0,0.0 +465.0,0.0 +466.0,0.0 +467.0,0.0 +468.0,0.0 +469.0,0.0 +470.0,0.0 +471.0,0.0 +472.0,0.0 +473.0,0.0 +474.0,0.0 +475.0,0.0 +476.0,0.0 +477.0,0.0 +478.0,0.0 +479.0,0.0 +480.0,0.0 +481.0,0.0 +482.0,0.0 +483.0,0.0 +484.0,0.0 +485.0,0.0 +486.0,0.0 +487.0,0.0 +488.0,0.0 +489.0,0.0 +490.0,0.0 +491.0,0.0 +492.0,0.0 +493.0,0.0 +494.0,0.0 +495.0,0.0 +496.0,0.0 +497.0,0.0 +498.0,0.0 +499.0,0.0 +500.0,0.0 +501.0,0.0 +502.0,0.0 +503.0,0.0 +504.0,0.0 +505.0,0.0 +506.0,0.0 +507.0,0.0 +508.0,0.0 +509.0,0.0 +510.0,0.0 +511.0,0.0 +512.0,0.0 +513.0,0.0 +514.0,0.0 +515.0,0.0 +516.0,0.0 +517.0,0.0 +518.0,0.0 +519.0,0.0 +520.0,0.0 +521.0,0.0 +522.0,0.0 +523.0,0.0 +524.0,0.0 +525.0,0.0 +526.0,0.0 +527.0,0.0 +528.0,0.0 +529.0,0.0 +530.0,0.0 +531.0,0.0 +532.0,0.0 +533.0,0.0 +534.0,0.0 +535.0,0.0 +536.0,0.0 +537.0,0.0 +538.0,0.0 +539.0,0.0 +540.0,0.0 +541.0,0.0 +542.0,0.0 +543.0,0.0 +544.0,0.0 +545.0,0.0 +546.0,0.0 +547.0,0.0 +548.0,0.0 +549.0,0.0 +550.0,0.0 +551.0,0.0 +552.0,0.0 +553.0,0.0 +554.0,0.0 +555.0,0.0 +556.0,0.0 +557.0,0.0 +558.0,0.0 +559.0,0.0 +560.0,0.0 +561.0,0.0 +562.0,0.0 +563.0,0.0 +564.0,0.0 +565.0,0.0 +566.0,0.0 +567.0,0.0 +568.0,0.0 +569.0,0.0 +570.0,0.0 +571.0,0.0 +572.0,0.0 +573.0,0.0 +574.0,0.0 +575.0,0.0 +576.0,0.0 +577.0,0.0 +578.0,0.0 +579.0,0.0 +580.0,0.0 +581.0,0.0 +582.0,0.0 +583.0,0.0 +584.0,0.0 +585.0,0.0 +586.0,0.0 +587.0,0.0 +588.0,0.0 +589.0,0.0 +590.0,0.0 +591.0,0.0 +592.0,0.0 +593.0,0.0 +594.0,0.0 +595.0,0.0 +596.0,0.0 +597.0,0.0 +598.0,0.0 +599.0,0.0 +600.0,0.0 +601.0,0.0 +602.0,0.0 +603.0,0.0 +604.0,0.0 +605.0,0.0 +606.0,0.0 +607.0,0.0 +608.0,0.0 +609.0,0.0 +610.0,0.0 +611.0,0.0 +612.0,0.0 +613.0,0.0 +614.0,0.0 +615.0,0.0 +616.0,0.0 +617.0,0.0 +618.0,0.0 +619.0,0.0 +620.0,0.0 +621.0,0.0 +622.0,0.0 +623.0,0.0 +624.0,0.0 +625.0,0.0 +626.0,0.0 +627.0,0.0 +628.0,0.0 +629.0,0.0 +630.0,0.0 +631.0,0.0 +632.0,0.0 +633.0,0.0 +634.0,0.0 +635.0,0.0 +636.0,0.0 +637.0,0.0 +638.0,0.0 +639.0,0.0 +640.0,0.0 +641.0,0.0 +642.0,0.0 +643.0,0.0 +644.0,0.0 +645.0,0.0 +646.0,0.0 +647.0,0.0 +648.0,0.0 +649.0,0.0 +650.0,0.0 +651.0,0.0 +652.0,0.0 +653.0,0.0 +654.0,0.0 +655.0,0.0 +656.0,0.0 +657.0,0.0 +658.0,0.0 +659.0,0.0 +660.0,0.0 +661.0,0.0 +662.0,0.0 +663.0,0.0 +664.0,0.0 +665.0,0.0 +666.0,0.0 +667.0,0.0 +668.0,0.0 +669.0,0.0 +670.0,0.0 +671.0,0.0 +672.0,0.0 +673.0,0.0 +674.0,0.0 +675.0,0.0 +676.0,0.0 +677.0,0.0 +678.0,0.0 +679.0,0.0 +680.0,0.0 +681.0,0.0 +682.0,0.0 +683.0,0.0 +684.0,0.0 +685.0,0.0 +686.0,0.0 +687.0,0.0 +688.0,0.0 +689.0,0.0 +690.0,0.0 +691.0,0.0 +692.0,0.0 +693.0,0.0 +694.0,0.0 +695.0,0.0 +696.0,0.0 +697.0,0.0 +698.0,0.0 +699.0,0.0 +700.0,0.0 +701.0,0.0 +702.0,0.0 +703.0,0.0 +704.0,0.0 +705.0,0.0 +706.0,0.0 +707.0,0.0 +708.0,0.0 +709.0,0.0 +710.0,0.0 +711.0,0.0 +712.0,0.0 +713.0,0.0 +714.0,0.0 +715.0,0.0 +716.0,0.0 +717.0,0.0 +718.0,0.0 +719.0,0.0 +720.0,0.0 +721.0,0.0 +722.0,0.0 +723.0,0.0 +724.0,0.0 +725.0,0.0 +726.0,0.0 +727.0,0.0 +728.0,0.0 +729.0,0.0 +730.0,0.0 +731.0,0.0 +732.0,0.0 +733.0,0.0 +734.0,0.0 +735.0,0.0 +736.0,0.0 +737.0,0.0 +738.0,0.0 +739.0,0.0 +740.0,0.0 +741.0,0.0 +742.0,0.0 +743.0,0.0 +744.0,0.0 +745.0,0.0 +746.0,0.0 +747.0,0.0 +748.0,0.0 +749.0,0.0 +750.0,0.0 +751.0,0.0 +752.0,0.0 +753.0,0.0 +754.0,0.0 +755.0,0.0 +756.0,0.0 +757.0,0.0 +758.0,0.0 +759.0,0.0 +760.0,0.0 +761.0,0.0 +762.0,0.0 +763.0,0.0 +764.0,0.0 +765.0,0.0 +766.0,0.0 +767.0,0.0 +768.0,0.0 +769.0,0.0 +770.0,0.0 +771.0,0.0 +772.0,0.0 +773.0,0.0 +774.0,0.0 +775.0,0.0 +776.0,0.0 +777.0,0.0 +778.0,0.0 +779.0,0.0 +780.0,0.0 +781.0,0.0 +782.0,0.0 +783.0,0.0 +784.0,1.115159958580487e-07 +785.0,1.3127215294519595e-07 +786.0,1.5775410121301525e-07 +787.0,1.9194871784352324e-07 +788.0,2.3446566443777305e-07 +789.0,2.833234277585785e-07 +790.0,3.353454631205617e-07 +791.0,3.882216963150526e-07 +792.0,4.418963989827613e-07 +793.0,5.006581147313438e-07 +794.0,5.707049737227295e-07 +795.0,6.579518703121835e-07 +796.0,7.733594103301811e-07 +797.0,9.355421286795921e-07 +798.0,1.1710062782652981e-06 +799.0,1.5179584458548496e-06 +800.0,2.0251750674304198e-06 +801.0,2.7214013998604813e-06 +802.0,3.570272220024113e-06 +803.0,4.4225359718889684e-06 +804.0,5.091557176409134e-06 +805.0,5.529001710793614e-06 +806.0,5.862733871000118e-06 +807.0,6.23679160833905e-06 +808.0,6.750531371580482e-06 +809.0,7.411223663711324e-06 +810.0,8.108251074695288e-06 +811.0,8.74803897711989e-06 +812.0,9.307435948216283e-06 +813.0,9.802716263346164e-06 +814.0,1.0290625143600776e-05 +815.0,1.0870003607331213e-05 +816.0,1.1601330630856536e-05 +817.0,1.2610560657046144e-05 +818.0,1.3988235476717497e-05 +819.0,1.5648595894089416e-05 +820.0,1.7397702169013204e-05 +821.0,1.8968574884451206e-05 +822.0,2.014721524773184e-05 +823.0,2.092024158542356e-05 +824.0,2.1407211025493592e-05 +825.0,2.178891932238345e-05 +826.0,2.2243315278178114e-05 +827.0,2.2885356898459427e-05 +828.0,2.370878621342506e-05 +829.0,2.4688379177919774e-05 +830.0,2.5744668413029447e-05 +831.0,2.677428014928141e-05 +832.0,2.765860753195978e-05 +833.0,2.8351640895577016e-05 +834.0,2.8794611390453102e-05 +835.0,2.9031762823158753e-05 +836.0,2.9118746956760135e-05 +837.0,2.913121612486907e-05 +838.0,2.915275250557106e-05 +839.0,2.9158107597921425e-05 +840.0,2.9149746643550804e-05 +841.0,2.9184042255885013e-05 +842.0,2.9232864630396435e-05 +843.0,2.9250217705214275e-05 +844.0,2.9259962278278435e-05 +845.0,2.92381090069486e-05 +846.0,2.918107026220869e-05 +847.0,2.918079762930394e-05 +848.0,2.9216570625541965e-05 +849.0,2.9203744541352722e-05 +850.0,2.9178286898914633e-05 +851.0,2.917245355016384e-05 +852.0,2.917540502532347e-05 +853.0,2.919663218515144e-05 +854.0,2.921863048443842e-05 +855.0,2.921126153576146e-05 +856.0,2.92198048713065e-05 +857.0,2.9258833696246936e-05 +858.0,2.9285994575343837e-05 +859.0,2.9319180778854792e-05 +860.0,2.9436897973099823e-05 +861.0,2.959843769796494e-05 +862.0,2.9598871358686967e-05 +863.0,2.9578940930085925e-05 +864.0,2.9531492322943073e-05 +865.0,2.9499294383628046e-05 +866.0,2.948570840353667e-05 +867.0,2.946560758273072e-05 +868.0,2.945941894189461e-05 +869.0,2.9462609229642436e-05 +870.0,2.947779256038085e-05 +871.0,2.951348858655135e-05 +872.0,2.954919776242812e-05 +873.0,2.957282116919233e-05 +874.0,2.9580795462773157e-05 +875.0,2.9566118000183888e-05 +876.0,2.953002167509384e-05 +877.0,2.948877906319165e-05 +878.0,2.9444819834741877e-05 +879.0,2.9400160618972566e-05 +880.0,2.9366904157774804e-05 +881.0,2.9358476373318866e-05 +882.0,2.9351927941787884e-05 +883.0,2.9350159080343877e-05 +884.0,2.9357029616046107e-05 +885.0,2.9367061897710473e-05 +886.0,2.936723894998416e-05 +887.0,2.935436661774121e-05 +888.0,2.9339035932276273e-05 +889.0,2.9319260422107735e-05 +890.0,2.9294035396820313e-05 +891.0,2.925747653234604e-05 +892.0,2.919900628988654e-05 +893.0,2.9145016732513666e-05 +894.0,2.9076003344673152e-05 +895.0,2.8982308810386704e-05 +896.0,2.8851372224219714e-05 +897.0,2.868535817941379e-05 +898.0,2.8490252224862293e-05 +899.0,2.822910520313522e-05 +900.0,2.790651268424766e-05 +901.0,2.752350842516271e-05 +902.0,2.7083175423572334e-05 +903.0,2.6598097399525574e-05 +904.0,2.6080307384997505e-05 +905.0,2.5518615987924638e-05 +906.0,2.491804684040474e-05 +907.0,2.432599943988343e-05 +908.0,2.3739328390329575e-05 +909.0,2.3152940446483317e-05 +910.0,2.2597092147273374e-05 +911.0,2.2063805078252072e-05 +912.0,2.154118214868528e-05 +913.0,2.1023246616104977e-05 +914.0,2.04774853044739e-05 +915.0,1.990142404427816e-05 +916.0,1.9294729246066008e-05 +917.0,1.8627931530337116e-05 +918.0,1.7899975295272483e-05 +919.0,1.7139796584524088e-05 +920.0,1.6332641125614606e-05 +921.0,1.5460040512826466e-05 +922.0,1.4573184968893042e-05 +923.0,1.3675321479726583e-05 +924.0,1.2756968001473472e-05 +925.0,1.1862220499896293e-05 +926.0,1.0990601181105182e-05 +927.0,1.0158210840525592e-05 +928.0,9.368266298483716e-06 +929.0,8.617022220608527e-06 +930.0,7.914964807838188e-06 +931.0,7.2506982497391826e-06 +932.0,6.62610046063534e-06 +933.0,6.045883045661501e-06 +934.0,5.502665408595489e-06 +935.0,4.990179820557904e-06 +936.0,4.513793930644575e-06 +937.0,4.075151704501867e-06 +938.0,3.6723465040807456e-06 +939.0,3.302611805195918e-06 +940.0,2.961967792536721e-06 +941.0,2.6554036937249287e-06 +942.0,2.3780164439777208e-06 +943.0,2.1320733291411043e-06 +944.0,1.9105311152535852e-06 +945.0,1.7127358474577422e-06 +946.0,1.5360888778488818e-06 +947.0,1.3793970049061152e-06 +948.0,1.240839434488208e-06 +949.0,1.116119652267728e-06 +950.0,1.0052226934519316e-06 +951.0,9.069791163262361e-07 +952.0,8.197708638959728e-07 +953.0,7.415120955334102e-07 +954.0,6.718466410618249e-07 +955.0,6.086029420582432e-07 +956.0,5.527248995452555e-07 +957.0,5.02370186932629e-07 +958.0,4.569790642997826e-07 +959.0,4.161069172453031e-07 +960.0,3.7954849995524653e-07 +961.0,3.4747404154368665e-07 +962.0,3.1817202403895645e-07 +963.0,2.9162577769528753e-07 +964.0,2.6751899906128884e-07 +965.0,2.460113325450046e-07 +966.0,2.2630022835081454e-07 +967.0,2.08216768746974e-07 +968.0,1.9187689049446527e-07 +969.0,1.768543776442376e-07 +970.0,1.6324729719344972e-07 +971.0,0.0 +972.0,0.0 +973.0,0.0 +974.0,0.0 +975.0,0.0 +976.0,0.0 +977.0,0.0 +978.0,0.0 +979.0,0.0 +980.0,0.0 +981.0,0.0 +982.0,0.0 +983.0,0.0 +984.0,0.0 +985.0,0.0 +986.0,0.0 +987.0,0.0 +988.0,0.0 +989.0,0.0 +990.0,0.0 +991.0,0.0 +992.0,0.0 +993.0,0.0 +994.0,0.0 +995.0,0.0 +996.0,0.0 +997.0,0.0 +998.0,0.0 +999.0,0.0 +1000.0,0.0 +1001.0, +1002.0, +1003.0, +1004.0, +1005.0, +1006.0, +1007.0, +1008.0, +1009.0, +1010.0, +1011.0, +1012.0, +1013.0, +1014.0, +1015.0, +1016.0, +1017.0, +1018.0, +1019.0, +1020.0, +1021.0, +1022.0, +1023.0, +1024.0, +1025.0, +1026.0, +1027.0, +1028.0, +1029.0, +1030.0, +1031.0, +1032.0, +1033.0, +1034.0, +1035.0, +1036.0, +1037.0, +1038.0, +1039.0, +1040.0, +1041.0, +1042.0, +1043.0, +1044.0, +1045.0, +1046.0, +1047.0, +1048.0, +1049.0, +1050.0, +1051.0, +1052.0, +1053.0, +1054.0, +1055.0, +1056.0, +1057.0, +1058.0, +1059.0, +1060.0, +1061.0, +1062.0, +1063.0, +1064.0, +1065.0, +1066.0, +1067.0, +1068.0, +1069.0, +1070.0, +1071.0, +1072.0, +1073.0, +1074.0, +1075.0, +1076.0, +1077.0, +1078.0, +1079.0, +1080.0, +1081.0, +1082.0, +1083.0, +1084.0, +1085.0, +1086.0, +1087.0, +1088.0, +1089.0, +1090.0, +1091.0, +1092.0, +1093.0, +1094.0, +1095.0, +1096.0, +1097.0, +1098.0, +1099.0, +1100.0, +1101.0, +1102.0, +1103.0, +1104.0, +1105.0, +1106.0, +1107.0, +1108.0, +1109.0, +1110.0, +1111.0, +1112.0, +1113.0, +1114.0, +1115.0, +1116.0, +1117.0, +1118.0, +1119.0, +1120.0, +1121.0, +1122.0, +1123.0, +1124.0, +1125.0, diff --git a/python/lsst/ts/observatory/control/cal_curves/hamamatsu_responsivity.csv b/python/lsst/ts/observatory/control/cal_curves/hamamatsu_responsivity.csv new file mode 100644 index 00000000..b9237838 --- /dev/null +++ b/python/lsst/ts/observatory/control/cal_curves/hamamatsu_responsivity.csv @@ -0,0 +1,162 @@ +wavelength,responsivity,uncertainty +300,0.1257,0.66 +305,0.1301,0.64 +310,0.133,0.64 +315,0.1357,0.62 +320,0.1378,0.6 +325,0.1396,0.6 +330,0.1409,0.58 +335,0.1426,0.58 +340,0.1438,0.58 +345,0.1446,0.56 +350,0.1446,0.56 +355,0.1432,0.54 +360,0.1411,0.44 +365,0.1402,0.38 +370,0.1416,0.36 +375,0.1463,0.36 +380,0.1529,0.34 +385,0.1598,0.34 +390,0.1661,0.32 +395,0.1719,0.32 +400,0.1773,0.3 +405,0.1823,0.3 +410,0.187,0.28 +415,0.1915,0.28 +420,0.1958,0.28 +425,0.2,0.26 +430,0.2039,0.26 +435,0.2078,0.24 +440,0.2116,0.24 +445,0.2153,0.22 +450,0.2189,0.22 +455,0.2225,0.2 +460,0.2259,0.19 +465,0.2292,0.18 +470,0.2326,0.18 +475,0.2362,0.17 +480,0.2395,0.16 +485,0.2428,0.15 +490,0.246,0.14 +495,0.2491,0.13 +500,0.2523,0.13 +505,0.2555,0.12 +510,0.2586,0.11 +515,0.2617,0.1 +520,0.2648,0.098 +525,0.2678,0.092 +530,0.2709,0.086 +535,0.2739,0.082 +540,0.2769,0.08 +545,0.2799,0.076 +550,0.2829,0.076 +555,0.2859,0.076 +560,0.2888,0.076 +565,0.2917,0.076 +570,0.2947,0.076 +575,0.2977,0.076 +580,0.3006,0.074 +585,0.3035,0.076 +590,0.3064,0.074 +595,0.3093,0.074 +600,0.3121,0.074 +605,0.315,0.074 +610,0.3179,0.074 +615,0.3207,0.074 +620,0.3235,0.074 +625,0.3264,0.074 +630,0.3292,0.072 +635,0.3321,0.072 +640,0.3349,0.072 +645,0.3377,0.072 +650,0.3406,0.072 +655,0.3435,0.072 +660,0.3464,0.072 +665,0.3491,0.072 +670,0.352,0.072 +675,0.3548,0.074 +680,0.3576,0.072 +685,0.3604,0.072 +690,0.3632,0.072 +695,0.366,0.072 +700,0.3688,0.072 +705,0.3716,0.072 +710,0.3744,0.072 +715,0.3771,0.072 +720,0.3799,0.072 +725,0.3827,0.072 +730,0.3855,0.072 +735,0.3883,0.072 +740,0.3911,0.072 +745,0.3938,0.072 +750,0.3965,0.072 +755,0.3993,0.074 +760,0.4021,0.072 +765,0.4048,0.072 +770,0.4075,0.072 +775,0.4103,0.072 +780,0.4131,0.072 +785,0.4158,0.072 +790,0.4186,0.072 +795,0.4213,0.072 +800,0.424,0.074 +805,0.4267,0.072 +810,0.4295,0.072 +815,0.4322,0.072 +820,0.4349,0.072 +825,0.4377,0.07 +830,0.4404,0.07 +835,0.4432,0.07 +840,0.4459,0.07 +845,0.4487,0.07 +850,0.4514,0.07 +855,0.4541,0.07 +860,0.4569,0.072 +865,0.4595,0.072 +870,0.4623,0.072 +875,0.4649,0.07 +880,0.4676,0.072 +885,0.4704,0.072 +890,0.4731,0.074 +895,0.4759,0.072 +900,0.4786,0.074 +905,0.4813,0.078 +910,0.4841,0.076 +915,0.4868,0.08 +920,0.4894,0.092 +925,0.492,0.098 +930,0.4945,0.11 +935,0.4968,0.096 +940,0.499,0.1 +945,0.5009,0.11 +950,0.5027,0.11 +955,0.5038,0.12 +960,0.5044,0.12 +965,0.5044,0.15 +970,0.5033,0.16 +975,0.5014,0.19 +980,0.4981,0.22 +985,0.4935,0.24 +990,0.4874,0.28 +995,0.4796,0.3 +1000,0.4705,0.3 +1005,0.4599,0.3 +1010,0.4457,0.3 +1015,0.429,0.32 +1020,0.4101,0.3 +1025,0.3883,0.32 +1030,0.364,0.3 +1035,0.338,0.32 +1040,0.3106,0.32 +1045,0.2819,0.32 +1050,0.2532,0.32 +1055,0.2253,0.34 +1060,0.1965,0.32 +1065,0.1724,0.3 +1070,0.1546,0.32 +1075,0.1386,0.32 +1080,0.1242,0.32 +1085,0.1108,0.32 +1090,0.0985,0.3 +1095,0.0873,0.32 +1100,0.0772,0.32 diff --git a/tests/test_calsys_logic.py b/tests/test_calsys_logic.py new file mode 100644 index 00000000..be8d941e --- /dev/null +++ b/tests/test_calsys_logic.py @@ -0,0 +1,31 @@ +import typing +import unittest +import logging +import pytest +from lsst.ts import salobj +from lsst.ts.observatory.control.utils import RemoteGroupTestCase + + +from lsst.ts.observatory.control.mock.latiss_mock import LATISSMock +from lsst.ts.observatory.control.base_calsys import HardcodeCalsysThroughput + + +class TestBaseCalsysLogic(unittest.TestCase): + """ Test cases for the abstract calculation logic and shared functionality + of BaseCalsys """ + + def test_load_calibration(self): + calfile = HardcodeCalsysThroughput.load_calibration_csv("hamamatsu_responsivity.csv") + + self.assertIn("wavelength", calfile) + self.assertIn("responsivity", calfile) + + def test_interpolate(self): + obj = HardcodeCalsysThroughput() + throughput_low = obj.radiometer_throughput(875.0) + + + +class TestATCalsys(unittest.TestCase): ... +class TestMTCalsys(unittest.TestCase): ... + From 0d3c9bfcfe86334efbaf19ddbfe5415d94d4dbdf Mon Sep 17 00:00:00 2001 From: Dan Weatherill Date: Thu, 5 Oct 2023 16:17:41 +0100 Subject: [PATCH 03/17] fix CSV reading function for throughput curves missing 0s in some places support for getting maintel throughput numbers too black reformatting logic for limiting maximum exposure time of radiometer port function to run electrometer exposures start on turning on auxtel light start on checking states of lamps etc start on chiller control stuff helpers, etc etc fleshing out startup methods power on and off sequences for AT done --- .../ts/observatory/control/auxtel/atcalsys.py | 293 +++++++++++++++--- .../ts/observatory/control/base_calsys.py | 289 ++++++++++++----- .../observatory/control/maintel/mtcalsys.py | 20 +- tests/test_calsys_logic.py | 2 +- 4 files changed, 482 insertions(+), 122 deletions(-) diff --git a/python/lsst/ts/observatory/control/auxtel/atcalsys.py b/python/lsst/ts/observatory/control/auxtel/atcalsys.py index 66011062..b3a0004f 100644 --- a/python/lsst/ts/observatory/control/auxtel/atcalsys.py +++ b/python/lsst/ts/observatory/control/auxtel/atcalsys.py @@ -1,81 +1,290 @@ -from typing import List, Optional, NamedTuple -from ..base_calsys import BaseCalsys, HardcodeCalsysThroughput +from typing import List, Optional, NamedTuple, TYPE_CHECKING +from ..base_calsys import BaseCalsys, HardcodeCalsysThroughput, CalibrationSequenceStepBase +from ..base_calsys import CalsysScriptIntention from lsst.ts import salobj from lsst.ts.idl.enums import ATMonochromator +from lsst.ts.idl.enums import ATWhiteLight import asyncio +import astropy.units as un +from astropy.units import Quantity +from datetime import datetime +from collections.abc import Awaitable +from dataclasses import dataclass + class ATSpectrographSlits(NamedTuple): FRONTENTRANCE: float FRONTEXIT: float - + +@dataclass +class ATCalibrationSequenceStep(CalibrationSequenceStepBase): + grating: ATMonochromator.Grating + latiss_filter: str + latiss_grating: str + entrance_slit_width: float + exit_slit_width: float + fs_exp_time: float + fs_n_exp: int + em_exp_time: float + em_n_exp: int class ATCalsys(BaseCalsys, HardcodeCalsysThroughput): - """ class which specifically handles the calibration system for auxtel""" - _AT_SAL_COMPONENTS: List[str] = ["ATMonochromator", "FiberSpectrograph", "Electrometer"] - CHANGE_GRATING_TIME: int = 60 + """class which specifically handles the calibration system for auxtel""" + + _AT_SAL_COMPONENTS: List[str] = [ + "ATMonochromator", + "FiberSpectrograph", + "Electrometer", + "ATWhiteLight" + ] + CHANGE_GRATING_TIME: Quantity[un.physical.time] = 60 << un.s + + # these below numbers should be able to be loaded from a (fairly static) config! + GRATING_CHANGEOVER_WL: Quantity[un.physical.length] = 532.0 << un.nm + GRATING_CHANGEOVER_BW: Quantity[un.physical.length] = 55.0 << un.nm # WARNING! PLACEHOLDER VALUE!!! - #these below numbers should be able to be loaded from a (fairly static) config! - GRATING_CHANGEOVER_WL: float = 532.0 #WARNING: PLACEHOLDER VALUE!!! - GRATING_CHANGEOVER_BW: float = 55.0 #WARNING! PLACEHOLDER VALUE!!! + CHILLER_COOLDOWN_TIMEOUT: Quantity[un.physical.time] = 15 << un.min + CHILLER_SETPOINT_TEMP: Quantity[un.physical.temperature] = 20 << un.deg_C + CHILLER_TEMP_REL_TOL: float = 0.2 + WHITELIGHT_POWER: Quantity[un.physical.power] = 910 << un.W + WHITELIGHT_LAMP_WARMUP_TIMEOUT: Quantity[un.physical.time] = 15 << un.min - def __init__(self, **kwargs:) - super().__init__(self._AT_SAL_COMPONENTS, **kwargs) + SHUTTER_OPEN_TIMEOUT: Quantity[un.physical.time] = 15 << un.min + + def __init__(self, intention: CalsysScriptIntention, **kwargs): + super().__init__(intention, components=self._AT_SAL_COMPONENTS, **kwargs) self._specsposure_time: Optional[float] = None self._elecsposure_time: Optional[float] = None + async def setup_for_wavelength( + self, wavelen: float, nelec: float, spectral_res: float + ) -> None: - async def setup_for_wavelength(self, wavelen: float, nelec: float, spectral_res: float) -> None: - # to be copied basically from existing SAL script mechanisms - grating = self.calculate_grating_type(wavelen, spectral_res) slit_widths = self.calculate_slit_widths(spectral_res, grating) - self.log.debug(f"setting up monochromtor with wavlength {wavelen} nm and spectral resolution {spectral_res}") + self.log.debug( + f"setting up monochromtor with wavlength {wavelen} nm and spectral resolution {spectral_res}" + ) self.log.debug(f"calculated slit widthsare {slit_widths}") self.log.debug(f"calculated grating is {grating}") - - monoch_fut = self._sal_cmd_helper("monochromator", "updateMonochromatorSetup", - gratingType = grating, - frontExitSlitWidth = slit_widths.FRONTEXIT, - frontEntranceSlitWdth = slit_widths.FRONTENTRACE, - wavelength = wavelen) - - elect_fut = self._sal_cmd_helper("electrometer", "performZeroCalib") - elect_fut2 = self._sal_cmd_helper("electrometer", "setDigitalFilter", - activateFilter=False, - activateAvgFilter=False, - activateMedFilter=False) - - + monoch_fut = self._sal_cmd( + self.ATMonoChromator, + "updateMonochromatorSetup", + gratingType=grating, + frontExitSlitWidth=slit_widths.FRONTEXIT, + frontEntranceSlitWdth=slit_widths.FRONTENTRACE, + wavelength=wavelen, + ) - #TODO: electrometer - #TODO: fibre spectrograph + elect_fut = self._sal_cmd("electrometer", "performZeroCalib") + elect_fut2 = self._sal_cmd( + self.Electrometer, + "setDigitalFilter", + activateFilter=False, + activateAvgFilter=False, + activateMedFilter=False, + ) - asyncio.wait([monoch_fut, elect_fut, elect_fut2], return_when=asyncio.ALL_COMPLETED) + await asyncio.wait( + [monoch_fut, elect_fut, elect_fut2], return_when=asyncio.ALL_COMPLETED + ) self.log.debug("all SAL setup commands returned") - specsposure_time = self.spectrograph_exposure_time_for_nelectrons(nelec) - - return - def calculate_slit_width(self, spectral_res: float, grating) -> ATSpectrographSlits: - #NOTE: this will either need to be derived by doing calculations on the Grating equation, or by loading in calibration data (which I couldn't find yet!) + # NOTE: this will either need to be derived by doing calculations on the Grating equation, or by loading in calibration data (which I couldn't find yet!) pass - def calculate_grating_type(self, wavelen: float, spectral_res: float): - #TODO: placeholder logic, in particular the exact numbers will be WRONG! - #likely something like the below + def calculate_grating_type(self, wavelen: float, spectral_res: float) -> ATMonochromator.Grating: + # TODO: placeholder logic, in particular the exact numbers will be WRONG! + # likely something like the below if spectral_res > self.GRATING_CHANGEOVER_BW: return ATMonochromator.Grating.MIRROR elif wavelen < self.GRATING_CHANGEOVER_WL: return ATMonochromator.Grating.BLUE - return ATMonochromator.Grating.RED - + return ATMonochromator.Grating.RED async def _setup_spectrograph(self, int_time: float) -> None: pass + + async def _setup_electrometer(self, int_time: float): + pass + + + + async def verify_chiller_operation(self): + chiller_temps = await self._sal_readvalue_helper(self.ATWhiteLight, "chillerTemperatures") + + self.log.debug(f"Chiller supply temperature: {chiller_temps.supplyTemperature:0.1f} C" + f"Chiller return temperature: {chiller_temps.returnTemperature:0.1f} C" + f"Chiller set temperature: {chiller_temps.setTemperature:0.1f} C" + f"Chiller ambient temperature: {chiller_temps.ambientTemperature:0.1f} C") + + + + async def turn_on_light(self, lamp_power: Quantity["power"]) -> None: + #check lamp state first + lamp_state = await self._sal_readvalue_helper(self.ATWhiteLight, "lampState") + if lamp_state == ATWhiteLight.LampBasicState.On: + #nothing to do + return + + #check the shutter state + shutter_state = await self._sal_waitevent(self.ATWhiteLight, "shutterState") + if shutter_state in [ATWhiteLight.ShutterState.Unknown, ATWhiteLight.ShutterState.Open]: + await self._sal_cmd(self.ATWhiteLight, "pcloseShutter") + + power_watts = int(lamp_power.to(un.W).value) + #turn on lamp and let it warm up + await self._sal_cmd(self.ATWhiteLight, "turnLampOn", power=power_watts) + + async def wait_ready(self) -> None: + #in the case of auxtel, need to wait for lamp to have warmed up + #check that lamp state + lamp_state = await self._sal_waitevent(self.ATWhiteLight, "lampState", run_immediate=False) + if lamp_state == ATWhiteLight.LampBasicState.On: + return + + if lamp_state not in {ATWhiteLight.LampBasicState.Warmup, ATWhiteLight.LampBasicState.TurningOn}: + raise RuntimeError("unexpected lamp state when waiting for readiness!") + + + async def _electrometer_expose(self, exp_time: float) -> Awaitable[str]: + await self._sal_cmd(self.Electrometer, "startScanDt", scanDuration=exp_time, + run_immediate=False) + lfa_obj = self._sal_waitevent(self.Electrometer, "largeFileObjectAvailable", + run_immediate=False) + return lfa_obj.url + + async def _spectrograph_expose(self, exp_time: float, numExposures: int) -> Awaitable[str]: + await self._sal_cmd(self.ATSpectrograph, "expose", numExposures = numExposures) + lfa_obj = await self._sal_waitevent(self.ATSpectrograph, "largeFileObjectAvailable", + run_immediate=False) + return lfa_obj.url + + + @property + def script_time_estimate_s(self) -> float: + """Property that returns the estimated time for the script to run in units of seconds + For script time estimation purposes. + For now just returns a default long time""" + + match(self._intention): + case(CalsysScriptIntention.POWER_ON): + #for now just use fixed values from previous script + #start out with chiller time maximum + total_time: Quantity[un.physical.time] = self.CHILLER_COOLDOWN_TIMEOUT + #add on the lamp warmup timeout + total_time += self.WHITELIGHT_LAMP_WARMUP_TIMEOUT + total_time += self.SHUTTER_OPEN_TIMEOUT + return total_time.to(un.s).value + case(_): + raise NotImplementedError("don't know how to handle this script intention") + + async def power_sequence_run(self, scriptobj: salobj.BaseScript): + match(self._intention): + case(CalsysScriptIntention.POWER_ON): + await self._chiller_power(True) + await scriptobj.checkpoint("Chiller started") + chiller_start, chiller_end = await self._chiller_settle(True) + await scriptobj.checkpoint("Chiller setpoint temperature reached") + shutter_wait_fut = asyncio.create_task(self._lamp_power(True), "lamp_start_shutter_open") + lamp_settle_fut = asyncio.create_task(self._lamp_settle(True), "lamp_power_settle") + shutter_start, shutter_end = await shutter_wait_fut + + await scriptobj.checkpoint("shutter open and lamp started") + lamp_settle_start, lamp_settle_end = await lamp_settle_fut + self.log.info("lamp is warmed up, ATCalsys is powered on and ready") + + case(CalsysScriptIntention.POWER_OFF): + await self._lamp_power(False) + await scriptobj.checkpoint("lamp commanded off and shutter commanded closed") + shutter_wait_fut = asyncio.create_task(self._lamp_power(False), "lamp stop shutter close") + lamp_settle_fut = asyncio.create_task(self._lamp_settle(False), "lamp power settle") + + shutter_start, shutter_end = await shutter_wait_fut + await scriptobj.checkpoint("shutter closed annd lamp turned off") + lamp_settle_start, lamp_settle_end = await lamp_settle_fut + + await scriptobj.checkpoint("lamp has cooled down") + await self._chiller_power(False) + self.log.info("chiller has been turned off, ATCalsys is powered down"!) + + + case(_): + raise NotImplementedError("don't know how to handle this script intention") + + #TODO: log the start and end times + + def _chiller_temp_check(self, temps) -> bool: + self.log.debug(f"Chiller supply temperature: {temps.supplyTemperature:0.1f} C " + f"[set:{temps.setTemperature} deg].") + pct_dev: float = (temps.supplyTemperature - temps.setTemperature) / temps.setTemperature + + if pct_dev <= self.CHILLER_TEMP_REL_TOL: + self.log.info( + f"Chiller reached target temperature, {temps.supplyTemperature:0.1f} deg ") + return True + return False + + async def _chiller_power(self, onoff: bool): + cmd_target = "startChiller" if onoff else "stopChiller" + if onoff: + chiller_setpoint_temp: float = self.CHILLER_SETPOINT_TEMP.to(un.s).value + await self._sal_cmd(self.ATWhiteLight, "setChillerTemperature", + temperature=chiller_setpoint_temp) + await self._sal_cmd(self.ATWhiteLight, cmd_target) + + async def _chiller_settle(self) -> Awaitable[tuple[datetime,datetime]]: + chiller_wait_timeout: float = self.CHILLER_COOLDOWN_TIMEOUT.to(un.s).value + chiller_temp_gen = self._sal_telem_gen(self.ATWhiteLight, "chillerTemperatures") + + return await self._long_wait_err_handle(chiller_temp_gen, chiller_wait_timeout, + self._chiller_temp_check, "chiller temperature range settle") + + async def _lamp_power(self, onoff:bool) -> Awaitable: + shutter_cmd_target = "openShutter" if onoff else "closeShutter" + lamp_cmd_target = "turnLampOn" if onoff else "turnLampOff" + #TODO: do we want asserts etc here to check the lamp state is correct first? + #first, open the shutter + shutter_task = self._sal_cmd(self.ATWhiteLight, shutter_cmd_target, run_immediate=False) + + #now start the lamp + lamp_start_task = self._sal_cmd(self.ATWhiteLight, lamp_cmd_target, run_immediate=False) + + await asyncio.wait([shutter_task, lamp_start_task], timeout=self._cmd_timeout, + return_when=asyncio.FIRST_EXCEPTION) + + #now run long wait for shutter + shutter_evt_gen = self._sal_evt_gen(self.ATWhiteLight, "shutterState") + shutter_wait_timeout: float = self.SHUTTER_OPEN_TIMEOUT.to(un.s).value + + #TODO: also probably bail out if this reports an unexpected state + def shutter_verify(evt): + return evt.actualState == evt.commandedState + + #TODO: this can be smarter, lamp reports time when it will be warm, + #can use this to update scripts etc + return self._long_wait_err_handle(shutter_evt_gen, shutter_wait_timeout, + shutter_verify, "shutter state") + + + async def _lamp_settle(self, onoff: bool) -> Awaitable: + lamp_evt_gen = self._sal_evt_gen(self.ATWhiteLight, "lampState") + lamp_settle_timeout: float = self.WHITELIGHT_LAMP_WARMUP_TIMEOUT.to(un.s).value + lamp_tgt_state = ATWhiteLight.LampBasicState.On if onoff else ATWhiteLight.LampBasicState.Off + lamp_transition_name = "lamp warming up" if onoff else "lamp cooling down" + + def lamp_verify(evt): + nonlocal lamp_tgt_state + return evt.basicState == lamp_tgt_state + + return self._long_wait_err_handle(lamp_evt_gen, lamp_settle_timeout, + lamp_verify, lamp_transition_name) + + diff --git a/python/lsst/ts/observatory/control/base_calsys.py b/python/lsst/ts/observatory/control/base_calsys.py index c5c77656..e5b084f0 100644 --- a/python/lsst/ts/observatory/control/base_calsys.py +++ b/python/lsst/ts/observatory/control/base_calsys.py @@ -1,8 +1,11 @@ from abc import ABCMeta, abstractmethod +from dataclasses import dataclass from .remote_group import RemoteGroup from typing import Iterable, Optional, Tuple, List, Union -from typing import Sequence, Callable, Mapping, TypeAlias +from typing import Sequence, Mapping, TypeAlias, Any +from typing import TypeVar from functools import reduce +from collections.abc import Coroutine, Callable, AsyncGenerator from operator import mul from lsst.ts import salobj import logging @@ -12,15 +15,32 @@ from scipy.interpolate import InterpolatedUnivariateSpline from astropy.units import ampere, watt, nm, Quantity import astropy.units as un +import enum -Responsivity: TypeAlias = Quantity[ampere/watt] +Responsivity: TypeAlias = Quantity[ampere / watt] + +@dataclass +class CalibrationSequenceStepBase: + wavelength: float + n_exp: int + exp_time: float + + +class CalsysScriptIntention(enum.IntEnum): + TURN_ON = 0 + TURN_OFF = 1 + QUICK_CALIBRATION_RUN = 2 + LONG_CALIBRATION_RUN = 3 + +TaskOrCoro = Union[asyncio.Task, Coroutine] class CalsysThroughputCalculationMixin: """mixin class to allow pluggable source for calculation of throughputs""" + POWER_LINE_FREQUENCY = 60 / (un.s) + @abstractmethod - @property def detector_throughput(self, wavelen: float) -> float: """the throughput value will return (in appropriate units TBD) the detector throughput of the particular calibration system specified in the class for which this mixin class is specified as a base. @@ -42,27 +62,47 @@ def detector_throughput(self, wavelen: float) -> float: @abstractmethod def spectrograph_throughput(self, wavelen: float, calsys_power: float) -> float: - """ the throughput expected of the fiber spectrograph of the calibration system. + """the throughput expected of the fiber spectrograph of the calibration system. To aid calculations of total throughput """ @abstractmethod - def radiometer_responsivity(self, wavelen: Quantity["length"]) -> Responsivity: - """ return the responsivity of the radiometer """ + def radiometer_responsivity(self, wavelen: Quantity[un.physical.length]) -> Responsivity: + """return the responsivity of the radiometer""" def end_to_end_throughput(self, wavelen: float, calsys_power: float) -> float: - #intended to be SOMETHING LIKE - return reduce(lambda t, f: t*f(wavelen, calsys_power), - [self.detector_throughput, self.spectrograph_throughput, - self.radiometer_throughput]) - - def total_radiometer_exposure_time(self, cam_integration_time: Quantity["time"], - nplc: Quantity["time"]) -> Quantity["time"]: + # intended to be SOMETHING LIKE + return reduce( + lambda t, f: t * f(wavelen, calsys_power), + [ + self.detector_throughput, + self.spectrograph_throughput, + self.radiometer_throughput, + ], + ) + + def total_radiometer_exposure_time( + self, rad_exposure_time: Quantity[un.physical.time], nplc: float + ) -> Quantity["time"]: + # Note comm from Parker: "valid nplc values are from 0.01 to 10 (seconds) + if not (0.01 <= nplc <= 10.0): + raise ValueError( + f"supplied valud for nplc: {nplc} is not within allowed values 0.01 <= nplc <= 10" + ) # Note: magic numbers from communication with Parker F. To be added to electrometer CSC docs - rad_int_time = nplc / (60 * un.s) + rad_int_time = nplc / self.POWER_LINE_FREQUENCY + time_sep = (rad_int_time * 3.07) + 0.00254 + # NOTE: is 0.00254 a metric -> imperial conversion? + max_exp_time = 16667 * time_sep + n_meas: int = rad_exposure_time / time_sep + total_time = n_meas * 0.01 + rad_exposure_time - # FIXME: need to ask about units of nplc and what the units of the 3.07 magic number are + if total_time > max_exp_time: + raise ValueError( + f"total exposure time {total_time} is longer than max allowed {max_exp_time}" + ) + return total_time class ButlerCalsysThroughput(CalsysThroughputCalculationMixin): @@ -72,70 +112,167 @@ class ButlerCalsysThroughput(CalsysThroughputCalculationMixin): class HardcodeCalsysThroughput(CalsysThroughputCalculationMixin): """Mixin class for calculating throughput of the calibration system with hardcoded values, - i.e. which can be directly imported from python code """ + i.e. which can be directly imported from python code""" + BASERES: str = "lsst.ts.observatory.control.cal_curves" RADIOMETER_CALFILE: str = "hamamatsu_responsivity.csv" + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._itps: dict[str, InterpolatedUnivariateSpline] = dict() + self._intention = intention + @classmethod def load_calibration_csv(cls, fname: str) -> Mapping[str, Sequence[float]]: res = files(cls.BASERES).joinpath(fname) with res.open("r") as f: rdr = csv.DictReader(f) - out = { k : [] for k in rdr.fieldnames} + out = {k: [] for k in rdr.fieldnames} for row in rdr: - for k,v in row.items(): - out[k].append(float(v)) + for k, v in row.items(): + val: float = float(v) if len(v) > 0 else 0.0 + out[k].append(val) return out + def _ensure_itp( + self, itpname: str, fname: str, xaxis: str, yaxis: str, **itpargs + ) -> InterpolatedUnivariateSpline: + if itpname not in self._itps: + calres = self.load_calibration_csv(fname) + itp = InterpolatedUnivariateSpline(calres[xaxis], calres[yaxis], **itpargs) + self._itps[itpname] = itp + else: + return self._itps[itpname] + def radiometer_responsivity(self, wavelen: Quantity["length"]) -> Responsivity: - calres = self.load_calibration_csv(self.RADIOMETER_CALFILE) - itp = InterpolatedUnivariateSpline(calres["wavelength"], calres["responsivity"]) + wlin: float = wavelen.to(nm).value + + itp = self._ensure_itp( + "radiometer", self.RADIOMETER_CALFILE, "wavelength", "responsivity" + ) - wlin : float = wavelen.to(nm).value + wlin: float = wavelen.to(nm).value Rawout: float = itp(wlin) + return Rawout << un.ampere / un.watt - return (Rawout << un.ampere / un.watt) + def maintel_throughput( + self, wavelen: Quantity["length"], filter_band: chr + ) -> Quantity[un.dimensionless_unscaled]: + calfilename: str = f"calibration_tput_init_{filter_band}.csv" + itp = self._ensure_itp( + f"maintel_{filter_band}", calfilename, "Wavelength[nm]", "Throughput" + ) + + wlin: float = wavelen.to(nm).value + return Quantity(itp(wlin), un.dimensionless_unscaled) class BaseCalsys(RemoteGroup, metaclass=ABCMeta): """Base class for calibration systems""" - - - def __init__(self, - components: Iterable[str], - domain: Optional[salobj.domain] = None, - cmd_timeout: Optional[int] = 10, - log: Optional[logging.Logger] = None): - - super().__init__(components, domain, log=log, - intended_usage=salobj.BaseUsages.StateTransition, - concurrent_operation = False) #QUESTION: is this last one true???? - - self._cmd_timeout = cmd_timeout - - - def _sal_cmd_helper(self, salobj, cmdname: str, run_immediate: bool= True, **setargs): - if isinstance(salobj, str): - salobj = getattr(self, salobj) - cmdfun = getattr(salobj, f"cmd_{cmdname}") - cmdfun.set(**setargs) - pkgtask = lambda : cmdfun.start(timeout = self._cmd_timeout) + CMD_TIMEOUT: Quantity[un.physical.time] = 30 << un.s + EVT_TIMEOUT: Quantity[un.physical.time] = 30 << un.s + TELEM_TIMEOUT: Quantity[un.physical.time] = 30 << un.s + + def __init__( + self, + intention: CalsysScriptIntention, + components: Iterable[str], + domain: Optional[salobj.domain] = None, + cmd_timeout: Optional[int] = 10, + log: Optional[logging.Logger] = None, + ): + super().__init__( + components, + domain, + log=log, + intended_usage=salobj.BaseUsages.StateTransition, + concurrent_operation=False, + ) # QUESTION: is this last one true???? + + self._intention = intention + + def _sal_cmd( + self, obj: salobj.Remote, cmdname: str, run_immediate: bool = True, **setargs + ) -> TaskOrCoro: + timeout = self.CMD_TIMEOUT.to(un.s).value + cmdfun: salobj.topics.RemoteCommand = getattr(obj, f"cmd_{cmdname}") + pkgtask = cmdfun.set_start(**setargs, timeout=timeout) if run_immediate: - return asyncio.createtask(pkgtask()) - return pkgtask() - - def _lfa_event_helper(self, salobj, run_immediate: bool=True, **evtargs): - if isinstance(salobj, str): - salobj = getattr(self, salobj) - cmdfun = getattr(salobj, "evt_largeFileObjectAvailable") - pkgtask = lambda: cmdfun.start(timeout = self._cmd_timeout) + return asyncio.create_task(pkgtask) + return pkgtask + + def _sal_waitevent(self, obj: salobj.Remote, evtname: str, run_immediate: bool=True, flush: bool=True, + **evtargs) -> TaskOrCoro: + timeout = self.EVT_TIMEOUT.to(un.s).value + cmdfun: salobj.topics.RemoteEvent = getattr(obj, f"evt_{evtname}") + pkgtask = cmdfun.next(timeout=timeout, flush=flush) if run_immediate: - return asyncio.create_task(pkgtask()) - return pkgtask() - - - def detector_exposure_time_for_nelectrons(self, wavelen: Quantity["length"], nelec: float) -> float: - """ using the appropriate mixin for obtaining calibration data on throughput, + return asyncio.create_task(pkgtask) + return pkgtask + + def _lfa_event(self, obj: salobj.Remote, telname: str, run_immediate: bool = True, + flush: bool=True, **evtargs) -> TaskOrCoro: + return self._sal_waitevent(obj, "largeFileObjectAvailable", run_immediate, flush, **evtargs) + + def _sal_evt_gen(self, obj:salobj.Remote, evtname: str, flush: bool=True) -> AsyncGenerator: + pkgtask = self._sal_waitevent(obj, evtname, run_immediate=False, flush=flush) + async def gen(): + while True: + v = await pkgtask + yield v + return gen() + + def _sal_telem_gen(self, obj: salobj.Remote, telname: str) -> AsyncGenerator: + timeout = self.TELEM_TIMEOUT.to(un.s).value + cmdfun: salobj.topics.RemoteTelemetry = getattr(obj, f"tel_{telname}") + + async def gen(): + while True: + v = await cmdfun.next(timeout=timeout, flush=True) + yield v + return gen() + + def _long_wait(self, gen: AsyncGenerator, timeout_seconds, validate_fun: Callable[[Any], bool], + run_immediate: bool=True) -> TaskOrCoro: + async def completer() -> None: + async for value in gen: + if(validate_fun(value)): + return + + coro = asyncio.wait_for(completer(), timeout_seconds) + if run_immediate: + return asyncio.create_task(coro) + return coro + + async def _long_wait_err_handle(self, gen: AsyncGenerator, timeout_seconds, + validate_fun: Callable[[Any], bool], name_of_wait: str) -> tuple[datetime,datetime]: + starttime = datetime.now() + try: + await self._long_wait(gen, timeout_seconds, validate_fun, run_immediate=False) + endtime = datetime.now() + return starttime, endtime + except TimeoutError as err: + nowfail = datetime.now() + wait_time: float = (nowfail - starttime).total_seconds() + self.log.error(f"waited {wait_time} seconds but {name_of_wait} did not succeed") + raise err + + + + async def take_electrometer_exposures( + self, electrobj, exp_time_s: float, n: int + ) -> List[str]: + urlout: List[str] = [] + for i in range(n): + await electrobj.cmd_StartScanDt.set_start(scanDuration=exp_time) + lfaurl = await self._lfa_event_helper(electrobj) + urlout.append(lfaurl) + return urlout + + def detector_exposure_time_for_nelectrons( + self, wavelen: Quantity["length"], nelec: float + ) -> float: + """using the appropriate mixin for obtaining calibration data on throughput, will calculate and return the exposure time needed to obtain a flat field calibration of n electrons at the imager specified in the class definition @@ -152,31 +289,33 @@ def detector_exposure_time_for_nelectrons(self, wavelen: Quantity["length"], nel """ - #must have a way to access the calibration data + # must have a way to access the calibration data assert issubclass(type(self), CalsysThroughputCalculationMixin) - def spectrograph_exposure_time_for_nelectrons(self, nelec: float) -> float: pass def pd_exposure_time_for_nelectrons(self, nelec: float) -> float: pass + @abstractmethod - async def turn_on_light(self) -> None: - """awaitable command which turns on the calibration light, having - already set up the appropriate wavelength and (if applicable) time delays for stabilization etc + async def power_sequence_run(self, scriptobj, **kwargs): + pass + @abstractmethod + async def turn_on_light(self, **kwargs) -> None: + """awaitable command which turns on the calibration light, having + already set up the appropriate wavelength and (if applicable) time delays for stabilization etc """ @abstractmethod async def turn_off_light(self) -> None: - """ awaitable which turns off the calibration light""" - + """awaitable which turns off the calibration light""" @abstractmethod async def setup_for_wavelength(self, wavelen: float, **extra_params) -> None: - """ awaitable which sets up the various remote components of a calibration system + """awaitable which sets up the various remote components of a calibration system to perform a calibration at a particular wavelength. Intended to be a 'high level' setup function, such that user doesn't have to worry about e.g. setting up integration times for spectrographs etc @@ -194,16 +333,24 @@ async def setup_for_wavelength(self, wavelen: float, **extra_params) -> None: @abstractmethod async def take_calibration_instr_exposures(self) -> None: - """ awaitable which starts the exposures for the calibration instruments (i.e the spectrographs, electrometers etc) according to the setup. It does not take images with the instrument under test, it is intended that script components which use this class do that themselves""" + """awaitable which starts the exposures for the calibration instruments (i.e the spectrographs, electrometers etc) according to the setup. It does not take images with the instrument under test, it is intended that script components which use this class do that themselves""" pass - @property @abstractmethod def wavelen(self) -> Quantity[nm]: - """ returns the currently configured wavelength""" + """returns the currently configured wavelength""" + + @abstractmethod + async def take_detector_data(self): + """This will fire off all async tasks to take calibration data in sequence, and return locations a + nd metadata about the files supplied etc""" + @abstractmethod - async def take_data(self): - """This will fire off all async tasks to take calibration data in sequence, and return locations and metadata about the files supplied etc""" + async def gen_calibration_auxiliaries(self): + pass + + async def wait_ready(self): + """ Method to wait for prepared state for taking data - e.g. lamps on, warmed up, laser warmed up etc""" diff --git a/python/lsst/ts/observatory/control/maintel/mtcalsys.py b/python/lsst/ts/observatory/control/maintel/mtcalsys.py index 02eb4ac4..76b75d7d 100644 --- a/python/lsst/ts/observatory/control/maintel/mtcalsys.py +++ b/python/lsst/ts/observatory/control/maintel/mtcalsys.py @@ -2,16 +2,20 @@ from ..base_calsys import BaseCalsys + class MTCalsys(BaseCalsys): - """ class which specifically handles the calibration system for maintel """ - _MT_SAL_COMPONENTS: List[str] = [] #TODO, what do we actually need here???! + """class which specifically handles the calibration system for maintel""" + + _MT_SAL_COMPONENTS: List[str] = [] # TODO, what do we actually need here???! - def __init__(self, - domain: Optional[salobj.Domain] = None): + def __init__(self, domain: Optional[salobj.Domain] = None): super().__init__(self._MT_SAL_COMPONENTS, domain) - - async def turn_on_light(self) -> None: ... - async def turn_off_light(self) -> None: ... + async def turn_on_light(self) -> None: + ... + + async def turn_off_light(self) -> None: + ... - async def setup_for_wavelength(self, wavelen: float) -> None: ... + async def setup_for_wavelength(self, wavelen: float) -> None: + ... diff --git a/tests/test_calsys_logic.py b/tests/test_calsys_logic.py index be8d941e..8c28c1eb 100644 --- a/tests/test_calsys_logic.py +++ b/tests/test_calsys_logic.py @@ -23,7 +23,7 @@ def test_load_calibration(self): def test_interpolate(self): obj = HardcodeCalsysThroughput() throughput_low = obj.radiometer_throughput(875.0) - + class TestATCalsys(unittest.TestCase): ... From 8045a4b87e06f1c5aa98e3a9f831944cc626652c Mon Sep 17 00:00:00 2001 From: Dan Date: Wed, 22 Nov 2023 16:44:12 +0000 Subject: [PATCH 04/17] working on configuration parsing --- .../ts/observatory/control/auxtel/atcalsys.py | 90 +++++++++---------- .../ts/observatory/control/base_calsys.py | 27 +++--- 2 files changed, 60 insertions(+), 57 deletions(-) diff --git a/python/lsst/ts/observatory/control/auxtel/atcalsys.py b/python/lsst/ts/observatory/control/auxtel/atcalsys.py index b3a0004f..b034f2d9 100644 --- a/python/lsst/ts/observatory/control/auxtel/atcalsys.py +++ b/python/lsst/ts/observatory/control/auxtel/atcalsys.py @@ -15,7 +15,7 @@ class ATSpectrographSlits(NamedTuple): FRONTENTRANCE: float FRONTEXIT: float - + @dataclass class ATCalibrationSequenceStep(CalibrationSequenceStepBase): grating: ATMonochromator.Grating @@ -116,44 +116,6 @@ async def _setup_electrometer(self, int_time: float): pass - - async def verify_chiller_operation(self): - chiller_temps = await self._sal_readvalue_helper(self.ATWhiteLight, "chillerTemperatures") - - self.log.debug(f"Chiller supply temperature: {chiller_temps.supplyTemperature:0.1f} C" - f"Chiller return temperature: {chiller_temps.returnTemperature:0.1f} C" - f"Chiller set temperature: {chiller_temps.setTemperature:0.1f} C" - f"Chiller ambient temperature: {chiller_temps.ambientTemperature:0.1f} C") - - - - async def turn_on_light(self, lamp_power: Quantity["power"]) -> None: - #check lamp state first - lamp_state = await self._sal_readvalue_helper(self.ATWhiteLight, "lampState") - if lamp_state == ATWhiteLight.LampBasicState.On: - #nothing to do - return - - #check the shutter state - shutter_state = await self._sal_waitevent(self.ATWhiteLight, "shutterState") - if shutter_state in [ATWhiteLight.ShutterState.Unknown, ATWhiteLight.ShutterState.Open]: - await self._sal_cmd(self.ATWhiteLight, "pcloseShutter") - - power_watts = int(lamp_power.to(un.W).value) - #turn on lamp and let it warm up - await self._sal_cmd(self.ATWhiteLight, "turnLampOn", power=power_watts) - - async def wait_ready(self) -> None: - #in the case of auxtel, need to wait for lamp to have warmed up - #check that lamp state - lamp_state = await self._sal_waitevent(self.ATWhiteLight, "lampState", run_immediate=False) - if lamp_state == ATWhiteLight.LampBasicState.On: - return - - if lamp_state not in {ATWhiteLight.LampBasicState.Warmup, ATWhiteLight.LampBasicState.TurningOn}: - raise RuntimeError("unexpected lamp state when waiting for readiness!") - - async def _electrometer_expose(self, exp_time: float) -> Awaitable[str]: await self._sal_cmd(self.Electrometer, "startScanDt", scanDuration=exp_time, run_immediate=False) @@ -175,7 +137,7 @@ def script_time_estimate_s(self) -> float: For now just returns a default long time""" match(self._intention): - case(CalsysScriptIntention.POWER_ON): + case CalsysScriptIntention.POWER_ON | CalsysScriptIntention.POWER_OFF: #for now just use fixed values from previous script #start out with chiller time maximum total_time: Quantity[un.physical.time] = self.CHILLER_COOLDOWN_TIMEOUT @@ -183,43 +145,77 @@ def script_time_estimate_s(self) -> float: total_time += self.WHITELIGHT_LAMP_WARMUP_TIMEOUT total_time += self.SHUTTER_OPEN_TIMEOUT return total_time.to(un.s).value - case(_): + case _: raise NotImplementedError("don't know how to handle this script intention") async def power_sequence_run(self, scriptobj: salobj.BaseScript): match(self._intention): - case(CalsysScriptIntention.POWER_ON): + case CalsysScriptIntention.POWER_ON: await self._chiller_power(True) await scriptobj.checkpoint("Chiller started") chiller_start, chiller_end = await self._chiller_settle(True) + self.log_event_timings(self.log, "chiller cooldown time", chiller_start, chiller_end, + self.CHILLER_COOLDOWN_TIMEOUT) + await scriptobj.checkpoint("Chiller setpoint temperature reached") shutter_wait_fut = asyncio.create_task(self._lamp_power(True), "lamp_start_shutter_open") lamp_settle_fut = asyncio.create_task(self._lamp_settle(True), "lamp_power_settle") shutter_start, shutter_end = await shutter_wait_fut + self.log_event_timings(self.log, "shutter open time", shutter_start, shutter_end, + self.SHUTTER_OPEN_TIMEOUT) await scriptobj.checkpoint("shutter open and lamp started") lamp_settle_start, lamp_settle_end = await lamp_settle_fut + self.log_event_timings(self.log, "lamp warm up", lamp_settle_start, lamp_settle_end, + self.WHITELIGHT_LAMP_WARMUP_TIMEOUT) self.log.info("lamp is warmed up, ATCalsys is powered on and ready") - case(CalsysScriptIntention.POWER_OFF): + case CalsysScriptIntention.POWER_OFF: await self._lamp_power(False) await scriptobj.checkpoint("lamp commanded off and shutter commanded closed") shutter_wait_fut = asyncio.create_task(self._lamp_power(False), "lamp stop shutter close") lamp_settle_fut = asyncio.create_task(self._lamp_settle(False), "lamp power settle") shutter_start, shutter_end = await shutter_wait_fut + self.log_event_timings(self.log, "shutter close", shutter_start, shutter_end, + self.SHUTTER_OPEN_TIMEOUT) await scriptobj.checkpoint("shutter closed annd lamp turned off") lamp_settle_start, lamp_settle_end = await lamp_settle_fut - + self.log_event_timings(self.log, "lamp cooldown", lamp_settle_start, lamp_settle_end, + self.WHITELIGHT_LAMP_WARMUP_TIMEOUT) await scriptobj.checkpoint("lamp has cooled down") await self._chiller_power(False) self.log.info("chiller has been turned off, ATCalsys is powered down"!) - - case(_): + case _: raise NotImplementedError("don't know how to handle this script intention") - #TODO: log the start and end times + async def validate_hardware_status_for_acquisition(self) -> Awaitable[float]: + shutter_fut = self._sal_waitevent(self.ATWhiteLight, "shutterState") + lamp_fut = self._sal_waitevent(self.ATWhiteLight, "lampState") + + shutter_state = await shutter_fut + if shutter_state.commandedState != ATWhiteLight.ShutterState.OPEN: + errmsg = f"shutter has not been commanded to open, likely a programming error. Commanded state is {repr(shutter_state.commandedState)}" + self.log.error(errmsg) + raise RuntimeError(errmsg) + + if shutter_state.actualState != ATWhiteLight.ShutterState.OPEN: + errmsg = f"shutter is not open, its state is reported as {repr(shutter_state.actualState)}") + self.log.error(errmsg) + raise RuntimeError(errmsg) + + lamp_state = await lamp_fut + if lamp_state.basicState != ATWhiteLight.LampBasicState.ON: + errmsg = f"lamp state is not on, its state is reported as {repr(lamp_state.basicState)}" + self.log.error(errmsg) + raise RuntimeError(errmsg) + + if !lamp_state.lightDetected: + self.log.warning(f"all states seem fine, but lamp is not reporting light detected!") + + lamp_power: float = lamp_state.setPower + return lamp_power def _chiller_temp_check(self, temps) -> bool: self.log.debug(f"Chiller supply temperature: {temps.supplyTemperature:0.1f} C " diff --git a/python/lsst/ts/observatory/control/base_calsys.py b/python/lsst/ts/observatory/control/base_calsys.py index e5b084f0..d3615ed5 100644 --- a/python/lsst/ts/observatory/control/base_calsys.py +++ b/python/lsst/ts/observatory/control/base_calsys.py @@ -16,6 +16,7 @@ from astropy.units import ampere, watt, nm, Quantity import astropy.units as un import enum +from datetime import datetime Responsivity: TypeAlias = Quantity[ampere / watt] @@ -257,7 +258,18 @@ async def _long_wait_err_handle(self, gen: AsyncGenerator, timeout_seconds, self.log.error(f"waited {wait_time} seconds but {name_of_wait} did not succeed") raise err - + + @classmethod + def log_event_timings(cls, logger, time_evt_name: str, + start_time: datetime, end_time: datetime, + expd_duration: Quantity[un.physical.time]) -> None: + logstr = f"event: {time_evt_name} started at {start_time} and finished at {end_time}" + logger.info(logstr) + duration = (start_time - end_time).total_seconds() << un.s + logstr2 = f"the duration was: {duration}, and our timeout allowance was: {expt_duration}" + logger.info(logstr2) + + async def take_electrometer_exposures( self, electrobj, exp_time_s: float, n: int @@ -300,18 +312,13 @@ def pd_exposure_time_for_nelectrons(self, nelec: float) -> float: @abstractmethod - async def power_sequence_run(self, scriptobj, **kwargs): + async def validate_hardware_status_for_acquisition(self) -> Awaitable: pass - + @abstractmethod - async def turn_on_light(self, **kwargs) -> None: - """awaitable command which turns on the calibration light, having - already set up the appropriate wavelength and (if applicable) time delays for stabilization etc - """ + async def power_sequence_run(self, scriptobj, **kwargs): + pass - @abstractmethod - async def turn_off_light(self) -> None: - """awaitable which turns off the calibration light""" @abstractmethod async def setup_for_wavelength(self, wavelen: float, **extra_params) -> None: From d30b4c6d28e6d731a13433db6368e695d1a8471c Mon Sep 17 00:00:00 2001 From: Dan Date: Tue, 12 Dec 2023 03:11:32 +0000 Subject: [PATCH 05/17] fixup some syntax and other errors --- .../ts/observatory/control/auxtel/atcalsys.py | 108 ++++++++++++------ .../ts/observatory/control/base_calsys.py | 77 +++++++++---- 2 files changed, 126 insertions(+), 59 deletions(-) diff --git a/python/lsst/ts/observatory/control/auxtel/atcalsys.py b/python/lsst/ts/observatory/control/auxtel/atcalsys.py index b034f2d9..2cf503dd 100644 --- a/python/lsst/ts/observatory/control/auxtel/atcalsys.py +++ b/python/lsst/ts/observatory/control/auxtel/atcalsys.py @@ -1,8 +1,8 @@ from typing import List, Optional, NamedTuple, TYPE_CHECKING from ..base_calsys import BaseCalsys, HardcodeCalsysThroughput, CalibrationSequenceStepBase -from ..base_calsys import CalsysScriptIntention +from ..base_calsys import CalsysScriptIntention, _calsys_get_parameter from lsst.ts import salobj -from lsst.ts.idl.enums import ATMonochromator +from lsst.ts.idl.enums import ATMonochromator, Electrometer from lsst.ts.idl.enums import ATWhiteLight import asyncio import astropy.units as un @@ -51,20 +51,25 @@ class ATCalsys(BaseCalsys, HardcodeCalsysThroughput): WHITELIGHT_LAMP_WARMUP_TIMEOUT: Quantity[un.physical.time] = 15 << un.min SHUTTER_OPEN_TIMEOUT: Quantity[un.physical.time] = 15 << un.min + CAL_PROGRAM_NAME: str = "AT_flats" def __init__(self, intention: CalsysScriptIntention, **kwargs): super().__init__(intention, components=self._AT_SAL_COMPONENTS, **kwargs) + + #instance variables we'll set later self._specsposure_time: Optional[float] = None self._elecsposure_time: Optional[float] = None + self._n_spec_exps: Optional[int] = None + self._n_elec_exps: Optional[int] = None async def setup_for_wavelength( - self, wavelen: float, nelec: float, spectral_res: float + self, wavelen: float, nelec: float, spectral_res: float, **override_kwargs ) -> None: - - grating = self.calculate_grating_type(wavelen, spectral_res) - slit_widths = self.calculate_slit_widths(spectral_res, grating) - + grating = _calsys_get_parameter(override_kwargs, "grating", self.calculate_grating_type, + wavelen, spectral_res) + slit_widths = _calsys_get_parameter(override_kwargs, "slit_widths", self.calculate_slit_widths, + wavelen, spectral_res, grating) self.log.debug( f"setting up monochromtor with wavlength {wavelen} nm and spectral resolution {spectral_res}" ) @@ -93,12 +98,22 @@ async def setup_for_wavelength( [monoch_fut, elect_fut, elect_fut2], return_when=asyncio.ALL_COMPLETED ) self.log.debug("all SAL setup commands returned") - specsposure_time = self.spectrograph_exposure_time_for_nelectrons(nelec) - return + self._specsposure_time = _calsys_get_parameter(override_kwargs, "specsposure_time", + self.spectrograph_exposure_time_for_nelectrons, + nelec) + self._elecsposure_time = _calsys_get_parameter(override_kwargs, "elecsposure_time", + self.pd_exposure_time_for_nelectrons, + nelec) + self._n_spec_exps = _calsys_get_parameter(override_kwargs, "n_spec_exps", + self.spectrograph_n_exps_for_nelectrons, nelec) + self._n_elec_exps = _calsys_get_parameter(override_kwargs, "n_elec_exps", + self.pd_n_exps_for_nelectrons, nelec) + - def calculate_slit_width(self, spectral_res: float, grating) -> ATSpectrographSlits: - # NOTE: this will either need to be derived by doing calculations on the Grating equation, or by loading in calibration data (which I couldn't find yet!) - pass + + def calculate_slit_width(self,wavelen: float, spectral_res: float, grating) -> Optional[ATSpectrographSlits]: + # NOTE: this will either need to be derived by doing calculations on the Grating equation, or by loading in calibration data (which I couldn't find yet!). For now we just return the + raise NotImplementedError("calculation of slit widths not available yet, override in script parameters!") def calculate_grating_type(self, wavelen: float, spectral_res: float) -> ATMonochromator.Grating: # TODO: placeholder logic, in particular the exact numbers will be WRONG! @@ -109,27 +124,35 @@ def calculate_grating_type(self, wavelen: float, spectral_res: float) -> ATMonoc return ATMonochromator.Grating.BLUE return ATMonochromator.Grating.RED - async def _setup_spectrograph(self, int_time: float) -> None: - pass - async def _setup_electrometer(self, int_time: float): - pass + async def _electrometer_expose(self) -> Awaitable[list[str]]: + assert self._n_elec_exps is not None + assert self._elecsposure_time is not None + out_urls: list[str] = [] + for i in range(self._n_elec_exps): + await self._sal_cmd(self.Electrometer, "startScanDt", scanDuration=self._elecsposure_time) + lfa_obj_fut = await self._sal_waitevent(self.Electrometer, "largeFileObjectAvailable") + out_urls.append(lfa_obj_fut.url) + return out_urls - async def _electrometer_expose(self, exp_time: float) -> Awaitable[str]: - await self._sal_cmd(self.Electrometer, "startScanDt", scanDuration=exp_time, - run_immediate=False) - lfa_obj = self._sal_waitevent(self.Electrometer, "largeFileObjectAvailable", - run_immediate=False) - return lfa_obj.url + async def _spectrograph_expose(self) -> Awaitable[list[str]]: + assert self._n_spec_exps is not None + assert self._specsposure_time is not None - async def _spectrograph_expose(self, exp_time: float, numExposures: int) -> Awaitable[str]: - await self._sal_cmd(self.ATSpectrograph, "expose", numExposures = numExposures) - lfa_obj = await self._sal_waitevent(self.ATSpectrograph, "largeFileObjectAvailable", - run_immediate=False) - return lfa_obj.url + out_urls: list[str] = [] + for i in range(self._n_spec_exps): + await self._sal_cmd(self.ATSpectrograph, "expose", numExposures = numExposures) + lfa_obj_fut = await self._sal_waitevent(self.ATSpectrograph, "largeFileObjectAvailable", + run_immediate=true) + out_urls.append(lfa_obj_fut.url) + return out_urls + @property + def _electrometer_object(self): + return self.Electrometer + @property def script_time_estimate_s(self) -> float: """Property that returns the estimated time for the script to run in units of seconds @@ -185,12 +208,12 @@ async def power_sequence_run(self, scriptobj: salobj.BaseScript): self.WHITELIGHT_LAMP_WARMUP_TIMEOUT) await scriptobj.checkpoint("lamp has cooled down") await self._chiller_power(False) - self.log.info("chiller has been turned off, ATCalsys is powered down"!) + self.log.info("chiller has been turned off, ATCalsys is powered down") case _: raise NotImplementedError("don't know how to handle this script intention") - async def validate_hardware_status_for_acquisition(self) -> Awaitable[float]: + async def validate_hardware_status_for_acquisition(self) -> Awaitable: shutter_fut = self._sal_waitevent(self.ATWhiteLight, "shutterState") lamp_fut = self._sal_waitevent(self.ATWhiteLight, "lampState") @@ -201,7 +224,7 @@ async def validate_hardware_status_for_acquisition(self) -> Awaitable[float]: raise RuntimeError(errmsg) if shutter_state.actualState != ATWhiteLight.ShutterState.OPEN: - errmsg = f"shutter is not open, its state is reported as {repr(shutter_state.actualState)}") + errmsg = f"shutter is not open, its state is reported as {repr(shutter_state.actualState)}" self.log.error(errmsg) raise RuntimeError(errmsg) @@ -211,11 +234,9 @@ async def validate_hardware_status_for_acquisition(self) -> Awaitable[float]: self.log.error(errmsg) raise RuntimeError(errmsg) - if !lamp_state.lightDetected: + if not lamp_state.lightDetected: self.log.warning(f"all states seem fine, but lamp is not reporting light detected!") - lamp_power: float = lamp_state.setPower - return lamp_power def _chiller_temp_check(self, temps) -> bool: self.log.debug(f"Chiller supply temperature: {temps.supplyTemperature:0.1f} C " @@ -251,7 +272,8 @@ async def _lamp_power(self, onoff:bool) -> Awaitable: shutter_task = self._sal_cmd(self.ATWhiteLight, shutter_cmd_target, run_immediate=False) #now start the lamp - lamp_start_task = self._sal_cmd(self.ATWhiteLight, lamp_cmd_target, run_immediate=False) + lamp_start_task = self._sal_cmd(self.ATWhiteLight, lamp_cmd_target, run_immediate=False, + power=self.WHITELIGHT_POWER) await asyncio.wait([shutter_task, lamp_start_task], timeout=self._cmd_timeout, return_when=asyncio.FIRST_EXCEPTION) @@ -283,4 +305,22 @@ def lamp_verify(evt): return self._long_wait_err_handle(lamp_evt_gen, lamp_settle_timeout, lamp_verify, lamp_transition_name) + + async def take_calibration_data(self) -> Awaitable[dict[str, list[str]]]: + spec_fut = self._spectrograph_expose() + elec_fut = self._electrometer_expose() + + spec_results, elec_results = await asyncio.gather(spec_fut, elec_fut) + return {"spectrometer_urls" : spec_results, + "electrometer_urls" : elec_results} + + + @property + def program_reason(self) -> str: + return "AT_flats" + + @property + def prgoram_note(self) -> str: + return "TODO" + diff --git a/python/lsst/ts/observatory/control/base_calsys.py b/python/lsst/ts/observatory/control/base_calsys.py index d3615ed5..27431652 100644 --- a/python/lsst/ts/observatory/control/base_calsys.py +++ b/python/lsst/ts/observatory/control/base_calsys.py @@ -1,13 +1,12 @@ from abc import ABCMeta, abstractmethod from dataclasses import dataclass from .remote_group import RemoteGroup -from typing import Iterable, Optional, Tuple, List, Union -from typing import Sequence, Mapping, TypeAlias, Any -from typing import TypeVar +from typing import Iterable, Optional, Union +from typing import Sequence, Mapping, TypeAlias, Any, Awaitable from functools import reduce from collections.abc import Coroutine, Callable, AsyncGenerator -from operator import mul from lsst.ts import salobj +from lsst.ts.idl.enums import Electrometer import logging import asyncio from importlib.resources import files @@ -17,9 +16,17 @@ import astropy.units as un import enum from datetime import datetime +from itertools import count + Responsivity: TypeAlias = Quantity[ampere / watt] +def _calsys_get_parameter(indct: dict[str, Any], key: str, factory_callable: Callable, + *factory_args, **factory_kwargs): + if indct.get(key, None) is None: + return factory_callable(*factory_args, **factory_kwargs) + + @dataclass class CalibrationSequenceStepBase: wavelength: float @@ -173,12 +180,13 @@ class BaseCalsys(RemoteGroup, metaclass=ABCMeta): CMD_TIMEOUT: Quantity[un.physical.time] = 30 << un.s EVT_TIMEOUT: Quantity[un.physical.time] = 30 << un.s TELEM_TIMEOUT: Quantity[un.physical.time] = 30 << un.s + CAL_PROGRAM_NAME: str = "flats" def __init__( self, intention: CalsysScriptIntention, components: Iterable[str], - domain: Optional[salobj.domain] = None, + domain: Optional[salobj.domain.Domain] = None, cmd_timeout: Optional[int] = 10, log: Optional[logging.Logger] = None, ): @@ -269,12 +277,11 @@ def log_event_timings(cls, logger, time_evt_name: str, logstr2 = f"the duration was: {duration}, and our timeout allowance was: {expt_duration}" logger.info(logstr2) - async def take_electrometer_exposures( self, electrobj, exp_time_s: float, n: int - ) -> List[str]: - urlout: List[str] = [] + ) -> list[str]: + urlout: list[str] = [] for i in range(n): await electrobj.cmd_StartScanDt.set_start(scanDuration=exp_time) lfaurl = await self._lfa_event_helper(electrobj) @@ -305,21 +312,31 @@ def detector_exposure_time_for_nelectrons( assert issubclass(type(self), CalsysThroughputCalculationMixin) def spectrograph_exposure_time_for_nelectrons(self, nelec: float) -> float: - pass + raise NotImplementerError("throughput calc for spectrograph not implemented yet!") + + def spectrograph_n_exps_for_nelectrons(self, nelec: float) -> int: + raise NotImplementedError("throughput calc for spectrograph not implemented yet!") def pd_exposure_time_for_nelectrons(self, nelec: float) -> float: - pass + raise NotImplementedError("throughput calc for electrometer not implemented yet!") + + def pd_n_exps_for_nelectrons(self, nelec: float) -> int: + raise NotImplementedError("throughput calc for electrometer not implemented yet") + + @property + @abstractmethod + def _electrometer_object(self) -> Electrometer: + pass @abstractmethod async def validate_hardware_status_for_acquisition(self) -> Awaitable: pass @abstractmethod - async def power_sequence_run(self, scriptobj, **kwargs): + async def power_sequence_run(self, scriptobj, **kwargs) -> Awaitable: pass - @abstractmethod async def setup_for_wavelength(self, wavelen: float, **extra_params) -> None: """awaitable which sets up the various remote components of a calibration system @@ -339,25 +356,35 @@ async def setup_for_wavelength(self, wavelen: float, **extra_params) -> None: pass @abstractmethod - async def take_calibration_instr_exposures(self) -> None: + async def take_calibration_data(self): """awaitable which starts the exposures for the calibration instruments (i.e the spectrographs, electrometers etc) according to the setup. It does not take images with the instrument under test, it is intended that script components which use this class do that themselves""" pass - @property - @abstractmethod - def wavelen(self) -> Quantity[nm]: - """returns the currently configured wavelength""" - @abstractmethod - async def take_detector_data(self): - """This will fire off all async tasks to take calibration data in sequence, and return locations a - nd metadata about the files supplied etc""" + async def generate_data_flats(self, instrobj, scriptobj, exposure_time_s: float, n_iter: Optional[int] = None): + """returns an async generator which yields sets of exposures from """ + + # Run forever if n_iter was not given, don't worry it's just a generator + nrange = count() if n_iter is None else range(n_iter) + for i in nrange: + self.log.info("taking flats number %d", i) + instr_task = instrobj.take_flats(exposure_time_s, nflats=1, groupid=scriptobj.group_id, + program = self.CAL_PROGRAM_NAME, + reason = self.program_reason, + note = self.program_note + ) + instr_fut = asyncio.create_task(instr_task) + aux_cal_fut = self.take_calibration_data() + instr_results, aux_cal_results = await asyncio.gather(aux_cal_fut, instr_fut) + yield instr_results, aux_cal_results + @property @abstractmethod - async def gen_calibration_auxiliaries(self): - pass + def program_reason(self) -> str: ... + + @property + @abstractmethod + def program_note(self) -> str: ... - async def wait_ready(self): - """ Method to wait for prepared state for taking data - e.g. lamps on, warmed up, laser warmed up etc""" From 652cba838eb684585859934b77c8ec9fcc0583db Mon Sep 17 00:00:00 2001 From: Dan Date: Tue, 12 Dec 2023 03:24:20 +0000 Subject: [PATCH 06/17] refactor taking n exposures in spectrometer and electrometer --- .../ts/observatory/control/auxtel/atcalsys.py | 27 +++++++------------ .../ts/observatory/control/base_calsys.py | 8 ++++++ 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/python/lsst/ts/observatory/control/auxtel/atcalsys.py b/python/lsst/ts/observatory/control/auxtel/atcalsys.py index 2cf503dd..7a38c9a4 100644 --- a/python/lsst/ts/observatory/control/auxtel/atcalsys.py +++ b/python/lsst/ts/observatory/control/auxtel/atcalsys.py @@ -1,8 +1,8 @@ -from typing import List, Optional, NamedTuple, TYPE_CHECKING +from typing import List, Optional, NamedTuple from ..base_calsys import BaseCalsys, HardcodeCalsysThroughput, CalibrationSequenceStepBase from ..base_calsys import CalsysScriptIntention, _calsys_get_parameter from lsst.ts import salobj -from lsst.ts.idl.enums import ATMonochromator, Electrometer +from lsst.ts.idl.enums import ATMonochromator from lsst.ts.idl.enums import ATWhiteLight import asyncio import astropy.units as un @@ -16,6 +16,7 @@ class ATSpectrographSlits(NamedTuple): FRONTENTRANCE: float FRONTEXIT: float + @dataclass class ATCalibrationSequenceStep(CalibrationSequenceStepBase): grating: ATMonochromator.Grating @@ -108,7 +109,7 @@ async def setup_for_wavelength( self.spectrograph_n_exps_for_nelectrons, nelec) self._n_elec_exps = _calsys_get_parameter(override_kwargs, "n_elec_exps", self.pd_n_exps_for_nelectrons, nelec) - + def calculate_slit_width(self,wavelen: float, spectral_res: float, grating) -> Optional[ATSpectrographSlits]: @@ -128,31 +129,21 @@ def calculate_grating_type(self, wavelen: float, spectral_res: float) -> ATMonoc async def _electrometer_expose(self) -> Awaitable[list[str]]: assert self._n_elec_exps is not None assert self._elecsposure_time is not None - out_urls: list[str] = [] + return await self._cal_expose_helper(self.Electrometer, self._n_elec_exps, + "startScanDt", scanDuration=self._elecsposure_time) - for i in range(self._n_elec_exps): - await self._sal_cmd(self.Electrometer, "startScanDt", scanDuration=self._elecsposure_time) - lfa_obj_fut = await self._sal_waitevent(self.Electrometer, "largeFileObjectAvailable") - out_urls.append(lfa_obj_fut.url) - return out_urls async def _spectrograph_expose(self) -> Awaitable[list[str]]: assert self._n_spec_exps is not None assert self._specsposure_time is not None - out_urls: list[str] = [] - for i in range(self._n_spec_exps): - await self._sal_cmd(self.ATSpectrograph, "expose", numExposures = numExposures) - lfa_obj_fut = await self._sal_waitevent(self.ATSpectrograph, "largeFileObjectAvailable", - run_immediate=true) - - out_urls.append(lfa_obj_fut.url) - return out_urls + return await self._cal_expose_helper(self.ATSpectrograph, self._n_spec_exps, + "expose", numExposures=1, duration=self._specsposure_time) @property def _electrometer_object(self): return self.Electrometer - + @property def script_time_estimate_s(self) -> float: """Property that returns the estimated time for the script to run in units of seconds diff --git a/python/lsst/ts/observatory/control/base_calsys.py b/python/lsst/ts/observatory/control/base_calsys.py index 27431652..6321210f 100644 --- a/python/lsst/ts/observatory/control/base_calsys.py +++ b/python/lsst/ts/observatory/control/base_calsys.py @@ -253,6 +253,14 @@ async def completer() -> None: return asyncio.create_task(coro) return coro + async def _cal_expose_helper(self, obj, n: int, cmdname: str, **extra_kwargs) -> Awaitable[list[str]]: + out_urls: list[str] = [] + for i in range(n): + await self._sal_cmd(obj, cmdname, **extra_kwargs) + lfa_obj = await self._sal_waitevent(obj, "largeFileObjectAvailable") + out_urls.append(lfa_obj.url) + return out_urls + async def _long_wait_err_handle(self, gen: AsyncGenerator, timeout_seconds, validate_fun: Callable[[Any], bool], name_of_wait: str) -> tuple[datetime,datetime]: starttime = datetime.now() From a4ba86e3be65f319746ea492685308587f5adacb Mon Sep 17 00:00:00 2001 From: Dan Date: Tue, 12 Dec 2023 03:55:31 +0000 Subject: [PATCH 07/17] black reformat --- .../ts/observatory/control/auxtel/atcalsys.py | 288 ++++++++++++------ .../ts/observatory/control/base_calsys.py | 161 +++++++--- 2 files changed, 305 insertions(+), 144 deletions(-) diff --git a/python/lsst/ts/observatory/control/auxtel/atcalsys.py b/python/lsst/ts/observatory/control/auxtel/atcalsys.py index 7a38c9a4..696294bf 100644 --- a/python/lsst/ts/observatory/control/auxtel/atcalsys.py +++ b/python/lsst/ts/observatory/control/auxtel/atcalsys.py @@ -1,5 +1,9 @@ from typing import List, Optional, NamedTuple -from ..base_calsys import BaseCalsys, HardcodeCalsysThroughput, CalibrationSequenceStepBase +from ..base_calsys import ( + BaseCalsys, + HardcodeCalsysThroughput, + CalibrationSequenceStepBase, +) from ..base_calsys import CalsysScriptIntention, _calsys_get_parameter from lsst.ts import salobj from lsst.ts.idl.enums import ATMonochromator @@ -29,6 +33,7 @@ class ATCalibrationSequenceStep(CalibrationSequenceStepBase): em_exp_time: float em_n_exp: int + class ATCalsys(BaseCalsys, HardcodeCalsysThroughput): """class which specifically handles the calibration system for auxtel""" @@ -36,13 +41,15 @@ class ATCalsys(BaseCalsys, HardcodeCalsysThroughput): "ATMonochromator", "FiberSpectrograph", "Electrometer", - "ATWhiteLight" + "ATWhiteLight", ] CHANGE_GRATING_TIME: Quantity[un.physical.time] = 60 << un.s # these below numbers should be able to be loaded from a (fairly static) config! GRATING_CHANGEOVER_WL: Quantity[un.physical.length] = 532.0 << un.nm - GRATING_CHANGEOVER_BW: Quantity[un.physical.length] = 55.0 << un.nm # WARNING! PLACEHOLDER VALUE!!! + GRATING_CHANGEOVER_BW: Quantity[un.physical.length] = ( + 55.0 << un.nm + ) # WARNING! PLACEHOLDER VALUE!!! CHILLER_COOLDOWN_TIMEOUT: Quantity[un.physical.time] = 15 << un.min CHILLER_SETPOINT_TEMP: Quantity[un.physical.temperature] = 20 << un.deg_C @@ -57,20 +64,30 @@ class ATCalsys(BaseCalsys, HardcodeCalsysThroughput): def __init__(self, intention: CalsysScriptIntention, **kwargs): super().__init__(intention, components=self._AT_SAL_COMPONENTS, **kwargs) - #instance variables we'll set later + # instance variables we'll set later self._specsposure_time: Optional[float] = None self._elecsposure_time: Optional[float] = None self._n_spec_exps: Optional[int] = None self._n_elec_exps: Optional[int] = None async def setup_for_wavelength( - self, wavelen: float, nelec: float, spectral_res: float, **override_kwargs + self, wavelen: float, nelec: float, spectral_res: float, **override_kwargs ) -> None: - - grating = _calsys_get_parameter(override_kwargs, "grating", self.calculate_grating_type, - wavelen, spectral_res) - slit_widths = _calsys_get_parameter(override_kwargs, "slit_widths", self.calculate_slit_widths, - wavelen, spectral_res, grating) + grating = _calsys_get_parameter( + override_kwargs, + "grating", + self.calculate_grating_type, + wavelen, + spectral_res, + ) + slit_widths = _calsys_get_parameter( + override_kwargs, + "slit_widths", + self.calculate_slit_widths, + wavelen, + spectral_res, + grating, + ) self.log.debug( f"setting up monochromtor with wavlength {wavelen} nm and spectral resolution {spectral_res}" ) @@ -87,7 +104,7 @@ async def setup_for_wavelength( ) elect_fut = self._sal_cmd("electrometer", "performZeroCalib") - elect_fut2 = self._sal_cmd( + elect_fut2 = self._sal_cmd( self.Electrometer, "setDigitalFilter", activateFilter=False, @@ -99,24 +116,39 @@ async def setup_for_wavelength( [monoch_fut, elect_fut, elect_fut2], return_when=asyncio.ALL_COMPLETED ) self.log.debug("all SAL setup commands returned") - self._specsposure_time = _calsys_get_parameter(override_kwargs, "specsposure_time", - self.spectrograph_exposure_time_for_nelectrons, - nelec) - self._elecsposure_time = _calsys_get_parameter(override_kwargs, "elecsposure_time", - self.pd_exposure_time_for_nelectrons, - nelec) - self._n_spec_exps = _calsys_get_parameter(override_kwargs, "n_spec_exps", - self.spectrograph_n_exps_for_nelectrons, nelec) - self._n_elec_exps = _calsys_get_parameter(override_kwargs, "n_elec_exps", - self.pd_n_exps_for_nelectrons, nelec) - - + self._specsposure_time = _calsys_get_parameter( + override_kwargs, + "specsposure_time", + self.spectrograph_exposure_time_for_nelectrons, + nelec, + ) + self._elecsposure_time = _calsys_get_parameter( + override_kwargs, + "elecsposure_time", + self.pd_exposure_time_for_nelectrons, + nelec, + ) + self._n_spec_exps = _calsys_get_parameter( + override_kwargs, + "n_spec_exps", + self.spectrograph_n_exps_for_nelectrons, + nelec, + ) + self._n_elec_exps = _calsys_get_parameter( + override_kwargs, "n_elec_exps", self.pd_n_exps_for_nelectrons, nelec + ) - def calculate_slit_width(self,wavelen: float, spectral_res: float, grating) -> Optional[ATSpectrographSlits]: - # NOTE: this will either need to be derived by doing calculations on the Grating equation, or by loading in calibration data (which I couldn't find yet!). For now we just return the - raise NotImplementedError("calculation of slit widths not available yet, override in script parameters!") + def calculate_slit_width( + self, wavelen: float, spectral_res: float, grating + ) -> Optional[ATSpectrographSlits]: + # NOTE: this will either need to be derived by doing calculations on the Grating equation, or by loading in calibration data (which I couldn't find yet!). For now we just return the + raise NotImplementedError( + "calculation of slit widths not available yet, override in script parameters!" + ) - def calculate_grating_type(self, wavelen: float, spectral_res: float) -> ATMonochromator.Grating: + def calculate_grating_type( + self, wavelen: float, spectral_res: float + ) -> ATMonochromator.Grating: # TODO: placeholder logic, in particular the exact numbers will be WRONG! # likely something like the below if spectral_res > self.GRATING_CHANGEOVER_BW: @@ -125,20 +157,27 @@ def calculate_grating_type(self, wavelen: float, spectral_res: float) -> ATMonoc return ATMonochromator.Grating.BLUE return ATMonochromator.Grating.RED - async def _electrometer_expose(self) -> Awaitable[list[str]]: assert self._n_elec_exps is not None assert self._elecsposure_time is not None - return await self._cal_expose_helper(self.Electrometer, self._n_elec_exps, - "startScanDt", scanDuration=self._elecsposure_time) - + return await self._cal_expose_helper( + self.Electrometer, + self._n_elec_exps, + "startScanDt", + scanDuration=self._elecsposure_time, + ) async def _spectrograph_expose(self) -> Awaitable[list[str]]: assert self._n_spec_exps is not None assert self._specsposure_time is not None - return await self._cal_expose_helper(self.ATSpectrograph, self._n_spec_exps, - "expose", numExposures=1, duration=self._specsposure_time) + return await self._cal_expose_helper( + self.ATSpectrograph, + self._n_spec_exps, + "expose", + numExposures=1, + duration=self._specsposure_time, + ) @property def _electrometer_object(self): @@ -150,59 +189,98 @@ def script_time_estimate_s(self) -> float: For script time estimation purposes. For now just returns a default long time""" - match(self._intention): + match (self._intention): case CalsysScriptIntention.POWER_ON | CalsysScriptIntention.POWER_OFF: - #for now just use fixed values from previous script - #start out with chiller time maximum + # for now just use fixed values from previous script + # start out with chiller time maximum total_time: Quantity[un.physical.time] = self.CHILLER_COOLDOWN_TIMEOUT - #add on the lamp warmup timeout + # add on the lamp warmup timeout total_time += self.WHITELIGHT_LAMP_WARMUP_TIMEOUT total_time += self.SHUTTER_OPEN_TIMEOUT return total_time.to(un.s).value case _: - raise NotImplementedError("don't know how to handle this script intention") + raise NotImplementedError( + "don't know how to handle this script intention" + ) async def power_sequence_run(self, scriptobj: salobj.BaseScript): - match(self._intention): + match (self._intention): case CalsysScriptIntention.POWER_ON: await self._chiller_power(True) await scriptobj.checkpoint("Chiller started") chiller_start, chiller_end = await self._chiller_settle(True) - self.log_event_timings(self.log, "chiller cooldown time", chiller_start, chiller_end, - self.CHILLER_COOLDOWN_TIMEOUT) - + self.log_event_timings( + self.log, + "chiller cooldown time", + chiller_start, + chiller_end, + self.CHILLER_COOLDOWN_TIMEOUT, + ) + await scriptobj.checkpoint("Chiller setpoint temperature reached") - shutter_wait_fut = asyncio.create_task(self._lamp_power(True), "lamp_start_shutter_open") - lamp_settle_fut = asyncio.create_task(self._lamp_settle(True), "lamp_power_settle") + shutter_wait_fut = asyncio.create_task( + self._lamp_power(True), "lamp_start_shutter_open" + ) + lamp_settle_fut = asyncio.create_task( + self._lamp_settle(True), "lamp_power_settle" + ) shutter_start, shutter_end = await shutter_wait_fut - self.log_event_timings(self.log, "shutter open time", shutter_start, shutter_end, - self.SHUTTER_OPEN_TIMEOUT) + self.log_event_timings( + self.log, + "shutter open time", + shutter_start, + shutter_end, + self.SHUTTER_OPEN_TIMEOUT, + ) await scriptobj.checkpoint("shutter open and lamp started") lamp_settle_start, lamp_settle_end = await lamp_settle_fut - self.log_event_timings(self.log, "lamp warm up", lamp_settle_start, lamp_settle_end, - self.WHITELIGHT_LAMP_WARMUP_TIMEOUT) + self.log_event_timings( + self.log, + "lamp warm up", + lamp_settle_start, + lamp_settle_end, + self.WHITELIGHT_LAMP_WARMUP_TIMEOUT, + ) self.log.info("lamp is warmed up, ATCalsys is powered on and ready") case CalsysScriptIntention.POWER_OFF: await self._lamp_power(False) - await scriptobj.checkpoint("lamp commanded off and shutter commanded closed") - shutter_wait_fut = asyncio.create_task(self._lamp_power(False), "lamp stop shutter close") - lamp_settle_fut = asyncio.create_task(self._lamp_settle(False), "lamp power settle") + await scriptobj.checkpoint( + "lamp commanded off and shutter commanded closed" + ) + shutter_wait_fut = asyncio.create_task( + self._lamp_power(False), "lamp stop shutter close" + ) + lamp_settle_fut = asyncio.create_task( + self._lamp_settle(False), "lamp power settle" + ) shutter_start, shutter_end = await shutter_wait_fut - self.log_event_timings(self.log, "shutter close", shutter_start, shutter_end, - self.SHUTTER_OPEN_TIMEOUT) + self.log_event_timings( + self.log, + "shutter close", + shutter_start, + shutter_end, + self.SHUTTER_OPEN_TIMEOUT, + ) await scriptobj.checkpoint("shutter closed annd lamp turned off") lamp_settle_start, lamp_settle_end = await lamp_settle_fut - self.log_event_timings(self.log, "lamp cooldown", lamp_settle_start, lamp_settle_end, - self.WHITELIGHT_LAMP_WARMUP_TIMEOUT) + self.log_event_timings( + self.log, + "lamp cooldown", + lamp_settle_start, + lamp_settle_end, + self.WHITELIGHT_LAMP_WARMUP_TIMEOUT, + ) await scriptobj.checkpoint("lamp has cooled down") await self._chiller_power(False) self.log.info("chiller has been turned off, ATCalsys is powered down") case _: - raise NotImplementedError("don't know how to handle this script intention") + raise NotImplementedError( + "don't know how to handle this script intention" + ) async def validate_hardware_status_for_acquisition(self) -> Awaitable: shutter_fut = self._sal_waitevent(self.ATWhiteLight, "shutterState") @@ -224,19 +302,25 @@ async def validate_hardware_status_for_acquisition(self) -> Awaitable: errmsg = f"lamp state is not on, its state is reported as {repr(lamp_state.basicState)}" self.log.error(errmsg) raise RuntimeError(errmsg) - - if not lamp_state.lightDetected: - self.log.warning(f"all states seem fine, but lamp is not reporting light detected!") + if not lamp_state.lightDetected: + self.log.warning( + f"all states seem fine, but lamp is not reporting light detected!" + ) def _chiller_temp_check(self, temps) -> bool: - self.log.debug(f"Chiller supply temperature: {temps.supplyTemperature:0.1f} C " - f"[set:{temps.setTemperature} deg].") - pct_dev: float = (temps.supplyTemperature - temps.setTemperature) / temps.setTemperature + self.log.debug( + f"Chiller supply temperature: {temps.supplyTemperature:0.1f} C " + f"[set:{temps.setTemperature} deg]." + ) + pct_dev: float = ( + temps.supplyTemperature - temps.setTemperature + ) / temps.setTemperature if pct_dev <= self.CHILLER_TEMP_REL_TOL: self.log.info( - f"Chiller reached target temperature, {temps.supplyTemperature:0.1f} deg ") + f"Chiller reached target temperature, {temps.supplyTemperature:0.1f} deg " + ) return True return False @@ -244,67 +328,83 @@ async def _chiller_power(self, onoff: bool): cmd_target = "startChiller" if onoff else "stopChiller" if onoff: chiller_setpoint_temp: float = self.CHILLER_SETPOINT_TEMP.to(un.s).value - await self._sal_cmd(self.ATWhiteLight, "setChillerTemperature", - temperature=chiller_setpoint_temp) + await self._sal_cmd( + self.ATWhiteLight, + "setChillerTemperature", + temperature=chiller_setpoint_temp, + ) await self._sal_cmd(self.ATWhiteLight, cmd_target) - async def _chiller_settle(self) -> Awaitable[tuple[datetime,datetime]]: + async def _chiller_settle(self) -> Awaitable[tuple[datetime, datetime]]: chiller_wait_timeout: float = self.CHILLER_COOLDOWN_TIMEOUT.to(un.s).value chiller_temp_gen = self._sal_telem_gen(self.ATWhiteLight, "chillerTemperatures") - return await self._long_wait_err_handle(chiller_temp_gen, chiller_wait_timeout, - self._chiller_temp_check, "chiller temperature range settle") + return await self._long_wait_err_handle( + chiller_temp_gen, + chiller_wait_timeout, + self._chiller_temp_check, + "chiller temperature range settle", + ) - async def _lamp_power(self, onoff:bool) -> Awaitable: + async def _lamp_power(self, onoff: bool) -> Awaitable: shutter_cmd_target = "openShutter" if onoff else "closeShutter" lamp_cmd_target = "turnLampOn" if onoff else "turnLampOff" - #TODO: do we want asserts etc here to check the lamp state is correct first? - #first, open the shutter - shutter_task = self._sal_cmd(self.ATWhiteLight, shutter_cmd_target, run_immediate=False) + # TODO: do we want asserts etc here to check the lamp state is correct first? + # first, open the shutter + shutter_task = self._sal_cmd( + self.ATWhiteLight, shutter_cmd_target, run_immediate=False + ) - #now start the lamp - lamp_start_task = self._sal_cmd(self.ATWhiteLight, lamp_cmd_target, run_immediate=False, - power=self.WHITELIGHT_POWER) + # now start the lamp + lamp_start_task = self._sal_cmd( + self.ATWhiteLight, + lamp_cmd_target, + run_immediate=False, + power=self.WHITELIGHT_POWER, + ) - await asyncio.wait([shutter_task, lamp_start_task], timeout=self._cmd_timeout, - return_when=asyncio.FIRST_EXCEPTION) + await asyncio.wait( + [shutter_task, lamp_start_task], + timeout=self._cmd_timeout, + return_when=asyncio.FIRST_EXCEPTION, + ) - #now run long wait for shutter + # now run long wait for shutter shutter_evt_gen = self._sal_evt_gen(self.ATWhiteLight, "shutterState") shutter_wait_timeout: float = self.SHUTTER_OPEN_TIMEOUT.to(un.s).value - #TODO: also probably bail out if this reports an unexpected state + # TODO: also probably bail out if this reports an unexpected state def shutter_verify(evt): return evt.actualState == evt.commandedState - #TODO: this can be smarter, lamp reports time when it will be warm, - #can use this to update scripts etc - return self._long_wait_err_handle(shutter_evt_gen, shutter_wait_timeout, - shutter_verify, "shutter state") - + # TODO: this can be smarter, lamp reports time when it will be warm, + # can use this to update scripts etc + return self._long_wait_err_handle( + shutter_evt_gen, shutter_wait_timeout, shutter_verify, "shutter state" + ) async def _lamp_settle(self, onoff: bool) -> Awaitable: lamp_evt_gen = self._sal_evt_gen(self.ATWhiteLight, "lampState") lamp_settle_timeout: float = self.WHITELIGHT_LAMP_WARMUP_TIMEOUT.to(un.s).value - lamp_tgt_state = ATWhiteLight.LampBasicState.On if onoff else ATWhiteLight.LampBasicState.Off + lamp_tgt_state = ( + ATWhiteLight.LampBasicState.On if onoff else ATWhiteLight.LampBasicState.Off + ) lamp_transition_name = "lamp warming up" if onoff else "lamp cooling down" - + def lamp_verify(evt): nonlocal lamp_tgt_state return evt.basicState == lamp_tgt_state - return self._long_wait_err_handle(lamp_evt_gen, lamp_settle_timeout, - lamp_verify, lamp_transition_name) - + return self._long_wait_err_handle( + lamp_evt_gen, lamp_settle_timeout, lamp_verify, lamp_transition_name + ) async def take_calibration_data(self) -> Awaitable[dict[str, list[str]]]: spec_fut = self._spectrograph_expose() elec_fut = self._electrometer_expose() - spec_results, elec_results = await asyncio.gather(spec_fut, elec_fut) - return {"spectrometer_urls" : spec_results, - "electrometer_urls" : elec_results} - + spec_results, elec_results = await asyncio.gather(spec_fut, elec_fut) + return {"spectrometer_urls": spec_results, "electrometer_urls": elec_results} @property def program_reason(self) -> str: @@ -313,5 +413,3 @@ def program_reason(self) -> str: @property def prgoram_note(self) -> str: return "TODO" - - diff --git a/python/lsst/ts/observatory/control/base_calsys.py b/python/lsst/ts/observatory/control/base_calsys.py index 6321210f..5b494023 100644 --- a/python/lsst/ts/observatory/control/base_calsys.py +++ b/python/lsst/ts/observatory/control/base_calsys.py @@ -21,8 +21,14 @@ Responsivity: TypeAlias = Quantity[ampere / watt] -def _calsys_get_parameter(indct: dict[str, Any], key: str, factory_callable: Callable, - *factory_args, **factory_kwargs): + +def _calsys_get_parameter( + indct: dict[str, Any], + key: str, + factory_callable: Callable, + *factory_args, + **factory_kwargs, +): if indct.get(key, None) is None: return factory_callable(*factory_args, **factory_kwargs) @@ -40,6 +46,7 @@ class CalsysScriptIntention(enum.IntEnum): QUICK_CALIBRATION_RUN = 2 LONG_CALIBRATION_RUN = 3 + TaskOrCoro = Union[asyncio.Task, Coroutine] @@ -75,7 +82,9 @@ def spectrograph_throughput(self, wavelen: float, calsys_power: float) -> float: """ @abstractmethod - def radiometer_responsivity(self, wavelen: Quantity[un.physical.length]) -> Responsivity: + def radiometer_responsivity( + self, wavelen: Quantity[un.physical.length] + ) -> Responsivity: """return the responsivity of the radiometer""" def end_to_end_throughput(self, wavelen: float, calsys_power: float) -> float: @@ -177,6 +186,7 @@ def maintel_throughput( class BaseCalsys(RemoteGroup, metaclass=ABCMeta): """Base class for calibration systems""" + CMD_TIMEOUT: Quantity[un.physical.time] = 30 << un.s EVT_TIMEOUT: Quantity[un.physical.time] = 30 << un.s TELEM_TIMEOUT: Quantity[un.physical.time] = 30 << un.s @@ -184,11 +194,11 @@ class BaseCalsys(RemoteGroup, metaclass=ABCMeta): def __init__( self, - intention: CalsysScriptIntention, - components: Iterable[str], - domain: Optional[salobj.domain.Domain] = None, - cmd_timeout: Optional[int] = 10, - log: Optional[logging.Logger] = None, + intention: CalsysScriptIntention, + components: Iterable[str], + domain: Optional[salobj.domain.Domain] = None, + cmd_timeout: Optional[int] = 10, + log: Optional[logging.Logger] = None, ): super().__init__( components, @@ -201,7 +211,7 @@ def __init__( self._intention = intention def _sal_cmd( - self, obj: salobj.Remote, cmdname: str, run_immediate: bool = True, **setargs + self, obj: salobj.Remote, cmdname: str, run_immediate: bool = True, **setargs ) -> TaskOrCoro: timeout = self.CMD_TIMEOUT.to(un.s).value cmdfun: salobj.topics.RemoteCommand = getattr(obj, f"cmd_{cmdname}") @@ -210,8 +220,14 @@ def _sal_cmd( return asyncio.create_task(pkgtask) return pkgtask - def _sal_waitevent(self, obj: salobj.Remote, evtname: str, run_immediate: bool=True, flush: bool=True, - **evtargs) -> TaskOrCoro: + def _sal_waitevent( + self, + obj: salobj.Remote, + evtname: str, + run_immediate: bool = True, + flush: bool = True, + **evtargs, + ) -> TaskOrCoro: timeout = self.EVT_TIMEOUT.to(un.s).value cmdfun: salobj.topics.RemoteEvent = getattr(obj, f"evt_{evtname}") pkgtask = cmdfun.next(timeout=timeout, flush=flush) @@ -219,16 +235,28 @@ def _sal_waitevent(self, obj: salobj.Remote, evtname: str, run_immediate: bool=T return asyncio.create_task(pkgtask) return pkgtask - def _lfa_event(self, obj: salobj.Remote, telname: str, run_immediate: bool = True, - flush: bool=True, **evtargs) -> TaskOrCoro: - return self._sal_waitevent(obj, "largeFileObjectAvailable", run_immediate, flush, **evtargs) + def _lfa_event( + self, + obj: salobj.Remote, + telname: str, + run_immediate: bool = True, + flush: bool = True, + **evtargs, + ) -> TaskOrCoro: + return self._sal_waitevent( + obj, "largeFileObjectAvailable", run_immediate, flush, **evtargs + ) - def _sal_evt_gen(self, obj:salobj.Remote, evtname: str, flush: bool=True) -> AsyncGenerator: + def _sal_evt_gen( + self, obj: salobj.Remote, evtname: str, flush: bool = True + ) -> AsyncGenerator: pkgtask = self._sal_waitevent(obj, evtname, run_immediate=False, flush=flush) + async def gen(): while True: v = await pkgtask yield v + return gen() def _sal_telem_gen(self, obj: salobj.Remote, telname: str) -> AsyncGenerator: @@ -239,13 +267,19 @@ async def gen(): while True: v = await cmdfun.next(timeout=timeout, flush=True) yield v + return gen() - def _long_wait(self, gen: AsyncGenerator, timeout_seconds, validate_fun: Callable[[Any], bool], - run_immediate: bool=True) -> TaskOrCoro: + def _long_wait( + self, + gen: AsyncGenerator, + timeout_seconds, + validate_fun: Callable[[Any], bool], + run_immediate: bool = True, + ) -> TaskOrCoro: async def completer() -> None: async for value in gen: - if(validate_fun(value)): + if validate_fun(value): return coro = asyncio.wait_for(completer(), timeout_seconds) @@ -253,7 +287,9 @@ async def completer() -> None: return asyncio.create_task(coro) return coro - async def _cal_expose_helper(self, obj, n: int, cmdname: str, **extra_kwargs) -> Awaitable[list[str]]: + async def _cal_expose_helper( + self, obj, n: int, cmdname: str, **extra_kwargs + ) -> Awaitable[list[str]]: out_urls: list[str] = [] for i in range(n): await self._sal_cmd(obj, cmdname, **extra_kwargs) @@ -261,30 +297,44 @@ async def _cal_expose_helper(self, obj, n: int, cmdname: str, **extra_kwargs) -> out_urls.append(lfa_obj.url) return out_urls - async def _long_wait_err_handle(self, gen: AsyncGenerator, timeout_seconds, - validate_fun: Callable[[Any], bool], name_of_wait: str) -> tuple[datetime,datetime]: + async def _long_wait_err_handle( + self, + gen: AsyncGenerator, + timeout_seconds, + validate_fun: Callable[[Any], bool], + name_of_wait: str, + ) -> tuple[datetime, datetime]: starttime = datetime.now() try: - await self._long_wait(gen, timeout_seconds, validate_fun, run_immediate=False) + await self._long_wait( + gen, timeout_seconds, validate_fun, run_immediate=False + ) endtime = datetime.now() return starttime, endtime except TimeoutError as err: nowfail = datetime.now() wait_time: float = (nowfail - starttime).total_seconds() - self.log.error(f"waited {wait_time} seconds but {name_of_wait} did not succeed") + self.log.error( + f"waited {wait_time} seconds but {name_of_wait} did not succeed" + ) raise err - @classmethod - def log_event_timings(cls, logger, time_evt_name: str, - start_time: datetime, end_time: datetime, - expd_duration: Quantity[un.physical.time]) -> None: - logstr = f"event: {time_evt_name} started at {start_time} and finished at {end_time}" + def log_event_timings( + cls, + logger, + time_evt_name: str, + start_time: datetime, + end_time: datetime, + expd_duration: Quantity[un.physical.time], + ) -> None: + logstr = ( + f"event: {time_evt_name} started at {start_time} and finished at {end_time}" + ) logger.info(logstr) duration = (start_time - end_time).total_seconds() << un.s - logstr2 = f"the duration was: {duration}, and our timeout allowance was: {expt_duration}" + logstr2 = f"the duration was: {duration}, and our timeout allowance was: {expt_duration}" logger.info(logstr2) - async def take_electrometer_exposures( self, electrobj, exp_time_s: float, n: int @@ -320,17 +370,24 @@ def detector_exposure_time_for_nelectrons( assert issubclass(type(self), CalsysThroughputCalculationMixin) def spectrograph_exposure_time_for_nelectrons(self, nelec: float) -> float: - raise NotImplementerError("throughput calc for spectrograph not implemented yet!") + raise NotImplementerError( + "throughput calc for spectrograph not implemented yet!" + ) def spectrograph_n_exps_for_nelectrons(self, nelec: float) -> int: - raise NotImplementedError("throughput calc for spectrograph not implemented yet!") + raise NotImplementedError( + "throughput calc for spectrograph not implemented yet!" + ) def pd_exposure_time_for_nelectrons(self, nelec: float) -> float: - raise NotImplementedError("throughput calc for electrometer not implemented yet!") + raise NotImplementedError( + "throughput calc for electrometer not implemented yet!" + ) def pd_n_exps_for_nelectrons(self, nelec: float) -> int: - raise NotImplementedError("throughput calc for electrometer not implemented yet") - + raise NotImplementedError( + "throughput calc for electrometer not implemented yet" + ) @property @abstractmethod @@ -340,7 +397,7 @@ def _electrometer_object(self) -> Electrometer: @abstractmethod async def validate_hardware_status_for_acquisition(self) -> Awaitable: pass - + @abstractmethod async def power_sequence_run(self, scriptobj, **kwargs) -> Awaitable: pass @@ -368,31 +425,37 @@ async def take_calibration_data(self): """awaitable which starts the exposures for the calibration instruments (i.e the spectrographs, electrometers etc) according to the setup. It does not take images with the instrument under test, it is intended that script components which use this class do that themselves""" pass - - async def generate_data_flats(self, instrobj, scriptobj, exposure_time_s: float, n_iter: Optional[int] = None): - """returns an async generator which yields sets of exposures from """ + async def generate_data_flats( + self, instrobj, scriptobj, exposure_time_s: float, n_iter: Optional[int] = None + ): + """returns an async generator which yields sets of exposures from""" # Run forever if n_iter was not given, don't worry it's just a generator nrange = count() if n_iter is None else range(n_iter) for i in nrange: self.log.info("taking flats number %d", i) - instr_task = instrobj.take_flats(exposure_time_s, nflats=1, groupid=scriptobj.group_id, - program = self.CAL_PROGRAM_NAME, - reason = self.program_reason, - note = self.program_note - ) + instr_task = instrobj.take_flats( + exposure_time_s, + nflats=1, + groupid=scriptobj.group_id, + program=self.CAL_PROGRAM_NAME, + reason=self.program_reason, + note=self.program_note, + ) instr_fut = asyncio.create_task(instr_task) aux_cal_fut = self.take_calibration_data() - instr_results, aux_cal_results = await asyncio.gather(aux_cal_fut, instr_fut) + instr_results, aux_cal_results = await asyncio.gather( + aux_cal_fut, instr_fut + ) yield instr_results, aux_cal_results @property @abstractmethod - def program_reason(self) -> str: ... + def program_reason(self) -> str: + ... @property @abstractmethod - def program_note(self) -> str: ... - - + def program_note(self) -> str: + ... From 1e352cf3945897ab6aa7bcd7d7f07e2f6143e6be Mon Sep 17 00:00:00 2001 From: Dan Date: Tue, 12 Dec 2023 04:05:07 +0000 Subject: [PATCH 08/17] fix most egregious lint --- .../ts/observatory/control/auxtel/atcalsys.py | 7 +++--- .../ts/observatory/control/base_calsys.py | 25 ++++++------------- 2 files changed, 11 insertions(+), 21 deletions(-) diff --git a/python/lsst/ts/observatory/control/auxtel/atcalsys.py b/python/lsst/ts/observatory/control/auxtel/atcalsys.py index 696294bf..4290886f 100644 --- a/python/lsst/ts/observatory/control/auxtel/atcalsys.py +++ b/python/lsst/ts/observatory/control/auxtel/atcalsys.py @@ -35,7 +35,7 @@ class ATCalibrationSequenceStep(CalibrationSequenceStepBase): class ATCalsys(BaseCalsys, HardcodeCalsysThroughput): - """class which specifically handles the calibration system for auxtel""" + """class which specifically handles the calibration system for auxtel.""" _AT_SAL_COMPONENTS: List[str] = [ "ATMonochromator", @@ -185,7 +185,8 @@ def _electrometer_object(self): @property def script_time_estimate_s(self) -> float: - """Property that returns the estimated time for the script to run in units of seconds + """Property that returns the estimated time for the script to run in units of seconds. + For script time estimation purposes. For now just returns a default long time""" @@ -305,7 +306,7 @@ async def validate_hardware_status_for_acquisition(self) -> Awaitable: if not lamp_state.lightDetected: self.log.warning( - f"all states seem fine, but lamp is not reporting light detected!" + "all states seem fine, but lamp is not reporting light detected!" ) def _chiller_temp_check(self, temps) -> bool: diff --git a/python/lsst/ts/observatory/control/base_calsys.py b/python/lsst/ts/observatory/control/base_calsys.py index 5b494023..46ef6bbf 100644 --- a/python/lsst/ts/observatory/control/base_calsys.py +++ b/python/lsst/ts/observatory/control/base_calsys.py @@ -78,6 +78,7 @@ def detector_throughput(self, wavelen: float) -> float: @abstractmethod def spectrograph_throughput(self, wavelen: float, calsys_power: float) -> float: """the throughput expected of the fiber spectrograph of the calibration system. + To aid calculations of total throughput """ @@ -100,7 +101,7 @@ def end_to_end_throughput(self, wavelen: float, calsys_power: float) -> float: def total_radiometer_exposure_time( self, rad_exposure_time: Quantity[un.physical.time], nplc: float - ) -> Quantity["time"]: + ) -> Quantity[un.physical.time]: # Note comm from Parker: "valid nplc values are from 0.01 to 10 (seconds) if not (0.01 <= nplc <= 10.0): raise ValueError( @@ -137,7 +138,6 @@ class HardcodeCalsysThroughput(CalsysThroughputCalculationMixin): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._itps: dict[str, InterpolatedUnivariateSpline] = dict() - self._intention = intention @classmethod def load_calibration_csv(cls, fname: str) -> Mapping[str, Sequence[float]]: @@ -161,7 +161,7 @@ def _ensure_itp( else: return self._itps[itpname] - def radiometer_responsivity(self, wavelen: Quantity["length"]) -> Responsivity: + def radiometer_responsivity(self, wavelen: Quantity[un.physical.length]) -> Responsivity: wlin: float = wavelen.to(nm).value itp = self._ensure_itp( @@ -173,7 +173,7 @@ def radiometer_responsivity(self, wavelen: Quantity["length"]) -> Responsivity: return Rawout << un.ampere / un.watt def maintel_throughput( - self, wavelen: Quantity["length"], filter_band: chr + self, wavelen: Quantity[un.physical.length], filter_band: chr ) -> Quantity[un.dimensionless_unscaled]: calfilename: str = f"calibration_tput_init_{filter_band}.csv" itp = self._ensure_itp( @@ -333,21 +333,12 @@ def log_event_timings( ) logger.info(logstr) duration = (start_time - end_time).total_seconds() << un.s - logstr2 = f"the duration was: {duration}, and our timeout allowance was: {expt_duration}" + logstr2 = f"the duration was: {duration}, and our timeout allowance was: {expd_duration}" logger.info(logstr2) - async def take_electrometer_exposures( - self, electrobj, exp_time_s: float, n: int - ) -> list[str]: - urlout: list[str] = [] - for i in range(n): - await electrobj.cmd_StartScanDt.set_start(scanDuration=exp_time) - lfaurl = await self._lfa_event_helper(electrobj) - urlout.append(lfaurl) - return urlout def detector_exposure_time_for_nelectrons( - self, wavelen: Quantity["length"], nelec: float + self, wavelen: Quantity[un.physical.length], nelec: float ) -> float: """using the appropriate mixin for obtaining calibration data on throughput, will calculate and return the exposure time needed to obtain a flat field calibration of n @@ -355,13 +346,11 @@ def detector_exposure_time_for_nelectrons( Parameters ---------- - wavelen: float - wavelength (in nm??) of the intended calibration nelec: float - number of electrons (probably measured in ke-??) desired in calibration field Returns ------- - exposure time: float - time (in seconds??) needed for the imager to obtain desired calibration field """ @@ -370,7 +359,7 @@ def detector_exposure_time_for_nelectrons( assert issubclass(type(self), CalsysThroughputCalculationMixin) def spectrograph_exposure_time_for_nelectrons(self, nelec: float) -> float: - raise NotImplementerError( + raise NotImplementedError( "throughput calc for spectrograph not implemented yet!" ) From 9233948ee551ef80255a621f2c3828d4cbcb1ea6 Mon Sep 17 00:00:00 2001 From: Dan Date: Tue, 12 Dec 2023 04:24:05 +0000 Subject: [PATCH 09/17] couple more mypy fixes --- .../ts/observatory/control/auxtel/atcalsys.py | 44 ++++++++++--------- .../ts/observatory/control/base_calsys.py | 35 +++++++++------ 2 files changed, 45 insertions(+), 34 deletions(-) diff --git a/python/lsst/ts/observatory/control/auxtel/atcalsys.py b/python/lsst/ts/observatory/control/auxtel/atcalsys.py index 4290886f..a127540b 100644 --- a/python/lsst/ts/observatory/control/auxtel/atcalsys.py +++ b/python/lsst/ts/observatory/control/auxtel/atcalsys.py @@ -1,19 +1,21 @@ -from typing import List, Optional, NamedTuple +import asyncio +from collections.abc import Awaitable +from dataclasses import dataclass +from datetime import datetime +from typing import List, NamedTuple, Optional + +import astropy.units as un +from astropy.units import Quantity +from lsst.ts import salobj +from lsst.ts.idl.enums import ATMonochromator, ATWhiteLight + from ..base_calsys import ( BaseCalsys, - HardcodeCalsysThroughput, CalibrationSequenceStepBase, + CalsysScriptIntention, + HardcodeCalsysThroughput, + _calsys_get_parameter, ) -from ..base_calsys import CalsysScriptIntention, _calsys_get_parameter -from lsst.ts import salobj -from lsst.ts.idl.enums import ATMonochromator -from lsst.ts.idl.enums import ATWhiteLight -import asyncio -import astropy.units as un -from astropy.units import Quantity -from datetime import datetime -from collections.abc import Awaitable -from dataclasses import dataclass class ATSpectrographSlits(NamedTuple): @@ -83,7 +85,7 @@ async def setup_for_wavelength( slit_widths = _calsys_get_parameter( override_kwargs, "slit_widths", - self.calculate_slit_widths, + self.calculate_slit_width, wavelen, spectral_res, grating, @@ -95,7 +97,7 @@ async def setup_for_wavelength( self.log.debug(f"calculated grating is {grating}") monoch_fut = self._sal_cmd( - self.ATMonoChromator, + self.ATMonochromator, "updateMonochromatorSetup", gratingType=grating, frontExitSlitWidth=slit_widths.FRONTEXIT, @@ -160,7 +162,7 @@ def calculate_grating_type( async def _electrometer_expose(self) -> Awaitable[list[str]]: assert self._n_elec_exps is not None assert self._elecsposure_time is not None - return await self._cal_expose_helper( + return self._cal_expose_helper( self.Electrometer, self._n_elec_exps, "startScanDt", @@ -171,7 +173,7 @@ async def _spectrograph_expose(self) -> Awaitable[list[str]]: assert self._n_spec_exps is not None assert self._specsposure_time is not None - return await self._cal_expose_helper( + return self._cal_expose_helper( self.ATSpectrograph, self._n_spec_exps, "expose", @@ -204,7 +206,7 @@ def script_time_estimate_s(self) -> float: "don't know how to handle this script intention" ) - async def power_sequence_run(self, scriptobj: salobj.BaseScript): + async def power_sequence_run(self, scriptobj: salobj.BaseScript, **kwargs) -> None: match (self._intention): case CalsysScriptIntention.POWER_ON: await self._chiller_power(True) @@ -220,10 +222,10 @@ async def power_sequence_run(self, scriptobj: salobj.BaseScript): await scriptobj.checkpoint("Chiller setpoint temperature reached") shutter_wait_fut = asyncio.create_task( - self._lamp_power(True), "lamp_start_shutter_open" + self._lamp_power(True), name="lamp_start_shutter_open" ) lamp_settle_fut = asyncio.create_task( - self._lamp_settle(True), "lamp_power_settle" + self._lamp_settle(True), name="lamp_power_settle" ) shutter_start, shutter_end = await shutter_wait_fut self.log_event_timings( @@ -340,7 +342,7 @@ async def _chiller_settle(self) -> Awaitable[tuple[datetime, datetime]]: chiller_wait_timeout: float = self.CHILLER_COOLDOWN_TIMEOUT.to(un.s).value chiller_temp_gen = self._sal_telem_gen(self.ATWhiteLight, "chillerTemperatures") - return await self._long_wait_err_handle( + return self._long_wait_err_handle( chiller_temp_gen, chiller_wait_timeout, self._chiller_temp_check, @@ -400,7 +402,7 @@ def lamp_verify(evt): lamp_evt_gen, lamp_settle_timeout, lamp_verify, lamp_transition_name ) - async def take_calibration_data(self) -> Awaitable[dict[str, list[str]]]: + async def take_calibration_data(self) -> dict[str, list[str]]: spec_fut = self._spectrograph_expose() elec_fut = self._electrometer_expose() diff --git a/python/lsst/ts/observatory/control/base_calsys.py b/python/lsst/ts/observatory/control/base_calsys.py index 46ef6bbf..22a64ecf 100644 --- a/python/lsst/ts/observatory/control/base_calsys.py +++ b/python/lsst/ts/observatory/control/base_calsys.py @@ -1,23 +1,32 @@ +import asyncio +import csv +import enum +import logging from abc import ABCMeta, abstractmethod +from collections.abc import AsyncGenerator, Callable, Coroutine from dataclasses import dataclass -from .remote_group import RemoteGroup -from typing import Iterable, Optional, Union -from typing import Sequence, Mapping, TypeAlias, Any, Awaitable +from datetime import datetime from functools import reduce -from collections.abc import Coroutine, Callable, AsyncGenerator +from importlib.resources import files +from itertools import count +from typing import ( + Any, + Awaitable, + Iterable, + Mapping, + Optional, + Sequence, + TypeAlias, + Union, +) + +import astropy.units as un +from astropy.units import Quantity, ampere, nm, watt from lsst.ts import salobj from lsst.ts.idl.enums import Electrometer -import logging -import asyncio -from importlib.resources import files -import csv from scipy.interpolate import InterpolatedUnivariateSpline -from astropy.units import ampere, watt, nm, Quantity -import astropy.units as un -import enum -from datetime import datetime -from itertools import count +from .remote_group import RemoteGroup Responsivity: TypeAlias = Quantity[ampere / watt] From 69635d7180bb9aa8ae21da3175db577171a78596 Mon Sep 17 00:00:00 2001 From: Dan Date: Tue, 12 Dec 2023 04:45:13 +0000 Subject: [PATCH 10/17] fix more lint and mypy --- .../ts/observatory/control/auxtel/atcalsys.py | 9 +++--- .../ts/observatory/control/base_calsys.py | 29 +++++++++++-------- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/python/lsst/ts/observatory/control/auxtel/atcalsys.py b/python/lsst/ts/observatory/control/auxtel/atcalsys.py index a127540b..89965fa2 100644 --- a/python/lsst/ts/observatory/control/auxtel/atcalsys.py +++ b/python/lsst/ts/observatory/control/auxtel/atcalsys.py @@ -211,7 +211,7 @@ async def power_sequence_run(self, scriptobj: salobj.BaseScript, **kwargs) -> No case CalsysScriptIntention.POWER_ON: await self._chiller_power(True) await scriptobj.checkpoint("Chiller started") - chiller_start, chiller_end = await self._chiller_settle(True) + chiller_start, chiller_end = await self._chiller_settle() self.log_event_timings( self.log, "chiller cooldown time", @@ -253,10 +253,10 @@ async def power_sequence_run(self, scriptobj: salobj.BaseScript, **kwargs) -> No "lamp commanded off and shutter commanded closed" ) shutter_wait_fut = asyncio.create_task( - self._lamp_power(False), "lamp stop shutter close" + self._lamp_power(False), name="lamp stop shutter close" ) lamp_settle_fut = asyncio.create_task( - self._lamp_settle(False), "lamp power settle" + self._lamp_settle(False), name="lamp power settle" ) shutter_start, shutter_end = await shutter_wait_fut @@ -366,9 +366,10 @@ async def _lamp_power(self, onoff: bool) -> Awaitable: power=self.WHITELIGHT_POWER, ) + timeout = self.CMD_TIMEOUT.to(un.s) await asyncio.wait( [shutter_task, lamp_start_task], - timeout=self._cmd_timeout, + timeout=timeout, return_when=asyncio.FIRST_EXCEPTION, ) diff --git a/python/lsst/ts/observatory/control/base_calsys.py b/python/lsst/ts/observatory/control/base_calsys.py index 22a64ecf..f0576fe1 100644 --- a/python/lsst/ts/observatory/control/base_calsys.py +++ b/python/lsst/ts/observatory/control/base_calsys.py @@ -17,6 +17,7 @@ Optional, Sequence, TypeAlias, + TypeVar, Union, ) @@ -29,15 +30,16 @@ from .remote_group import RemoteGroup Responsivity: TypeAlias = Quantity[ampere / watt] +T = TypeVar("T") def _calsys_get_parameter( - indct: dict[str, Any], + indct: dict[str, T], key: str, factory_callable: Callable, - *factory_args, - **factory_kwargs, -): + *factory_args: Any, + **factory_kwargs: Any, +) -> T: if indct.get(key, None) is None: return factory_callable(*factory_args, **factory_kwargs) @@ -104,7 +106,7 @@ def end_to_end_throughput(self, wavelen: float, calsys_power: float) -> float: [ self.detector_throughput, self.spectrograph_throughput, - self.radiometer_throughput, + self.radiometer_responsivity, ], ) @@ -153,7 +155,10 @@ def load_calibration_csv(cls, fname: str) -> Mapping[str, Sequence[float]]: res = files(cls.BASERES).joinpath(fname) with res.open("r") as f: rdr = csv.DictReader(f) - out = {k: [] for k in rdr.fieldnames} + if rdr.fieldnames is None: + raise ValueError("calibration curve has no fieldnames!") + + out: dict[str, list[float]] = {k: [] for k in rdr.fieldnames} for row in rdr: for k, v in row.items(): val: float = float(v) if len(v) > 0 else 0.0 @@ -177,12 +182,11 @@ def radiometer_responsivity(self, wavelen: Quantity[un.physical.length]) -> Resp "radiometer", self.RADIOMETER_CALFILE, "wavelength", "responsivity" ) - wlin: float = wavelen.to(nm).value Rawout: float = itp(wlin) return Rawout << un.ampere / un.watt def maintel_throughput( - self, wavelen: Quantity[un.physical.length], filter_band: chr + self, wavelen: Quantity[un.physical.length], filter_band: str ) -> Quantity[un.dimensionless_unscaled]: calfilename: str = f"calibration_tput_init_{filter_band}.csv" itp = self._ensure_itp( @@ -204,7 +208,7 @@ class BaseCalsys(RemoteGroup, metaclass=ABCMeta): def __init__( self, intention: CalsysScriptIntention, - components: Iterable[str], + components: list[str], domain: Optional[salobj.domain.Domain] = None, cmd_timeout: Optional[int] = 10, log: Optional[logging.Logger] = None, @@ -282,7 +286,7 @@ async def gen(): def _long_wait( self, gen: AsyncGenerator, - timeout_seconds, + timeout_seconds: float, validate_fun: Callable[[Any], bool], run_immediate: bool = True, ) -> TaskOrCoro: @@ -298,7 +302,7 @@ async def completer() -> None: async def _cal_expose_helper( self, obj, n: int, cmdname: str, **extra_kwargs - ) -> Awaitable[list[str]]: + ) -> list[str]: out_urls: list[str] = [] for i in range(n): await self._sal_cmd(obj, cmdname, **extra_kwargs) @@ -309,7 +313,7 @@ async def _cal_expose_helper( async def _long_wait_err_handle( self, gen: AsyncGenerator, - timeout_seconds, + timeout_seconds: float, validate_fun: Callable[[Any], bool], name_of_wait: str, ) -> tuple[datetime, datetime]: @@ -366,6 +370,7 @@ def detector_exposure_time_for_nelectrons( # must have a way to access the calibration data assert issubclass(type(self), CalsysThroughputCalculationMixin) + raise NotImplementedError("throughput calc for detector not implemented yet!") def spectrograph_exposure_time_for_nelectrons(self, nelec: float) -> float: raise NotImplementedError( From c18e689a9ee5e339e5748f6d5d870c363f9ea704 Mon Sep 17 00:00:00 2001 From: Dan Date: Mon, 1 Jan 2024 22:25:31 +0000 Subject: [PATCH 11/17] fix error in call in atcalsys, start documenting base_calsys --- .../ts/observatory/control/auxtel/atcalsys.py | 2 +- .../ts/observatory/control/base_calsys.py | 166 +++++++++++++++--- 2 files changed, 147 insertions(+), 21 deletions(-) diff --git a/python/lsst/ts/observatory/control/auxtel/atcalsys.py b/python/lsst/ts/observatory/control/auxtel/atcalsys.py index 89965fa2..3a49607f 100644 --- a/python/lsst/ts/observatory/control/auxtel/atcalsys.py +++ b/python/lsst/ts/observatory/control/auxtel/atcalsys.py @@ -105,7 +105,7 @@ async def setup_for_wavelength( wavelength=wavelen, ) - elect_fut = self._sal_cmd("electrometer", "performZeroCalib") + elect_fut = self._sal_cmd(self.Electrometer, "performZeroCalib") elect_fut2 = self._sal_cmd( self.Electrometer, "setDigitalFilter", diff --git a/python/lsst/ts/observatory/control/base_calsys.py b/python/lsst/ts/observatory/control/base_calsys.py index f0576fe1..1220a2da 100644 --- a/python/lsst/ts/observatory/control/base_calsys.py +++ b/python/lsst/ts/observatory/control/base_calsys.py @@ -1,10 +1,32 @@ +# This file is part of ts_observatory_control. +# +# Developed for the Vera Rubin Observatory Telescope and Site Systems. +# This product includes software developed by the LSST Project +# (https://www.lsst.org). +# See the COPYRIGHT file at the top-level directory of this distribution +# for details of code ownership. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License + +__all__ = ["CalsysScriptIntention", "CalsysThroughputCalculationMixin", "BaseCalsys"] + + import asyncio import csv import enum import logging from abc import ABCMeta, abstractmethod from collections.abc import AsyncGenerator, Callable, Coroutine -from dataclasses import dataclass from datetime import datetime from functools import reduce from importlib.resources import files @@ -12,7 +34,6 @@ from typing import ( Any, Awaitable, - Iterable, Mapping, Optional, Sequence, @@ -44,14 +65,9 @@ def _calsys_get_parameter( return factory_callable(*factory_args, **factory_kwargs) -@dataclass -class CalibrationSequenceStepBase: - wavelength: float - n_exp: int - exp_time: float - - class CalsysScriptIntention(enum.IntEnum): + """Enum which indicates what a SAL script will be using the calsys for.""" + TURN_ON = 0 TURN_OFF = 1 QUICK_CALIBRATION_RUN = 2 @@ -62,7 +78,7 @@ class CalsysScriptIntention(enum.IntEnum): class CalsysThroughputCalculationMixin: - """mixin class to allow pluggable source for calculation of throughputs""" + """Mixin class to allow pluggable source for calculation of throughputs.""" POWER_LINE_FREQUENCY = 60 / (un.s) @@ -113,12 +129,14 @@ def end_to_end_throughput(self, wavelen: float, calsys_power: float) -> float: def total_radiometer_exposure_time( self, rad_exposure_time: Quantity[un.physical.time], nplc: float ) -> Quantity[un.physical.time]: - # Note comm from Parker: "valid nplc values are from 0.01 to 10 (seconds) + # Note comm from Parker: + # "valid nplc values are from 0.01 to 10 (seconds) if not (0.01 <= nplc <= 10.0): raise ValueError( f"supplied valud for nplc: {nplc} is not within allowed values 0.01 <= nplc <= 10" ) - # Note: magic numbers from communication with Parker F. To be added to electrometer CSC docs + # Note: magic numbers from communication with Parker F. + # To be added to electrometer CSC docs rad_int_time = nplc / self.POWER_LINE_FREQUENCY time_sep = (rad_int_time * 3.07) + 0.00254 # NOTE: is 0.00254 a metric -> imperial conversion? @@ -135,13 +153,11 @@ def total_radiometer_exposure_time( class ButlerCalsysThroughput(CalsysThroughputCalculationMixin): - """Mixin class for calculating throughput of the calibration system backed by measurements stored - in a DM butler""" + """Calculating throughput of the calibration system backed by measurements stored in a DM butler""" class HardcodeCalsysThroughput(CalsysThroughputCalculationMixin): - """Mixin class for calculating throughput of the calibration system with hardcoded values, - i.e. which can be directly imported from python code""" + """Calculating throughput of the calibration system with hardcoded values, i.e. which can be directly imported from python code""" BASERES: str = "lsst.ts.observatory.control.cal_curves" RADIOMETER_CALFILE: str = "hamamatsu_responsivity.csv" @@ -157,7 +173,6 @@ def load_calibration_csv(cls, fname: str) -> Mapping[str, Sequence[float]]: rdr = csv.DictReader(f) if rdr.fieldnames is None: raise ValueError("calibration curve has no fieldnames!") - out: dict[str, list[float]] = {k: [] for k in rdr.fieldnames} for row in rdr: for k, v in row.items(): @@ -166,9 +181,36 @@ def load_calibration_csv(cls, fname: str) -> Mapping[str, Sequence[float]]: return out def _ensure_itp( - self, itpname: str, fname: str, xaxis: str, yaxis: str, **itpargs - ) -> InterpolatedUnivariateSpline: + self, itpname: str, fname: Optional[str] = None, xaxis: Optional[str] = None, + yaxis: Optional[str] = None, **itpargs) -> InterpolatedUnivariateSpline: + """Obtain an interpolated spline from a calibration curve. + + If the calibration curve has already been loaded, returns the existing spline interpolation object. + Otherwise, will load the calibration data from the filename supplied + + Parameters + ---------- + itpname : Optional[str] + The name from which to lookup the interpolation object. + When object is first loaded, will lookup an interpolation object with this name + fname : Optional[str] + The file name from which to load the calibration data. + xaxis : Optional[str] + The column name of the x-axis interpolator + yaxis : Optional[str] + The column name of the y-axis interpolator + **itpargs : 7 + Arguments which will be passed through to scipy InterpolatedUnivariateSpline + + Returns + ------- + a scipy InterpolatedUnivariateSpline object which is callable and can be used to + obtain interpolated calibration values. + """ + if itpname not in self._itps: + if any(_ is None for _ in [fname, xaxis, yaxis]): + raise ValueError("missing required value to load calibration data") calres = self.load_calibration_csv(fname) itp = InterpolatedUnivariateSpline(calres[xaxis], calres[yaxis], **itpargs) self._itps[itpname] = itp @@ -198,7 +240,8 @@ def maintel_throughput( class BaseCalsys(RemoteGroup, metaclass=ABCMeta): - """Base class for calibration systems""" + """Base class for calibration systems operation + """ CMD_TIMEOUT: Quantity[un.physical.time] = 30 << un.s EVT_TIMEOUT: Quantity[un.physical.time] = 30 << un.s @@ -213,6 +256,24 @@ def __init__( cmd_timeout: Optional[int] = 10, log: Optional[logging.Logger] = None, ): + """Construct a new BaseCalsys object + + NOTE: should not generally be constructed directly by a user either interactively or in a SAL script + + Parameters + ---------- + intention : CalsysScriptIntention + Configures the general behaviour of calls according to what the script is going to do + components : list[str] + SAL components to initialize, should be overridden in daughter class + domain : Optional[salobj.domain.Domain] + DDS OpenSplice domain to use + cmd_timeout : Optional[int] + timeout (measured in seconds) for DDS OpenSplice commands + log : Optional[logging.Logger] + existing logging object + + """ super().__init__( components, domain, @@ -226,6 +287,46 @@ def __init__( def _sal_cmd( self, obj: salobj.Remote, cmdname: str, run_immediate: bool = True, **setargs ) -> TaskOrCoro: + """Helper function that Runs a command on a remote SAL object + + This function is mainly here to avoid a lot of boilerplate that otherwise + accumulates in the daughter classes + + Parameters + ---------- + obj : salobj.Remote + SAL remote object to call the command on + cmdname : str + the name of the command to call (look up in the generated XML + definitions for the respective CSC), excluding the conventional 'cmd_' prefix + run_immediate : bool + chooses whether to return an event-loop posted future (which is done using + asyncio.create_task, or whether to return an un-posted coroutine function + + if True, returns the future, if False, returns the coroutine + + **setargs + extra arguments that are passed to the SAL remote object set function + + Returns + ------- + TaskOrCoro + the packaged future or coroutine (which type depends on the value of run_immediate) + + Examples + -------- + + # some function calls that get us the relevant objects, in this case an Electrometer + calsys: BaseCalsys = get_calsys_object() + salobj: sal.Remote = get_electrometer_object() + assert type(salobj) is Electrometer + + # cal the "cmd_performZeroCalib" operation on the electrometer, returning a future, + with the task having already been posted to the running event loop + + fut = calsys._sal_cmd(salobj, salobj, "performZeroCalib") + + """ timeout = self.CMD_TIMEOUT.to(un.s).value cmdfun: salobj.topics.RemoteCommand = getattr(obj, f"cmd_{cmdname}") pkgtask = cmdfun.set_start(**setargs, timeout=timeout) @@ -241,6 +342,31 @@ def _sal_waitevent( flush: bool = True, **evtargs, ) -> TaskOrCoro: + """A helper function which waits for an event on a SAL remote object + + Parameters + ---------- + obj : salobj.Remote + SAL remote object to wait for the telemetry or event on + evtname : str + name of the event to wait for (excluding the conventional prefix 'evt_') + run_immediate : bool + Whether to return a posted future or an un-posted coroutine, + see documentation for BaseCalsys._sal_cmd + flush : bool + Whether to flush the remote SALobject DDS event queue for this + particular event + (i.e. whether to definitely force wait for a new event rather than popping + from a queue) - see documentation for salobj.Remote + **evtargs : 7 + extra arguments that get passed to the .next() method of the SAL object + + Returns + ------- + TaskOrCoro + posted future or unposted coro, according to value of run_immediate, + as per BaseCalsys._sal_cmd + """ timeout = self.EVT_TIMEOUT.to(un.s).value cmdfun: salobj.topics.RemoteEvent = getattr(obj, f"evt_{evtname}") pkgtask = cmdfun.next(timeout=timeout, flush=flush) From e888b6fbfc7a4ca744e39a40c14128d8006951e1 Mon Sep 17 00:00:00 2001 From: Dan Date: Mon, 26 Feb 2024 02:43:23 +0000 Subject: [PATCH 12/17] more docstringts --- .../lsst/ts/observatory/control/__init__.py | 1 + .../ts/observatory/control/auxtel/__init__.py | 1 + .../ts/observatory/control/auxtel/atcalsys.py | 35 ++++++++++++++----- 3 files changed, 29 insertions(+), 8 deletions(-) diff --git a/python/lsst/ts/observatory/control/__init__.py b/python/lsst/ts/observatory/control/__init__.py index 608af791..709bb15a 100644 --- a/python/lsst/ts/observatory/control/__init__.py +++ b/python/lsst/ts/observatory/control/__init__.py @@ -20,6 +20,7 @@ # along with this program. If not, see . from .base_camera import * +from .base_calsys import * from .base_tcs import * from .remote_group import * from .utils import * diff --git a/python/lsst/ts/observatory/control/auxtel/__init__.py b/python/lsst/ts/observatory/control/auxtel/__init__.py index 53caf194..09ab2148 100644 --- a/python/lsst/ts/observatory/control/auxtel/__init__.py +++ b/python/lsst/ts/observatory/control/auxtel/__init__.py @@ -23,3 +23,4 @@ from .atqueue import * from .calsys import * from .latiss import * +from .atcalsys import * diff --git a/python/lsst/ts/observatory/control/auxtel/atcalsys.py b/python/lsst/ts/observatory/control/auxtel/atcalsys.py index 3a49607f..33d30616 100644 --- a/python/lsst/ts/observatory/control/auxtel/atcalsys.py +++ b/python/lsst/ts/observatory/control/auxtel/atcalsys.py @@ -1,3 +1,5 @@ +__all__ = ["ATSpectrographSlits", "ATCalibrationSequenceStep", "ATCalsys"] + import asyncio from collections.abc import Awaitable from dataclasses import dataclass @@ -190,8 +192,8 @@ def script_time_estimate_s(self) -> float: """Property that returns the estimated time for the script to run in units of seconds. For script time estimation purposes. - For now just returns a default long time""" - + For now just returns a default long time + """ match (self._intention): case CalsysScriptIntention.POWER_ON | CalsysScriptIntention.POWER_OFF: # for now just use fixed values from previous script @@ -207,6 +209,12 @@ def script_time_estimate_s(self) -> float: ) async def power_sequence_run(self, scriptobj: salobj.BaseScript, **kwargs) -> None: + """Run a power sequence operation + + A power sequence operation is, for example, turning on or off the calibration + system preparing for daily calibration runs. The sequence run depends + on the indicated intention of the current SAL script + """ match (self._intention): case CalsysScriptIntention.POWER_ON: await self._chiller_power(True) @@ -285,13 +293,19 @@ async def power_sequence_run(self, scriptobj: salobj.BaseScript, **kwargs) -> No "don't know how to handle this script intention" ) - async def validate_hardware_status_for_acquisition(self) -> Awaitable: + async def validate_hardware_status_for_acquisition(self) -> Awaitable[bool]: + """Calibration system readiness check. + + Check whether the calibration system hardware is nominally ready to perform + calibration data acquisition runs + """ shutter_fut = self._sal_waitevent(self.ATWhiteLight, "shutterState") lamp_fut = self._sal_waitevent(self.ATWhiteLight, "lampState") shutter_state = await shutter_fut if shutter_state.commandedState != ATWhiteLight.ShutterState.OPEN: - errmsg = f"shutter has not been commanded to open, likely a programming error. Commanded state is {repr(shutter_state.commandedState)}" + errmsg = f"shutter has not been commanded to open, likely a programming error. \ + Commanded state is {repr(shutter_state.commandedState)}" self.log.error(errmsg) raise RuntimeError(errmsg) @@ -312,6 +326,7 @@ async def validate_hardware_status_for_acquisition(self) -> Awaitable: ) def _chiller_temp_check(self, temps) -> bool: + """Validate the operating temperature of the calibration system chiller""" self.log.debug( f"Chiller supply temperature: {temps.supplyTemperature:0.1f} C " f"[set:{temps.setTemperature} deg]." @@ -327,8 +342,9 @@ def _chiller_temp_check(self, temps) -> bool: return True return False - async def _chiller_power(self, onoff: bool): - cmd_target = "startChiller" if onoff else "stopChiller" + async def _chiller_power(self, onoff: bool) -> None: + """Start or stop the power of the calibration system chiller""" + cmd_target = "startChiller" if onoff else "stopChiller"s if onoff: chiller_setpoint_temp: float = self.CHILLER_SETPOINT_TEMP.to(un.s).value await self._sal_cmd( @@ -339,17 +355,19 @@ async def _chiller_power(self, onoff: bool): await self._sal_cmd(self.ATWhiteLight, cmd_target) async def _chiller_settle(self) -> Awaitable[tuple[datetime, datetime]]: + """Wait for the calibration system chiller temperature to settle""" chiller_wait_timeout: float = self.CHILLER_COOLDOWN_TIMEOUT.to(un.s).value chiller_temp_gen = self._sal_telem_gen(self.ATWhiteLight, "chillerTemperatures") - return self._long_wait_err_handle( + return self._long_wait_err_handle( chiller_temp_gen, chiller_wait_timeout, self._chiller_temp_check, "chiller temperature range settle", ) - async def _lamp_power(self, onoff: bool) -> Awaitable: + async def _lamp_power(self, onoff: bool) -> Awaitable[bool]: + """Control the shutter and the lamp power of the calibration system""" shutter_cmd_target = "openShutter" if onoff else "closeShutter" lamp_cmd_target = "turnLampOn" if onoff else "turnLampOff" # TODO: do we want asserts etc here to check the lamp state is correct first? @@ -388,6 +406,7 @@ def shutter_verify(evt): ) async def _lamp_settle(self, onoff: bool) -> Awaitable: + """Wait for the lamp power to settle after a power on / off event""" lamp_evt_gen = self._sal_evt_gen(self.ATWhiteLight, "lampState") lamp_settle_timeout: float = self.WHITELIGHT_LAMP_WARMUP_TIMEOUT.to(un.s).value lamp_tgt_state = ( From 572ef44951ca5062330e5a9ce6d8edf91f5046a9 Mon Sep 17 00:00:00 2001 From: Dan Date: Mon, 26 Feb 2024 02:48:29 +0000 Subject: [PATCH 13/17] fixup some docstring formatting --- .../ts/observatory/control/base_calsys.py | 47 +++++++++++-------- 1 file changed, 28 insertions(+), 19 deletions(-) diff --git a/python/lsst/ts/observatory/control/base_calsys.py b/python/lsst/ts/observatory/control/base_calsys.py index 1220a2da..f7335cd3 100644 --- a/python/lsst/ts/observatory/control/base_calsys.py +++ b/python/lsst/ts/observatory/control/base_calsys.py @@ -84,28 +84,29 @@ class CalsysThroughputCalculationMixin: @abstractmethod def detector_throughput(self, wavelen: float) -> float: - """the throughput value will return (in appropriate units TBD) the detector throughput of the particular - calibration system specified in the class for which this mixin class is specified as a base. + """the throughput value will return (in appropriate units TBD) the + detector throughput of the particular calibration system specified in + the class for which this mixin class is specified as a base. Parameters ---------- - wavelen: float - wavelength of the calibration to be performed in nm Returns ------- - - A value (units TBD, likely electrons per pixel per second), which can be used to - either determine how long to integrate the sensor for to achieve a desired level of calibration - field in electrons, or determine how many electrons will be obtained for a specific integration time + A value (units TBD, likely electrons per pixel per second), which can + be used to either determine how long to integrate the sensor for to + achieve a desired level of calibration field in electrons, or + determine how many electrons will be obtained for a specific + integration time """ @abstractmethod def spectrograph_throughput(self, wavelen: float, calsys_power: float) -> float: - """the throughput expected of the fiber spectrograph of the calibration system. - + """Obtain throughput expected of the fiber spectrograph of the calibration system. + To aid calculations of total throughput """ @@ -113,7 +114,7 @@ def spectrograph_throughput(self, wavelen: float, calsys_power: float) -> float: def radiometer_responsivity( self, wavelen: Quantity[un.physical.length] ) -> Responsivity: - """return the responsivity of the radiometer""" + """Return the responsivity of the radiometer""" def end_to_end_throughput(self, wavelen: float, calsys_power: float) -> float: # intended to be SOMETHING LIKE @@ -533,32 +534,40 @@ async def power_sequence_run(self, scriptobj, **kwargs) -> Awaitable: @abstractmethod async def setup_for_wavelength(self, wavelen: float, **extra_params) -> None: - """awaitable which sets up the various remote components of a calibration system - to perform a calibration at a particular wavelength. + """awaitable which sets up the various remote components of a + calibration system to perform a calibration at a particular + wavelength. - Intended to be a 'high level' setup function, such that user doesn't have to worry about e.g. setting up integration times for spectrographs etc + Intended to be a 'high level' setup function, such that user doesn't + have to worry about e.g. setting up integration times for + spectrographs etc Parameters ---------- - wavelen: float - desired wavelength (in nm) - extra_params: to handle things which are specific to individual calibration systems - (e.g that the auxtel can also adjust spectral bandwidth) + extra_params: to handle things which are specific to individual + calibration systems (e.g that the auxtel can also adjust spectral + bandwidth) """ pass @abstractmethod async def take_calibration_data(self): - """awaitable which starts the exposures for the calibration instruments (i.e the spectrographs, electrometers etc) according to the setup. It does not take images with the instrument under test, it is intended that script components which use this class do that themselves""" + """Awaitable which starts the exposures for the calibration. + + instruments (i.e the spectrographs, electrometers etc) are chosen + according to the setup. It does not take images with the instrument + under test, it is intended that script components which use this class + do that themselves + """ pass async def generate_data_flats( self, instrobj, scriptobj, exposure_time_s: float, n_iter: Optional[int] = None ): - """returns an async generator which yields sets of exposures from""" - + """Return an async generator which yields sets of exposures from.""" # Run forever if n_iter was not given, don't worry it's just a generator nrange = count() if n_iter is None else range(n_iter) From 8c02427731609c2ec4b49cb970544aa11893df22 Mon Sep 17 00:00:00 2001 From: Dan Date: Mon, 26 Feb 2024 03:45:06 +0000 Subject: [PATCH 14/17] minor cleanups docs --- python/lsst/ts/observatory/control/base_calsys.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/python/lsst/ts/observatory/control/base_calsys.py b/python/lsst/ts/observatory/control/base_calsys.py index f7335cd3..812ec6f2 100644 --- a/python/lsst/ts/observatory/control/base_calsys.py +++ b/python/lsst/ts/observatory/control/base_calsys.py @@ -84,7 +84,9 @@ class CalsysThroughputCalculationMixin: @abstractmethod def detector_throughput(self, wavelen: float) -> float: - """the throughput value will return (in appropriate units TBD) the + """Return calculated detector throughput. + + The hroughput value will return (in appropriate units TBD) the detector throughput of the particular calibration system specified in the class for which this mixin class is specified as a base. From 0a9bdc850318c5e788dea901207ac94473af10cf Mon Sep 17 00:00:00 2001 From: Dan Weatherill Date: Tue, 26 Mar 2024 11:57:48 +0000 Subject: [PATCH 15/17] start fixing flake8 problems --- python/lsst/ts/observatory/control/auxtel/atcalsys.py | 4 ++-- python/lsst/ts/observatory/control/maintel/mtcalsys.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/python/lsst/ts/observatory/control/auxtel/atcalsys.py b/python/lsst/ts/observatory/control/auxtel/atcalsys.py index 33d30616..c7c5a199 100644 --- a/python/lsst/ts/observatory/control/auxtel/atcalsys.py +++ b/python/lsst/ts/observatory/control/auxtel/atcalsys.py @@ -164,7 +164,7 @@ def calculate_grating_type( async def _electrometer_expose(self) -> Awaitable[list[str]]: assert self._n_elec_exps is not None assert self._elecsposure_time is not None - return self._cal_expose_helper( + return self._cal_expose_helper( self.Electrometer, self._n_elec_exps, "startScanDt", @@ -344,7 +344,7 @@ def _chiller_temp_check(self, temps) -> bool: async def _chiller_power(self, onoff: bool) -> None: """Start or stop the power of the calibration system chiller""" - cmd_target = "startChiller" if onoff else "stopChiller"s + cmd_target = "startChiller" if onoff else "stopChiller" if onoff: chiller_setpoint_temp: float = self.CHILLER_SETPOINT_TEMP.to(un.s).value await self._sal_cmd( diff --git a/python/lsst/ts/observatory/control/maintel/mtcalsys.py b/python/lsst/ts/observatory/control/maintel/mtcalsys.py index 76b75d7d..6dcce553 100644 --- a/python/lsst/ts/observatory/control/maintel/mtcalsys.py +++ b/python/lsst/ts/observatory/control/maintel/mtcalsys.py @@ -1,5 +1,5 @@ -from typing import List - +from typing import List, Optional +from lsst.ts import salobj from ..base_calsys import BaseCalsys From c88a53fc2e64ca6e955f62181a405a30f3683a0e Mon Sep 17 00:00:00 2001 From: Dan Weatherill Date: Fri, 29 Mar 2024 15:48:24 +0000 Subject: [PATCH 16/17] some more docs --- .../ts/observatory/control/auxtel/atcalsys.py | 77 +++++++++++++++++-- 1 file changed, 72 insertions(+), 5 deletions(-) diff --git a/python/lsst/ts/observatory/control/auxtel/atcalsys.py b/python/lsst/ts/observatory/control/auxtel/atcalsys.py index c7c5a199..9b2c0d0d 100644 --- a/python/lsst/ts/observatory/control/auxtel/atcalsys.py +++ b/python/lsst/ts/observatory/control/auxtel/atcalsys.py @@ -49,7 +49,8 @@ class ATCalsys(BaseCalsys, HardcodeCalsysThroughput): ] CHANGE_GRATING_TIME: Quantity[un.physical.time] = 60 << un.s - # these below numbers should be able to be loaded from a (fairly static) config! + # these below numbers should be able to be loaded from a + # (fairly static) config! GRATING_CHANGEOVER_WL: Quantity[un.physical.length] = 532.0 << un.nm GRATING_CHANGEOVER_BW: Quantity[un.physical.length] = ( 55.0 << un.nm @@ -66,6 +67,15 @@ class ATCalsys(BaseCalsys, HardcodeCalsysThroughput): CAL_PROGRAM_NAME: str = "AT_flats" def __init__(self, intention: CalsysScriptIntention, **kwargs): + """ Initialise the ATCalsys System + + Parameters + ---------- + intention: CalsysScriptIntention + An instance of CalsysScriptIntention that tells us what the script will be doing + this allows us to customise the various manipulation routines of the calibration system + + """ super().__init__(intention, components=self._AT_SAL_COMPONENTS, **kwargs) # instance variables we'll set later @@ -77,6 +87,21 @@ def __init__(self, intention: CalsysScriptIntention, **kwargs): async def setup_for_wavelength( self, wavelen: float, nelec: float, spectral_res: float, **override_kwargs ) -> None: + """Set up the calibration system for running flats of a particular wavelength + + Parameters + ---------- + wavelen : float + the wavelength to setup for (in nm) + nelec : float + the tarket number of electrons for flat exposure (in kelec) + spectral_res : float + the target spectral resolution + **override_kwargs : FIXME: Add type. + keyword arguments passed in here will be forwarded to `_calss_get_parameter` + + """ + grating = _calsys_get_parameter( override_kwargs, "grating", @@ -142,10 +167,40 @@ async def setup_for_wavelength( override_kwargs, "n_elec_exps", self.pd_n_exps_for_nelectrons, nelec ) - def calculate_slit_width( - self, wavelen: float, spectral_res: float, grating - ) -> Optional[ATSpectrographSlits]: - # NOTE: this will either need to be derived by doing calculations on the Grating equation, or by loading in calibration data (which I couldn't find yet!). For now we just return the + def calculate_slit_width(self, wavelen: float, spectral_res: float, + grating: Optional[ATMonochromator.GRATING] = None + ) -> Optional[ATSpectrographSlits]: + """Calculate the slit width needed to achieve given spectral resolution. + NOTE: DO NOT USE! No calibration data available here yet! + + The fiber spectrograph output slit widths control the spectral bandwidth + of the flat fields. This function converts a targeted spectral resolution + into a slit width setting + + Parameters + ---------- + wavelen : float + the central wavelength of the spectrum (in units of nm) + spectral_res : float + the desired FWHM bandwidth of the spectrum (in units of nm) + grating : Optional[ATMonochromator.GRATING] + which grating to use - see ts enums. If not supplied, will be + calculated using `calculate_grating_type` + + Returns + ------- + Optional[ATSpectrographSlits] + FIXME: Add docs. + + Raises + ------ + NotImplementedError + FIXME: Add docs. + + """ + + if grating is None: + grating = self.calculate_grating_type(wavelen, spectral_res) raise NotImplementedError( "calculation of slit widths not available yet, override in script parameters!" ) @@ -162,6 +217,18 @@ def calculate_grating_type( return ATMonochromator.Grating.RED async def _electrometer_expose(self) -> Awaitable[list[str]]: + """begin an exposure of the electrometer subsystem + + the electrometer scan is immediately started on calling this function, + after coroutine suspension the caller may at a later point await + the result. See documentation for BaseCalsys._cal_expose_helper for + details. + + Returns + ------- + Awaitable[list[str]] deferred result of the electormeter exposure + """ + assert self._n_elec_exps is not None assert self._elecsposure_time is not None return self._cal_expose_helper( From 5f9480567eb4903e92e11a35434498668127972f Mon Sep 17 00:00:00 2001 From: Dan Weatherill Date: Wed, 3 Apr 2024 17:27:02 +0100 Subject: [PATCH 17/17] test mock setup start --- .../ts/observatory/control/auxtel/atcalsys.py | 5 ++++- tests/test_calsys_logic.py | 19 +++++++++++++++---- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/python/lsst/ts/observatory/control/auxtel/atcalsys.py b/python/lsst/ts/observatory/control/auxtel/atcalsys.py index 9b2c0d0d..01d0a467 100644 --- a/python/lsst/ts/observatory/control/auxtel/atcalsys.py +++ b/python/lsst/ts/observatory/control/auxtel/atcalsys.py @@ -490,6 +490,9 @@ def lamp_verify(evt): ) async def take_calibration_data(self) -> dict[str, list[str]]: + """Take the calibration data (i.e. the elecrometer and spectrometer data). + NOTE: explicitly does NOT take the LATISS flats, those should be handled separately + by the calling script """ spec_fut = self._spectrograph_expose() elec_fut = self._electrometer_expose() @@ -501,5 +504,5 @@ def program_reason(self) -> str: return "AT_flats" @property - def prgoram_note(self) -> str: + def program_note(self) -> str: return "TODO" diff --git a/tests/test_calsys_logic.py b/tests/test_calsys_logic.py index 8c28c1eb..56846d0f 100644 --- a/tests/test_calsys_logic.py +++ b/tests/test_calsys_logic.py @@ -4,8 +4,6 @@ import pytest from lsst.ts import salobj from lsst.ts.observatory.control.utils import RemoteGroupTestCase - - from lsst.ts.observatory.control.mock.latiss_mock import LATISSMock from lsst.ts.observatory.control.base_calsys import HardcodeCalsysThroughput @@ -25,7 +23,20 @@ def test_interpolate(self): throughput_low = obj.radiometer_throughput(875.0) +class TestATCalsys(RemoteGroupTestCase, unittest.IsolatedAsyncioTestCase): + """Test cases for the ATCalsys concrete implementation """ + + @classmethod + def setUpClass(cls) -> None: + """set up mocks and such that we need for all the tests""" + + + + async def basic_make_group( + self, usage: typing.Optional[int] = None) -> typing.Iterable[typing.Union[RemoteGroup, salobj.BaseCsc]]: + pass + + -class TestATCalsys(unittest.TestCase): ... -class TestMTCalsys(unittest.TestCase): ... +