Cameras (pyobs.modules.camera)

Camera modules. TODO: write doc

BaseCamera

class BaseCamera(fits_headers: dict[str, Any] | None = None, centre: tuple[float, float] | None = None, rotation: float = 0.0, flip: bool | None = None, flip_x: bool = False, flip_y: bool = False, filenames: str = '/cache/pyobs-{DAY-OBS|date:}-{FRAMENUM|string:04d}-{IMAGETYP|type}00.fits.gz', fits_namespaces: list[str] | None = None, meridian_flip_on: str | None = None, fits_header_timeout: float = 15.0, max_stack_bytes: int = 2147483648, stack_frame_overhead: float = 10.0, stack_timeout_margin: float = 60.0, **kwargs: Any)

Bases: Module, ImageFitsHeaderMixin, ICamera, IExposure, IExposureTime, IImageType, IResettable, DataSequenceMixin, DataPipelineMixin, IDataStack

Base class for all camera modules.

Creates a new BaseCamera.

Parameters:
  • fits_headers – Additional FITS headers.

  • centre – (x, y) tuple of camera centre.

  • rotation – Rotation east of north.

  • flip – Whether or not to flip the image along its first axis (deprecated, use flip_x).

  • flip_x – Whether or not to flip the image along its first axis.

  • flip_y – Whether or not to flip the image along its second axis.

  • filenames – Template for file naming.

  • fits_namespaces – List of namespaces for FITS headers that this camera should request

  • fits_header_timeout – Maximum seconds to wait for a peer’s FITS headers before skipping them.

  • max_stack_bytes – Memory cap for a grab_stack() cube (count * frame size), in bytes.

  • stack_frame_overhead – Estimated per-frame readout/transfer time in seconds, added to the exposure time when computing grab_stack()’s timeout.

  • stack_timeout_margin – Extra seconds added to grab_stack()’s timeout, covering header requests, cube assembly and the data pipeline.

async abort(**kwargs: Any) → None[source]

Aborts the current exposure and sequence. Derived class must implement IAbortable for this!

Returns:

Success or not.

async add_custom_fits_headers(image: Image) → None[source]

Add FITS headers in derived classes.

Parameters:

image – Image to write FITS headers to.

async apply_meridian_flip(image: Image) → None[source]

If the telescope has a meridian flip (MERIDIAN keyword in header), flip the image on both axes.

Parameters:

image – Image to be flipped.

async full_reset(**kwargs: Any) → None[source]

Reset the camera completely to its configured defaults, hardware included.

Raises:

DeviceBusyError – If the camera is currently exposing, or running a sequence or stack.

async grab_data(broadcast: bool = True, **kwargs: Any) → str[source]

Grabs an image ans returns reference.

Parameters:

broadcast – Broadcast existence of image.

Returns:

Name of image that was taken.

Raises:
  • DeviceBusyError – If the camera is already busy (exposing or running a sequence).

  • GrabImageError – If there was a problem grabbing the image.

async grab_stack(count: int, broadcast: bool = True, **kwargs: Any) → str[source]

Grab count consecutive frames into one product and return its name.

Without a pipeline selected (IDataPipeline), the product is a 3D cube of all frames. With one, the pipeline runs on the cube (e.g. to combine it) and only its result is stored.

Parameters:
  • count – Number of frames.

  • broadcast – Broadcast existence of the product.

Returns:

Name of the stored product.

Raises:
  • InvalidArgumentError – If count < 1, or the stack would exceed the module’s memory cap.

  • DeviceBusyError – If the camera is exposing, or running a sequence or another stack.

  • AbortedError – If the stack was aborted.

  • GrabImageError – If a frame could not be grabbed, frames don’t match, or the pipeline failed.

async open() → None[source]

Open module.

async reset(**kwargs: Any) → None[source]

Reset exposure time, image type, binning and window to their defaults.

Raises:

DeviceBusyError – If the camera is currently exposing, or running a sequence or stack.

static set_biassec_trimsec(hdr: Header, left: int, top: int, width: int, height: int) → None[source]

Calculates and sets the BIASSEC and TRIMSEC areas.

