pyobs-core 2.2.0__tar.gz → 2.4.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.2.0 → pyobs_core-2.4.0}/PKG-INFO +1 -1
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/modules/camera/dummycamera.py +21 -2
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/modules/focus/focusseries.py +2 -2
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/modules/image/imagewatcher.py +101 -32
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/modules/module.py +2 -2
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/modules/robotic/scheduler.py +76 -24
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/modules/roof/dummyroof.py +1 -1
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/modules/telescope/basetelescope.py +5 -2
- pyobs_core-2.4.0/pyobs/robotic/instruments.py +195 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/scheduler/astroplanscheduler.py +11 -1
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/scheduler/ondemandscheduler.py +97 -21
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/scheduler/taskscheduler.py +7 -1
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/scripts/calibration/darkbias.py +12 -1
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/scripts/calibration/pointing.py +4 -2
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/scripts/imaging/autofocus.py +4 -2
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/scripts/imaging/imaging.py +53 -4
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/storage/filesystem/observationarchive.py +6 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/storage/portal/observationarchive.py +50 -14
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/storage/portal/taskarchive.py +62 -4
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/storage/taskarchive.py +8 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/task.py +12 -2
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/utils/pipeline/reduction.py +9 -4
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/utils/serialization.py +49 -3
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/vfs/file.py +12 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/vfs/localfile.py +31 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/vfs/vfs.py +20 -1
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyproject.toml +1 -1
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/.gitignore +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/LICENSE +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/application.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/background_task.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/cli/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/cli/_cli.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/cli/pyobs.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/cli/pyobsd.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/cli/pyobsw.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/comm/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/comm/comm.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/comm/commlogging.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/comm/dummy/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/comm/dummy/dummycomm.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/comm/local/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/comm/local/localcomm.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/comm/local/localnetwork.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/comm/proxy.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/comm/xmpp/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/comm/xmpp/rpc.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/comm/xmpp/serializer.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/comm/xmpp/xep_0009/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/comm/xmpp/xep_0009/binding.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/comm/xmpp/xep_0009/rpc.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/comm/xmpp/xep_0009_timeout/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/comm/xmpp/xep_0009_timeout/rpc.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/comm/xmpp/xep_0009_timeout/stanza/RPC.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/comm/xmpp/xep_0009_timeout/stanza/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/comm/xmpp/xmppclient.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/comm/xmpp/xmppcomm.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/events/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/events/badweather.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/events/event.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/events/exposurestatuschanged.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/events/filterchanged.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/events/focusfound.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/events/goodweather.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/events/log.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/events/modechanged.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/events/moduleclosed.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/events/moduleopened.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/events/motionstatuschanged.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/events/move.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/events/newimage.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/events/newspectrum.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/events/offsets.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/events/roofclosing.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/events/roofopened.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/events/taskfailed.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/events/taskfinished.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/events/taskstarted.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/events/testevent.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/images/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/images/image.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/images/meta/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/images/meta/altazoffsets.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/images/meta/exptime.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/images/meta/genericoffset.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/images/meta/onskydistance.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/images/meta/pixeloffsets.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/images/meta/radecoffsets.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/images/meta/skyoffsets.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/images/processor.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/images/processors/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/images/processors/_daobackgroundremover.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/images/processors/annotation/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/images/processors/annotation/_pillow.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/images/processors/annotation/circle.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/images/processors/annotation/crosshair.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/images/processors/annotation/text.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/images/processors/astrometry/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/images/processors/astrometry/_dotnet_request.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/images/processors/astrometry/_dotnet_request_builder.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/images/processors/astrometry/_dotnet_request_logger.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/images/processors/astrometry/_dotnet_response_saver.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/images/processors/astrometry/astrometry.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/images/processors/astrometry/dotnet.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/images/processors/calibration/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/images/processors/calibration/_calibration_cache.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/images/processors/calibration/_ccddata_calibrator.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/images/processors/calibration/calibration.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/images/processors/detection/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/images/processors/detection/_pysep_stats_calculator.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/images/processors/detection/_source_catalog.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/images/processors/detection/daophot.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/images/processors/detection/pysep.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/images/processors/detection/simpledisk.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/images/processors/detection/sourcedetection.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/images/processors/exptime/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/images/processors/exptime/exptime.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/images/processors/exptime/star.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/images/processors/image/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/images/processors/image/addfitsheaders.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/images/processors/image/download.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/images/processors/image/flip.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/images/processors/image/grayscale.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/images/processors/image/httpserver.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/images/processors/image/normalize.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/images/processors/image/save.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/images/processors/image/saveimage.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/images/processors/image/smooth.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/images/processors/image/softbin.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/images/processors/misc/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/images/processors/misc/addmask.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/images/processors/misc/catalogcircularmask.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/images/processors/misc/circularmask.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/images/processors/misc/createfilename.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/images/processors/misc/imagesourcefilter.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/images/processors/misc/removebackground.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/images/processors/modules/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/images/processors/modules/getfitsheaders.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/images/processors/offsets/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/images/processors/offsets/astrometry.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/images/processors/offsets/brighteststar.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/images/processors/offsets/brighteststar_guiding.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/images/processors/offsets/dummyoffsets.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/images/processors/offsets/dummyskyoffsets.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/images/processors/offsets/fitsheader.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/images/processors/offsets/offsets.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/images/processors/offsets/pixeloffsets.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/images/processors/offsets/projected.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/images/processors/offsets/spilled_light.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/images/processors/photometry/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/images/processors/photometry/_photometry_calculator.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/images/processors/photometry/_photutil_aperture_photometry.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/images/processors/photometry/_sep_aperture_photometry.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/images/processors/photometry/aperture_photometry.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/images/processors/photometry/photometry.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/images/processors/photometry/photutil.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/images/processors/photometry/pysep.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/images/processors/wcs/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/images/processors/wcs/solarhelioprojective.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/interfaces/IAbortable.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/interfaces/IAcquisition.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/interfaces/IAutoFocus.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/interfaces/IAutoGuiding.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/interfaces/IAutonomous.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/interfaces/IBinning.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/interfaces/ICalibrate.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/interfaces/ICamera.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/interfaces/IConfig.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/interfaces/ICooling.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/interfaces/IData.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/interfaces/IDataSequence.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/interfaces/IDome.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/interfaces/IExposure.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/interfaces/IExposureTime.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/interfaces/IFilters.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/interfaces/IFitsHeaderAfter.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/interfaces/IFitsHeaderBefore.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/interfaces/IFlatField.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/interfaces/IFocusModel.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/interfaces/IFocuser.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/interfaces/IGain.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/interfaces/IImageFormat.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/interfaces/IImageType.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/interfaces/IMode.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/interfaces/IModule.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/interfaces/IMotion.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/interfaces/IMultiFiber.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/interfaces/IOffsetsAltAz.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/interfaces/IOffsetsRaDec.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/interfaces/IPointingAltAz.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/interfaces/IPointingBody.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/interfaces/IPointingHeliocentricPolar.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/interfaces/IPointingHeliographicStonyhurst.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/interfaces/IPointingHelioprojective.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/interfaces/IPointingOrbitalElements.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/interfaces/IPointingRaDec.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/interfaces/IPointingSeries.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/interfaces/IReady.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/interfaces/IRobotic.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/interfaces/IRoboticScheduler.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/interfaces/IRoof.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/interfaces/IRotation.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/interfaces/IRunnable.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/interfaces/IRunning.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/interfaces/IScriptRunner.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/interfaces/ISpectrograph.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/interfaces/IStartStop.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/interfaces/IStructuredConfig.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/interfaces/ISyncTarget.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/interfaces/ITelescope.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/interfaces/ITemperatures.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/interfaces/ITrackingMode.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/interfaces/ITrackingRate.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/interfaces/IVideo.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/interfaces/IWeather.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/interfaces/IWindow.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/interfaces/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/interfaces/interface.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/mixins/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/mixins/camerasettings.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/mixins/fitsheader.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/mixins/fitsnamespace.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/mixins/follow.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/mixins/motionstatus.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/mixins/pipeline.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/mixins/waitformotion.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/mixins/weatheraware.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/modules/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/modules/camera/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/modules/camera/adaptive.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/modules/camera/basecamera.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/modules/camera/basespectrograph.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/modules/camera/basevideo.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/modules/camera/dummyspectrograph.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/modules/camera/dummyvideo.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/modules/camera/pipelinecamera.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/modules/flatfield/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/modules/flatfield/flatfield.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/modules/flatfield/pointing.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/modules/flatfield/scheduler.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/modules/focus/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/modules/focus/dummyautofocus.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/modules/focus/focusmodel.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/modules/image/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/modules/image/imagewriter.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/modules/image/pipeline.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/modules/image/seeing.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/modules/pointing/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/modules/pointing/_base.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/modules/pointing/_baseguiding.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/modules/pointing/acquisition.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/modules/pointing/autoguiding.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/modules/pointing/dummyacquisition.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/modules/pointing/dummyguiding.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/modules/pointing/guidingstatistics/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/modules/pointing/guidingstatistics/guidingstatistics.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/modules/pointing/guidingstatistics/pixeloffset.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/modules/pointing/guidingstatistics/skyoffsets.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/modules/pointing/guidingstatistics/uptime.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/modules/pointing/scienceframeguiding.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/modules/robotic/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/modules/robotic/dummymastermind.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/modules/robotic/dummyscheduler.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/modules/robotic/mastermind.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/modules/robotic/pointing.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/modules/robotic/scriptrunner.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/modules/roof/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/modules/roof/basedome.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/modules/roof/baseroof.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/modules/telescope/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/modules/telescope/_dummytelescopebase.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/modules/telescope/dummyaltaztelescope.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/modules/telescope/dummyradectelescope.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/modules/telescope/dummysolartelescope.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/modules/test/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/modules/test/standalone.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/modules/utils/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/modules/utils/dummymode.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/modules/utils/dummystructuredconfig.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/modules/utils/fluentlogger.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/modules/utils/httpfilecache.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/modules/utils/kiosk.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/modules/utils/matrix.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/modules/utils/stellarium.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/modules/utils/telegram.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/modules/utils/trigger.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/modules/weather/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/modules/weather/mockweather.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/modules/weather/weather.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/modules/weather/weather_api.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/modules/weather/weather_state.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/object.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/py.typed +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/observation.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/scheduler/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/scheduler/_executor.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/scheduler/constraints/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/scheduler/constraints/airmassconstraint.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/scheduler/constraints/constraint.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/scheduler/constraints/moonilluminationconstraint.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/scheduler/constraints/moonseparationconstraint.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/scheduler/constraints/solarelevationconstraint.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/scheduler/constraints/timeconstraint.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/scheduler/dataprovider.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/scheduler/merits/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/scheduler/merits/aftertime.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/scheduler/merits/beforetime.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/scheduler/merits/constant.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/scheduler/merits/follow.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/scheduler/merits/interval.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/scheduler/merits/merit.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/scheduler/merits/pernight.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/scheduler/merits/random.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/scheduler/merits/timewindow.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/scheduler/merits/transit.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/scheduler/observationarchiveevolution.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/scheduler/targets/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/scheduler/targets/dynamictarget.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/scheduler/targets/heliocentricpolartarget.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/scheduler/targets/helioprojectivetarget.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/scheduler/targets/picker/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/scheduler/targets/picker/csvpicker.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/scheduler/targets/picker/picker.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/scheduler/targets/siderealtarget.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/scheduler/targets/target.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/scripts/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/scripts/calibration/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/scripts/calibration/skyflats.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/scripts/control/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/scripts/control/cases.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/scripts/control/conditional.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/scripts/control/parallel.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/scripts/control/selector.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/scripts/control/sequential.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/scripts/exceptions.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/scripts/imaging/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/scripts/imaging/transitimaging.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/scripts/script.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/scripts/utils/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/scripts/utils/callmodule.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/scripts/utils/debugtrigger.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/scripts/utils/log.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/storage/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/storage/filesystem/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/storage/filesystem/taskarchive.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/storage/lco/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/storage/lco/_portal.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/storage/lco/_schedulereader.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/storage/lco/_schedulewriter.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/storage/lco/configdb.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/storage/lco/mockobservationarchive.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/storage/lco/observationarchive.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/storage/lco/scripts/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/storage/lco/scripts/autofocus.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/storage/lco/scripts/default.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/storage/lco/scripts/script.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/storage/lco/task.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/storage/lco/taskarchive.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/storage/lco/taskrunner.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/storage/memory/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/storage/memory/observationarchive.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/storage/memory/taskarchive.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/storage/observationarchive.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/storage/portal/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/taskrunner.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/utils/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/utils/archive/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/utils/archive/archive.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/utils/archive/local_archive.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/utils/archive/pyobs_archive.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/utils/calibration.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/utils/exptime/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/utils/exptime/exptime.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/utils/exptime/stellarexptime.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/utils/skyflats/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/utils/skyflats/exptimeeval.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/utils/skyflats/flatfielder.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/utils/skyflats/pointing/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/utils/skyflats/pointing/base.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/utils/skyflats/pointing/static.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/utils/skyflats/priorities/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/utils/skyflats/priorities/archive.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/utils/skyflats/priorities/base.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/utils/skyflats/priorities/const.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/robotic/utils/skyflats/scheduler.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/utils/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/utils/average.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/utils/cache.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/utils/config.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/utils/config_schema.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/utils/coordinates.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/utils/curvefit.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/utils/enums.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/utils/exceptions.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/utils/exptime_grouping.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/utils/fits.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/utils/focusseries/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/utils/focusseries/base.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/utils/focusseries/photometry.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/utils/focusseries/projection.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/utils/grids/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/utils/grids/filters.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/utils/grids/grid.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/utils/grids/gridnode.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/utils/grids/pipeline.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/utils/gui/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/utils/gui/camera/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/utils/gui/camera/binningwidget.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/utils/gui/camera/datadisplaywidget.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/utils/gui/camera/exposewidget.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/utils/gui/camera/exposuretimewidget.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/utils/gui/camera/imageformatwidget.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/utils/gui/camera/listpickerdialog.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/utils/gui/camera/windowingwidget.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/utils/http.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/utils/influxdb.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/utils/logger.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/utils/logging/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/utils/logging/context.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/utils/logging/resolvableerror.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/utils/modulegui.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/utils/offsets/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/utils/offsets/applyaltazoffsets.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/utils/offsets/applyoffsets.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/utils/offsets/applyradecoffsets.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/utils/parallel.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/utils/pipeline/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/utils/pipeline/pipeline.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/utils/pipeline/progress.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/utils/pipeline/reduction_base.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/utils/publisher/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/utils/publisher/csv.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/utils/publisher/http.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/utils/publisher/log.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/utils/publisher/multi.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/utils/publisher/publisher.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/utils/shellcommand.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/utils/threads/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/utils/threads/lockwithabort.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/utils/time.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/utils/units.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/utils/versions.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/version.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/vfs/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/vfs/archivefile.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/vfs/bufferedfile.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/vfs/filelists/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/vfs/filelists/filelist.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/vfs/filelists/testing.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/vfs/httpfile.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/vfs/memfile.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/vfs/sftpfile.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/vfs/smbfile.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/vfs/sshfile.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.4.0}/pyobs/vfs/tempfile.py +0 -0
|
@@ -28,9 +28,11 @@ from pyobs.interfaces import (
|
|
|
28
28
|
IImageFormat,
|
|
29
29
|
ImageFormatCapabilities,
|
|
30
30
|
ImageFormatState,
|
|
31
|
+
IMotion,
|
|
31
32
|
IPointingRaDec,
|
|
32
33
|
ITemperatures,
|
|
33
34
|
IWindow,
|
|
35
|
+
MotionState,
|
|
34
36
|
RaDecState,
|
|
35
37
|
SensorReading,
|
|
36
38
|
TemperaturesState,
|
|
@@ -39,7 +41,7 @@ from pyobs.interfaces import (
|
|
|
39
41
|
)
|
|
40
42
|
from pyobs.modules.camera.basecamera import BaseCamera
|
|
41
43
|
from pyobs.utils import exceptions as exc
|
|
42
|
-
from pyobs.utils.enums import ExposureStatus, ImageFormat, ImageType
|
|
44
|
+
from pyobs.utils.enums import ExposureStatus, ImageFormat, ImageType, MotionStatus
|
|
43
45
|
from pyobs.utils.time import Time
|
|
44
46
|
|
|
45
47
|
log = logging.getLogger(__name__)
|
|
@@ -73,6 +75,7 @@ class DummyCamera(BaseCamera, IWindow, IBinning, ICooling, IGain, IImageFormat):
|
|
|
73
75
|
max_mag: float = 20.0,
|
|
74
76
|
seeing: float = 3.0,
|
|
75
77
|
telescope: str | None = None,
|
|
78
|
+
roof: str | None = None,
|
|
76
79
|
**kwargs: Any,
|
|
77
80
|
):
|
|
78
81
|
"""Creates a new dummy camera.
|
|
@@ -86,6 +89,10 @@ class DummyCamera(BaseCamera, IWindow, IBinning, ICooling, IGain, IImageFormat):
|
|
|
86
89
|
max_mag: Maximum magnitude of simulated stars.
|
|
87
90
|
seeing: Seeing in arcsec FWHM.
|
|
88
91
|
telescope: Name of telescope module to read pointing from (for star simulation).
|
|
92
|
+
roof: Name of roof module to read open/closed state from. When given, the sky (flat
|
|
93
|
+
flux and stars) is only simulated while the roof is not parked -- frames come
|
|
94
|
+
back dark otherwise. Fails closed: until the roof's state has been received, the
|
|
95
|
+
roof is assumed closed.
|
|
89
96
|
"""
|
|
90
97
|
BaseCamera.__init__(self, **kwargs)
|
|
91
98
|
self.add_background_task(self._cooling_thread, True)
|
|
@@ -112,6 +119,10 @@ class DummyCamera(BaseCamera, IWindow, IBinning, ICooling, IGain, IImageFormat):
|
|
|
112
119
|
self._telescope_module = telescope
|
|
113
120
|
self._telescope_pos: SkyCoord = SkyCoord(0.0 * u.deg, 0.0 * u.deg, frame="icrs")
|
|
114
121
|
|
|
122
|
+
# roof state (updated via subscribe_state); fail closed until a state arrives
|
|
123
|
+
self._roof_module = roof
|
|
124
|
+
self._roof_open: bool = roof is None
|
|
125
|
+
|
|
115
126
|
# camera state
|
|
116
127
|
self._cooling = CoolingStatus()
|
|
117
128
|
self._exposing = True
|
|
@@ -128,6 +139,10 @@ class DummyCamera(BaseCamera, IWindow, IBinning, ICooling, IGain, IImageFormat):
|
|
|
128
139
|
"""Update cached telescope position from IPointingRaDec state."""
|
|
129
140
|
self._telescope_pos = SkyCoord(ra=state.ra * u.deg, dec=state.dec * u.deg, frame="icrs")
|
|
130
141
|
|
|
142
|
+
def _on_roof_state(self, state: MotionState) -> None:
|
|
143
|
+
"""Update cached roof open/closed state from IMotion state."""
|
|
144
|
+
self._roof_open = state.status != MotionStatus.PARKED
|
|
145
|
+
|
|
131
146
|
async def open(self) -> None:
|
|
132
147
|
"""Opens camera."""
|
|
133
148
|
# publish capabilities before super().open()
|
|
@@ -154,6 +169,10 @@ class DummyCamera(BaseCamera, IWindow, IBinning, ICooling, IGain, IImageFormat):
|
|
|
154
169
|
if self._telescope_module:
|
|
155
170
|
await self.comm.subscribe_state(self._telescope_module, IPointingRaDec, self._on_telescope_state)
|
|
156
171
|
|
|
172
|
+
# subscribe to roof state if given
|
|
173
|
+
if self._roof_module:
|
|
174
|
+
await self.comm.subscribe_state(self._roof_module, IMotion, self._on_roof_state)
|
|
175
|
+
|
|
157
176
|
# publish initial states
|
|
158
177
|
await self.comm.set_state(
|
|
159
178
|
ICooling,
|
|
@@ -206,7 +225,7 @@ class DummyCamera(BaseCamera, IWindow, IBinning, ICooling, IGain, IImageFormat):
|
|
|
206
225
|
|
|
207
226
|
if exp_time > 0:
|
|
208
227
|
data += make_noise_image(shape, distribution="gaussian", mean=exp_time / 1e4, stddev=exp_time / 1e5)
|
|
209
|
-
if open_shutter:
|
|
228
|
+
if open_shutter and self._roof_open:
|
|
210
229
|
sun_alt = self._sun_alt()
|
|
211
230
|
flat_counts = 30000 / np.exp(-1.28 * (4.209 + sun_alt)) * exp_time
|
|
212
231
|
data += make_noise_image(shape, distribution="gaussian", mean=flat_counts, stddev=flat_counts / 10.0)
|
|
@@ -158,7 +158,7 @@ class AutoFocusSeries(Module, CameraSettingsMixin, IAutoFocus):
|
|
|
158
158
|
foc_state = focuser.get_state(IFocuser)
|
|
159
159
|
guess = foc_state.focus if foc_state is not None else 0.0
|
|
160
160
|
log.info("Using current focus of %.2fmm as initial guess.", guess)
|
|
161
|
-
except exc.PyobsError:
|
|
161
|
+
except (exc.PyobsError, ValueError):
|
|
162
162
|
raise exc.FocusError("Could not fetch current focus value.")
|
|
163
163
|
|
|
164
164
|
# define array of focus values to iterate
|
|
@@ -185,7 +185,7 @@ class AutoFocusSeries(Module, CameraSettingsMixin, IAutoFocus):
|
|
|
185
185
|
else:
|
|
186
186
|
await focuser.set_focus(float(foc))
|
|
187
187
|
|
|
188
|
-
except exc.PyobsError:
|
|
188
|
+
except (exc.PyobsError, ValueError):
|
|
189
189
|
raise exc.FocusError("Could not set new focus value.")
|
|
190
190
|
|
|
191
191
|
# do exposure
|
|
@@ -5,7 +5,7 @@ import os
|
|
|
5
5
|
import time
|
|
6
6
|
import warnings
|
|
7
7
|
from dataclasses import dataclass
|
|
8
|
-
from pathlib import PurePosixPath
|
|
8
|
+
from pathlib import Path, PurePosixPath
|
|
9
9
|
from typing import Any
|
|
10
10
|
|
|
11
11
|
from astropy.io import fits
|
|
@@ -15,6 +15,12 @@ from pyobs.utils.fits import format_filename
|
|
|
15
15
|
|
|
16
16
|
log = logging.getLogger(__name__)
|
|
17
17
|
|
|
18
|
+
# Filename suffixes whose content is attempted to be parsed as FITS (for templated destination
|
|
19
|
+
# names and derived classes' process_extra/cleanup_extra hooks). Files whose names don't match
|
|
20
|
+
# are still watched and copied as-is to non-templated destinations, just never handed to astropy
|
|
21
|
+
# -- which is what produced the header warnings on non-FITS files.
|
|
22
|
+
FITS_FILENAME_SUFFIXES = (".fits", ".fitz", ".fits.gz", ".fits.fz")
|
|
23
|
+
|
|
18
24
|
|
|
19
25
|
@dataclass
|
|
20
26
|
class CurrentFile:
|
|
@@ -47,6 +53,7 @@ class ImageWatcher(Module):
|
|
|
47
53
|
poll_interval: int = 5,
|
|
48
54
|
wait_time: int = 10,
|
|
49
55
|
pattern: str = "*",
|
|
56
|
+
flatten: bool = True,
|
|
50
57
|
**kwargs: Any,
|
|
51
58
|
):
|
|
52
59
|
"""Create a new image watcher.
|
|
@@ -59,6 +66,26 @@ class ImageWatcher(Module):
|
|
|
59
66
|
wait_time: Time in seconds between adding a file to the list and processing it. Gives a file that is
|
|
60
67
|
still being written time to finish (relevant for poll mode and the initial scan) and spaces out
|
|
61
68
|
re-queued files after failed destination copies.
|
|
69
|
+
pattern: Only watch/process files matching this fnmatch pattern, checked against the full path (so
|
|
70
|
+
e.g. "*.fits" also matches files found in subdirectories). This is the single point of filtering,
|
|
71
|
+
applied uniformly in ``add_file`` regardless of watch mode -- inotify events, polling, and the
|
|
72
|
+
initial scan in ``open()`` all discover candidates first and let ``add_file`` decide, none of
|
|
73
|
+
them pre-filter by pattern before that.
|
|
74
|
+
flatten: For a non-templated destination (one without ``{placeholder}``s), whether to collapse the
|
|
75
|
+
file's path to just its basename under that destination (the historical behavior, default) or to
|
|
76
|
+
preserve its path relative to ``watchpath`` under the destination instead. Files found via
|
|
77
|
+
recursive watching or polling only keep their subdirectory structure at the destination when this
|
|
78
|
+
is False. Note this relies on the destination VFS root creating missing parent directories on
|
|
79
|
+
write (the default for e.g. ``LocalFile``) -- a destination configured to not do that would
|
|
80
|
+
repeatedly fail and re-queue any file whose relative path needs a parent directory that doesn't
|
|
81
|
+
exist yet.
|
|
82
|
+
|
|
83
|
+
Note:
|
|
84
|
+
If a directory holding a file that's already queued (added but not yet processed by ``_worker``) is
|
|
85
|
+
renamed before that happens, the worker's read of the file at its captured (pre-rename) path fails
|
|
86
|
+
and the file is dropped rather than picked up at its new location -- this is unrelated to, and not
|
|
87
|
+
covered by, the recursive-watching support for directories renamed after they no longer hold queued
|
|
88
|
+
files. Avoid renaming directories that may still hold queued/retrying files.
|
|
62
89
|
"""
|
|
63
90
|
Module.__init__(self, **kwargs)
|
|
64
91
|
|
|
@@ -77,6 +104,7 @@ class ImageWatcher(Module):
|
|
|
77
104
|
self._poll_interval = poll_interval
|
|
78
105
|
self._wait_time = wait_time
|
|
79
106
|
self._pattern = pattern
|
|
107
|
+
self._flatten = flatten
|
|
80
108
|
self.current_file: CurrentFile | None = None
|
|
81
109
|
|
|
82
110
|
# filename patterns
|
|
@@ -85,49 +113,58 @@ class ImageWatcher(Module):
|
|
|
85
113
|
self._destinations = destinations
|
|
86
114
|
|
|
87
115
|
async def _watch_inotify(self) -> None:
|
|
88
|
-
from asyncinotify import
|
|
116
|
+
from asyncinotify import Mask, RecursiveWatcher
|
|
89
117
|
|
|
90
118
|
# get local directory
|
|
91
119
|
local = await self.vfs.local_path(self._watchpath)
|
|
92
120
|
|
|
93
|
-
#
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
121
|
+
# recursively watch the local directory: watches are added for subdirectories as they're
|
|
122
|
+
# created or moved in, and removed as they're moved out (deletion needs no explicit
|
|
123
|
+
# removal -- the kernel invalidates the watch on its own, see the plan doc for details).
|
|
124
|
+
# A directory can also vanish between an event being received and asyncinotify acting on
|
|
125
|
+
# it (its own add_watch/rm_watch racing a concurrent delete), which raises OSError from
|
|
126
|
+
# inside the library rather than being handled there. Restart with a fresh watcher (a
|
|
127
|
+
# full re-walk) on that instead of letting it kill this task outright -- self-heals faster
|
|
128
|
+
# than falling back to the module's outer background-task restart-with-backoff.
|
|
129
|
+
while True:
|
|
130
|
+
try:
|
|
131
|
+
watcher = RecursiveWatcher(Path(local), Mask.CLOSE_WRITE)
|
|
132
|
+
async for event in watcher.watch_recursive():
|
|
133
|
+
# get filename by replacing local with watchpath
|
|
134
|
+
filename = str(event.path).replace(local, self._watchpath)
|
|
102
135
|
|
|
103
|
-
|
|
104
|
-
|
|
136
|
+
# add file
|
|
137
|
+
await self.add_file(filename)
|
|
138
|
+
except OSError as e:
|
|
139
|
+
log.warning("Inotify watch error (directory likely vanished mid-scan), restarting watch: %s", e)
|
|
105
140
|
|
|
106
141
|
async def _watch_poll(self) -> None:
|
|
107
|
-
# init list
|
|
108
|
-
|
|
142
|
+
# init list (recursive, so subdirectories are picked up too; unfiltered by pattern here --
|
|
143
|
+
# add_file is the single point of pattern filtering, same as inotify mode)
|
|
144
|
+
files = set(await self.vfs.find(self._watchpath, "*"))
|
|
109
145
|
|
|
110
146
|
# run forever
|
|
111
|
-
path = PurePosixPath(self._watchpath)
|
|
112
147
|
while True:
|
|
148
|
+
await asyncio.sleep(self._poll_interval)
|
|
149
|
+
|
|
113
150
|
# get new list
|
|
114
|
-
new_files = await self.vfs.
|
|
151
|
+
new_files = set(await self.vfs.find(self._watchpath, "*"))
|
|
115
152
|
|
|
116
153
|
# find all new files and add them
|
|
117
|
-
for f in new_files:
|
|
118
|
-
|
|
119
|
-
print(str(path / f))
|
|
120
|
-
await self.add_file(str(path / f))
|
|
154
|
+
for f in new_files - files:
|
|
155
|
+
await self.add_file(os.path.join(self._watchpath, f))
|
|
121
156
|
|
|
122
157
|
# store new list
|
|
123
|
-
files =
|
|
158
|
+
files = new_files
|
|
124
159
|
|
|
125
160
|
async def open(self) -> None:
|
|
126
161
|
"""Open image watcher."""
|
|
127
162
|
await Module.open(self)
|
|
128
163
|
|
|
129
|
-
# add all files from directory to queue
|
|
130
|
-
|
|
164
|
+
# add all files from directory to queue (recursive, so subdirectories are picked up too;
|
|
165
|
+
# unfiltered by pattern here -- add_file is the single point of pattern filtering, same as
|
|
166
|
+
# inotify mode)
|
|
167
|
+
for filename in await self.vfs.find(self._watchpath, "*"):
|
|
131
168
|
await self.add_file(os.path.join(self._watchpath, filename))
|
|
132
169
|
|
|
133
170
|
async def close(self) -> None:
|
|
@@ -172,13 +209,20 @@ class ImageWatcher(Module):
|
|
|
172
209
|
async with self.vfs.open_file(filename, "rb") as fd:
|
|
173
210
|
data = await fd.read()
|
|
174
211
|
|
|
175
|
-
#
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
except
|
|
181
|
-
|
|
212
|
+
# only attempt to load the file as FITS when its name suggests it is one; any
|
|
213
|
+
# other file (e.g. a raw camera binary like "w123.0") is still copied as-is to
|
|
214
|
+
# non-templated destinations, just without the parse attempt and the astropy
|
|
215
|
+
# header warnings it raises on non-FITS content. A matching name is no
|
|
216
|
+
# guarantee, though -- the data may be corrupt or truncated -- hence the
|
|
217
|
+
# try/except below.
|
|
218
|
+
fits_file = None
|
|
219
|
+
if os.path.basename(filename).lower().endswith(FITS_FILENAME_SUFFIXES):
|
|
220
|
+
try:
|
|
221
|
+
with warnings.catch_warnings():
|
|
222
|
+
warnings.simplefilter("ignore", fits.verify.VerifyWarning)
|
|
223
|
+
fits_file = await asyncio.to_thread(fits.HDUList.fromstring, data)
|
|
224
|
+
except Exception:
|
|
225
|
+
fits_file = None
|
|
182
226
|
|
|
183
227
|
# fill current file
|
|
184
228
|
self.current_file = CurrentFile(filename=filename, data=data, hdu_list=fits_file)
|
|
@@ -193,10 +237,15 @@ class ImageWatcher(Module):
|
|
|
193
237
|
if out_filename is None:
|
|
194
238
|
raise ValueError("Could not create name for file.")
|
|
195
239
|
|
|
196
|
-
|
|
240
|
+
elif self._flatten:
|
|
197
241
|
# no formatting, so just add filename to destination
|
|
198
242
|
out_filename = os.path.join(pattern, os.path.basename(filename))
|
|
199
243
|
|
|
244
|
+
else:
|
|
245
|
+
# preserve the file's path relative to watchpath under the destination
|
|
246
|
+
rel = PurePosixPath(filename).relative_to(self._watchpath)
|
|
247
|
+
out_filename = str(PurePosixPath(pattern) / rel)
|
|
248
|
+
|
|
200
249
|
# store it
|
|
201
250
|
log.info("Storing file as %s...", out_filename)
|
|
202
251
|
self.current_file.out_filename = out_filename
|
|
@@ -223,6 +272,9 @@ class ImageWatcher(Module):
|
|
|
223
272
|
log.info("Removing file from watch directory...")
|
|
224
273
|
if not await self.vfs.remove(filename):
|
|
225
274
|
log.warning("Could not delete %s.", filename)
|
|
275
|
+
else:
|
|
276
|
+
# clean up any now-empty parent directories the file left behind
|
|
277
|
+
await self._cleanup_empty_parents(filename)
|
|
226
278
|
|
|
227
279
|
# cleanup extra
|
|
228
280
|
await self.cleanup_extra(filename)
|
|
@@ -230,6 +282,23 @@ class ImageWatcher(Module):
|
|
|
230
282
|
except Exception:
|
|
231
283
|
log.exception("Something went wrong.")
|
|
232
284
|
|
|
285
|
+
async def _cleanup_empty_parents(self, filename: str) -> None:
|
|
286
|
+
"""Remove now-empty parent directories left behind by a just-deleted file.
|
|
287
|
+
|
|
288
|
+
Walks from filename's immediate parent up towards (but excluding) watchpath, removing
|
|
289
|
+
each directory while it's empty and stopping at the first one that isn't (or otherwise
|
|
290
|
+
can't be removed) -- most commonly a directory that still holds sibling files/dirs, which
|
|
291
|
+
is the normal, expected way this loop ends. Only relevant for recursive watching
|
|
292
|
+
(watchpath itself is never removed); a flat watch directory has no subdirectories for
|
|
293
|
+
this to ever act on.
|
|
294
|
+
"""
|
|
295
|
+
watchpath = PurePosixPath(self._watchpath)
|
|
296
|
+
parent = PurePosixPath(filename).parent
|
|
297
|
+
while parent != watchpath and watchpath in parent.parents:
|
|
298
|
+
if not await self.vfs.rmdir(str(parent)):
|
|
299
|
+
break
|
|
300
|
+
parent = parent.parent
|
|
301
|
+
|
|
233
302
|
async def process_extra(self, filename: str) -> bool:
|
|
234
303
|
"""Can be overwritten by derived classes to do extra processing on files.
|
|
235
304
|
All information are stored in self.current_file and can be checked against the given filename.
|
|
@@ -439,8 +439,8 @@ class Module(Object, IModule, IConfig):
|
|
|
439
439
|
caps = proxy.get_capabilities(IModule)
|
|
440
440
|
module_version = caps.version if caps is not None else ""
|
|
441
441
|
remote_location = caps.location if caps is not None else None
|
|
442
|
-
except exc.PyobsError:
|
|
443
|
-
# however this failed (transport or
|
|
442
|
+
except (exc.PyobsError, ValueError):
|
|
443
|
+
# however this failed (transport, domain, or unresolvable peer), we just skip this module
|
|
444
444
|
return True
|
|
445
445
|
|
|
446
446
|
# log it
|
|
@@ -106,13 +106,21 @@ class Scheduler(Module, IRunnable, IRoboticScheduler):
|
|
|
106
106
|
|
|
107
107
|
# get scheduler
|
|
108
108
|
self._task_archive = self.add_child_object(tasks, TaskArchive, on_tasks_changed=self._update_schedule)
|
|
109
|
-
# PortalObservationArchive
|
|
110
|
-
#
|
|
111
|
-
#
|
|
112
|
-
#
|
|
109
|
+
# PortalObservationArchive's own polling loop (gated on auto_update) is the only channel
|
|
110
|
+
# by which this process learns about observation-state changes written by *other*
|
|
111
|
+
# processes (e.g. Mastermind marking an observation IN_PROGRESS/COMPLETED) -- it must stay
|
|
112
|
+
# on, or get_schedule()/get_current_observation()/get_next_observation() never see
|
|
113
|
+
# anything beyond the empty list they're constructed with. What it must NOT do is announce
|
|
114
|
+
# every update at INFO: when *this* process is the one that just computed and posted the
|
|
115
|
+
# schedule, _schedule_worker already logs it in detail (see _log_scheduled_task below), so
|
|
116
|
+
# the poller picking up its own write ~5s later would double-log it. announce_updates=False
|
|
117
|
+
# suppresses that one-line echo while leaving auto_update's poll (and the INFO log for
|
|
118
|
+
# externally-driven changes like Mastermind's) intact. Other ObservationArchive
|
|
119
|
+
# implementations (e.g. LcoObservationArchive) don't declare either param, so injection
|
|
120
|
+
# must stay conditional, not unconditional.
|
|
113
121
|
extra_schedule_kwargs: dict[str, Any] = {}
|
|
114
|
-
if _class_accepts_param(schedule, "
|
|
115
|
-
extra_schedule_kwargs["
|
|
122
|
+
if _class_accepts_param(schedule, "announce_updates"):
|
|
123
|
+
extra_schedule_kwargs["announce_updates"] = False
|
|
116
124
|
self._schedule = self.add_child_object(schedule, ObservationArchive, **extra_schedule_kwargs)
|
|
117
125
|
extra_scheduler_kwargs: dict[str, Any] = {}
|
|
118
126
|
if _class_accepts_param(scheduler, "observation_archive"):
|
|
@@ -180,23 +188,38 @@ class Scheduler(Module, IRunnable, IRoboticScheduler):
|
|
|
180
188
|
)
|
|
181
189
|
log.info("Downloaded %d schedulable tasks(s).", len(tasks))
|
|
182
190
|
|
|
183
|
-
# download projects
|
|
184
|
-
self.
|
|
185
|
-
|
|
186
|
-
|
|
191
|
+
# download projects -- keep the old list around long enough to diff against below,
|
|
192
|
+
# mirroring self._tasks (only overwritten once _need_update has been decided)
|
|
193
|
+
old_projects = self._projects
|
|
194
|
+
projects = await self._task_archive.get_projects()
|
|
195
|
+
# `updated_at` is excluded: it's a portal-side save timestamp (round-trip-only field, not
|
|
196
|
+
# scheduling-relevant), and a no-op re-save still bumps it -- comparing it in would force
|
|
197
|
+
# a reschedule on every no-op edit, not just real content changes.
|
|
198
|
+
projects_changed = {p.code: p.model_dump(exclude={"updated_at"}) for p in projects} != {
|
|
199
|
+
p.code: p.model_dump(exclude={"updated_at"}) for p in old_projects
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
# compare new and old lists, both by ID (removed/added) and by content (changed, same ID)
|
|
187
203
|
removed, added = self._compare_task_lists(self._tasks, tasks)
|
|
204
|
+
changed = self._changed_task_ids(self._tasks, tasks)
|
|
188
205
|
|
|
189
206
|
# schedule update
|
|
190
207
|
self._need_update = True
|
|
191
208
|
|
|
192
|
-
# no changes?
|
|
193
|
-
if
|
|
209
|
+
# no changes at all?
|
|
210
|
+
if (
|
|
211
|
+
not self._trigger_on_every_update
|
|
212
|
+
and len(removed) == 0
|
|
213
|
+
and len(added) == 0
|
|
214
|
+
and len(changed) == 0
|
|
215
|
+
and not projects_changed
|
|
216
|
+
):
|
|
194
217
|
# no need to re-schedule
|
|
195
218
|
log.info("No change in list of blocks detected.")
|
|
196
219
|
self._need_update = False
|
|
197
220
|
|
|
198
|
-
# has only the current block been removed?
|
|
199
|
-
log.info("Removed: %d, added: %d", len(removed), len(added))
|
|
221
|
+
# has only the current block been removed, with nothing else changed?
|
|
222
|
+
log.info("Removed: %d, added: %d, changed: %d", len(removed), len(added), len(changed))
|
|
200
223
|
if len(removed) == 1:
|
|
201
224
|
log.info(
|
|
202
225
|
"Found 1 removed block with ID %d. Last task ID was %s, current is %s.",
|
|
@@ -204,21 +227,20 @@ class Scheduler(Module, IRunnable, IRoboticScheduler):
|
|
|
204
227
|
str(self._last_task_id),
|
|
205
228
|
str(self._current_task_id),
|
|
206
229
|
)
|
|
207
|
-
if
|
|
230
|
+
if (
|
|
231
|
+
len(removed) == 1
|
|
232
|
+
and len(added) == 0
|
|
233
|
+
and len(changed) == 0
|
|
234
|
+
and not projects_changed
|
|
235
|
+
and removed[0] == self._last_task_id
|
|
236
|
+
):
|
|
208
237
|
# no need to re-schedule
|
|
209
238
|
log.info("Only one removed block detected, which is the one currently running.")
|
|
210
239
|
self._need_update = False
|
|
211
240
|
|
|
212
|
-
# check, if one of the removed blocks was actually in schedule
|
|
213
|
-
if len(removed) > 0 and self._need_update:
|
|
214
|
-
schedule = await self._schedule.get_schedule()
|
|
215
|
-
removed_from_schedule = [s for s in schedule if s.task.id in removed]
|
|
216
|
-
if len(removed_from_schedule) == 0:
|
|
217
|
-
log.info("Found %s tasks, but none of them was scheduled.", len(removed))
|
|
218
|
-
self._need_update = False
|
|
219
|
-
|
|
220
241
|
# store blocks
|
|
221
242
|
self._tasks = tasks
|
|
243
|
+
self._projects = projects
|
|
222
244
|
|
|
223
245
|
# schedule update
|
|
224
246
|
if self._need_update:
|
|
@@ -252,6 +274,33 @@ class Scheduler(Module, IRunnable, IRoboticScheduler):
|
|
|
252
274
|
|
|
253
275
|
return sorted(additional1), sorted(additional2) # type: ignore[type-var]
|
|
254
276
|
|
|
277
|
+
@staticmethod
|
|
278
|
+
def _content_dump(task: Task) -> dict[str, Any]:
|
|
279
|
+
"""``task.model_dump(exclude={"updated_at"})``.
|
|
280
|
+
|
|
281
|
+
``updated_at`` is a portal-side save timestamp, not scheduling-relevant content, and a
|
|
282
|
+
no-op re-save (e.g. an unchanged DRF PATCH) still bumps it -- leaving it in would report
|
|
283
|
+
every no-op edit as a "changed" task.
|
|
284
|
+
"""
|
|
285
|
+
return task.model_dump(exclude={"updated_at"})
|
|
286
|
+
|
|
287
|
+
@classmethod
|
|
288
|
+
def _changed_task_ids(cls, tasks1: list[Task], tasks2: list[Task]) -> set[Any]:
|
|
289
|
+
"""IDs present in both lists whose task content differs between them.
|
|
290
|
+
|
|
291
|
+
Comparison is via ``model_dump()`` (see :meth:`_content_dump`), not pydantic ``==``:
|
|
292
|
+
``Task.__eq__`` also compares runtime-only ``PrivateAttr``s (e.g. ``_cant_run_reason`` set
|
|
293
|
+
by ``can_run()``, or ``_resolved_target`` set while scheduling), which would make a task
|
|
294
|
+
that has merely been scheduled/run look "changed" on the next poll. ``model_dump()``
|
|
295
|
+
excludes private attributes by construction. What's left after also dropping
|
|
296
|
+
``updated_at`` (priority, duration, script, ...) are the fields that actually matter for
|
|
297
|
+
scheduling.
|
|
298
|
+
"""
|
|
299
|
+
by_id1 = {t.id: t for t in tasks1}
|
|
300
|
+
by_id2 = {t.id: t for t in tasks2}
|
|
301
|
+
common = set(by_id1).intersection(by_id2)
|
|
302
|
+
return {i for i in common if cls._content_dump(by_id1[i]) != cls._content_dump(by_id2[i])}
|
|
303
|
+
|
|
255
304
|
async def _schedule_worker(self) -> None:
|
|
256
305
|
await asyncio.sleep(5)
|
|
257
306
|
|
|
@@ -285,7 +334,10 @@ class Scheduler(Module, IRunnable, IRoboticScheduler):
|
|
|
285
334
|
# schedule
|
|
286
335
|
scheduled_tasks = ObservationList()
|
|
287
336
|
first = True
|
|
288
|
-
|
|
337
|
+
instrument_capabilities = self._task_archive.get_instrument_capabilities()
|
|
338
|
+
async for scheduled_task in self._scheduler.schedule(
|
|
339
|
+
self._tasks, self._projects, start, end, instrument_capabilities=instrument_capabilities
|
|
340
|
+
):
|
|
289
341
|
# remember for later
|
|
290
342
|
scheduled_tasks.append(scheduled_task)
|
|
291
343
|
|
|
@@ -122,7 +122,7 @@ class DummyRoof(BaseRoof, IRoof):
|
|
|
122
122
|
await self._change_motion_status(MotionStatus.ABORTING)
|
|
123
123
|
|
|
124
124
|
async with LockWithAbort(self._lock_motion, self._abort_motion):
|
|
125
|
-
await self._change_motion_status(MotionStatus.IDLE)
|
|
125
|
+
await self._change_motion_status(MotionStatus.PARKED if self._is_closed() else MotionStatus.IDLE)
|
|
126
126
|
|
|
127
127
|
|
|
128
128
|
__all__ = ["DummyRoof"]
|
|
@@ -78,9 +78,12 @@ class MissingObserverError(exc.MotionError):
|
|
|
78
78
|
pass
|
|
79
79
|
|
|
80
80
|
|
|
81
|
-
class AltitudeLimitError(exc.
|
|
81
|
+
class AltitudeLimitError(exc.PyobsError):
|
|
82
82
|
"""Destination altitude is below the configured limit -- an expected, deferrable condition
|
|
83
|
-
from a scheduler's point of view, not a failure to alert on.
|
|
83
|
+
from a scheduler's point of view, not a failure to alert on. Deliberately not a MotionError:
|
|
84
|
+
it's not a motion fault (nothing was attempted), so it must not count towards the module's
|
|
85
|
+
repeated-motion-failure escalation (see _register_exception(exc.MotionError, ...) below) --
|
|
86
|
+
picking a target below the horizon is normal, not a sign the telescope is broken."""
|
|
84
87
|
|
|
85
88
|
pass
|
|
86
89
|
|