panoptes.pipeline.processing¶
panoptes.pipeline.processing ¶
The two processing entry points, and the per-frame work they consume.
The cloud flow had two abilities worth keeping exactly: process a single FITS
as it arrives, or process everything in a batch. Both survive here, with bucket
events replaced by directory enumeration and papermill replaced by ordinary
function calls. process_frame is the expensive per-FITS work; process_
observation runs the work-list walk, processes what needs it, and writes the
sequence document.
These are library functions, not commands. The CLI belongs to improvement plan 4.2 and should wrap these rather than contain them.
See data contract 5.
ImagePathInfo
dataclass
¶
Parse the location path for an image.
This is a small dataclass that offers some convenience methods for dealing with a path based on the image id.
This would usually be instantiated via path:
from panoptes.utils.images.fits import ImagePathInfo # noqa bucket_path = 'gs://panoptes-images-background/PAN012/Hd189733/358d0f/20180824T035917/20180824T040118.fits' path_info = ImagePathInfo(path=bucket_path)
path_info.id 'PAN012_358d0f_20180824T035917_20180824T040118'
path_info.unit_id 'PAN012'
path_info.sequence_id 'PAN012_358d0f_20180824T035917'
path_info.image_id 'PAN012_358d0f_20180824T040118'
path_info.as_path(base='/tmp', ext='jpg') PosixPath('/tmp/PAN012/358d0f/20180824T035917/20180824T040118.jpg')
ImagePathInfo(path='foobar') Traceback (most recent call last): ... ValueError: Invalid path received: self.path='foobar'
Works from a fits file directly, which reads header.¶
fits_fn = getfixture('unsolved_fits_file') path_info = ImagePathInfo.from_fits(fits_fn) path_info.unit_id 'PAN001'
Source code in panoptes/utils/images/fits.py
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 | |
as_path ¶
Return a Path object.
Source code in panoptes/utils/images/fits.py
get_full_id ¶
Returns the full path id with the given separator.
Source code in panoptes/utils/images/fits.py
from_fits_header
classmethod
¶
Create ObservationPathInfo from FITS header.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
header
|
FITS header containing observation metadata. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
ImagePathInfo |
New instance with path information. |
Source code in panoptes/utils/images/fits.py
from_fits
classmethod
¶
Create ObservationPathInfo from FITS file.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
fits_file
|
Path to FITS file or file-like object. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
ImagePathInfo |
New instance with path information from file header. |
Source code in panoptes/utils/images/fits.py
SolverMissing ¶
Calibrated
dataclass
¶
One frame's pixels after bias, saturation masking and background.
Both resolutions of the background are here because they serve different consumers: source detection needs it evaluated at the frame's shape, while only the mesh is worth writing to disk.
Source code in src/panoptes/pipeline/processing.py
calibrate ¶
Bias, saturation mask and RGB background, in that order.
Saturation is masked in the raw domain, before the bias is removed.
Saturation is a property of the detector, and WHTLVLN -- the only source
that knows a given camera's real white level -- reports a raw ADU value.
The old code masked after subtracting the bias against a threshold that was
documented as post-bias (15872, which is 2**14 - 512), so feeding a raw
header value into that same comparison would have masked about 512 ADU too
aggressively on every frame. Doing it first removes the ambiguity.
The background is computed per Bayer color, because the colors sit on
different pedestals; that is why get_rgb_background exists rather than a
single Background2D.
Source code in src/panoptes/pipeline/processing.py
require_solver ¶
Fail before any pixels are read if solve-field is not installed.
Plate solving is not optional: a frame without a WCS cannot be matched to the catalogue, so it produces no photometry and there is nothing useful to write. Checking up front turns an hour of work that ends in an unusable tree into an immediate, actionable error.
Source code in src/panoptes/pipeline/processing.py
process_frame ¶
process_frame(raw_path: Path | str, processed_root: Path | str, params: PipelineParams | None = None, *, force_new: bool = True, files: FileSettings | None = None, solve_timeout: int = 30) -> dict[str, Path]
Process one raw frame and write its document and products.
Bias, background, plate solve, source detection, catalogue match, then the artifacts. Plate solving belongs at this single-frame stage, which is why it is here rather than in the observation-level function.
A frame that fails is still recorded, with ImageStatus.ERROR, so the next
work-list walk reports prior error rather than missing and the failure
is visible without re-reading logs.
Source code in src/panoptes/pipeline/processing.py
138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 | |
source_statistics ¶
What the document records about a frame's sources.
The FWHM is the useful one: it is what stamp sizes and apertures are expressed in multiples of, so it has to be recorded per frame rather than assumed.
Source code in src/panoptes/pipeline/processing.py
process_observation ¶
process_observation(raw_root: Path | str, processed_root: Path | str, params: PipelineParams | None = None, *, force_new: bool = False, files: FileSettings | None = None, solve_timeout: int = 30) -> pandas.DataFrame
Walk, process what needs processing, then write the sequence document.
Returns the work list as a table with the outcome filled in, so the caller
can see what was done and why without parsing logs. This is the deleted
observation.py structure with list_blobs replaced by a glob.
One frame's failure does not stop the batch: over a long sequence a single
unsolvable frame is ordinary, and it is already recorded with
ImageStatus.ERROR for the next walk to find.
Source code in src/panoptes/pipeline/processing.py
write_observation ¶
write_observation(processed_root: Path | str, frames: list[Frame], params: PipelineParams, *, files: FileSettings | None = None) -> dict[str, Path]
Aggregate a sequence's frames into one observation.json per sequence.
This is the one file two frame workers would contend on, which is why
nothing in products.write_frame touches it: the path hierarchy partitions
every other write, so the per-frame work stays embarrassingly parallel with
no shared mutable state. Here, at the end of a batch, there is exactly one
writer.
Source code in src/panoptes/pipeline/processing.py
group_by_sequence ¶
Frames grouped by the sequence they belong to, order preserved.
Source code in src/panoptes/pipeline/processing.py
read_image ¶
Read back what products.write_image wrote, by extension name.
PRIMARY comes back as reduced. Present so a consumer does not have
to know the extension order, only the names.
Source code in src/panoptes/pipeline/processing.py
find_sequences ¶
Every sequence id with at least one frame under raw_root.