Skip to content

MCRT output processors

Configure processors under mcrtsimulation.outputprocessors. This page covers photon streams, output formats, and compression. See MCRT simulation and sources for transport and emission settings, including packet weights.

Completed MCRT output files store line_name and line_rest_wavelength_angstrom as root HDF5 attributes in each enabled photon stream. The wavelength is the reference used for photon frequency offsets. Python postprocessing uses this value, so adding a line does not require a separate Python wavelength entry. Older files use the configured line.name or linename and the legacy wavelength table; an unknown line without metadata raises an error.

Output processors transform or aggregate photon data. The photon processor (raw photon dump) is the most common. For photon streams, raw is an alias of photon, and void performs no output processing.

Common parameters:

Parameter Type Default Description
type string required Processor type ("photon", "raw", "void", "histogram1d", "histogram2d", "apertureSpectrum", "sbmap", etc.)
active bool true Enable/disable this processor
streams list of strings processor-dependent Restrict processor to specific photon streams; [] allows all
assess_convergence bool false Enable convergence assessment in processors that support it
convergence_threshold float 0.01 Relative change threshold for convergence

active, streams, and convergence settings inherit driver-level values unless overridden in a processor. MCRT decodes all output settings before initialization. Physical-unit conversions and checks requiring the dataset or observer context run when constructing each processor. Set mcrtsimulation.suffix to label output filenames. Processors within a stream share that file.

Stream filtering

THOR runs up to three photon streams: original (escaped photons), peel (peeling-off photons), and input (initial photon state). The streams parameter selects which streams a processor receives:

mcrtsimulation:
  outputprocessors:
    - type: "photon"
      active: true

    - type: "sbmap"
      streams: ["peel"]        # only process peeling photons (default for sbmap)
      nbins_x: 64
      nbins_y: 64
      left_x: 0.0
      right_x: 1.0
      left_y: 0.0
      right_y: 1.0
      weight: "weight"

    - type: "histogram1d"
      # no streams → active for all streams
      quantity: "dlambda"
      weight: "weight"
      left: -1.5
      right: 1.5
      nbins: 30
      assess_convergence: true
      convergence_threshold: 0.1

sbmap defaults to streams: ["peel"]; apertureSpectrum and the histogram1d aperture do so when peeling is enabled, using the peeling observer directions. An explicit line_of_sight switches the default to streams: ["input"]; sbmap and apertureSpectrum also accept lines_of_sight for multiple input views. On the input stream, the histogram1d aperture requires a single explicit line_of_sight. An explicit streams setting overrides these defaults.

On the peel stream, aperture processors reject explicit line_of_sight and lines_of_sight; set observer directions under peeling.lines_of_sight.

Selecting dlambda_boxcenter in histogram1d or on either axis of histogram2d defaults to streams: ["peel"] and requires peeling, even with an explicit frame. apertureSpectrum also supports input and follows the frame defaults above. Set streams: ["input"] to compute intrinsic spectra along the peeling observer directions. Explicit streams always overrides the default.

Automatic raw output

If no raw, photon, or trace processor is listed, THOR adds a raw writer that inherits the driver's active and streams settings. With their defaults, it runs on all streams. To suppress it, list one of these types with active: false.

HDF5 compression

By default, compressed output datasets use HDF5's built-in shuffle + gzip filters. Both are part of libhdf5 itself — no filter plugin is needed, and h5py, h5ls, and any other stock HDF5 reader decompress transparently.

To use lossless LZ4 compression instead, set:

mcrtsimulation:
  compression:
    codec: lz4
    level: 5
    shuffle: bit

This driver-level block enables LZ4 compression for every output processor that inherits it, including raw, photon, and trace. Per-processor settings take precedence over inherited driver-level settings.

Available codecs are lz4, zlib, and raw. Selecting lz4 or zlib enables compression; raw disables it. Shuffle can be none or byte; LZ4 also supports bit. Existing compress_* settings still work; use either compression or compress / compress_level / compress_shuffle at each configuration level. Chunk sizing and compress_float32 remain separate options.

MCRT checks compression settings during preflight, including overrides in active output processors. Encoder availability is checked when preparing the HDF5 writer.

LZ4 output requires a Blosc-enabled reader. For Python, install hdf5plugin and import hdf5plugin before reading with h5py. If a selected codec is unavailable in your build, THOR warns and writes uncompressed output. Invalid settings are errors.

