pyobs-core 2.1.0__tar.gz → 2.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/PKG-INFO +1 -1
- pyobs_core-2.2.0/pyobs/images/processors/calibration/_calibration_cache.py +60 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/images/processors/calibration/_ccddata_calibrator.py +17 -4
- pyobs_core-2.2.0/pyobs/images/processors/calibration/calibration.py +513 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/modules/camera/dummycamera.py +31 -8
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/modules/utils/__init__.py +11 -1
- pyobs_core-2.2.0/pyobs/modules/utils/dummystructuredconfig.py +137 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/object.py +1 -1
- pyobs_core-2.2.0/pyobs/robotic/scripts/calibration/darkbias.py +305 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/storage/portal/observationarchive.py +9 -2
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/storage/portal/taskarchive.py +9 -2
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/utils/archive/archive.py +3 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/utils/archive/local_archive.py +38 -2
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/utils/archive/pyobs_archive.py +42 -3
- pyobs_core-2.2.0/pyobs/robotic/utils/calibration.py +112 -0
- pyobs_core-2.2.0/pyobs/utils/exptime_grouping.py +80 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/utils/fits.py +22 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/utils/http.py +51 -2
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/utils/pipeline/pipeline.py +32 -2
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/utils/pipeline/progress.py +3 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/utils/pipeline/reduction.py +143 -42
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/utils/pipeline/reduction_base.py +21 -6
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyproject.toml +1 -1
- pyobs_core-2.1.0/pyobs/images/processors/calibration/_calibration_cache.py +0 -46
- pyobs_core-2.1.0/pyobs/images/processors/calibration/calibration.py +0 -302
- pyobs_core-2.1.0/pyobs/robotic/scripts/calibration/darkbias.py +0 -98
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/.gitignore +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/LICENSE +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/__init__.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/application.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/background_task.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/cli/__init__.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/cli/_cli.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/cli/pyobs.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/cli/pyobsd.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/cli/pyobsw.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/comm/__init__.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/comm/comm.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/comm/commlogging.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/comm/dummy/__init__.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/comm/dummy/dummycomm.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/comm/local/__init__.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/comm/local/localcomm.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/comm/local/localnetwork.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/comm/proxy.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/comm/xmpp/__init__.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/comm/xmpp/rpc.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/comm/xmpp/serializer.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/comm/xmpp/xep_0009/__init__.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/comm/xmpp/xep_0009/binding.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/comm/xmpp/xep_0009/rpc.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/comm/xmpp/xep_0009_timeout/__init__.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/comm/xmpp/xep_0009_timeout/rpc.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/comm/xmpp/xep_0009_timeout/stanza/RPC.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/comm/xmpp/xep_0009_timeout/stanza/__init__.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/comm/xmpp/xmppclient.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/comm/xmpp/xmppcomm.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/events/__init__.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/events/badweather.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/events/event.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/events/exposurestatuschanged.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/events/filterchanged.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/events/focusfound.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/events/goodweather.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/events/log.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/events/modechanged.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/events/moduleclosed.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/events/moduleopened.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/events/motionstatuschanged.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/events/move.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/events/newimage.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/events/newspectrum.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/events/offsets.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/events/roofclosing.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/events/roofopened.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/events/taskfailed.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/events/taskfinished.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/events/taskstarted.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/events/testevent.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/images/__init__.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/images/image.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/images/meta/__init__.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/images/meta/altazoffsets.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/images/meta/exptime.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/images/meta/genericoffset.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/images/meta/onskydistance.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/images/meta/pixeloffsets.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/images/meta/radecoffsets.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/images/meta/skyoffsets.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/images/processor.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/images/processors/__init__.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/images/processors/_daobackgroundremover.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/images/processors/annotation/__init__.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/images/processors/annotation/_pillow.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/images/processors/annotation/circle.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/images/processors/annotation/crosshair.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/images/processors/annotation/text.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/images/processors/astrometry/__init__.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/images/processors/astrometry/_dotnet_request.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/images/processors/astrometry/_dotnet_request_builder.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/images/processors/astrometry/_dotnet_request_logger.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/images/processors/astrometry/_dotnet_response_saver.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/images/processors/astrometry/astrometry.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/images/processors/astrometry/dotnet.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/images/processors/calibration/__init__.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/images/processors/detection/__init__.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/images/processors/detection/_pysep_stats_calculator.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/images/processors/detection/_source_catalog.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/images/processors/detection/daophot.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/images/processors/detection/pysep.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/images/processors/detection/simpledisk.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/images/processors/detection/sourcedetection.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/images/processors/exptime/__init__.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/images/processors/exptime/exptime.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/images/processors/exptime/star.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/images/processors/image/__init__.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/images/processors/image/addfitsheaders.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/images/processors/image/download.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/images/processors/image/flip.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/images/processors/image/grayscale.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/images/processors/image/httpserver.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/images/processors/image/normalize.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/images/processors/image/save.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/images/processors/image/saveimage.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/images/processors/image/smooth.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/images/processors/image/softbin.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/images/processors/misc/__init__.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/images/processors/misc/addmask.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/images/processors/misc/catalogcircularmask.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/images/processors/misc/circularmask.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/images/processors/misc/createfilename.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/images/processors/misc/imagesourcefilter.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/images/processors/misc/removebackground.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/images/processors/modules/__init__.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/images/processors/modules/getfitsheaders.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/images/processors/offsets/__init__.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/images/processors/offsets/astrometry.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/images/processors/offsets/brighteststar.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/images/processors/offsets/brighteststar_guiding.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/images/processors/offsets/dummyoffsets.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/images/processors/offsets/dummyskyoffsets.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/images/processors/offsets/fitsheader.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/images/processors/offsets/offsets.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/images/processors/offsets/pixeloffsets.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/images/processors/offsets/projected.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/images/processors/offsets/spilled_light.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/images/processors/photometry/__init__.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/images/processors/photometry/_photometry_calculator.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/images/processors/photometry/_photutil_aperture_photometry.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/images/processors/photometry/_sep_aperture_photometry.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/images/processors/photometry/aperture_photometry.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/images/processors/photometry/photometry.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/images/processors/photometry/photutil.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/images/processors/photometry/pysep.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/images/processors/wcs/__init__.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/images/processors/wcs/solarhelioprojective.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/interfaces/IAbortable.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/interfaces/IAcquisition.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/interfaces/IAutoFocus.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/interfaces/IAutoGuiding.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/interfaces/IAutonomous.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/interfaces/IBinning.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/interfaces/ICalibrate.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/interfaces/ICamera.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/interfaces/IConfig.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/interfaces/ICooling.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/interfaces/IData.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/interfaces/IDataSequence.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/interfaces/IDome.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/interfaces/IExposure.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/interfaces/IExposureTime.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/interfaces/IFilters.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/interfaces/IFitsHeaderAfter.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/interfaces/IFitsHeaderBefore.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/interfaces/IFlatField.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/interfaces/IFocusModel.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/interfaces/IFocuser.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/interfaces/IGain.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/interfaces/IImageFormat.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/interfaces/IImageType.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/interfaces/IMode.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/interfaces/IModule.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/interfaces/IMotion.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/interfaces/IMultiFiber.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/interfaces/IOffsetsAltAz.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/interfaces/IOffsetsRaDec.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/interfaces/IPointingAltAz.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/interfaces/IPointingBody.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/interfaces/IPointingHeliocentricPolar.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/interfaces/IPointingHeliographicStonyhurst.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/interfaces/IPointingHelioprojective.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/interfaces/IPointingOrbitalElements.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/interfaces/IPointingRaDec.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/interfaces/IPointingSeries.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/interfaces/IReady.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/interfaces/IRobotic.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/interfaces/IRoboticScheduler.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/interfaces/IRoof.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/interfaces/IRotation.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/interfaces/IRunnable.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/interfaces/IRunning.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/interfaces/IScriptRunner.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/interfaces/ISpectrograph.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/interfaces/IStartStop.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/interfaces/IStructuredConfig.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/interfaces/ISyncTarget.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/interfaces/ITelescope.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/interfaces/ITemperatures.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/interfaces/ITrackingMode.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/interfaces/ITrackingRate.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/interfaces/IVideo.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/interfaces/IWeather.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/interfaces/IWindow.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/interfaces/__init__.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/interfaces/interface.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/mixins/__init__.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/mixins/camerasettings.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/mixins/fitsheader.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/mixins/fitsnamespace.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/mixins/follow.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/mixins/motionstatus.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/mixins/pipeline.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/mixins/waitformotion.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/mixins/weatheraware.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/modules/__init__.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/modules/camera/__init__.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/modules/camera/adaptive.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/modules/camera/basecamera.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/modules/camera/basespectrograph.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/modules/camera/basevideo.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/modules/camera/dummyspectrograph.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/modules/camera/dummyvideo.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/modules/camera/pipelinecamera.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/modules/flatfield/__init__.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/modules/flatfield/flatfield.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/modules/flatfield/pointing.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/modules/flatfield/scheduler.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/modules/focus/__init__.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/modules/focus/dummyautofocus.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/modules/focus/focusmodel.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/modules/focus/focusseries.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/modules/image/__init__.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/modules/image/imagewatcher.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/modules/image/imagewriter.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/modules/image/pipeline.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/modules/image/seeing.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/modules/module.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/modules/pointing/__init__.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/modules/pointing/_base.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/modules/pointing/_baseguiding.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/modules/pointing/acquisition.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/modules/pointing/autoguiding.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/modules/pointing/dummyacquisition.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/modules/pointing/dummyguiding.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/modules/pointing/guidingstatistics/__init__.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/modules/pointing/guidingstatistics/guidingstatistics.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/modules/pointing/guidingstatistics/pixeloffset.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/modules/pointing/guidingstatistics/skyoffsets.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/modules/pointing/guidingstatistics/uptime.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/modules/pointing/scienceframeguiding.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/modules/robotic/__init__.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/modules/robotic/dummymastermind.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/modules/robotic/dummyscheduler.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/modules/robotic/mastermind.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/modules/robotic/pointing.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/modules/robotic/scheduler.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/modules/robotic/scriptrunner.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/modules/roof/__init__.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/modules/roof/basedome.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/modules/roof/baseroof.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/modules/roof/dummyroof.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/modules/telescope/__init__.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/modules/telescope/_dummytelescopebase.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/modules/telescope/basetelescope.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/modules/telescope/dummyaltaztelescope.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/modules/telescope/dummyradectelescope.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/modules/telescope/dummysolartelescope.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/modules/test/__init__.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/modules/test/standalone.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/modules/utils/dummymode.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/modules/utils/fluentlogger.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/modules/utils/httpfilecache.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/modules/utils/kiosk.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/modules/utils/matrix.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/modules/utils/stellarium.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/modules/utils/telegram.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/modules/utils/trigger.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/modules/weather/__init__.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/modules/weather/mockweather.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/modules/weather/weather.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/modules/weather/weather_api.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/modules/weather/weather_state.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/py.typed +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/__init__.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/observation.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/scheduler/__init__.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/scheduler/_executor.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/scheduler/astroplanscheduler.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/scheduler/constraints/__init__.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/scheduler/constraints/airmassconstraint.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/scheduler/constraints/constraint.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/scheduler/constraints/moonilluminationconstraint.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/scheduler/constraints/moonseparationconstraint.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/scheduler/constraints/solarelevationconstraint.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/scheduler/constraints/timeconstraint.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/scheduler/dataprovider.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/scheduler/merits/__init__.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/scheduler/merits/aftertime.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/scheduler/merits/beforetime.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/scheduler/merits/constant.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/scheduler/merits/follow.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/scheduler/merits/interval.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/scheduler/merits/merit.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/scheduler/merits/pernight.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/scheduler/merits/random.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/scheduler/merits/timewindow.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/scheduler/merits/transit.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/scheduler/observationarchiveevolution.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/scheduler/ondemandscheduler.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/scheduler/targets/__init__.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/scheduler/targets/dynamictarget.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/scheduler/targets/heliocentricpolartarget.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/scheduler/targets/helioprojectivetarget.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/scheduler/targets/picker/__init__.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/scheduler/targets/picker/csvpicker.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/scheduler/targets/picker/picker.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/scheduler/targets/siderealtarget.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/scheduler/targets/target.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/scheduler/taskscheduler.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/scripts/__init__.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/scripts/calibration/__init__.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/scripts/calibration/pointing.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/scripts/calibration/skyflats.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/scripts/control/__init__.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/scripts/control/cases.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/scripts/control/conditional.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/scripts/control/parallel.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/scripts/control/selector.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/scripts/control/sequential.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/scripts/exceptions.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/scripts/imaging/__init__.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/scripts/imaging/autofocus.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/scripts/imaging/imaging.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/scripts/imaging/transitimaging.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/scripts/script.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/scripts/utils/__init__.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/scripts/utils/callmodule.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/scripts/utils/debugtrigger.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/scripts/utils/log.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/storage/__init__.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/storage/filesystem/__init__.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/storage/filesystem/observationarchive.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/storage/filesystem/taskarchive.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/storage/lco/__init__.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/storage/lco/_portal.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/storage/lco/_schedulereader.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/storage/lco/_schedulewriter.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/storage/lco/configdb.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/storage/lco/mockobservationarchive.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/storage/lco/observationarchive.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/storage/lco/scripts/__init__.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/storage/lco/scripts/autofocus.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/storage/lco/scripts/default.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/storage/lco/scripts/script.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/storage/lco/task.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/storage/lco/taskarchive.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/storage/lco/taskrunner.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/storage/memory/__init__.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/storage/memory/observationarchive.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/storage/memory/taskarchive.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/storage/observationarchive.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/storage/portal/__init__.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/storage/taskarchive.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/task.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/taskrunner.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/utils/__init__.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/utils/archive/__init__.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/utils/exptime/__init__.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/utils/exptime/exptime.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/utils/exptime/stellarexptime.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/utils/skyflats/__init__.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/utils/skyflats/exptimeeval.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/utils/skyflats/flatfielder.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/utils/skyflats/pointing/__init__.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/utils/skyflats/pointing/base.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/utils/skyflats/pointing/static.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/utils/skyflats/priorities/__init__.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/utils/skyflats/priorities/archive.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/utils/skyflats/priorities/base.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/utils/skyflats/priorities/const.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/robotic/utils/skyflats/scheduler.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/utils/__init__.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/utils/average.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/utils/cache.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/utils/config.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/utils/config_schema.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/utils/coordinates.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/utils/curvefit.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/utils/enums.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/utils/exceptions.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/utils/focusseries/__init__.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/utils/focusseries/base.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/utils/focusseries/photometry.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/utils/focusseries/projection.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/utils/grids/__init__.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/utils/grids/filters.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/utils/grids/grid.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/utils/grids/gridnode.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/utils/grids/pipeline.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/utils/gui/__init__.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/utils/gui/camera/__init__.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/utils/gui/camera/binningwidget.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/utils/gui/camera/datadisplaywidget.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/utils/gui/camera/exposewidget.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/utils/gui/camera/exposuretimewidget.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/utils/gui/camera/imageformatwidget.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/utils/gui/camera/listpickerdialog.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/utils/gui/camera/windowingwidget.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/utils/influxdb.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/utils/logger.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/utils/logging/__init__.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/utils/logging/context.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/utils/logging/resolvableerror.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/utils/modulegui.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/utils/offsets/__init__.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/utils/offsets/applyaltazoffsets.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/utils/offsets/applyoffsets.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/utils/offsets/applyradecoffsets.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/utils/parallel.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/utils/pipeline/__init__.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/utils/publisher/__init__.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/utils/publisher/csv.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/utils/publisher/http.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/utils/publisher/log.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/utils/publisher/multi.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/utils/publisher/publisher.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/utils/serialization.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/utils/shellcommand.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/utils/threads/__init__.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/utils/threads/lockwithabort.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/utils/time.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/utils/units.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/utils/versions.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/version.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/vfs/__init__.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/vfs/archivefile.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/vfs/bufferedfile.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/vfs/file.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/vfs/filelists/__init__.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/vfs/filelists/filelist.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/vfs/filelists/testing.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/vfs/httpfile.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/vfs/localfile.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/vfs/memfile.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/vfs/sftpfile.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/vfs/smbfile.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/vfs/sshfile.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/vfs/tempfile.py +0 -0
- {pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/vfs/vfs.py +0 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections import deque
|
|
4
|
+
from typing import cast
|
|
5
|
+
|
|
6
|
+
from pyobs.images import Image
|
|
7
|
+
from pyobs.utils.enums import ImageType
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class _CalibrationCache:
|
|
11
|
+
BINNING_FORMAT = "{0}x{0}"
|
|
12
|
+
|
|
13
|
+
def __init__(self, max_size: int):
|
|
14
|
+
self._cache: deque[tuple[tuple[ImageType, str, str, str | None, float | None], Image]] = deque([], max_size)
|
|
15
|
+
|
|
16
|
+
def add_to_cache(self, image: Image, image_type: ImageType, exptime: float | None = None) -> None:
|
|
17
|
+
cache_keys = self._get_cache_keys(image, image_type, exptime)
|
|
18
|
+
cache_entry = (cache_keys, image)
|
|
19
|
+
self._cache.append(cache_entry)
|
|
20
|
+
|
|
21
|
+
def get_from_cache(self, image: Image, image_type: ImageType, exptime: float | None = None) -> Image:
|
|
22
|
+
cache_keys = self._get_cache_keys(image, image_type, exptime)
|
|
23
|
+
return self._find_cache_entry(cache_keys)
|
|
24
|
+
|
|
25
|
+
def _find_cache_entry(self, keys: tuple[ImageType, str, str, str | None, float | None]) -> Image:
|
|
26
|
+
for m, item in self._cache:
|
|
27
|
+
if m == keys:
|
|
28
|
+
return item
|
|
29
|
+
|
|
30
|
+
raise ValueError("Calibration not found in cache.")
|
|
31
|
+
|
|
32
|
+
def _get_cache_keys(
|
|
33
|
+
self, image: Image, image_type: ImageType, exptime: float | None = None
|
|
34
|
+
) -> tuple[ImageType, str, str, str | None, float | None]:
|
|
35
|
+
instrument, binning, filter_name = self._get_image_cache_keys(image)
|
|
36
|
+
if image_type in (ImageType.BIAS, ImageType.DARK):
|
|
37
|
+
# BIAS/DARK lookups ignore filter (matching Calibration._find_master_in_archive,
|
|
38
|
+
# which never passes a science image's FILTER to Pipeline.find_master for these two
|
|
39
|
+
# types). Deriving filter_name from whichever image was passed -- the science image
|
|
40
|
+
# on a get, the master on an add -- would otherwise near-guarantee a cache miss:
|
|
41
|
+
# a master dark/bias frequently has no FILTER header at all (or a stale one from
|
|
42
|
+
# whatever was mounted at the time) while the science image usually does.
|
|
43
|
+
filter_name = None
|
|
44
|
+
# exptime is not derived from `image`'s own header: for DARK, callers pass the exptime
|
|
45
|
+
# they searched for (the science exptime for an exact match, or the configured
|
|
46
|
+
# reference exptime for a scale-down lookup), which generally differs from either the
|
|
47
|
+
# science image's or the master's own EXPTIME. None for BIAS/SKYFLAT, unchanged.
|
|
48
|
+
cache_keys = (image_type, instrument, binning, filter_name, exptime)
|
|
49
|
+
|
|
50
|
+
return cache_keys
|
|
51
|
+
|
|
52
|
+
def _get_image_cache_keys(self, image: Image) -> tuple[str, str, str | None]:
|
|
53
|
+
instrument = image.header["INSTRUME"]
|
|
54
|
+
binning = self.BINNING_FORMAT.format(image.header["XBINNING"]) # noqa: UP031
|
|
55
|
+
|
|
56
|
+
filter_name = None
|
|
57
|
+
if "FILTER" in image.header:
|
|
58
|
+
filter_name = cast(str, image.header["FILTER"])
|
|
59
|
+
|
|
60
|
+
return instrument, binning, filter_name
|
{pyobs_core-2.1.0 → pyobs_core-2.2.0}/pyobs/images/processors/calibration/_ccddata_calibrator.py
RENAMED
|
@@ -7,7 +7,14 @@ from pyobs.images import Image
|
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
class _CCDDataCalibrator:
|
|
10
|
-
def __init__(
|
|
10
|
+
def __init__(
|
|
11
|
+
self,
|
|
12
|
+
image: Image,
|
|
13
|
+
bias: Image | None = None,
|
|
14
|
+
dark: Image | None = None,
|
|
15
|
+
flat: Image | None = None,
|
|
16
|
+
dark_scale: bool = True,
|
|
17
|
+
):
|
|
11
18
|
# to_ccddata() below never carries a catalog through anyway, so drop it before trim() to
|
|
12
19
|
# avoid its stale-catalog guard rejecting an image whose catalog wouldn't survive regardless.
|
|
13
20
|
if image.safe_catalog is not None:
|
|
@@ -17,10 +24,16 @@ class _CCDDataCalibrator:
|
|
|
17
24
|
self._bias = self._optional_to_ccddata(bias)
|
|
18
25
|
self._dark = self._optional_to_ccddata(dark)
|
|
19
26
|
self._flat = self._optional_to_ccddata(flat)
|
|
27
|
+
# whether ccd_process() should rescale the dark to the science exptime -- False for an
|
|
28
|
+
# exact exptime-match master, which is used as-is (see Calibration._find_dark_master).
|
|
29
|
+
self._dark_scale = dark_scale
|
|
20
30
|
|
|
21
31
|
self._ccd_data = self._image.to_ccddata()
|
|
22
32
|
|
|
23
|
-
|
|
33
|
+
# only read EXPTIME when it's actually needed -- an unscaled exact-match dark
|
|
34
|
+
# (dark_scale=False) doesn't require it, and a legacy master combined from raw frames
|
|
35
|
+
# with no EXPTIME at all (see Reduction._create_master_darks' fallback) doesn't have it
|
|
36
|
+
if dark is not None and dark_scale:
|
|
24
37
|
self._dark_exp_time = dark.header["EXPTIME"]
|
|
25
38
|
|
|
26
39
|
@staticmethod
|
|
@@ -47,8 +60,8 @@ class _CCDDataCalibrator:
|
|
|
47
60
|
bad_pixel_mask=None,
|
|
48
61
|
gain=self._image.header["DET-GAIN"] * u.electron / u.adu,
|
|
49
62
|
readnoise=self._image.header["DET-RON"] * u.electron,
|
|
50
|
-
dark_exposure=self._dark_exp_time * u.second if self._dark is not None else None,
|
|
63
|
+
dark_exposure=self._dark_exp_time * u.second if (self._dark is not None and self._dark_scale) else None,
|
|
51
64
|
data_exposure=self._image.header["EXPTIME"] * u.second,
|
|
52
|
-
dark_scale=
|
|
65
|
+
dark_scale=self._dark_scale,
|
|
53
66
|
gain_corrected=False,
|
|
54
67
|
)
|
|
@@ -0,0 +1,513 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from typing import Any, cast
|
|
5
|
+
|
|
6
|
+
import astropy.units as u
|
|
7
|
+
|
|
8
|
+
from pyobs.images import Image
|
|
9
|
+
from pyobs.images.processor import ImageProcessor
|
|
10
|
+
from pyobs.images.processors.calibration._calibration_cache import _CalibrationCache
|
|
11
|
+
from pyobs.images.processors.calibration._ccddata_calibrator import _CCDDataCalibrator
|
|
12
|
+
from pyobs.robotic.utils.archive import Archive
|
|
13
|
+
from pyobs.utils.enums import ImageType
|
|
14
|
+
from pyobs.utils.exptime_grouping import exptimes_close
|
|
15
|
+
from pyobs.utils.pipeline import Pipeline
|
|
16
|
+
from pyobs.utils.time import Time
|
|
17
|
+
|
|
18
|
+
log = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class Calibration(ImageProcessor):
|
|
22
|
+
"""
|
|
23
|
+
Calibrate an image using master bias, dark, and flat frames fetched from an archive.
|
|
24
|
+
|
|
25
|
+
This processor locates appropriate master calibration frames
|
|
26
|
+
(bias, dark, flat) based on the input image metadata, applies calibration to the
|
|
27
|
+
image, and annotates the FITS header with provenance and reduction information.
|
|
28
|
+
Calibration frames are looked up via an :class:`~pyobs.robotic.utils.archive.Archive` and are
|
|
29
|
+
cached in a class-wide cache to reduce repeated lookups. If required calibration
|
|
30
|
+
frames cannot be found, the original image is returned unchanged.
|
|
31
|
+
|
|
32
|
+
:param dict | Archive archive: Archive configuration or an already constructed
|
|
33
|
+
:class:`~pyobs.robotic.utils.archive.Archive` instance used to
|
|
34
|
+
locate master calibration frames. If a dictionary
|
|
35
|
+
is provided, it is instantiated via
|
|
36
|
+
:func:`~pyobs.object.get_object`.
|
|
37
|
+
:param int max_cache_size: Maximum number of master frames kept in the shared
|
|
38
|
+
calibration cache. Default: ``20``.
|
|
39
|
+
:param bool require_bias: If ``True``, a master bias must be found; otherwise
|
|
40
|
+
calibration is aborted and the image is returned
|
|
41
|
+
unchanged. If ``False``, bias subtraction is skipped.
|
|
42
|
+
Default: ``True``.
|
|
43
|
+
:param bool require_dark: If ``True``, a master dark must be found; otherwise
|
|
44
|
+
calibration is aborted. If ``False``, dark subtraction
|
|
45
|
+
is skipped. Default: ``True``.
|
|
46
|
+
:param bool require_flat: If ``True``, a master flat must be found; otherwise
|
|
47
|
+
calibration is aborted. If ``False``, flat-fielding is
|
|
48
|
+
skipped. Default: ``True``.
|
|
49
|
+
:param float | None max_days_bias: Maximum age difference in days allowed when
|
|
50
|
+
selecting the master bias relative to the
|
|
51
|
+
science image ``DATE-OBS``. If ``None``, no
|
|
52
|
+
explicit age limit is applied. Default: ``None``.
|
|
53
|
+
:param float | None max_days_dark: Same as ``max_days_bias`` for dark frames.
|
|
54
|
+
Default: ``None``.
|
|
55
|
+
:param float | None max_days_flat: Same as ``max_days_bias`` for flat frames.
|
|
56
|
+
Default: ``None``.
|
|
57
|
+
:param float dark_exptime_tolerance: Relative tolerance for matching a science frame's
|
|
58
|
+
``EXPTIME`` against a dark master's, and for the
|
|
59
|
+
scale-down-only ceiling around ``dark_scale_exptime``.
|
|
60
|
+
Default: ``0.01`` (1%).
|
|
61
|
+
:param float | None dark_scale_exptime: Reference dark exposure time (seconds) that may be
|
|
62
|
+
scaled down to a shorter science exptime when no
|
|
63
|
+
exact-exptime master exists. ``None`` disables
|
|
64
|
+
scale-down entirely (branch 3 below never fires).
|
|
65
|
+
Default: ``600.0``.
|
|
66
|
+
:param bool allow_unmatched_dark_scale: If ``True``, a science exptime with neither an
|
|
67
|
+
exact-match master nor a usable reference master
|
|
68
|
+
falls back to today's always-scale-whatever-is-
|
|
69
|
+
nearest behavior instead of raising. For sites not
|
|
70
|
+
yet taking per-exptime darks. Default: ``False``.
|
|
71
|
+
:param float | None dark_min_exptime: Science exptimes below this (and with no exact-match
|
|
72
|
+
master) skip dark subtraction entirely and are
|
|
73
|
+
calibrated with bias only -- dark current is assumed
|
|
74
|
+
negligible there. ``None`` or ``0`` disables this
|
|
75
|
+
branch. Default: ``5.0``.
|
|
76
|
+
:param kwargs: Additional keyword arguments forwarded to
|
|
77
|
+
:class:`pyobs.images.processor.ImageProcessor`.
|
|
78
|
+
|
|
79
|
+
Behavior
|
|
80
|
+
--------
|
|
81
|
+
- Verifies required image header keys before searching: ``INSTRUME``, ``XBINNING``,
|
|
82
|
+
and ``DATE-OBS`` must be present.
|
|
83
|
+
- Attempts to retrieve required masters (bias, dark, flat) from a class-wide cache.
|
|
84
|
+
On cache miss, queries the configured archive via
|
|
85
|
+
:meth:`~pyobs.utils.pipeline.Pipeline.find_master`, matching:
|
|
86
|
+
|
|
87
|
+
- Instrument: exact value of ``INSTRUME``.
|
|
88
|
+
- Binning: string formatted as ``"{XBINNING}x{XBINNING}"`` (square binning assumed).
|
|
89
|
+
- Filter: ``FILTER`` only for flats; biases and darks ignore filter.
|
|
90
|
+
- Time constraint: centered on the image ``DATE-OBS`` with optional ``max_days_*``.
|
|
91
|
+
|
|
92
|
+
- Dark matching additionally follows ADR
|
|
93
|
+
``0015-dark-master-strict-exptime-matching-reference-scale-down-only.md``, checked in
|
|
94
|
+
order against the science frame's own ``EXPTIME``:
|
|
95
|
+
|
|
96
|
+
1. An exact match (within ``dark_exptime_tolerance``) is used unscaled.
|
|
97
|
+
2. No exact match and ``EXPTIME < dark_min_exptime``: bias-only, no dark subtraction,
|
|
98
|
+
not an error.
|
|
99
|
+
3. No exact match, ``EXPTIME >= dark_min_exptime``, and a reference master with
|
|
100
|
+
``EXPTIME <= dark_scale_exptime`` exists: used scaled down to the science exptime.
|
|
101
|
+
4. ``allow_unmatched_dark_scale=True``: fall back to whatever ``find_master`` returns,
|
|
102
|
+
scaled, regardless of direction (today's behavior).
|
|
103
|
+
5. Otherwise: a ``ValueError`` naming the requested exptime, caught the same way as any
|
|
104
|
+
other missing-master case (see below).
|
|
105
|
+
|
|
106
|
+
- If any required master is missing, logs a warning and returns the original image
|
|
107
|
+
unchanged.
|
|
108
|
+
- Applies calibration using the internal ``_CCDDataCalibrator`` helper
|
|
109
|
+
with the found master frames (``None`` for any non-required step to be skipped).
|
|
110
|
+
- Copies provenance into the output FITS header:
|
|
111
|
+
|
|
112
|
+
- ``L1RAW`` set from the original ``ORIGNAME`` (file stem without ``.fits``).
|
|
113
|
+
- ``L1BIAS``, ``L1DARK``, ``L1FLAT`` set from the respective master frame
|
|
114
|
+
``FNAME`` values (with common FITS extensions removed) and descriptive comments.
|
|
115
|
+
|
|
116
|
+
- Sets calibration metadata in the output header:
|
|
117
|
+
|
|
118
|
+
- ``BUNIT = "electron"`` to indicate calibrated pixel units.
|
|
119
|
+
- ``RLEVEL = 1`` to indicate reduction level.
|
|
120
|
+
|
|
121
|
+
Input/Output
|
|
122
|
+
------------
|
|
123
|
+
- Input: :class:`pyobs.images.Image` with FITS header containing at least
|
|
124
|
+
``INSTRUME``, ``XBINNING``, and ``DATE-OBS``; optional ``FILTER`` improves flat matching.
|
|
125
|
+
- Output: :class:`pyobs.images.Image` calibrated by subtracting bias, subtracting
|
|
126
|
+
dark, and dividing by flat as available. Pixel data are modified; header is
|
|
127
|
+
augmented with provenance and reduction keys.
|
|
128
|
+
|
|
129
|
+
Configuration (YAML)
|
|
130
|
+
--------------------
|
|
131
|
+
Use an archive object and allow calibration frames up to 7 days old:
|
|
132
|
+
|
|
133
|
+
.. code-block:: yaml
|
|
134
|
+
|
|
135
|
+
class: pyobs.images.processors.calibration.Calibration
|
|
136
|
+
archive:
|
|
137
|
+
class: your.archive.Class # replace with your Archive implementation
|
|
138
|
+
# ... archive-specific configuration ...
|
|
139
|
+
max_cache_size: 50
|
|
140
|
+
max_days_bias: 7
|
|
141
|
+
max_days_dark: 7
|
|
142
|
+
max_days_flat: 7
|
|
143
|
+
require_bias: true
|
|
144
|
+
require_dark: true
|
|
145
|
+
require_flat: true
|
|
146
|
+
|
|
147
|
+
Skip flats but require bias and dark:
|
|
148
|
+
|
|
149
|
+
.. code-block:: yaml
|
|
150
|
+
|
|
151
|
+
class: pyobs.images.processors.calibration.Calibration
|
|
152
|
+
archive: { class: your.archive.Class, ... }
|
|
153
|
+
require_flat: false
|
|
154
|
+
|
|
155
|
+
Notes
|
|
156
|
+
-----
|
|
157
|
+
- The calibration cache is shared across all instances of this class within the
|
|
158
|
+
process and is bounded by ``max_cache_size``.
|
|
159
|
+
- Only ``XBINNING`` is considered; this implementation assumes square binning.
|
|
160
|
+
If your data use asymmetric binning, adjust the matching logic.
|
|
161
|
+
- Flats are matched by filter when available; biases and darks ignore filter.
|
|
162
|
+
- If ``ORIGNAME`` is present in the input header, it is copied to ``L1RAW`` as a
|
|
163
|
+
stem without the ``.fits`` suffix. Master frame names are copied from ``FNAME``.
|
|
164
|
+
- Setting ``BUNIT = "electron"`` assumes calibrated units are electrons; ensure
|
|
165
|
+
your calibration products and gains are consistent with this convention.
|
|
166
|
+
"""
|
|
167
|
+
|
|
168
|
+
__module__ = "pyobs.images.processors.calibration"
|
|
169
|
+
|
|
170
|
+
"""Cache for calibration frames."""
|
|
171
|
+
_calib_cache: _CalibrationCache | None = None
|
|
172
|
+
|
|
173
|
+
def __init__(
|
|
174
|
+
self,
|
|
175
|
+
archive: dict[str, Any] | Archive,
|
|
176
|
+
max_cache_size: int = 20,
|
|
177
|
+
require_bias: bool = True,
|
|
178
|
+
require_dark: bool = True,
|
|
179
|
+
require_flat: bool = True,
|
|
180
|
+
max_days_bias: float | None = None,
|
|
181
|
+
max_days_dark: float | None = None,
|
|
182
|
+
max_days_flat: float | None = None,
|
|
183
|
+
dark_exptime_tolerance: float = 0.01,
|
|
184
|
+
dark_scale_exptime: float | None = 600.0,
|
|
185
|
+
allow_unmatched_dark_scale: bool = False,
|
|
186
|
+
dark_min_exptime: float | None = 5.0,
|
|
187
|
+
**kwargs: Any,
|
|
188
|
+
):
|
|
189
|
+
"""Init a new image calibration pipeline step.
|
|
190
|
+
|
|
191
|
+
Args:
|
|
192
|
+
archive: Archive to fetch calibration frames from.
|
|
193
|
+
"""
|
|
194
|
+
ImageProcessor.__init__(self, **kwargs)
|
|
195
|
+
|
|
196
|
+
self._max_cache_size = max_cache_size
|
|
197
|
+
self._max_days_bias = max_days_bias
|
|
198
|
+
self._max_days_dark = max_days_dark
|
|
199
|
+
self._max_days_flat = max_days_flat
|
|
200
|
+
self._require_bias = require_bias
|
|
201
|
+
self._require_dark = require_dark
|
|
202
|
+
self._require_flat = require_flat
|
|
203
|
+
self._dark_exptime_tolerance = dark_exptime_tolerance
|
|
204
|
+
self._dark_scale_exptime = dark_scale_exptime
|
|
205
|
+
self._allow_unmatched_dark_scale = allow_unmatched_dark_scale
|
|
206
|
+
self._dark_min_exptime = dark_min_exptime
|
|
207
|
+
|
|
208
|
+
self._archive = self.pyobs_model_validate(Archive, archive)
|
|
209
|
+
|
|
210
|
+
if self._calib_cache is None:
|
|
211
|
+
self._calib_cache = _CalibrationCache(self._max_cache_size)
|
|
212
|
+
|
|
213
|
+
async def __call__(self, image: Image) -> Image:
|
|
214
|
+
"""Calibrate an image.
|
|
215
|
+
|
|
216
|
+
Args:
|
|
217
|
+
image: Image to calibrate.
|
|
218
|
+
|
|
219
|
+
Returns:
|
|
220
|
+
Calibrated image.
|
|
221
|
+
"""
|
|
222
|
+
|
|
223
|
+
try:
|
|
224
|
+
bias, dark, flat, dark_scale = await self._get_calibrations_masters(image)
|
|
225
|
+
except ValueError as e:
|
|
226
|
+
log.warning("Could not find calibration frames: %s", e)
|
|
227
|
+
return image
|
|
228
|
+
|
|
229
|
+
calibrator = _CCDDataCalibrator(image, bias, dark, flat, dark_scale=dark_scale)
|
|
230
|
+
calibrated = calibrator()
|
|
231
|
+
|
|
232
|
+
self._copy_original_filename(calibrated, image)
|
|
233
|
+
self._copy_calibration_filename(calibrated, bias, dark, flat)
|
|
234
|
+
|
|
235
|
+
self._set_calibration_headers(calibrated)
|
|
236
|
+
|
|
237
|
+
return calibrated
|
|
238
|
+
|
|
239
|
+
async def _get_calibrations_masters(self, image: Image) -> tuple[Image | None, Image | None, Image | None, bool]:
|
|
240
|
+
bias = (
|
|
241
|
+
None
|
|
242
|
+
if not self._require_bias
|
|
243
|
+
else await self._find_master(image, ImageType.BIAS, max_days=self._max_days_bias)
|
|
244
|
+
)
|
|
245
|
+
dark, dark_scale = await self._find_dark_master(image)
|
|
246
|
+
flat = (
|
|
247
|
+
None
|
|
248
|
+
if not self._require_flat
|
|
249
|
+
else await self._find_master(image, ImageType.SKYFLAT, max_days=self._max_days_flat)
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
return bias, dark, flat, dark_scale
|
|
253
|
+
|
|
254
|
+
async def _find_dark_master(self, image: Image) -> tuple[Image | None, bool]:
|
|
255
|
+
"""Implements ADR 0015's dark-master matching/scaling policy, checked in order against
|
|
256
|
+
the science image's own EXPTIME:
|
|
257
|
+
|
|
258
|
+
1. An exact match (within dark_exptime_tolerance) -- used unscaled.
|
|
259
|
+
2. No exact match and EXPTIME < dark_min_exptime -- bias-only, not an error.
|
|
260
|
+
3. No exact match, EXPTIME >= dark_min_exptime, and a reference master with
|
|
261
|
+
EXPTIME <= dark_scale_exptime *and* EXPTIME >= science_exptime exists -- used
|
|
262
|
+
scaled down to the science exptime. The second condition is what actually
|
|
263
|
+
enforces "scaled down only": without it, a site whose longest master happens to be
|
|
264
|
+
shorter than the science exptime would otherwise get that master scaled *up*.
|
|
265
|
+
4. allow_unmatched_dark_scale=True -- fall back to today's always-scale-whatever-is-
|
|
266
|
+
nearest behavior. A candidate with no EXPTIME at all (a legacy master) can't be
|
|
267
|
+
scaled either way, so it's used unscaled (dark_scale=False) rather than skipped.
|
|
268
|
+
5. Otherwise -- ValueError, caught by __call__ like any other missing-master case.
|
|
269
|
+
|
|
270
|
+
Returns:
|
|
271
|
+
(master, scale). master is None only for the legitimate bias-only branch (2), or
|
|
272
|
+
when require_dark is False. scale tells the caller whether _CCDDataCalibrator
|
|
273
|
+
should rescale the returned master to the science exptime.
|
|
274
|
+
|
|
275
|
+
Raises:
|
|
276
|
+
ValueError: no usable dark master under the configured policy (branch 5).
|
|
277
|
+
"""
|
|
278
|
+
if not self._require_dark:
|
|
279
|
+
return None, False
|
|
280
|
+
|
|
281
|
+
self._verify_image_header(image)
|
|
282
|
+
science_exptime = float(image.header["EXPTIME"])
|
|
283
|
+
|
|
284
|
+
# (1) exact match, used unscaled
|
|
285
|
+
exact = await self._find_dark_at(image, science_exptime, self._max_days_dark)
|
|
286
|
+
if exact is not None:
|
|
287
|
+
return exact, False
|
|
288
|
+
|
|
289
|
+
# (2) below the minimum, no exact match -- bias-only, not an error
|
|
290
|
+
if self._dark_min_exptime is not None and science_exptime < self._dark_min_exptime:
|
|
291
|
+
return None, False
|
|
292
|
+
|
|
293
|
+
# (3) reference master, scaled down only (never up). exptime_max=dark_scale_exptime only
|
|
294
|
+
# bounds the reference search from above; a site with no master anywhere near
|
|
295
|
+
# dark_scale_exptime (e.g. only a 45s one) could otherwise get that shorter master back
|
|
296
|
+
# as the "reference" and have it scaled UP to a longer science exptime -- exactly the
|
|
297
|
+
# direction ADR 0015 forbids. Guard against that explicitly: reject a candidate whose
|
|
298
|
+
# own exptime doesn't cover science_exptime (within tolerance), falling through instead.
|
|
299
|
+
if self._dark_scale_exptime is not None and science_exptime <= self._dark_scale_exptime * (
|
|
300
|
+
1 + self._dark_exptime_tolerance
|
|
301
|
+
):
|
|
302
|
+
reference = await self._find_dark_at(
|
|
303
|
+
image, self._dark_scale_exptime, self._max_days_dark, exptime_max=self._dark_scale_exptime
|
|
304
|
+
)
|
|
305
|
+
if reference is not None and self._covers_scale_down(reference, science_exptime):
|
|
306
|
+
return reference, True
|
|
307
|
+
|
|
308
|
+
# (4) opt back into today's always-scale-whatever-is-nearest behavior
|
|
309
|
+
if self._allow_unmatched_dark_scale:
|
|
310
|
+
fallback = await self._find_master(image, ImageType.DARK, max_days=self._max_days_dark)
|
|
311
|
+
# a legacy master with no EXPTIME at all can't be scaled -- ccd_process needs the
|
|
312
|
+
# dark's own exposure time to compute the scale factor (see _CCDDataCalibrator)
|
|
313
|
+
return fallback, "EXPTIME" in fallback.header
|
|
314
|
+
|
|
315
|
+
# (5) strict -- no usable dark master under the configured policy
|
|
316
|
+
ceiling = (
|
|
317
|
+
None if self._dark_scale_exptime is None else self._dark_scale_exptime * (1 + self._dark_exptime_tolerance)
|
|
318
|
+
)
|
|
319
|
+
available = await self._available_dark_exptimes(image)
|
|
320
|
+
raise ValueError(
|
|
321
|
+
f"No usable dark master for EXPTIME={science_exptime}s (no exact match within "
|
|
322
|
+
f"{self._dark_exptime_tolerance:.0%}"
|
|
323
|
+
+ (
|
|
324
|
+
f", no reference master <= {ceiling:.1f}s to scale down"
|
|
325
|
+
if ceiling is not None
|
|
326
|
+
else ", no reference exptime configured"
|
|
327
|
+
)
|
|
328
|
+
+ f"); available master exptimes: {available if available else 'none'}."
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
def _covers_scale_down(self, reference: Image, science_exptime: float) -> bool:
|
|
332
|
+
"""Whether `reference`'s own exptime is long enough to be scaled *down* to
|
|
333
|
+
science_exptime -- ADR 0015 forbids the reverse. False (not usable as a reference) for
|
|
334
|
+
a master shorter than science_exptime, or one with no EXPTIME to check at all (a legacy
|
|
335
|
+
master -- direction can't be verified, so it's rejected rather than assumed safe)."""
|
|
336
|
+
if "EXPTIME" not in reference.header:
|
|
337
|
+
return False
|
|
338
|
+
return float(reference.header["EXPTIME"]) >= science_exptime * (1 - self._dark_exptime_tolerance)
|
|
339
|
+
|
|
340
|
+
async def _available_dark_exptimes(self, image: Image) -> list[float]:
|
|
341
|
+
"""Best-effort list of exptimes among this instrument/binning's archived DARK masters
|
|
342
|
+
near the science image's DATE-OBS, for the strict-policy error message only (ADR 0015
|
|
343
|
+
asks that it name what's available). Bounded to max_days_dark (falling back to a wide
|
|
344
|
+
30-day window if unset) so this doesn't scan an archive's entire DARK history on every
|
|
345
|
+
strict-failure frame. Never raises -- an archive issue here shouldn't mask the real
|
|
346
|
+
ValueError being raised."""
|
|
347
|
+
try:
|
|
348
|
+
instrument = image.header["INSTRUME"]
|
|
349
|
+
binning = "{0}x{0}".format(image.header["XBINNING"]) # noqa: UP031
|
|
350
|
+
time = Time(image.header["DATE-OBS"])
|
|
351
|
+
max_days = self._max_days_dark if self._max_days_dark is not None else 30.0
|
|
352
|
+
infos = await self._archive.list_frames(
|
|
353
|
+
start=time - max_days * u.day,
|
|
354
|
+
end=time + max_days * u.day,
|
|
355
|
+
instrument=instrument,
|
|
356
|
+
image_type=ImageType.DARK,
|
|
357
|
+
binning=binning,
|
|
358
|
+
rlevel=1,
|
|
359
|
+
)
|
|
360
|
+
except Exception:
|
|
361
|
+
return []
|
|
362
|
+
return sorted({i.exptime for i in infos if i.exptime is not None})
|
|
363
|
+
|
|
364
|
+
async def _find_dark_at(
|
|
365
|
+
self, image: Image, target_exptime: float, max_days: float | None, exptime_max: float | None = None
|
|
366
|
+
) -> Image | None:
|
|
367
|
+
"""Looks up a DARK master near target_exptime via the shared calibration cache, keyed
|
|
368
|
+
by that target rather than either image's own EXPTIME (see _CalibrationCache), falling
|
|
369
|
+
back to an archive query on a cache miss.
|
|
370
|
+
|
|
371
|
+
Args:
|
|
372
|
+
target_exptime: Exptime to search near -- the science exptime for an exact-match
|
|
373
|
+
lookup, or dark_scale_exptime for a reference lookup.
|
|
374
|
+
exptime_max: If given, only accept candidates with EXPTIME <= this (scale-down-only
|
|
375
|
+
enforcement); the result is otherwise accepted whatever its exptime. If not
|
|
376
|
+
given, this is an exact-match lookup: the result is discarded (returns None)
|
|
377
|
+
unless it's actually within dark_exptime_tolerance of target_exptime.
|
|
378
|
+
|
|
379
|
+
Returns:
|
|
380
|
+
The master, or None if nothing usable was found. Never raises.
|
|
381
|
+
"""
|
|
382
|
+
if self._calib_cache is None:
|
|
383
|
+
return None
|
|
384
|
+
|
|
385
|
+
try:
|
|
386
|
+
return self._calib_cache.get_from_cache(image, ImageType.DARK, exptime=target_exptime)
|
|
387
|
+
except ValueError:
|
|
388
|
+
pass
|
|
389
|
+
|
|
390
|
+
self._verify_image_header(image)
|
|
391
|
+
instrument = image.header["INSTRUME"]
|
|
392
|
+
binning = "{0}x{0}".format(image.header["XBINNING"]) # noqa: UP031
|
|
393
|
+
time = Time(image.header["DATE-OBS"])
|
|
394
|
+
|
|
395
|
+
master = await Pipeline.find_master(
|
|
396
|
+
self._archive,
|
|
397
|
+
ImageType.DARK,
|
|
398
|
+
time,
|
|
399
|
+
instrument,
|
|
400
|
+
binning,
|
|
401
|
+
None,
|
|
402
|
+
max_days=max_days,
|
|
403
|
+
exptime=target_exptime,
|
|
404
|
+
exptime_tolerance=self._dark_exptime_tolerance,
|
|
405
|
+
exptime_max=exptime_max,
|
|
406
|
+
)
|
|
407
|
+
if master is None:
|
|
408
|
+
return None
|
|
409
|
+
|
|
410
|
+
if exptime_max is None and (
|
|
411
|
+
"EXPTIME" not in master.header
|
|
412
|
+
or not exptimes_close(float(master.header["EXPTIME"]), target_exptime, self._dark_exptime_tolerance)
|
|
413
|
+
):
|
|
414
|
+
# nearest available, but not actually an exact match (or a legacy pre-#831 master
|
|
415
|
+
# with no EXPTIME header at all) -- not usable for this lookup
|
|
416
|
+
return None
|
|
417
|
+
|
|
418
|
+
self._calib_cache.add_to_cache(master, ImageType.DARK, exptime=target_exptime)
|
|
419
|
+
return master
|
|
420
|
+
|
|
421
|
+
async def _find_master(self, image: Image, image_type: ImageType, max_days: float | None = None) -> Image:
|
|
422
|
+
"""Find master calibration frame for given parameters using a cache.
|
|
423
|
+
|
|
424
|
+
Args:
|
|
425
|
+
image_type: image type.
|
|
426
|
+
|
|
427
|
+
Returns:
|
|
428
|
+
Image or None
|
|
429
|
+
|
|
430
|
+
Raises:
|
|
431
|
+
ValueError: if no calibration frame could be found.
|
|
432
|
+
"""
|
|
433
|
+
|
|
434
|
+
self._verify_image_header(image)
|
|
435
|
+
|
|
436
|
+
if self._calib_cache is None:
|
|
437
|
+
raise ValueError("No cache.")
|
|
438
|
+
|
|
439
|
+
try:
|
|
440
|
+
return self._calib_cache.get_from_cache(image, image_type)
|
|
441
|
+
except ValueError:
|
|
442
|
+
master = await self._find_master_in_archive(image, image_type, max_days)
|
|
443
|
+
self._calib_cache.add_to_cache(master, image_type)
|
|
444
|
+
return master
|
|
445
|
+
|
|
446
|
+
@staticmethod
|
|
447
|
+
def _verify_image_header(image: Image) -> None:
|
|
448
|
+
has_instrument = "INSTRUME" in image.header
|
|
449
|
+
has_binning = "XBINNING" in image.header
|
|
450
|
+
has_time = "DATE-OBS" in image.header
|
|
451
|
+
# EXPTIME is needed by dark-exptime matching (_find_dark_master) and was already an
|
|
452
|
+
# implicit requirement of _CCDDataCalibrator itself (data_exposure=...EXPTIME...) --
|
|
453
|
+
# checked upfront here instead of surfacing as a bare KeyError mid-calibration.
|
|
454
|
+
has_exptime = "EXPTIME" in image.header
|
|
455
|
+
|
|
456
|
+
if not (has_instrument and has_binning and has_time and has_exptime):
|
|
457
|
+
raise ValueError("Could not fetch items from image header.")
|
|
458
|
+
|
|
459
|
+
async def _find_master_in_archive(
|
|
460
|
+
self, image: Image, image_type: ImageType, max_days: float | None = None
|
|
461
|
+
) -> Image:
|
|
462
|
+
instrument = image.header["INSTRUME"]
|
|
463
|
+
binning = "{0}x{0}".format(image.header["XBINNING"]) # noqa: UP031
|
|
464
|
+
filter_name = cast(str, image.header["FILTER"]) if "FILTER" in image.header else None
|
|
465
|
+
time = Time(image.header["DATE-OBS"])
|
|
466
|
+
|
|
467
|
+
master = await Pipeline.find_master(
|
|
468
|
+
self._archive,
|
|
469
|
+
image_type,
|
|
470
|
+
time,
|
|
471
|
+
instrument,
|
|
472
|
+
binning,
|
|
473
|
+
None if image_type in [ImageType.BIAS, ImageType.DARK] else filter_name,
|
|
474
|
+
max_days=max_days,
|
|
475
|
+
)
|
|
476
|
+
|
|
477
|
+
if master is None:
|
|
478
|
+
raise ValueError("No master frame found.")
|
|
479
|
+
|
|
480
|
+
return master
|
|
481
|
+
|
|
482
|
+
@staticmethod
|
|
483
|
+
def _copy_original_filename(calibrated: Image, original: Image) -> None:
|
|
484
|
+
if "ORIGNAME" in original.header:
|
|
485
|
+
calibrated.header["L1RAW"] = original.header["ORIGNAME"].replace(".fits", "")
|
|
486
|
+
|
|
487
|
+
@staticmethod
|
|
488
|
+
def _copy_calibration_filename(
|
|
489
|
+
calibrated: Image, bias: Image | None = None, dark: Image | None = None, flat: Image | None = None
|
|
490
|
+
) -> None:
|
|
491
|
+
if bias is not None:
|
|
492
|
+
calibrated.header["L1BIAS"] = (
|
|
493
|
+
bias.header["FNAME"].replace(".fits.fz", "").replace(".fits", ""),
|
|
494
|
+
"Name of BIAS frame",
|
|
495
|
+
)
|
|
496
|
+
if dark is not None:
|
|
497
|
+
calibrated.header["L1DARK"] = (
|
|
498
|
+
dark.header["FNAME"].replace(".fits.fz", "").replace(".fits", ""),
|
|
499
|
+
"Name of DARK frame",
|
|
500
|
+
)
|
|
501
|
+
if flat is not None:
|
|
502
|
+
calibrated.header["L1FLAT"] = (
|
|
503
|
+
flat.header["FNAME"].replace(".fits.fz", "").replace(".fits", ""),
|
|
504
|
+
"Name of FLAT frame",
|
|
505
|
+
)
|
|
506
|
+
|
|
507
|
+
@staticmethod
|
|
508
|
+
def _set_calibration_headers(calibrated: Image) -> None:
|
|
509
|
+
calibrated.header["BUNIT"] = ("electron", "Unit of pixel values")
|
|
510
|
+
calibrated.header["RLEVEL"] = (1, "Reduction level")
|
|
511
|
+
|
|
512
|
+
|
|
513
|
+
__all__ = ["Calibration"]
|