Skip to content

Dev neb synth - #3

Merged
brentmantooth merged 14 commits into
mainfrom
dev_NebSynth
Jul 12, 2026
Merged

Dev neb synth#3
brentmantooth merged 14 commits into
mainfrom
dev_NebSynth

Conversation

@brentmantooth

Copy link
Copy Markdown
Owner

No description provided.

brentmantooth and others added 14 commits July 2, 2026 15:32
…checkbox

Before/after slider margins now synced via QTimer.singleShot(0) so the
deferred call runs after draw_idle() and constrained_layout have settled,
preventing the slider from spanning the full window width on mode switch.

Adds a Subtract image median checkbox alongside Normalize to peak. When
checked, each image's global median is subtracted from its cross-section
profile before any normalization, removing the STF background pedestal
and shifting profiles to a near-zero baseline. Y-axis label updates to
reflect the active combination of both options.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
STF stretch parameters are computed per-image from each image's own sky
median and MAD, so cross-sections of the display image can reflect the
nonlinear transform rather than real optical differences between filters.

Adds _inspector_linear() to report_builder.py that stores the pre-stretch
science data as float32 [0, 1] alongside the existing display images.
Integer data (uint16 ADUs) is divided by the dtype max; PixInsight float32
data is already in [0, 1]. The 2048 px max-dimension cap from _inspector_display
is reused to control NPZ size growth (~2-4x per image vs uint8).

Two new entries appear in the inspector section combo — "Original (linear)"
and "Starless (linear)" — giving unbiased PSF and filter transmission
cross-sections on a common linear scale. Y-axis label updates to reflect
the data type and active checkbox state. Old NPZ files without linear_*
keys open cleanly with no change in behaviour.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add bottleneck as a dependency and route NaN-aware and median operations
on full-image or near-full-image arrays through bn.* instead of np.*,
with a transparent ImportError fallback so environments without bottleneck
are unaffected.

Hot paths benefiting from this change:
- core/stretch.py: two bn.median calls in stf_stretch() and
  stf_stretch_matched() run on the full flattened image array on every
  display refresh.
- analysis/snr_analyzer.py: bn.median on the 2D background model array.
- analysis/halo_analyzer.py: bn.median on stacked radial profiles;
  bn.nanmean / bn.nanstd on the NaN-padded per-star RDF matrix.

analysis/image_filters.py has the same one-line bn.median change in
_estimate_noise() but is left out of this commit because it also carries
unrelated in-progress work.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ion 8e

Michelson contrast (C = ΔL/(I_max+I_min)) proved less useful in practice.
Weber fraction contrast (c = ΔL/L where L = kernel median) better represents
how much brighter-than-background a feature appears, matching Weber's Law.
The median denominator is robust to bright filaments and residual star halos
that would inflate the mean. Scalar metric uses the 99th percentile of the
Weber map (not max) to avoid dark-sky pixels with near-zero median driving the
comparison value to extremes. Maps use PowerNorm for display since the output
is unbounded.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Change the central load-time conversion in core/astro_image.py from
float64 to float32. All FITS/XISF/TIFF input data is at most 16-bit
integer before stacking; float32 (24-bit mantissa) represents every
possible value exactly while halving memory footprint and yielding
1.5-2x faster element-wise operations through better cache utilisation
and wider SIMD lanes. Byte-order normalisation (big-endian FITS >f4
from astropy) remains a free side-effect of the astype() call.

Cascading cleanup removes or narrows nine redundant float64 casts in
snr_analyzer.py, edge_analyzer.py, and report_builder.py that were
no-ops when self.data was already float64.

The astroalign registration call in gui/analysis_thread.py retains its
explicit float64 cast — astroalign requires it and the cast now does
real conversion work.

All 205 fast tests pass after the change.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The log-Y radial power overlay hides small, consistent A-vs-B differences
in the noise floor. Add a 10*log10(P_A/P_B) ratio curve in a separate
panel (not twinx -- see CLAUDE.md) for both the primary/starless and
with-stars pairs, readable directly against a 0 dB reference line.

Also documents the new pattern and a pre-existing, unrelated bug found
during verification (_section_snr crashes when SNR is unchecked) in
CLAUDE.md.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
_plot_snr_pair built a panels list filtered to non-None entries but never
checked whether it ended up empty before calling plt.subplots(1, len(panels)),
which raised ValueError: Number of columns must be a positive integer, not 0.
Reachable any time SNR is unchecked while another metric (e.g. Power Spectrum)
is run. Return None instead, matching the guard pattern already used by
_plot_radial_overlay/_plot_radial_ratio_db -- both call sites already pipe
the result through _img_tag, which turns None into "".

Verified end-to-end via the real ReportBuilder.generate() pipeline: the
original crash scenario now succeeds, and a real SNR-populated run still
renders both SNR map panels correctly (no regression).

Updates the CLAUDE.md pitfall entry documented in the prior commit to
reflect the fix.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds a shared-nebula-ROI, per-scale A/B ratio metric across std, LoG,
wavelet, and Weber contrast (score = median(|detail|) over the region
both images agree is nebula, divided by each image's own background
noise floor at that scale), plus a new Gradient Magnitude / Edge
Sharpness method and noise-normalised display maps so filter A vs B
comparisons aren't confounded by differing absolute noise levels.

Also fixes the Report Inspector's panel catalog, which used a
hardcoded map that had gone stale against STD_KERNEL_SIZES and never
included Weber maps at all — it's now derived dynamically from the
computed panels so nothing can silently disappear from the Inspector
again.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds the exact mean-normalised, ROI-cropped array every std/LoG/wavelet/
Weber/gradient map is computed from as its own "Original Image (ROI)"
panel, so the Report Inspector can show the source image content
alongside a map, pixel-aligned to the same crop.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…lity gate

_extract_esf() rotated the entire ROI and averaged across all rows to build
the edge spread function. scipy.ndimage.rotate(reshape=False) clips the
box's corners for any oblique angle (worst at 45deg, ~30% of the area), and
that zero-padding was getting mixed into the column averages, fabricating a
second, spurious transition in the ESF for otherwise-clean edges.

Averaging is now restricted to the disc inscribed in the ROI square, the
largest region mathematically guaranteed to contain only genuine data at
any rotation angle. A new ESF monotonicity check (_esf_quality) catches
edges that are still contaminated (e.g. a scan line that genuinely crosses
two physical edges, like a thin filament) — in auto-detect mode these are
skipped in favour of the next-strongest gradient peak, and if every
candidate fails, the least-bad one is shown but clearly flagged "low
confidence" in the report rather than silently reporting a meaningless
width.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@brentmantooth
brentmantooth merged commit 1807261 into main Jul 12, 2026
6 checks passed
@brentmantooth
brentmantooth deleted the dev_NebSynth branch July 17, 2026 12:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant