A compositor for adding one or more overlays — images, text, auto-populated technical cards, callouts with connector lines — on top of the active document, transforming them interactively, and baking the result back into the image.
The dialog is non-modal and designed around a scene / preview model: your overlays live as free-floating objects on a canvas on top of the base image, you drag, rotate, scale, and snap them until you like what you see, then Affix Inserts flattens everything into the document. Nothing is destructive until you affix.
Two columns separated by a draggable splitter whose position is remembered across sessions:
Ctrl + wheel
zooms with the cursor as the anchor. Right-click on an overlay
pops a 9-position snap menu.Most sections start collapsed; the ones expanded by default are the ones you reach for first (Load, Transform, Inset Callout, Apply).
Three ways to add a pixmap overlay, in the Load / Add section:
Transparency hint. The yellow hint at the top of the Load section says it plainly: use Load from File if you want to keep PNG alpha. Loading from an open View captures RGB only.
Every overlay — pixmap, text, or technical card — is a free-floating scene item with the same interaction model.
A small blue dot sits on the top-right of each item's bounding box. Click and drag it to scale and rotate simultaneously, both anchored at the item's centre:
Letting go leaves the item at its new scale and rotation. See § 11.1 for the exact math.
The Transform section gives three controls that act on whichever items are selected:
The Text section (collapsed by default) adds freely-styled text overlays with optional outlines.
Changing any of these controls applies instantly to the current selection. Font, size, bold, italic, fill colour, outline colour, and outline width all persist to QSettings and come back the way you left them next session.
The Technical Card is a special overlay that auto-populates with FITS header / document metadata. It's the right tool for the little “OBJECT / DATE / EXPOSURE / FILTER” corner block you see on finished astrophotos.
Toggle Enable Tech Card to create (or remove) the card. Enabling drops the card at the top-left corner, populated with your default field set. You can drag, scale, rotate, snap, and style it just like any other overlay.
The Fields combo lists every available field: the actual FITS header keys from the current document (OBJECT, DATE-OBS, EXPTIME, FILTER, INSTRUME, TELESCOP, FOCALLEN, XPIXSZ, GAIN, OFFSET, BAYERPAT, … all of them, straight from the header SASpro loaded with the image), plus four computed convenience fields:
W×H, from NAXIS1/NAXIS2 if
present, otherwise computed from the image shape.doc.metadata["bit_depth"]
if set.The default set is OBJECT, DATE-OBS, EXPTIME, FILTER, GAIN, OFFSET, INSTRUME, TELESCOP, FOCALLEN, XPIXSZ, BAYERPAT, Size, Bit Depth, Mono — whatever subset of those actually exists in the current document's header. Customise with:
Build / Update re-renders the card with the current field list and style; most style changes also re-render live. Reset restores the whole configuration to defaults.
Text inside the card uses the same Font / Fill / Outline / Outline width controls as regular text inserts (§ 4). Every setting here persists to QSettings, including the ordered field list.
Pixmap inserts get a little rectangle drawn around them on screen so you can see where they are. The Bounding Box Style section controls this overlay guide (which is not baked into the image unless Draw Bounding Box is on at affix time):
Turn Draw off when you're ready to affix if you want the final image without any outline around the insert.
The Inset Callout section attaches a source region to a pixmap insert, drawing a rectangle around that region on the main image and connecting it to the insert with two straight lines. Common use: drop a 3× crop of the galaxy's core in the corner and show visually where it came from.
Clear Source Region drops the callout from the selected insert without removing the insert itself.
The Connector mode combo picks which corners the two lines connect:
Connector lines auto-track: moving, scaling, or rotating the insert redraws the lines so they always start at the source and end at the insert's current corners. A 16-ms refresh timer keeps this live.
A fast way to make a zoomed inset of a feature in your own image. Click Create Insert from Current… — a dialog asks:
After the choice, drag on the preview to define the source rectangle. The region is cropped from the base pixmap, upscaled 3× with smooth interpolation, and added as a new insert at the centre of the scene — drag it, snap it, scale it, position it just like any other insert. Right-click during the drag cancels the operation.
Snap the selected items to any of nine standard anchor positions, relative to the base image, with configurable margins.
These same nine positions are reachable by right-clicking any overlay on the canvas. The nine-position + margin model also drives the headless replay, so a signature placed in the top-right once with margins (30, 30) can be re-applied to any other image via the Replay system with the same arguments.
The Zoom section has –, +, and Fit buttons;
the preview also responds to Ctrl + wheel for cursor-anchored
zoom (0.10× to 10×) and left-click-drag anywhere empty for
pan.
The Apply / Clear section has three actions:
QGraphicsScene.render(). The result replaces the
active document's image as an undoable edit named
“Signature / Insert”.Non-destructive until you affix. Loading, moving, styling, deleting individual overlays, closing and reopening the dialog — none of that changes the document. Only Affix Inserts commits.
Overlays can extend past the base image. If an insert hangs off the edge of your base image, the affixed output grows to include it. The output rectangle is the union of every committed item's bounding box in scene coordinates, so a signature placed at (−20, H+30) will push the final image a few pixels outside the original edges. If you don't want that, keep overlays inside the base image bounds, or use Send to Position with positive margins.
Nearly every control value is persisted to QSettings under the
signature_insert/ namespace and comes back next session:
Changing any of these also triggers a save, so you never need to remember to confirm.
Signature / Insert is built on
QGraphicsScene / QGraphicsView. The base image
is the scene's bottom item (z = 0) and every overlay is a free-floating
scene item above it (z = 1). Interaction model, item types, and the
bake step are described below.
Each movable overlay carries a child TransformHandle
object (small blue circle) parked on the item's top-right corner by a
16-ms sync timer. On mouse press the handle records the vector from the
item's centre to its own position:
On mouse move, both are recomputed from the new handle position, and the item's scale and rotation are updated to:
Scale is floored at 0.05 so the item never collapses to zero size. Each item's transform origin is set to its own centre, so both scale and rotation apply around the centre (the natural pivot for signature placement).
Text overlays extend QGraphicsTextItem with an outline
pass. When outline width is zero, painting uses the fast
QGraphicsTextItem code path (plain text rendering). When
outline width is > 0, painting switches to a vector approach:
build a QPainterPath from the text's glyph outlines line by
line, stroke it with the outline colour and width, fill with the fill
colour. The stroke uses RoundCap + RoundJoin
so sharp inner corners don't produce visible spikes. This path is
rebuilt on every paint, which is cheap for the character counts
typically on screen; the pay-off is a crisp outline at any scale and
rotation.
The tech card is a composite QGraphicsItem that owns
three children: a background rectangle, a border rectangle, and the
OutlinedTextItem for the field block. The card's padding
determines the offset between the text and the two rectangles; its
bounding rect is the union of all three. Rebuilding (changing padding,
text, or style) calls prepareGeometryChange(), repositions
the text at (padding, padding), resizes bg and border
to the new content extent, and refreshes the transform origin to the
new centre. The item itself paints nothing — its children do.
The 9 snap positions compute the item's new top-left in base-image local coordinates:
and the same three rules on the y axis, giving nine (x, y) combinations. The result is mapped to scene coordinates through the base item's transform, so the snap stays correct even if the view is zoomed or the base is positioned somewhere other than (0, 0).
Callouts connect two corners of the source rectangle to two corners of the insert's bounding rectangle. The direction mode selects which corners:
|Δx| ≥ |Δy| picks LR or RL
depending on the sign of Δx; otherwise TB or BT depending
on the sign of Δy.All callout items use cosmetic pens so their stroke width is in pixels, not scene units — zooming the preview doesn't change how thick the lines look on screen.
Flattening the scene to the output image works in these steps:
QImage sized to the union, fill with
transparency, and call
scene.render(painter, target, source). Any item not in
the render list is temporarily hidden during the render.set_image()
with step name “Signature / Insert”.For scripted / replay use, apply_signature_preset_to_doc(doc,
preset) performs a single-image overlay with the preset keys:
file_path — path to the overlay image, PNG
recommended for alpha preservation.position — one of the nine snap keys.margin_x, margin_y — pixels from
the edge.scale — percent, default 100.rotation — degrees, default 0.opacity — percent, default 100.The headless path recomputes the overlay's visual bounding box after scale and rotation (because a rotated rectangle's axis-aligned bbox is larger than the original), snaps that visual bbox to the chosen anchor with the margins applied, draws through a QImage canvas with full alpha handling, and returns an RGB float32 image in [0, 1]. This is the one replayable through SASpro's last-command / preset system; the full interactive composition (multiple inserts, callouts, text, tech card) is not auto-replayable — use it for a quick signature stamp.
Header objects via duck typing (no
astropy import), as well as plain Python dicts and lists of
(key, value) pairs, so it works with whatever header representation
DocManager is holding for the current document.