Move the processing code under pipeline/
Preparing to merge this repository into a combined astrophotography repo. session-scripts/ becomes pipeline/ because the scripts import layout.py from their own directory and must stay together, and because 'pipeline' says what it is rather than how it came about. observing/ stays at the top level: observing plans are not processing code.
This commit is contained in:
parent
653ca103cd
commit
c6299f41ab
53 changed files with 0 additions and 0 deletions
|
|
@ -1,102 +0,0 @@
|
|||
"""Image 4: the close-up, framed by the galaxy's own measured extent.
|
||||
|
||||
The crop box is not chosen by eye. The surface photometry produced an isophote
|
||||
model of NGC 5128, and the region where that model carries real signal defines
|
||||
how much sky the galaxy actually occupies; the frame is that region plus a
|
||||
margin. So "keeping the object in frame" is a measured statement rather than a
|
||||
judgement, and the same rule would frame any other galaxy without retuning.
|
||||
|
||||
It is cut from image 3's 16-bit TIFF rather than from the PNG, and never
|
||||
resampled, so every pixel is exactly the pixel that came out of the pipeline.
|
||||
Re-rendering was considered and rejected: the stretch in image 3 is already
|
||||
anchored on sky statistics measured across the whole frame, and a tight crop
|
||||
contains too little empty sky to measure a better one. Cropping is therefore
|
||||
not a compromise here - it is the correct operation.
|
||||
"""
|
||||
import os
|
||||
|
||||
import numpy as np
|
||||
import tifffile
|
||||
from astropy.io import fits
|
||||
from astropy.wcs import WCS
|
||||
from PIL import Image
|
||||
|
||||
import layout
|
||||
|
||||
OUT = layout.SESSION
|
||||
# The finished renderings live in their own directory: they are the
|
||||
# deliverables, and keeping them apart from the masters, the
|
||||
# intermediates and the analysis figures makes it obvious which
|
||||
# files are meant to be looked at.
|
||||
FINAL = layout.path("final")
|
||||
SOURCE = "NGC5128-final-3-best.tif"
|
||||
CROP = 48 # the border already removed from image 3
|
||||
MARGIN = 0.08 # sky margin around the galaxy, as a fraction
|
||||
MODEL_FLOOR = 5.0 # ADU/px: where the isophote model still has signal
|
||||
|
||||
|
||||
def main():
|
||||
model = fits.getdata(layout.path("sb-model.fits")).astype(np.float32)
|
||||
ys, xs = np.where(model > MODEL_FLOOR)
|
||||
# Model coordinates are on the uncropped grid; image 3 lost CROP px.
|
||||
x0, x1 = xs.min() - CROP, xs.max() - CROP
|
||||
y0, y1 = ys.min() - CROP, ys.max() - CROP
|
||||
print(f"galaxy extent (model > {MODEL_FLOOR} ADU/px): "
|
||||
f"x {x0}-{x1}, y {y0}-{y1} = {x1 - x0} x {y1 - y0} px")
|
||||
|
||||
with fits.open(layout.path("master-Luminance.fit")) as hd:
|
||||
wcs = WCS(hd[0].header, naxis=2)
|
||||
|
||||
img = tifffile.imread(layout.path(SOURCE))
|
||||
ny, nx = img.shape[:2]
|
||||
print(f"source {SOURCE}: {nx} x {ny}, {img.dtype}")
|
||||
|
||||
# Centre the frame on the galaxy's own centre, not on the frame's.
|
||||
cx, cy = (x0 + x1) / 2.0, (y0 + y1) / 2.0
|
||||
half_x = (x1 - x0) / 2.0 * (1 + MARGIN)
|
||||
half_y = (y1 - y0) / 2.0 * (1 + MARGIN)
|
||||
# One box for both axes keeps the galaxy from touching a short edge, and a
|
||||
# round object in a near-square frame reads better than in a letterbox.
|
||||
half = max(half_x, half_y)
|
||||
|
||||
# Clamp inside the image while keeping the galaxy centred as far as
|
||||
# possible; report honestly if the frame has to shrink.
|
||||
half = min(half, cx, cy, nx - cx, ny - cy)
|
||||
left, right = int(round(cx - half)), int(round(cx + half))
|
||||
top, bottom = int(round(cy - half)), int(round(cy + half))
|
||||
print(f"crop box x {left}-{right}, y {top}-{bottom} "
|
||||
f"({right - left} x {bottom - top} px)")
|
||||
|
||||
# Verify the whole galaxy really is inside before writing anything.
|
||||
assert left <= x0 and right >= x1 and top <= y0 and bottom >= y1, \
|
||||
"galaxy would be clipped - refusing to write"
|
||||
inset_x = min(x0 - left, right - x1)
|
||||
inset_y = min(y0 - top, bottom - y1)
|
||||
print(f"clearance to the nearest edge: {inset_x} px horizontally, "
|
||||
f"{inset_y} px vertically")
|
||||
|
||||
scale = 0.5376
|
||||
print(f"field of view {(right - left) * scale / 60:.1f}' x "
|
||||
f"{(bottom - top) * scale / 60:.1f}'")
|
||||
corner = wcs.pixel_to_world(left + CROP, top + CROP)
|
||||
centre = wcs.pixel_to_world((left + right) / 2 + CROP,
|
||||
(top + bottom) / 2 + CROP)
|
||||
print(f"frame centre {centre.to_string('hmsdms')}")
|
||||
print(f"top-left corner {corner.to_string('hmsdms')}")
|
||||
|
||||
crop = img[top:bottom, left:right]
|
||||
tifffile.imwrite(layout.path("NGC5128-final-4-closeup.tif"), crop,
|
||||
photometric="rgb")
|
||||
u8 = (crop.astype(np.float32) / 65535.0 * 255 + 0.5).astype(np.uint8)
|
||||
Image.fromarray(u8).save(layout.path("NGC5128-final-4-closeup.png"))
|
||||
prev = Image.fromarray(u8)
|
||||
prev.thumbnail((2400, 2400), Image.LANCZOS)
|
||||
prev.save(layout.path("NGC5128-final-4-closeup-preview.jpg"),
|
||||
quality=93)
|
||||
print("wrote NGC5128-final-4-closeup.tif / .png / -preview.jpg")
|
||||
|
||||
|
||||
os.makedirs(FINAL, exist_ok=True)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Loading…
Add table
Add a link
Reference in a new issue