Parameters:
  • hdr – FITS header (in/out)

  • left – left edge of data area

  • top – top edge of data area

  • width – width of data area

  • height – height of data area

async set_exposure_time(exposure_time: float, **kwargs: Any) → None[source]

Set the exposure time in seconds.

Parameters:

exposure_time – Exposure time in seconds.

Raises:

ValueError – If exposure time could not be set.

async set_image_type(image_type: ImageType, **kwargs: Any) → None[source]

Set the image type.

Parameters:

image_type – New image type.

BaseSpectrograph

class BaseSpectrograph(fits_headers: dict[str, Any] | None = None, filenames: str = '/cache/pyobs-{DAY-OBS|date:}-{FRAMENUM|string:04d}.fits.gz', fits_namespaces: list[str] | None = None, **kwargs: Any)

Bases: Module, SpectrumFitsHeaderMixin, ISpectrograph, IExposure, IResettable, DataSequenceMixin

Base class for all spectrograph modules.

Creates a new BaseCamera.

Parameters:
  • fits_headers – Additional FITS headers.

  • flip – Whether or not to flip the image along its first axis.

  • filenames – Template for file naming.

  • fits_namespaces – List of namespaces for FITS headers that this camera should request

async abort(**kwargs: Any) → None[source]

Aborts the current exposure and sequence. Derived class must implement the actual hardware abort for this!

Raises:

ValueError – If exposure could not be aborted.

async full_reset(**kwargs: Any) → None[source]

Reset the spectrograph completely to its configured defaults, hardware included.

Raises:

DeviceBusyError – If the spectrograph is currently exposing or running a sequence.

async grab_data(broadcast: bool = True, **kwargs: Any) → str[source]

Grabs a spectrum and returns reference.

Parameters:

broadcast – Broadcast existence of image.

Returns:

Name of image that was taken.

Raises:
async open() → None[source]

Open module.

async reset(**kwargs: Any) → None[source]

Reset the spectrograph. The base class has no settings of its own, this is a hook for drivers.

Raises:

DeviceBusyError – If the spectrograph is currently exposing or running a sequence.

async store_spectrum(hdulist: HDUList, filename: str, broadcast: bool) → None[source]

Store spectrum at given destination. Can be overwritten by derived classes to custom store a file. In those cases, this version should be called!

Parameters:
  • hdulist – HDU list with spectrum.

  • filename – Name to store file with.

  • broadcast – Whether to broadcast new spectrum.

BaseVideo

class BaseVideo(http_port: int = 37077, interval: float = 0.5, video_path: str | None = '/webcam/video.mjpg', raw_path: str | None = '/webcam/video.raw', filenames: str = '/webcam/pyobs-{DAY-OBS|date:}-{FRAMENUM|string:04d}.fits', fits_namespaces: list[str] | None = None, fits_headers: dict[str, Any] | None = None, centre: tuple[float, float] | None = None, rotation: float = 0.0, cache_size: int = 5, flip: bool = False, sleep_time: int = 60, fits_header_timeout: float = 15.0, token: str | None = None, max_stack_bytes: int = 2147483648, stack_frame_overhead: float = 2.0, stack_timeout_margin: float = 60.0, buffer_frames: int = 4, readout_time: float = 0.0, stretch: str = 'linear', cuts: str | None = None, cut_lo: float | None = None, cut_hi: float | None = None, jpeg_quality: int = 80, cors_origins: list[str] | None = None, **kwargs: Any)

Bases: Module, ImageFitsHeaderMixin, IVideo, IImageType, IResettable, DataPipelineMixin, IDataStack

Base class for all webcam modules.

Drivers implement frames(), an async iterator yielding Frame objects; BaseVideo runs it while the camera is active and puts every frame into a small ring buffer, from which grab_data(), grab_stack(), the MJPEG live view and the raw stream read. Drivers that still push frames themselves via _set_image() keep working, but that path is deprecated.

The built-in HTTP server serves the MJPEG live view, the raw frame stream and cached FITS images. Set the token parameter to protect all of them: machine clients send Authorization: Bearer <token> (as pyobs.vfs.HttpFile does), browsers log in once at /login and get an HMAC-signed session cookie that rides the same-origin <img> requests. Without a token (the default), no auth is enforced.

