Image (pyobs.images.processors.image)

AddFitsHeaders

class AddFitsHeaders(headers: dict[str, int | float | str] | list[Keyword], overwrite: bool = True, **kwargs: Any)

Add or update FITS header keywords on an image.

This processor inserts user-defined FITS header cards into a pyobs pyobs.images.Image. It is typically used to attach observatory, instrument, or processing metadata (e.g., OBSERVAT, TELESCOP, FILTER) to images so they can be archived or analyzed with standard FITS-aware tools.

Parameters:
  • headers (dict|list) –

    Header definitions to add. Can be provided as:

    • A mapping of KEY -> VALUE for simple additions.

    • A list of dictionaries for per-key options, each with:

      • key (str): FITS keyword name.

      • value (any): The value to set for the keyword.

      • comment (str, optional): A comment string to attach to the card.

      • overwrite (bool, optional): Override existing value for this key. If not given, the global overwrite setting applies.

  • overwrite (bool) – Whether to overwrite existing keywords when they already exist in the header. Default: True.

Behavior

  • For each specified header card, the processor will add the keyword and value to the image’s FITS header. If the keyword is already present:

    • If overwrite is True (globally or per-card), its value/comment will be replaced.

    • If overwrite is False, the existing card will be left unchanged.

  • The output image’s data array is not modified.

  • FITS keyword names should follow FITS conventions (typically up to 8 ASCII characters, uppercase) to ensure compatibility with FITS tools.

Input/Output

Configuration (YAML)

Simple mapping:

class: pyobs.images.processors.image.addfitsheaders.AddFitsHeaders
headers:
  OBSERVAT: "Example Observatory"
  TELESCOP: "1.2m RC"
  INSTRUME: "CCD Camera"
overwrite: true

Per-key options:

class: pyobs.images.processors.image.addfitsheaders.AddFitsHeaders
headers:
  - key: OBSERVER
    value: "Jane Doe"
    comment: "Observer name"
  - key: FILTER
    value: "R"
    comment: "Photometric filter"
    overwrite: false
overwrite: true

Examples

  • Add observatory and instrument metadata:

    class: pyobs.images.processors.image.addfitsheaders.AddFitsHeaders
    headers:
      OBSERVAT: "Example Observatory"
      INSTRUME: "CCD Camera"
    
  • Preserve existing FILTER value while updating other cards:

    class: pyobs.images.processors.image.addfitsheaders.AddFitsHeaders
    headers:
      - key: FILTER
        value: "R"
        overwrite: false
      - key: TELESCOP
        value: "1.2m RC"
    overwrite: true
    

Notes

  • Be cautious when modifying orientation- or calibration-sensitive keywords (e.g., WCS-related keys); downstream tools may rely on their original values.

  • Values will be written as provided; ensure types are appropriate for FITS (strings, integers, floats, booleans, or FITS-compliant date strings).

Download

class Download(url: str, ssl_check: bool = True, **kwargs: Any)

Download an image from an HTTP(S) URL and return it as a pyobs.images.Image.

This processor uses aiohttp to fetch image bytes from the configured url and converts them into a pyobs.images.Image. The conversion is chosen automatically based on the URL’s filename extension:

  • .fits files are parsed with pyobs.images.Image.from_bytes(), preserving their FITS header.

  • Other common image formats (e.g., PNG, JPEG) are decoded via OpenCV and converted to RGB. For 3-channel color images, the channel axis is moved to the front (C, H, W) and basic header cards are set (DATE-OBS and EXPTIME=0).

The input argument to __call__ is ignored; the processor always downloads and returns a new image.

Parameters:
  • url (str) – The HTTP(S) URL to download. The file type is inferred from the URL’s extension using os.path.splitext(); .fits triggers FITS parsing, all other extensions are treated as encoded images to be decoded via OpenCV.

  • kwargs – Additional keyword arguments forwarded to pyobs.images.processor.ImageProcessor.

Behavior

  • Performs an HTTP GET request to url using aiohttp.ClientSession.

  • If the URL ends with .fits, the image is constructed with pyobs.images.Image.from_bytes(), preserving existing FITS headers.

  • Otherwise, the image is decoded with OpenCV: - Bytes are passed to cv2.imdecode (with cv2.IMREAD_UNCHANGED), then converted from BGR to RGB. - For 3-channel color images, the channel axis is moved to the front (C, H, W). - The processor sets DATE-OBS to the current ISO timestamp and EXPTIME to 0 in the header.

  • The input parameter image to __call__ is ignored (no-op); a new image object is returned.

