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->VALUEfor 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 globaloverwritesetting 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
overwriteisTrue(globally or per-card), its value/comment will be replaced.If
overwriteisFalse, 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
Input:
pyobs.images.ImageOutput:
pyobs.images.Imagewith updated FITS headers.
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
aiohttpto fetch image bytes from the configuredurland converts them into apyobs.images.Image. The conversion is chosen automatically based on the URL’s filename extension:.fitsfiles are parsed withpyobs.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-OBSandEXPTIME=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();.fitstriggers 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
urlusingaiohttp.ClientSession.If the URL ends with
.fits, the image is constructed withpyobs.images.Image.from_bytes(), preserving existing FITS headers.Otherwise, the image is decoded with OpenCV: - Bytes are passed to
cv2.imdecode(withcv2.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 setsDATE-OBSto the current ISO timestamp andEXPTIMEto0in the header.The input parameter
imageto__call__is ignored (no-op); a new image object is returned.
Input/Output
Input:
pyobs.images.Image(ignored).Output:
pyobs.images.Imageconstructed from the downloaded bytes.
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
.fitssuffix), 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.Imagealong 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_xandflip_yareTrue, 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)whenflip_xisTrueandy -> (height - 1 - y)whenflip_yisTrue.
Input/Output
Input:
pyobs.images.ImageOutput:
pyobs.images.Imagewith 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.Imageto grayscale by forming a weighted linear combination of the red, green, and blue channels:gray = r * R + g * G + b * BBy default, the weights
r=0.2126,g=0.7152, andb=0.0722correspond to the ITU-R BT.709 (Rec. 709) luma coefficients. The conversion is delegated topyobs.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)withC=3(RGB).
Input/Output
Input:
pyobs.images.Image(color image with 3 channels).Output:
pyobs.images.Image(single-channel grayscale image, shape dependent on the implementation ofto_grayscale()).
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
aiohttpweb 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 configuredfilenameextension or an explicitly providedformat.- Parameters:
filename (str) – The filename to serve the image as, which also determines the path (e.g.,
"image.jpg"served at/image.jpg) and, ifformatis 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"). IfNone, the format is inferred fromfilename. 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.TCPSitebound tourl:portand registers two routes: -GET /<filename>returns the currently stored image bytes with content typeimage/*. -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
Input:
pyobs.images.ImageOutput:
pyobs.images.Image(unchanged), while the encoded bytes are exposed via HTTP.
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/jpegorimage/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
uint8using user-specified or automatically determined bounds:normalized = ((data - vmin) / (vmax - vmin) * 255).astype(uint8)If
vminorvmaxare not provided, they are computed from the image data vianumpy.minandnumpy.maxacross the entire array.- Parameters:
vmin (float) – Minimum value to normalize from. If
None, usesnp.min(image.data). Default:None.vmax (float) – Maximum value to normalize to. If
None, usesnp.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
datawith a normalizednumpy.uint8array.If
vmin/vmaxareNone, 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 globalvmin/vmaxis used for all channels (no per-channel normalization).Header metadata are preserved; only the pixel data and dtype change.
Input/Output
Input:
pyobs.images.ImageOutput:
pyobs.images.Image(copied) with data normalized touint8.
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/vmaxthat do not bracket the data, values below/above the range will map outside [0, 255]; when cast touint8, 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.maxwill 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 viafilename), writes the image to the pyobs virtual file system (vfs), and, if enabled, broadcasts aNewImageEventvia 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 aFilenameFormatter. Default:"/pyobs/image.fits".broadcast (bool) – If
True, broadcast aNewImageEventafter 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 ofNewImageEventinopen().The broadcasted event’s
image_typeis derived from the FITS header keyIMAGETYP. This key must be present and convertible toImageType; otherwise, an exception may be raised.
Input/Output
Input:
pyobs.images.ImageOutput:
pyobs.images.Image(unchanged), with side effects:Image is saved to the virtual file system.
Optional event broadcast after saving.
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
IMAGETYPif broadcasting is enabled.The actual filename may include formatted components depending on your
FilenameFormatterandpyobs.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.
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 inputpyobs.images.Imageto 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 aFilenameFormatter. The file extension is used to infer the image format ifformatis not provided. Default:"/pyobs/image.jpg".format (str) – Explicit image format to use for encoding (e.g.,
"JPEG","PNG","TIFF"). IfNone, 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
formatisNone, the format is inferred from the filename extension (uppercased) with a special case mappingJPG -> JPEG.Conversion from
pyobs.images.Imageto a Pillow image is performed bypyobs.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
Input:
pyobs.images.ImageOutput:
pyobs.images.Image(unchanged), with side effect of writing encoded bytes to the virtual file system at the formatted path.
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.
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.Imageto reduce noise or gently blur features. The operation is performed withscipy.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 (
0for 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 * sigmaare 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()tooutput_image.datawith 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
Input:
pyobs.images.ImageOutput:
pyobs.images.Image(copied) with Gaussian-smoothed data.
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
orderparameter controls derivatives of the Gaussian;order=0performs standard smoothing, whileorder>0computes 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
SciPyhttps//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.Imageby partitioning the array intobinning × binningblocks 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,NAXIS2are set to the new image dimensions.CRPIX1,CRPIX2are divided bybinning(reference pixel scales with binning).DET-BIN1,DET-BIN2,XBINNING,YBINNING,CDELT1,CDELT2are multiplied bybinning(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 internalreshapewill raise aValueError.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
Input:
pyobs.images.Image(2D data).Output:
pyobs.images.Image(copied) with binned data and updated headers.
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.