Creates a new BaseWebcam.

On the receiving end, a VFS root with a HTTPFile must exist with the same name as in image_path and video_path, i.e. “webcam” in the default settings.

Parameters:
  • http_port – HTTP port for webserver.

  • interval – Min interval between two frames sent to one MJPEG live-view connection.

  • video_path – VFS path to video. None disables the MJPEG live view.

  • raw_path – VFS path to the raw frame stream. None disables it.

  • filename – Filename pattern for FITS images.

  • fits_namespaces – List of namespaces for FITS headers that this camera should request.

  • fits_headers – Additional FITS headers.

  • centre – (x, y) tuple of camera centre.

  • rotation – Rotation east of north.

  • cache_size – Size of cache for previous images.

  • flip – Whether to flip around Y axis.

  • sleep_time – Time in s with inactivity after which the camera should go to sleep.

  • fits_header_timeout – Maximum seconds to wait for a peer’s FITS headers before skipping them.

  • token – Shared secret required in the “Authorization: Bearer <token>” header (or a login-page cookie) for stream and data access. If None (default), no auth is enforced.

  • max_stack_bytes – Memory cap for a grab_stack() cube (count * frame size), in bytes. A stacked product also sits in the in-memory DataCache (cache_size) once stored, so peak memory use with several cached stacks can be well above this cap.

  • stack_frame_overhead – Estimated per-frame overhead in seconds, added to the frame interval when computing grab_stack()’s timeout.

  • stack_timeout_margin – Extra seconds added to grab_stack()’s timeout, covering header requests, cube assembly and the data pipeline.

  • buffer_frames – Number of recent frames kept in memory for grab_data(), grab_stack() and the streams. grab_stack() fails if it falls behind by more than this.

  • readout_time – Readout time in seconds, used to estimate a frame’s exposure start when the driver doesn’t provide one (start = arrival - exposure time - readout time).

  • stretch – Default stretch function for the MJPEG live view (linear, sqrt, asinh, log).

  • cuts – Default cuts for the MJPEG live view (full, minmax, percentile, manual). None means “full” for 8-bit data and “minmax” otherwise.

  • cut_lo – Default low cut (percentile or value, depending on cuts).

  • cut_hi – Default high cut (percentile or value, depending on cuts).

  • jpeg_quality – Default JPEG quality for the MJPEG live view.

  • cors_origins – Origins allowed to read /video.raw from a browser on another origin, or [“*”] for any. None (default) sends no CORS headers. Browsers authenticate with the Bearer header only (no credentials mode), so “*” is valid, but without a token it lets any web page the user visits read the stream.

async abort(**kwargs: Any) → None[source]

Abort a running stack.

Pending single grab_data() requests are not affected – BaseVideo already serves those concurrently with a running stack.

async activate_camera() → None[source]

Activate camera.

property camera_active: bool

Whether camera is currently active.

async close() → None[source]

Close server

static create_jpeg(data: ndarray[tuple[Any, ...], dtype[Any]], params: StretchParams | None = None, quality: int = 80) → bytes[source]

Create a JPEG image from a numpy array and return as bytes.

Parameters:
  • data – Numpy array to convert to JPEG.

  • params – How to stretch the data to 8 bit, defaults if None.

  • quality – JPEG quality.

Returns:

Bytes containing JPEG image.

async deactivate_camera() → None[source]

Deactivate camera.

frames() → AsyncIterator[Frame][source]

Yield frames from the camera, to be implemented by drivers as an async generator.

BaseVideo starts iterating when the camera gets activated and closes the iterator (aclose(), i.e. GeneratorExit/CancelledError at the current await) when it goes to sleep, so drivers start acquisition at the top and stop it in a finally block. If the iterator raises, it is restarted with a back-off.

Blocking SDK calls must not run on the event loop; use a worker thread.

Drivers that don’t implement this push frames via the deprecated _set_image().

async full_reset(**kwargs: Any) → None[source]

Reset the camera completely to its configured defaults, hardware included.

Raises:

DeviceBusyError – If a stack is currently running.

property generation: int

Current settings generation.

async grab_data(broadcast: bool = True, **kwargs: Any) → str[source]

Grabs an image and returns reference.