Input/Output

Configuration (YAML)

class: pyobs.images.processors.image.Download
url: "https://example.org/data/image.fits"

Decoding a JPEG/PNG (requires OpenCV):

class: pyobs.images.processors.image.Download
url: "https://example.org/data/preview.jpg"

Notes

  • File type detection is based on the URL’s extension. If the URL does not reflect the true format (e.g., a FITS file served without a .fits suffix), decoding may fail or produce unintended results.

  • For encoded images, only minimal FITS-like header information is set. If you need full metadata, prefer FITS sources or augment headers downstream.

Flip

class Flip(flip_x: bool = False, flip_y: bool = False, **kwargs: Any)

Flip image pixels horizontally (x-axis) and/or vertically (y-axis).

This processor flips the pixel data of a pyobs pyobs.images.Image along the horizontal (left–right) and/or vertical (top–bottom) axes. It is typically used to correct camera orientation or mirror inversions so that subsequent processing and display match the desired coordinate convention.

Parameters:
  • flip_x (bool) – If True, flip the image left–right (along the x-axis). Default: False.

  • flip_y (bool) – If True, flip the image top–bottom (along the y-axis). Default: False.

Note

  • If both flip_x and flip_y are True, the result is equivalent to a 180° rotation.

  • If both are False, this processor performs no operation (no-op).

  • Pixel coordinates transform as: x -> (width - 1 - x) when flip_x is True and y -> (height - 1 - y) when flip_y is True.

Input/Output

  • Input: pyobs.images.Image

  • Output: pyobs.images.Image with pixel data flipped according to the configured axes. The output image has the same shape and dtype as the input. Header metadata (including WCS) are preserved and not modified by this processor; workflows relying on orientation-sensitive metadata may need to update them downstream.

Configuration (YAML)

Instantiate and configure via YAML, for example:

class: pyobs.images.processors.image.flip.Flip
flip_x: true
flip_y: false

Examples

  • Correct a mirror inversion by flipping horizontally:

    class: pyobs.images.processors.image.flip.Flip
    flip_x: true
    
  • Flip vertically to match an optical path or mount orientation:

    class: pyobs.images.processors.image.flip.Flip
    flip_y: true
    

Grayscale

class Grayscale(r: float = 0.2126, g: float = 0.7152, b: float = 0.0722, **kwargs: Any)

Convert a color image to grayscale using weighted RGB channels.

This processor converts a 3-channel color pyobs.images.Image to grayscale by forming a weighted linear combination of the red, green, and blue channels:

gray = r * R + g * G + b * B

By default, the weights r=0.2126, g=0.7152, and b=0.0722 correspond to the ITU-R BT.709 (Rec. 709) luma coefficients. The conversion is delegated to pyobs.images.Image.to_grayscale().

Parameters:
  • r (float) – Weight for the red channel. Default: 0.2126.

  • g (float) – Weight for the green channel. Default: 0.7152.

  • b (float) – Weight for the blue channel. Default: 0.0722.

  • kwargs – Additional keyword arguments forwarded to pyobs.images.processor.ImageProcessor.

Behavior

  • Calls pyobs.images.Image.to_grayscale(r, g, b)() on the input image and returns the resulting image.

  • The weights need not sum to 1.0; they are used as provided for a linear combination.

  • Header metadata are preserved by the underlying conversion method.

  • Typical input layout for color images is channel-first (C, H, W) with C=3 (RGB).

Input/Output

Configuration (YAML)

Default Rec. 709 weights:

class: pyobs.images.processors.misc.Grayscale
# r: 0.2126
# g: 0.7152
# b: 0.0722

Custom weights:

class: pyobs.images.processors.misc.Grayscale
r: 0.3
g: 0.59
b: 0.11

Notes

  • Ensure the input image is a 3-channel color image; otherwise the underlying conversion may be a no-op or raise an error, depending on implementation.

  • The default weights correspond to BT.709 luma; other choices (e.g., BT.601) may be preferable depending on your imaging pipeline.

HttpServer

class HttpServer(filename: str = 'image.jpg', format: str | None = None, url: str = 'localhost', port: int = 9400, **kwargs: Any)

Serve the latest processed image via a minimal HTTP server.

This processor starts an aiohttp web server on first invocation and serves the most recently processed image at two endpoints:

  • GET /: A simple HTML page embedding the image.

  • GET /<filename>: The raw encoded image bytes.

Images are encoded using pyobs.images.processors.image.saveimage.SaveImage.encode_image() based on the configured filename extension or an explicitly provided format.

