From 6e8959e6d5cad8dbf3f0a1c601bc8c26bbc07eac Mon Sep 17 00:00:00 2001 From: Christoffer Fremling Date: Wed, 28 Oct 2026 21:14:58 +0200 Subject: [PATCH] slicecamd: subtract the sky level of the lit area from the displayed slicecam image The L and R slicecam images sit at different sky levels, so ds9 needs a different scale and stretch for each to show background detail. write_image() now subtracts each camera's sky level from the copy it writes to the file, so both display at the same level with one scale, and records the value as BGSUB (raw = data + BGSUB). The camera buffer that fine acquisition and auto-exposure read is not changed. Frames kept with saveframes are copies of the file. No geometry is used, because the lit area moves with slit width, slit offset and binning (DATASEC does not follow it). Lit pixels are those above halfway between the 5th and 90th percentiles of the image, and the sky level is their 3-sigma clipped median (sigma from the MAD). On 13 archived frames (binning 1 and 4, slit 0.36-1.5", offset +-3", 0.1-6 s; night, twilight and day) it is within 0.55 sigma of the histogram mode of the lit sky in both cameras, and an injected saturated star moves it by less than 1 sigma until the star covers ~10% of the frame. It needs the lit area to cover more than 10% of the frame (17-49% in those frames). Co-Authored-By: Claude Opus 5.5 --- slicecamd/slicecam_fits.cpp | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/slicecamd/slicecam_fits.cpp b/slicecamd/slicecam_fits.cpp index 4a79bb35..d2b0faf3 100644 --- a/slicecamd/slicecam_fits.cpp +++ b/slicecamd/slicecam_fits.cpp @@ -1,4 +1,7 @@ #include "slicecam_fits.h" +#include +#include +#include namespace Slicecam { @@ -227,6 +230,28 @@ logwrite( function, file_in ); return ERROR; } + // subtract the sky level of the illuminated area from this copy only, so that L and R + // display at the same level. Illuminated pixels are those above halfway between the 5th + // and 90th percentiles of the image; their level is the 3-sigma clipped median (sigma from + // the median absolute deviation), which stars, even very bright ones, do not move. + // + std::vector lit( std::begin(array), std::end(array) ), dev; + auto nth = []( std::vector &v, double f ) { auto it = v.begin() + long( f*(v.size()-1) ); + std::nth_element( v.begin(), it, v.end() ); return *it; }; + const float threshold = 0.5f * ( nth( lit, 0.05 ) + nth( lit, 0.90 ) ); + lit.erase( std::remove_if( lit.begin(), lit.end(), [threshold]( float x ) { return x <= threshold; } ), lit.end() ); + float bg = 0; + for ( int iter=0; iter < 10 && !lit.empty(); ++iter ) { + bg = nth( lit, 0.5 ); + dev.resize( lit.size() ); + std::transform( lit.begin(), lit.end(), dev.begin(), [bg]( float x ) { return std::abs( x - bg ); } ); + const float clip = 3 * 1.4826f * nth( dev, 0.5 ); + const auto n = lit.size(); + lit.erase( std::remove_if( lit.begin(), lit.end(), [bg,clip]( float x ) { return std::abs( x - bg ) >= clip; } ), lit.end() ); + if ( lit.size() == n ) break; + } + array -= bg; + long fpixel(1); // start with the first pixel always @@ -248,6 +273,7 @@ logwrite( function, file_in ); for ( const auto &keydb : slicecam->fitskeys.keydb ) { this->add_key( keydb.second.keyword, keydb.second.keytype, keydb.second.keyvalue, keydb.second.keycomment ); } + this->imageExt->addKey( "BGSUB", bg, "sky level subtracted (raw = data + BGSUB)" ); // Write and flush to make sure imeage is written to disk //