Returns the first frame that started exposing after this call, with the camera settings that were current at the time of the call. FITS headers from other modules are requested right away, i.e. before that exposure starts. Nothing of this runs in the frame loop, so the streams keep flowing while the image is built.

Parameters:

broadcast – Broadcast existence of image.

Returns:

Name of image that was taken.

Raises:

GrabImageError – If there was a problem grabbing the image.

async grab_stack(count: int, broadcast: bool = True, **kwargs: Any) → str[source]

Grab count consecutive frames into one product and return its name.

Without a pipeline selected (IDataPipeline), the product is a 3D cube of all frames. With one, the pipeline runs on the cube (e.g. to combine it) and only its result is stored. Single grab_data() calls during a stack are still served concurrently.

The first frame is chosen like in grab_data(); all following ones must be consecutive and taken with the same settings.

Parameters:
  • count – Number of frames.

  • broadcast – Broadcast existence of the product.

Returns:

Name of the stored product.

Raises:
  • InvalidArgumentError – If count < 1, or the stack would exceed the module’s memory cap.

  • DeviceBusyError – If another stack is already running.

  • AbortedError – If the stack was aborted.

  • GrabImageError – If a frame could not be grabbed, frames were dropped or don’t match, or the pipeline failed.

async image_handler(request: Request) → Response[source]