Parameters:
  • filename (str) – The filename to serve the image as, which also determines the path (e.g., "image.jpg" served at /image.jpg) and, if format is not given, the encoding derived from its extension. Default: "image.jpg".

  • format (str) – Explicit image format to use for encoding (e.g., "jpeg", "png", "tiff"). If None, the format is inferred from filename. Default: None.

  • url (str) – Host/interface to bind the HTTP server to (e.g., "localhost" or "0.0.0.0"). Default: "localhost".

  • port (int) – TCP port to serve on. Default: 9400.

  • kwargs – Additional keyword arguments forwarded to pyobs.images.processor.ImageProcessor.

Behavior

  • On the first call, starts an aiohttp.web.TCPSite bound to url:port and registers two routes: - GET /<filename> returns the currently stored image bytes with content type image/*. - GET / returns a minimal HTML page embedding the image via <img src="<filename>">.

  • Encodes the input image using pyobs.images.processors.image.saveimage.SaveImage.encode_image(image, filename, format)() and stores it as the “current” image to be served by the endpoints.

  • Subsequent calls update the stored image; clients fetching /<filename> will receive the latest version.

  • If no image has been processed yet, GET /<filename> responds with 404 Not Found.

Input/Output

Configuration (YAML)

Serve a JPEG on localhost:

class: pyobs.images.processors.misc.HttpServer
filename: "image.jpg"
url: "localhost"
port: 9400

Serve a PNG on all interfaces:

class: pyobs.images.processors.misc.HttpServer
filename: "latest.png"
url: "0.0.0.0"
port: 8080

Explicitly set the format (overrides filename extension):

class: pyobs.images.processors.misc.HttpServer
filename: "image.out"
format: "png"

Notes

  • Binding to "localhost" exposes the server only on the local machine. Use "0.0.0.0" to accept external connections, but be mindful of security implications.

  • No authentication or TLS is implemented; do not expose this endpoint on untrusted networks without additional protection.

  • The response content type is image/*; some clients may expect a specific MIME type if the chosen format is known (e.g., image/jpeg or image/png).

  • If the encoding fails (e.g., due to unsupported format), the underlying encoder may raise an exception; those propagate from SaveImage.encode_image().

Normalize

class Normalize(vmin: float | None = None, vmax: float | None = None, **kwargs: Any)

Normalize image pixel values to the 8-bit range [0, 255].

This processor linearly rescales the input image data to uint8 using user-specified or automatically determined bounds:

normalized = ((data - vmin) / (vmax - vmin) * 255).astype(uint8)

If vmin or vmax are not provided, they are computed from the image data via numpy.min and numpy.max across the entire array.

Parameters:
  • vmin (float) – Minimum value to normalize from. If None, uses np.min(image.data). Default: None.

  • vmax (float) – Maximum value to normalize to. If None, uses np.max(image.data). Default: None.

  • kwargs – Additional keyword arguments forwarded to pyobs.images.processor.ImageProcessor.

Behavior

  • Creates a copy of the input image and replaces its data with a normalized numpy.uint8 array.

  • If vmin/vmax are None, they are computed over the entire array (all channels and pixels).

  • The normalization is applied element-wise to the entire array; for multi-channel images (e.g., channel-first (C, H, W)), a single global vmin/vmax is used for all channels (no per-channel normalization).

  • Header metadata are preserved; only the pixel data and dtype change.

Input/Output

Configuration (YAML)

Automatic bounds:

class: pyobs.images.processors.misc.Normalize
# vmin: null
# vmax: null

Explicit bounds (e.g., for 16-bit images):

class: pyobs.images.processors.misc.Normalize
vmin: 0
vmax: 65535

Notes

  • Precision loss: Converting to 8-bit discards dynamic range; use only when appropriate for visualization or downstream tools that require uint8.

  • Out-of-range values: If you specify vmin/vmax that do not bracket the data, values below/above the range will map outside [0, 255]; when cast to uint8, such values wrap modulo 256 rather than clip. If clipping is desired, pre-clip the data or choose bounds based on the data range.

  • NaNs: If the data contain NaNs, np.min/np.max will propagate NaNs to the bounds, producing NaNs in the normalized array. Clean or mask NaNs beforehand if necessary.

  • This processor is asynchronous; call it within an event loop (using await).

Save

class Save(filename: str = '/pyobs/image.fits', broadcast: bool = False, **kwargs: Any)

Save an image to the virtual file system and optionally broadcast a NewImageEvent.

This processor formats a destination filename using a pyobs.utils.formatter.FilenameFormatter (configured via filename), writes the image to the pyobs virtual file system (vfs), and, if enabled, broadcasts a NewImageEvent via the communication interface (comm). The original image is returned unchanged.

Parameters:
  • filename (str) – Filename template for saving the image. The actual path is produced by pyobs.images.Image.format_filename() using a FilenameFormatter. Default: "/pyobs/image.fits".

  • broadcast (bool) – If True, broadcast a NewImageEvent after saving the image. Default: False.

  • kwargs – Additional keyword arguments forwarded to pyobs.images.processor.ImageProcessor.

Behavior and requirements

  • The processor requires a configured virtual file system (self.vfs) to persist images.

  • Broadcasting requires a communication interface (self.comm) and successful registration of NewImageEvent in open().

  • The broadcasted event’s image_type is derived from the FITS header key IMAGETYP. This key must be present and convertible to ImageType; otherwise, an exception may be raised.

Input/Output

Configuration (YAML)

Save and broadcast new images:

class: pyobs.images.processors.misc.Save
filename: "/pyobs/image.fits"
broadcast: true

Save only:

class: pyobs.images.processors.misc.Save
filename: "/data/latest.fits"
broadcast: false

Notes

  • Ensure the image header contains a valid IMAGETYP if broadcasting is enabled.

  • The actual filename may include formatted components depending on your FilenameFormatter and pyobs.images.Image.format_filename() implementation.

  • Errors raised by the virtual file system (e.g., write failures) or communication subsystem (e.g., event dispatch errors) propagate to the caller.

async open() None[source]

Initialize processor.

SaveImage

class SaveImage(filename: str = '/pyobs/image.jpg', format: str | None = None, **kwargs: Any)

Save an image as an encoded byte stream (e.g., JPEG, PNG) via the virtual file system.

This processor formats a destination filename using a pyobs.utils.formatter.FilenameFormatter, encodes the input pyobs.images.Image to the desired image format using Pillow, and writes the resulting bytes to the pyobs virtual file system (vfs). The original image object is returned unchanged.

Parameters:
  • filename (str) – Filename template for saving the encoded image. The actual path is produced by pyobs.images.Image.format_filename() using a FilenameFormatter. The file extension is used to infer the image format if format is not provided. Default: "/pyobs/image.jpg".

  • format (str) – Explicit image format to use for encoding (e.g., "JPEG", "PNG", "TIFF"). If None, the format is inferred from the filename extension. Default: None.

  • kwargs – Additional keyword arguments forwarded to pyobs.images.processor.ImageProcessor.

Behavior

  • Computes the target path via image.format_filename(self._formatter).

  • Encodes the image to bytes using SaveImage.encode_image(), which leverages Pillow:

    • If format is None, the format is inferred from the filename extension (uppercased) with a special case mapping JPG -> JPEG.

    • Conversion from pyobs.images.Image to a Pillow image is performed by pyobs.utils.image.PillowHelper.

  • Writes the encoded bytes to the virtual file system using self.vfs.write_bytes(filename, data).

  • Returns the original image without modification.

Input/Output

Configuration (YAML)

Save as JPEG inferred from extension:

class: pyobs.images.processors.misc.SaveImage
filename: "/pyobs/latest.jpg"

Explicitly set format (overrides extension):

class: pyobs.images.processors.misc.SaveImage
filename: "/data/snapshot.out"
format: "PNG"

Notes

  • Format inference is based on the filename extension. If the extension is .jpg, it is mapped to Pillow’s "JPEG" format.

  • Ensure the image is convertible to a Pillow image; the helper is responsible for handling channel order and dtype conversions.

  • Errors from the virtual file system (e.g., write failures) or Pillow (unsupported format) propagate to the caller.

static encode_image(image: Image, filename: str, image_format: str | None = None) bytes[source]

Smooth

class Smooth(sigma: float, order: int = 0, mode: Literal['reflect', 'constant', 'nearest', 'mirror', 'wrap', 'grid-constant', 'grid-mirror', 'grid-wrap'] = 'reflect', cval: float = 0.0, truncate: float = 4.0, **kwargs: Any)

Gaussian smoothing of image data using SciPy’s ndimage.gaussian_filter.

This processor applies a Gaussian filter to the pixel data of a pyobs.images.Image to reduce noise or gently blur features. The operation is performed with scipy.ndimage.gaussian_filter() and affects all axes of the image array.

Parameters:
  • sigma (float) – Standard deviation of the Gaussian kernel (in pixels). Larger values produce stronger smoothing. Required.

  • order (int) – The order of the filter along each axis (0 for smoothing; higher values compute derivatives of the Gaussian). Default: 0.

  • mode ({"reflect","constant","nearest","mirror","wrap","grid-constant","grid-mirror","grid-wrap"}) – How the input array is extended at borders. See SciPy’s documentation for details. Default: "reflect".

  • cval (float) – Constant value used to fill beyond edges when mode="constant". Default: 0.0.

  • truncate (float) – Truncate the filter at this many standard deviations. Values beyond truncate * sigma are ignored. Default: 4.0.

  • kwargs – Additional keyword arguments forwarded to pyobs.images.processor.ImageProcessor.

Behavior

  • Creates a copy of the input image. If no data are present (safe_data is None), logs a warning and returns the original image unchanged.

  • Applies scipy.ndimage.gaussian_filter() to output_image.data with the configured parameters.

  • For 2D images (H, W), smoothing is applied over both spatial axes.

  • For 3D arrays (e.g., channel-first C, H, W), smoothing is applied over all axes, including the channel axis. This mixes channels; if this is not desired, consider processing each channel separately upstream.

  • Header metadata are preserved; only the pixel data are modified.

Input/Output

Configuration (YAML)

Basic smoothing:

class: pyobs.images.processors.misc.Smooth
sigma: 1.5

Edge handling with constant padding:

class: pyobs.images.processors.misc.Smooth
sigma: 2.0
mode: "constant"
cval: 0.0

Derivative of Gaussian (edge detection-like):

class: pyobs.images.processors.misc.Smooth
sigma: 1.0
order: 1

Notes

  • Dtype considerations: SciPy performs filtering in floating point and may cast the result back to the input dtype. If the input is integer-typed, fractional values will be rounded; for best results, use floating-point image data.

  • The order parameter controls derivatives of the Gaussian; order=0 performs standard smoothing, while order>0 computes Gaussian derivatives and may enhance edges.

  • For multi-channel images where you do not want cross-channel blurring, split channels and smooth each channel independently before recombining.

See also

SciPy

https

//docs.scipy.org/doc/scipy/reference/generated/scipy.ndimage.gaussian_filter.html#scipy-ndimage-gaussian-filter

SoftBin

class SoftBin(binning: int = 2, **kwargs: Any)

Bin a 2D image by averaging non-overlapping blocks, updating relevant FITS headers.

This processor performs software binning on a 2D pyobs.images.Image by partitioning the array into binning × binning blocks and replacing each block with its arithmetic mean. The result is a downsampled image of shape (H // binning, W // binning). After binning, common FITS/WCS header keywords are updated to reflect the new pixel grid.

Parameters:
  • binning (int) – The binning factor (block size in pixels along each axis). Default: 2.

  • kwargs – Additional keyword arguments forwarded to pyobs.images.processor.ImageProcessor.

Behavior

  • Creates a copy of the input image. If no data are present (safe_data is None), logs a warning and returns the original image unchanged.

  • Reshapes the 2D array into blocks of size (binning, binning) and computes the mean over both block dimensions: image.reshape(H//b, b, W//b, b).mean(-1).mean(1).

  • Updates FITS header keywords:

    • NAXIS1, NAXIS2 are set to the new image dimensions.

    • CRPIX1, CRPIX2 are divided by binning (reference pixel scales with binning).

    • DET-BIN1, DET-BIN2, XBINNING, YBINNING, CDELT1, CDELT2 are multiplied by binning (detector binning and pixel scale increase).

  • Header values not listed above (e.g., CRVAL*, CTYPE*) are left unchanged.

Requirements and limitations

  • The input must be a 2D array (H, W). Multi-channel arrays are not handled; bin channels separately if needed.

  • Both dimensions must be divisible by binning. Otherwise, the internal reshape will raise a ValueError.

  • The output dtype will typically be floating-point because block averages are computed (e.g., float64); if an integer dtype is required, cast explicitly after processing.

Flux and photometry considerations

  • This processor uses the arithmetic mean per block. If pixel values represent counts (e.g., electrons per pixel) and you wish to conserve total flux, consider summing blocks instead of averaging. Averaging changes the per-pixel scaling and reduces summed flux.

Input/Output

Configuration (YAML)

Bin by a factor of 2:

class: pyobs.images.processors.misc.SoftBin
binning: 2

Bin by a factor of 4:

class: pyobs.images.processors.misc.SoftBin
binning: 4

Notes

  • If any of the updated header keys are absent, they are skipped; only present keys are modified.

  • The processor logs a warning and returns the original image if no data are available.