Compression can reduce file size at a CPU cost. Test photon-column compression on your workload; use compress_float32 only when reduced precision is acceptable.

The following defaults apply only when neither the driver nor the processor sets compression or compress:

processor class compress default why
sbmap, apertureSpectrum, histogram1d, histogram2d true Compressed once at finalization
raw, photon, trace (and the driver's trace dump) false Written in batches during transport

Without compress_float32, compression preserves values exactly.

mcrtsimulation:
  compress_float32: true       # inherited by every processor below
  outputprocessors:
    - type: "photon"
      compress: true           # opt the streamed columns in, against their class default
      compress_level: 0        # narrow to float32 without deflate
    - type: "sbmap"
      compress_float32: false  # override one key; still compressed by class default
      nbins_x: 512
      nbins_y: 512
      left_x: 0.0
      right_x: 1.0
      left_y: 0.0
      right_y: 1.0
Parameter Type Default Description
compress bool class-specific (see above) Chunked layout + filters
compress_level int 4 gzip/deflate level, 0–9. 0 keeps chunking and shuffle but skips deflate.
compress_shuffle bool true Byte-shuffle filter. Groups the like-valued exponent bytes of a float array together so gzip can exploit them.
compress_float32 bool false Store float64 arrays as float32. Lossy — about 7 significant decimal digits survive. Accumulation always stays in full precision; only the written array is narrowed. Requires compress.
compress_min_bytes int 65536 Write-once datasets only. Maps, spectra and histograms below this size stay contiguous and unfiltered — chunk and filter overhead is not worth it. Set to 0 to always chunk. Streamed columns are exempt: they are resizable, so their final length is unknown when the dataset is created and the decision cannot depend on it.
compress_chunk_bytes int 1048576 Target uncompressed bytes per chunk for write-once datasets.
compress_append_chunk_bytes int 262144 Target bytes per chunk for streamed columns. Should stay comfortably under both HDF5's default 1 MiB chunk cache and one batch's worth of rows (nphotons_step_max), or partially-filled chunks get decompressed and recompressed on every batch that touches them.

compress_float32 and geometry

The downcast applies to bulk payloads only (images, spectra, photon columns). The line_of_sight, e_x, and e_y frame datasets always keep full precision, because readers use them to reconstruct image-plane coordinates — a narrowed basis vector would propagate error into positions rather than only into a plotted value. Dataset attributes (bin edges, weights, radii) are never downcast either.

Chunks are sized by target bytes, trimming the slowest-varying axis first, so a [nlos, nx, ny] map chunks into whole images or contiguous row bands of one image.

Photon output

Writes photon data for the selected streams to HDF5. Select columns under mcrtsimulation using photon_fields for original photons, photon_fields_peel for peeling photons, and photon_fields_input for input photons. These lists take precedence over a raw processor's fields. For raw trace output, tfields takes precedence over fields; an explicit tfields: [] writes no columns.

With multiple peeling observers, peel columns are grouped under los_000/, los_001/, and so on. Single-observer peel columns and original/input columns are stored at the file root.

Parameter Type Description
active bool Enable this processor

Histogram 1D

Experimental

This feature is experimental and may change.

Bins photon field values into a histogram (e.g., spectrum). Binning settings inherit driver-level values unless overridden. Bin counts must be positive and bounds must give finite, positive widths in the build's floating-point precision. The lower bound is inclusive; the upper bound is exclusive.

Parameter Type Default Description
quantity string "dlambda" Field to histogram (e.g., "dlambda" for a spectrum)
weight string "weight" Weight field (currently only "weight")
left float required Left bin edge
right float required Right bin edge
nbins int 100 Number of bins
name string (=quantity) Output dataset name under /histogram1d/; set it to run several histograms of the same quantity (e.g. a full-field and an aperture spectrum) in one file without colliding
aperture_radius number or string (off) If set, restrict to photons within this radius of the aperture center — enables aperture mode. Unit-aware: a bare number is a box fraction; a string takes a unit, e.g. "7 pkpc" or "1 arcsec" (see below)
aperture_center vec2 [0.5, 0.5] Aperture center in image-plane coordinates (default the projection center)
center vec3 [0.5, 0.5, 0.5] Projection center for the image-plane mapping (shared with sbmap)
north vec3 [0, 0, 1] Image orientation hint (shared with sbmap)
assess_convergence bool false Check convergence
convergence_threshold float 0.01 Convergence tolerance

The optional aperture restricts the spectrum to a circular region of the image plane, using the same centered projection convention as sbmap (box center → image (0.5, 0.5)). With multiple peeling observers it applies per-observer (each photon is masked against its own line-of-sight frame) and the output is stacked [nlos, nbins], matching the full-field spectrum. This produces the aperture-integrated line profile during the run, without dumping raw photons for offline masking.

The single aperture_radius key is unit-aware:

Form Meaning
aperture_radius: 0.02 Box fraction — same coordinates as the sbmap left/right bounds (0.02 = 2%-of-box radius)
aperture_radius: "7 pkpc" Physical kpc — converted via the dataset's length_unit (already physical cm, so no redshift factor)
aperture_radius: "1 arcsec" Angular — converted to physical kpc via the flat-ΛCDM angular-diameter distance at the snapshot redshift, using the dataset's cosmology (Ω_m, Ω_Λ, h from the simulation header). Requires a cosmological dataset (z > 0)

kpc is accepted as an alias for pkpc. An unknown unit, or arcsec on a non-cosmological dataset, is a config error.

Histogram 2D

Set type: histogram2d to bin two photon fields into a weighted 2D histogram. Both axes follow the 1D histogram inheritance and bin-edge rules.

Parameter Type Default Description
quantity_x / quantity_y string required Photon field for each axis
left_x / right_x float required Bounds in quantity_x units
left_y / right_y float required Bounds in quantity_y units
nbins_x / nbins_y int 100 Number of bins on each axis
weight string weight Only weight is supported

Output is written to /histogram2d/<quantity_x>_vs_<quantity_y> with shape [nbins_x, nbins_y]. Multiple peeling observers produce [nlos, nbins_x, nbins_y], with one histogram per observer.

With photon-rate weights, if both axes describe photon frequency or wavelength, they must use the same reference frame.

Aperture spectrum (apertureSpectrum)

Experimental

This feature is experimental and may change.

Produces a spectrum for several concentric circular apertures at once, in a single binning pass. It is the multi-aperture generalization of the histogram1d aperture: instead of one aperture_radius, you give a list of aperture_radii and get one enclosed spectrum per radius (all flux within that radius), written to /aperturespectrum/.

Parameter Type Default Description
aperture_radii list required Aperture radii. Each entry is unit-aware exactly like the histogram1d aperture_radius (bare number = box fraction, or "7 pkpc" / "1 arcsec"). Sorted ascending and de-duplicated at init
nbins int 100 Number of spectral bins
left / right number required Spectral binning range (in quantity units)
quantity string dlambda Photon field to bin. On the input stream, dlambda projects the emitting-cell velocity separately for each LOS
units string weight Energy-weighted sums without physical calibration, or erg/s, erg/s/cm2, erg/s/cm2/Angstrom, erg/s/cm2/arcsec2, or erg/s/cm2/arcsec2/Angstrom
observer_redshift float Snapshot redshift Observing redshift for distance, surface-brightness dimming, and observed wavelength-bin width
luminosity_distance_cm float From redshift and cosmology Explicit positive luminosity distance in cm; required for flux at effectively zero redshift
luminosity_unit_erg_s float 1e42 Luminosity represented by one weight unit at the line rest frequency; calibrate this if packet weights use another convention
weight string weight Photon weight field (only weight supported)
aperture_center vec2 [0.5, 0.5] Aperture center in image-plane coordinates
center vec3 [0.5, 0.5, 0.5] Projection center for the image-plane mapping (shared with sbmap/histogram1d)
north vec3 [0, 0, 1] Image orientation hint (shared with sbmap)
line_of_sight vec3 (from peeling) Explicit observer direction for non-peel streams
lines_of_sight list of vectors absent Observer directions for non-peel streams; mutually exclusive with line_of_sight
name string (=quantity) Output dataset name under /aperturespectrum/

Each photon is projected onto its observer's image plane and assigned to the radial bucket between the sorted radii (a single upper_bound); a photon beyond the largest radius is dropped. At finalize the buckets are summed inner→outer, so aperture k holds the flux enclosed within radius r_k — the same "inside radius" meaning as the single histogram1d aperture, for every radius simultaneously. Doing the bucket sort once avoids re-masking the photon stream per aperture.

Like the histogram1d aperture, it needs a fixed observer frame. With peeling enabled and no explicit frame, it defaults to the peel stream (peeling.lines_of_sight); an explicit line_of_sight or lines_of_sight defaults to the input stream. See Stream filtering for frame restrictions when overriding streams. Output is [n_apertures, nbins] for a single observer, or stacked [nlos, n_apertures, nbins] with N>1 observers or any explicit lines_of_sight broadcast list (even a one-entry list → [1, n_apertures, nbins]); per-observer frames are in /aperturespectrum/line_of_sight, /e_x, /e_y. The sorted radii and projection geometry are stored as dataset attributes (aperture_radii, n_apertures, aperture_center, center, north).

Physical units are available for peel and input. The peel stream describes radiation directed toward an observer after transport. Input projections describe intrinsic emission, assuming isotropic emission without attenuation or scattering. Each input view receives the full projected source luminosity; THOR does not divide it among viewing directions. Physical units are unavailable for original, whose packets sample different escape directions. The radiation_origin attribute records how to interpret the output.

With units: erg/s, each bin contains the isotropic-equivalent luminosity enclosed by the aperture. Flux modes divide this luminosity by \(4\pi D_L^2\). Mean surface-brightness modes divide it by \(4\pi A(1+z_\mathrm{obs})^4(206265)^2\), where \(A\) is the full circular aperture area in physical cm², including any area outside the dataset. Because brightness is averaged over the whole aperture, increasing its radius need not increase the brightness.

Per-Å modes also divide by the observed wavelength-bin width, \(\Delta\lambda_\mathrm{obs}=(1+z_\mathrm{obs})(\mathrm{right}-\mathrm{left})/\mathrm{nbins}\). Set quantity to dlambda or dlambda_boxcenter; peel spectra also support dlambda_peel_shifted. The left and right attributes describe the transport wavelength offsets in Å. Flux and surface-brightness modes preserve the absolute normalization; neither divides by the spectrum sum.

THOR treats \(|z_\mathrm{obs}|<10^{-10}\) as zero. Luminosity and mean surface brightness remain valid at zero redshift. Flux requires an explicit luminosity_distance_cm. At positive redshift, THOR can calculate the distance from a valid flat matter-plus-Λ cosmology. You can also supply a distance for local or non-cosmological datasets.

Angular apertures in physical modes use the selected observer distance, with \(D_A=D_L/(1+z)^2\). Thus arcsec radii also require an explicit distance at zero redshift. Apertures in pkpc or box fractions need no distance for luminosity or surface brightness. Observer overrides change output calibration and angular aperture sizes. They leave photon transport and the gas state unchanged.

mcrtsimulation:
  outputprocessors:
    - type: apertureSpectrum
      units: erg/s/cm2/arcsec2/Angstrom
      aperture_radii: [10 pkpc, 30 pkpc]
      left: -5
      right: 5
      nbins: 80

THOR writes one spectrum per configured processor. Its dataset, such as /aperturespectrum/dlambda, contains values in the selected units and records units, observer_redshift, luminosity_unit_erg_s, and weight_to_spectrum_factors. It also records the available physical scale, distance, aperture areas, and observed bin width.

Bin sums use the build's FP32 or FP64 precision. Scaling to physical units produces float64 output arrays. With compress_float32: true, these are stored as float32 if all values fit its numeric range; otherwise, they remain float64. Lossless compression remains available. Uncalibrated output records units: weight.

In MPI runs, aperture spectra sum contributions from all ranks during finalization. Only rank 0 writes the complete spectra and observer frames, in data.0.h5. Each spectrum records mpi_aggregation: sum and mpi_rank_count. The Python read_aperture_spectrum() reader reads these complete products directly and still sums older rank-local spectrum files. It rejects mixed global and rank-local products to prevent double-counting.

For intrinsic aperture spectra and IFU cubes, quantity: dlambda uses each observer's direction and the emitter velocity: dfreq_los = dfreq_rest + (nu0 + dfreq_rest) × (v · los) / c. With write_inputphotons_restframe: false, THOR first inverts the Doppler shift along the original launch direction, then applies the observer shift. This does not include the final peel-flight Hubble shift used by dlambda_peel_shifted.

quantity: dlambda_boxcenter adds the signed Hubble shift from the emission position to [0.5, 0.5, 0.5] after the LOS Doppler correction. With periodic boundaries, aperture position and frequency use the source's primary box image; the crop's center does not change the frequency reference. Intrinsic spectra use the emitting cell's bulk velocity and unattenuated weights. dlambda_peel_shifted supports only peel photons.

mcrtsimulation:
  outputprocessors:
    - type: "apertureSpectrum"
      quantity: "dlambda"
      lines_of_sight: [[1, 0, 0], [0, 1, 0], [-1, 0, 0]]
      aperture_radii: ["1 pkpc", "5 pkpc"]
      nbins: 200
      left: -10.0
      right: 10.0
mcrtsimulation:
  outputprocessors:
    - type: "apertureSpectrum"
      quantity: "dlambda_peel_shifted"
      aperture_radii: ["1 arcsec", "2 arcsec", "5 arcsec"]
      nbins: 200
      left: -10.0
      right: 10.0

Surface brightness map (sbmap)

Experimental

This feature is experimental and may change.

Projects photon positions onto a 2D image plane perpendicular to the line of sight. Produces an HDF5 dataset at /sbmap/image.

Parameter Type Default Description
nbins_x int 100 Number of pixels in x
nbins_y int 100 Number of pixels in y
left_x float required Left edge of image in x
right_x float required Right edge of image in x
left_y float required Left edge of image in y
right_y float required Right edge of image in y
weight string "weight" Weight field (currently only "weight" supported)
units string "weight" Energy-weighted sums without physical calibration, or "erg/s/cm2/arcsec2" for absolute line-integrated surface brightness
spectral mapping absent Enable wavelength-resolved image output; see Wavelength cubes
observer_redshift float Dataset redshift Observing redshift for cosmological surface-brightness dimming; physical units only
luminosity_unit_erg_s float 1e42 Luminosity represented by one weight unit at the line rest frequency; physical units only
north vec3 [0, 0, 1] North hint vector for image orientation
center vec3 [0.5, 0.5, 0.5] Projection center; maps to image coordinate (0.5, 0.5) for every observer
line_of_sight vec3 (from photons) Explicit observer direction for non-peel streams
lines_of_sight list of vectors absent Observer directions for non-peel streams; mutually exclusive with line_of_sight
los_indices list of ints (all observers) Select peeling observers by original zero-based index, preserving the listed order
los_stride int (all observers) Select original observer indices 0, N, 2N, ...; must be positive
streams list ["peel"], or ["input"] with an explicit frame Allowed streams; peel-only by default, input-only when line_of_sight/lines_of_sight is set (an explicit streams overrides)

The image frame uses the line-of-sight direction and north hint. Peel maps use peeling.lines_of_sight. Setting line_of_sight or lines_of_sight on the processor defaults to the input stream, imaging intrinsic source photons. A lines_of_sight list projects each source photon into every frame and produces [nlos, nbins_x, nbins_y] output. Peel maps reject lines_of_sight overrides and ignore line_of_sight with a warning.

Image coordinates use a centered convention: image_x = (pos - center)·e_x + 0.5 (and likewise for y), so the projection center maps to (0.5, 0.5). Both axes require explicit bounds. With the default north and box center, [0, 1] covers an axis-aligned unit-box projection. Off-axis views need wider bounds to avoid clipping the projected corners. Zoom by narrowing left/right around 0.5 or moving center onto the target. THOR warns at finalization if every photon received by an observer falls outside the binning range.

With multiple peeling observer directions (peeling.lines_of_sight, N>1), one image slab per observer is accumulated and written as a stacked [nlos, nbins_x, nbins_y] dataset, with per-observer frames stored as [nlos, 3] datasets (/sbmap/line_of_sight, /sbmap/e_x, /sbmap/e_y).

To write absolute surface brightness, add these settings to the sbmap processor:

units: erg/s/cm2/arcsec2
observer_redshift: 0.0  # optional; defaults to the snapshot redshift

THOR needs the physical domain length to convert pixel areas. Physical units support two streams:

  • peel: radiation directed toward an observer after transport.
  • input: intrinsic emission, assuming isotropic emission without attenuation or scattering. Each view receives the full projected source luminosity.

The radiation_origin attribute records which interpretation applies. Physical units are unavailable for original, whose packets sample different escape directions.

/sbmap/image contains surface brightness integrated over the line. The selected units apply to this single image dataset.

Bin sums use the build's FP32 or FP64 precision. Scaling to physical units produces float64 output arrays. With compress_float32: true, these are stored as float32 if all values fit its numeric range; otherwise, they remain float64. Lossless compression remains available.

Each pixel accumulates energy-converted weights, \(W = \sum_i w_i\nu_{\mathrm{recorded},i}/\nu_0\). Standard emitters use a luminosity scale of \(10^{42}\) erg/s; set luminosity_unit_erg_s if your packet normalization differs. With the default scale and projected physical pixel area \(A\) in cm², the surface brightness is

\[ \frac{10^{42}W}{4\pi A(1+z_\mathrm{obs})^4(206265)^2}. \]

This preserves the absolute luminosity without normalizing by the map total. The distance cancels from the conversion, so no cosmological distance integration is needed. At zero observing redshift, the brightness has no cosmological dimming. Changing observer_redshift affects only the output conversion; the gas state and photon transport stay the same.

The image records units, length_unit_cm, observer_redshift, pixel_area_cm2, luminosity_unit_erg_s, and weight_to_image_factor so you can reproduce the conversion. Uncalibrated output records units: weight and the physical length when available.

los_indices and los_stride are mutually exclusive, per-processor selectors. They reduce only the SB-map slabs retained in memory and written to disk; peeling photons are still generated and transported for every configured observer, so other processors such as apertureSpectrum continue to receive all observers. Explicit indices must be unique, non-negative, in range, and non-empty. These selectors are supported only on the peel stream.

In MPI runs, SB maps sum contributions from all ranks during finalization. Only rank 0 writes /sbmap/image and its observer frames, in data.0.h5. Other rank files retain their raw photon outputs but contain no SB map. The image records mpi_aggregation: sum and mpi_rank_count; do not sum this image again across rank files. Empty ranks contribute zero pixels. Other histograms retain their existing per-rank output behavior.

For a subset selected from a multi-observer context, /sbmap/image keeps the shape [K, nbins_x, nbins_y] and the frame datasets keep [K, 3], including when K is one. The image dataset receives a los_indices attribute whose entries give the original observer index for each output slab.

Wavelength cubes

Add spectral to a peel- or input-stream sbmap:

spectral:
  frame: box_center  # default: peel_endpoint on peel, box_center on input
  wavelength_range_angstrom: [1024, 1240]
  bin_width_angstrom: 2

For unequal bins, supply explicit edges instead of the range and width:

spectral:
  frame: box_center
  wavelength_edges_angstrom: [1190, 1210, 1215, 1215.5, 1216, 1220, 1240]

Edges must be finite, positive and strictly increasing, with at least two entries. Do not combine wavelength_edges_angstrom with wavelength_range_angstrom or bin_width_angstrom.

/sbmap/cube has shape [nx, ny, nwavelength], or [nlos, nx, ny, nwavelength] for stacked observers. /sbmap/wavelength_edges_angstrom stores the absolute wavelength edges. Bins include the left edge, exclude the right, and use the supplied edges exactly. With range-and-width configuration, the final bin is clipped to the requested range. Both the cube and /sbmap/image exclude photons outside this range; summing over wavelength reproduces the image.

spectral.frame selects the frequency and position reference:

  • peel_endpoint projects the transport endpoint and includes the final peel-flight Hubble shift. It supports only the peel stream.
  • box_center projects the launch event in the primary periodic box image and references its frequency to [0.5, 0.5, 0.5], independently of the image crop. Peel wrapping leaves the pixel and frequency label unchanged; peel distance still affects attenuation. This is a frozen-snapshot view of one box image.

For box_center, the linear Hubble law gives νcentre = νlaunch + (H L / c) ν₀ (xlaunch − 0.5) · n; the fractional law gives νcentre = νlaunch exp[(H L / c) (xlaunch − 0.5) · n]. Here L is proper box length, H is hubble_flow, and n points toward the observer. The launch frequency includes the LOS Doppler shift and prior transport.

For intrinsic cubes, enable save_inputphotons and set streams: ["input"]. Supply unit line_of_sight/lines_of_sight vectors or reuse the peeling directions. Each LOS receives the full isotropic, unattenuated source weight, with the cell-velocity and box-centre corrections used by intrinsic aperture spectra.

Both frames precede observer redshift: multiply edges by 1 + observer_redshift for observed wavelengths. Values are bin-integrated brightness in the image's units; divide by each bin's rest-frame or observed width for the corresponding per-Å density. Raw peel output exposes the same box-centre conversion as dlambda_boxcenter; include origin to retain the launch position.

Cube memory per rank is nlos × nx × ny × nwavelength × sizeof(FP) bytes, in addition to image and transport memory. Compression affects disk size only. MPI sums the cube onto rank zero. Convergence checks use the band-integrated image.

For angular pixels, use the dataset's proper scale; observer_redshift changes brightness calibration, not physical size. A periodic peel distance cutoff does not define a common observer plane.