Handles access to /* and returns a specified image.

Parameters:

request – Request to respond to.

Returns:

Response containing image.

async image_jpeg() → tuple[int | None, bytes | None][source]

Return the newest frame as JPEG with the module’s default stretch.

async login_handler(request: Request) → Response[source]

Handles GET access to /login and returns the login form.

Served unauthenticated: this is the bootstrap a browser needs, since it cannot attach an “Authorization” header to an <img> request. Only registered when a token is configured.

Parameters:

request – Request to respond to.

Returns:

Response containing the login form.

async login_post_handler(request: Request) → Response[source]

Handles POST access to /login, verifying the password and issuing the session cookie.

Parameters:

request – Request to respond to.

Returns:

303 redirect to / on success, 401 on wrong password.

async logout_handler(request: Request) → Response[source]

Handles GET access to /logout and clears the session cookie.

Parameters:

request – Request to respond to.

Returns:

303 redirect to /login.

async open() → None[source]

Open module.

property opened: bool

Whether the server is started.

async ping_handler(request: Request) → Response[source]

Handles GET access to /ping for testing connectivity.

Parameters:

request – Request to respond to.

Returns:

Response with a JSON status.

async raw_handler(request: Request) → StreamResponse[source]

Handles access to /video.raw and returns raw frames.

Optional query parameters: x, y, w, h crop the frame (unbinned pixels, all four or none), bin bins it in software (block mean, after cropping), max_rate caps the frames per second sent to this connection (newest frame wins).

Parameters:

request – Request to respond to.

Returns:

Response containing raw frame stream.

async reset(**kwargs: Any) → None[source]

Reset the image type to its default.

Raises:

DeviceBusyError – If a stack is currently running.

async set_image_type(image_type: ImageType, **kwargs: Any) → None[source]

Set the image type.

Parameters:

image_type – New image type.

async video_handler(request: Request) → StreamResponse[source]

Handles access to /video.mjpg and returns the video.

Optional query parameters set the stretch for this connection (defaults from the module config): stretch, cuts, lo, hi, scale (downsampling factor) and quality (JPEG quality). Connections with the same settings share the encoding work.

Parameters:

request – Request to respond to.

Returns:

Response containing video stream.

async web_handler(request: Request) → Response[source]

Handles access to / and returns HTML page.

Parameters:

request – Request to respond to.

Returns:

Response containing web page.

DummyCamera

class DummyCamera(readout_time: float = 2, image_size: tuple[int, int] | None = None, pixel_size: float = 0.015, focal_length: float = 5000.0, images: str | None = None, max_mag: float = 20.0, seeing: float = 3.0, telescope: str | None = None, roof: str | None = None, **kwargs: Any)

Bases: BaseCamera, IWindow, IBinning, ICooling, IGain, IImageFormat

A dummy camera for testing.

Creates a new dummy camera.

Parameters:
  • readout_time – Readout time in seconds.

  • image_size – Size of simulated image in pixels (width, height).

  • pixel_size – Square pixel size in mm.

  • focal_length – Focal length in mm (for plate scale calculation).

  • images – Filename pattern for pre-recorded images to use instead of simulation.

  • max_mag – Maximum magnitude of simulated stars.

  • seeing – Seeing in arcsec FWHM.

  • telescope – Name of telescope module to read pointing from (for star simulation).

  • roof – Name of roof module to read open/closed state from. When given, the sky (flat flux and stars) is only simulated while the roof is not parked – frames come back dark otherwise. Fails closed: until the roof’s state has been received, the roof is assumed closed.

async full_reset(**kwargs: Any) → None[source]

Reset the camera completely to its configured defaults, hardware included.

Raises:

DeviceBusyError – If the camera is currently exposing, or running a sequence or stack.

async open() → None[source]

Opens camera.

async reset(**kwargs: Any) → None[source]

Reset exposure time, image type, binning and window to their defaults.

Raises:

DeviceBusyError – If the camera is currently exposing, or running a sequence or stack.

async set_binning(x: int, y: int, **kwargs: Any) → None[source]

Set the camera binning.

Parameters:
  • x – X binning.

  • y – Y binning.

Raises:

ValueError – If binning could not be set.

async set_cooling(enabled: bool, setpoint: float, **kwargs: Any) → None[source]

Enables/disables cooling and sets setpoint.

Parameters:
  • enabled – Enable or disable cooling.

  • setpoint – Setpoint in celsius for the cooling.

Raises:

ValueError – If cooling could not be set.

async set_gain(gain: float, **kwargs: Any) → None[source]

Set the camera gain.

Parameters:

gain – New camera gain.

Raises:

ValueError – If gain could not be set.

async set_image_format(fmt: ImageFormat, **kwargs: Any) → None[source]

Set the camera image format.

Parameters:

fmt – New image format.

Raises:

ValueError – If format could not be set.

async set_offset(offset: float, **kwargs: Any) → None[source]

Set the camera offset.

Parameters:

offset – New camera offset.

Raises:

ValueError – If offset could not be set.

async set_window(left: int, top: int, width: int, height: int, **kwargs: Any) → None[source]

Set the camera window.

Parameters:
  • left – X offset of window.

  • top – Y offset of window.

  • width – Width of window.

  • height – Height of window.

Raises:

ValueError – If window could not be set.

DummySpectrograph

class DummySpectrograph(**kwargs: Any)

Bases: BaseSpectrograph

A dummy spectrograph for testing.

Creates a new dummy cammera.

Parameters:
  • readout_time – Readout time in seconds.

  • sim – Dictionary with config for image simulator.

PipelineCamera

class PipelineCamera(**kwargs: Any)

Bases: Module, ICamera, ImageFitsHeaderMixin, PipelineMixin

A virtual camera based on an image pipeline.

Creates a new pipeline cammera.

async grab_data(broadcast: bool = True, **kwargs: Any) → str[source]

Grabs an image and returns reference.

Parameters:

broadcast – Broadcast existence of image.

Returns:

Name of image that was taken.

Raises:

GrabImageError – If there was a problem grabbing the image.

Selecting FITS header providers

Cameras using FitsHeaderMixin can select remote header providers at startup:

class: pyobs.modules.camera.DummyCamera
fits_header_modules: [telescope1, mastermind1, filterwheel1, focuser1]

Use the communication names of your actual modules. Only modules implementing IFitsHeaderBefore or IFitsHeaderAfter contribute in the respective phase. Include the mastermind for observation/task cards and separate filter/focuser providers for their cards. The default, null (or omitting the option), requests all discovered providers. [] requests none; local camera headers still apply. The list selects membership, not merge priority. Unknown/offline names contribute nothing, and selected slow providers retain the existing header timeout.

Unselected providers receive no FITS header RPCs. Capability checks still inspect interface caches and may wait for background discovery; the header collection timeout does not bound that setup step.

Provider selection does not resolve overlapping cards from intentionally selected modules. Telescope namespace mappings can additionally define keyword ownership; see Telescopes (pyobs.modules.telescope).