Module-by-module description

Each leaf module under picmaker has one responsibility. The summary below describes that responsibility and links to the API reference for each public symbol.

picmaker (src/picmaker/__init__.py)

Top-level package. Re-exports the public API surface from the leaf modules, defines __all__, and resolves __version__ from importlib.metadata (with a fallback to the setuptools_scm-generated _version.py when the package is run from a source checkout that has not been installed). New code should import from here (from picmaker import images_to_pics) rather than from the individual leaf modules.

picmaker.cli (src/picmaker/cli.py)

The argparse-based command-line entry point. picmaker.cli.main() parses sys.argv, dispatches to picmaker.pipeline.process_images(), and converts --versions FILE into a list of normalized option dicts. Each CLI phase lives in its own private helper: _build_parser() (argparse setup), _separate_files_and_dirs() (positional-arg classification), _normalize_and_validate() (mutex / value-validity checks), _collect_option_dicts() (--versions re-parse loop), and _process_directory() (per-directory walk). They are unit-tested directly in tests/test_cli_unit.py and tests/test_cli_helpers.py.

picmaker.pipeline (src/picmaker/pipeline.py)

The orchestration layer. picmaker.pipeline.process_images() walks the input list (with optional --movie mode that shares a stretch across frames) and dispatches each file to picmaker.pipeline.images_to_pics(). The per-image work is split across three module-private helpers: _pds3_resolve_pointer() (PDS3 .LBL pointer resolution), _hst_mosaic_rgb() (HST ACS/WFC and WFPC2 mosaic assembly, with _hst_wfpc2_mosaic() and _hst_acs_panel_mosaic() as geometry sub-helpers), and _process_one_image() (the per-file read → slice → colormap → write chain). Each helper has direct unit-test coverage in tests/test_pipeline_helpers.py. picmaker.pipeline.find_common_path() is a small directory-walking helper used to derive the output tree’s root when --recursive is set.

picmaker.options (src/picmaker/options.py)

PicmakerOptions is the dataclass that owns all post-normalization mutex / value-validity invariants. Both picmaker.cli._normalize_and_validate() (early, at CLI parse time) and picmaker.pipeline.images_to_pics() (late, at library call time) build a PicmakerOptions from their kwargs and call validate() so the invariants live in exactly one place.

picmaker.io (src/picmaker/io.py)

The file-reader cascade and output-path helpers. The cascade lives in picmaker.io.read_one_image_array() and tries each known format in turn (pickle → numpy .npy → VICAR → FITS → PIL → PDS3-label); the first match returns a ReadResult typing.NamedTuple of (array3d, default_is_up, filter_info). read_image_array() is the multi-file variant that stacks the per-file results along the band axis. read_pds_labeled_image_array() is the PDS3 label parser. get_outfile() builds the output path, honoring the replace= policy.

The module’s name shadows the stdlib io; use absolute imports (from picmaker.io import ) and never write import io inside the picmaker package. A rename to picmaker.readers is tracked in issue #14.

picmaker.enhance (src/picmaker/enhance.py)

Intensity-space operations. get_limits() chooses the stretch endpoints from the array, optional masks, and the --limits / --percentiles / --trim / --footprint options. apply_colormap() maps a 2-D array to RGB through a named or per-instrument tint. apply_gamma() is the final power-law correction. fill_zebra_stripes() is the optional pre-stretch cleanup for legacy spacecraft compression artifacts.

picmaker.geometry (src/picmaker/geometry.py)

Shape-space operations. slice_array() selects the sample / line / band range from the raw 3-D input. crop_array() strips constant-valued borders. rotate_array_rgb() applies the requested flip / rotation. get_size() is the resize planner that consumes --size / --scale / --frame / --wrap-ratio and returns the final dimensions plus wrap geometry. resize_image(), wrap_image(), and pad_image() execute the plan against a PIL image. circle_mask() is a small helper used by the median-filter footprint option in enhance.

picmaker.color (src/picmaker/color.py)

Mission-agnostic tint dispatch. tinted_colormap() takes the filter_info triple from the reader cascade, normalizes the HST CL1 / CL2 / CLEAR* / N/A quirks, then delegates to the per-instrument modules in picmaker.instruments. The wavelength-to-RGB lookup tables (picmaker._rgb.RGB_BY_NM, picmaker._rgb.RFUNC, picmaker._rgb.GFUNC, picmaker._rgb.BFUNC) are re-exported here for convenience but live in picmaker._rgb.

picmaker.pil_utils (src/picmaker/pil_utils.py)

The bridge between numpy arrays and PIL images. array_to_pil() converts an (lines, samples, bands) array into a PIL image (or a list of three PIL images for 16-bit RGB). pil_to_array() is the inverse. write_pil() writes a PIL image to disk and routes 16-bit output through picmaker.tiff16.WriteTiff16().

picmaker._filters (src/picmaker/_filters.py)

A thin dispatch around PIL’s PIL.ImageFilter presets. picmaker._filters.FILTER_DICT maps the case-folded filter name to the ImageFilter instance; filter_image() applies one to a PIL image (or raises ValueError for the 16-bit-RGB-as-list path, which is not supported).

picmaker._rgb (src/picmaker/_rgb.py)

The wavelength → RGB table that the HST tint inferer uses. Private to the package; re-exported via picmaker.color.

picmaker.tiff16 (src/picmaker/tiff16.py)

Hand-rolled 16-bit TIFF reader and writer. Used because Pillow’s 16-bit support is uneven across grayscale / RGB / palette modes. WriteTiff16() and ReadTiff16() keep their PascalCase names for backward compatibility (the one remaining per-file ruff ignore for this module is the N802 allow).

picmaker.colornames (src/picmaker/colornames.py)

The X11 color-name table (~800 entries) plus a normalizing lookup helper. Used by the --pad-color / --gap-color / colormap options.

picmaker.instruments (src/picmaker/instruments/)

Per-mission detectors and tint chains. Each module under this subpackage exposes the same four-function protocol described in Adding a new instrument. picmaker.instruments.lookup() picks the right instrument module given a host string; VICAR_INSTRUMENTS and FITS_INSTRUMENTS are the per-format dispatch lists that picmaker.io.read_one_image_array() iterates.