Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions Config/slicecamd.cfg.in
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,15 @@ FINE_ACQUIRE_SATURATION=55000
FINE_ACQUIRE_COUNTS_BRIGHT=10000
FINE_ACQUIRE_COUNTS_BRIGHT_GOAL=2500

# FINE_ACQUIRE_MIN_SNR=<snr>
# Peak signal-to-noise (background-subtracted peak / background sigma) at or above
# which a source is considered well enough defined to centroid reliably. Above this
# fine-acquire and autoexpose stop RAISING the exposure even if the source is still
# below FINE_ACQUIRE_COUNTS_FAINT.
# Must be >=2 if set, or leave unset to disable SNR requirement and use only count band.
#
FINE_ACQUIRE_MIN_SNR=3

# FINE_ACQUIRE_AUTOEXPOSE_WINDOW=<n>
# Number of frames the pre-acquisition auto-exposure averages before each
# decision.
Expand Down
54 changes: 48 additions & 6 deletions slicecamd/slicecam_interface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -305,12 +305,12 @@ namespace Slicecam {
// find the star centroid near the aim point
//
Point centroid;
double peak_raw = 0.0, top10 = 0.0;
double peak_raw = 0.0, top10 = 0.0, peak_snr = 0.0;

if ( Math::calculate_centroid( img_data, ncols, nrows,
this->fineacquire_state.bg_region,
this->fineacquire_state.aimpoint,
centroid, peak_raw, top10 ) != NO_ERROR ) {
centroid, peak_raw, top10, peak_snr ) != NO_ERROR ) {
const int max_failures = 3 * this->fineacquire_state.max_samples;

// ----- Auto-Adjust exposure time while finding centroid ---------------
Expand Down Expand Up @@ -405,6 +405,7 @@ namespace Slicecam {
this->fineacquire_state.dra_samp.push_back( offsets.first );
this->fineacquire_state.ddec_samp.push_back( offsets.second );
this->fineacquire_state.top10_samp.push_back( top10 );
this->fineacquire_state.snr_samp.push_back( peak_snr );

const int n = static_cast<int>( this->fineacquire_state.dra_samp.size() );
const int max_samples = this->fineacquire_state.max_samples;
Expand Down Expand Up @@ -481,11 +482,25 @@ namespace Slicecam {
std::sort( sorted_top10.begin(), sorted_top10.end() );
const double median_top10 = sorted_top10[ sorted_top10.size() / 2 ];

std::vector<double> sorted_snr = this->fineacquire_state.snr_samp;
std::sort( sorted_snr.begin(), sorted_snr.end() );
const double median_snr = sorted_snr.empty() ? 0.0 : sorted_snr[ sorted_snr.size() / 2 ];

const double cur = this->camera.andor.empty() ? 0.0
: this->camera.andor.begin()->second->camera_info.exptime;
const double new_exptime = this->banded_exptime( cur, median_top10 );
double new_exptime = this->banded_exptime( cur, median_top10 );

// meeting SNR requirement overrides count requirement
//
if ( new_exptime > cur && std::isfinite( this->fineacquire_state.min_snr ) &&
median_snr >= this->fineacquire_state.min_snr ) {
logwrite( function, "exptime raise vetoed: peak SNR "+std::to_string(median_snr)
+" >= "+std::to_string(this->fineacquire_state.min_snr)+" (adequate for centroid)" );
new_exptime = cur;
}

// banded_exptime returns cur when in band; only act on a material change
//
if ( std::abs( new_exptime - cur ) >= 0.02 ) {
logwrite( function, "exptime trim "+std::to_string(cur)+" -> "+std::to_string(new_exptime)
+" s (top10="+std::to_string(median_top10)
Expand Down Expand Up @@ -651,15 +666,16 @@ namespace Slicecam {
// is fine -- it just means "no source in this frame")
//
Point centroid;
double peak_raw = 0.0, top10 = 0.0;
double peak_raw = 0.0, top10 = 0.0, peak_snr = 0.0;
const bool detected = ( Math::calculate_centroid( img_data, ncols, nrows,
this->fineacquire_state.bg_region,
this->default_aimpoint,
centroid, peak_raw, top10 ) == NO_ERROR );
centroid, peak_raw, top10, peak_snr ) == NO_ERROR );

if (detected) {
this->autoexpose_state.top10_window.push_back( top10 );
if (peak_raw > this->autoexpose_state.max_peak_raw) this->autoexpose_state.max_peak_raw = peak_raw;
if (peak_snr > this->autoexpose_state.max_snr) this->autoexpose_state.max_snr = peak_snr;
this->autoexpose_state.detect_count++;
}

Expand Down Expand Up @@ -695,7 +711,11 @@ namespace Slicecam {
const double estimate = window[idx];
this->autoexpose_state.no_detect_count = 0;

const double new_exptime = this->banded_exptime( cur, estimate );
double new_exptime = this->banded_exptime( cur, estimate );
if ( new_exptime > cur && std::isfinite( this->fineacquire_state.min_snr ) &&
this->autoexpose_state.max_snr >= this->fineacquire_state.min_snr ) {
new_exptime = cur; // already well above noise; do not raise
}
if (std::abs(new_exptime - cur) >= 0.02) {
logwrite(function, "exptime "+std::to_string(cur)+" -> "+std::to_string(new_exptime)
+" s (top10="+std::to_string(estimate)
Expand Down Expand Up @@ -1416,6 +1436,19 @@ namespace Slicecam {
applied++;
}

if ( config.param[entry] == "FINE_ACQUIRE_MIN_SNR" ) {
try { this->fineacquire_state.min_snr = std::stod( config.arg[entry] ); }
catch ( const std::exception &e ) {
message.str(""); message << "ERROR invalid FINE_ACQUIRE_MIN_SNR "
<< config.arg[entry] << ": " << e.what();
logwrite( function, message.str() );
return ERROR;
}
message.str(""); message << "SLICECAMD:config:" << config.param[entry] << "=" << config.arg[entry];
logwrite( function, message.str() );
applied++;
}

if ( config.param[entry] == "FINE_ACQUIRE_COUNTS_BRIGHT" ) {
try { this->fineacquire_state.counts_bright = std::stod( config.arg[entry] ); }
catch ( const std::exception &e ) {
Expand Down Expand Up @@ -1515,6 +1548,15 @@ namespace Slicecam {
}
}

// min_snr, when set, must be at least 2
// NAN (unset) disables SNR override
//
if ( std::isfinite( this->fineacquire_state.min_snr ) &&
this->fineacquire_state.min_snr < 2.0 ) {
logwrite( function, "ERROR FINE_ACQUIRE_MIN_SNR must be >= 2 when set" );
return ERROR;
}

message.str(""); message << "applied " << applied << " configuration lines to the slicecam interface";
logwrite(function, message.str());

Expand Down
11 changes: 7 additions & 4 deletions slicecamd/slicecam_interface.h
Original file line number Diff line number Diff line change
Expand Up @@ -103,10 +103,12 @@ namespace Slicecam {
double counts_faint_goal = NAN; ///< faint-mode brightness goal
double counts_bright = NAN; ///< above this lower toward counts_bright_goal
double counts_bright_goal = NAN; ///< bright-mode brightness goal
int autoexpose_window = 2; ///< frames per pre-acquisition auto-exposure decision
std::vector<double> top10_samp; ///< per-frame top-10%-mean brightness samples, parallel to dra_samp
double min_snr = NAN; ///< peak SNR at/above which faint raises are vetoed; NAN disables
int autoexpose_window = 2; ///< frames per pre-acquisition auto-exposure decision
std::vector<double> top10_samp; ///< per-frame top-10%-mean brightness samples, parallel to dra_samp
std::vector<double> snr_samp; ///< per-frame peak-SNR samples, parallel to top10_samp

void reset() { dra_samp.clear(); ddec_samp.clear(); top10_samp.clear();
void reset() { dra_samp.clear(); ddec_samp.clear(); top10_samp.clear(); snr_samp.clear();
settle_frames = 0; consecutive_centroid_failures = 0; }
bool is_valid() const noexcept {
return !which.empty() && aimpoint.is_valid() && bg_region.is_valid();
Expand Down Expand Up @@ -148,11 +150,12 @@ namespace Slicecam {
struct AutoExpState {
std::vector<double> top10_window; ///< per-frame top-10%-mean values in the window
double max_peak_raw = 0.0; ///< max raw peak in the window (saturation)
double max_snr = 0.0; ///< max peak-SNR in the window
int detect_count = 0; ///< detections in the window
int frames_seen = 0; ///< frames accumulated in the window
int no_detect_count = 0; ///< consecutive empty windows
int settle_frames = 0; ///< skip stale frames after an exptime change
void start_window() { top10_window.clear(); max_peak_raw = 0.0; detect_count = 0; frames_seen = 0; }
void start_window() { top10_window.clear(); max_peak_raw = 0.0; max_snr = 0.0; detect_count = 0; frames_seen = 0; }
void reset() { start_window(); no_detect_count = 0; settle_frames = 0; }
} autoexpose_state;

Expand Down
7 changes: 6 additions & 1 deletion slicecamd/slicecam_math.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,8 @@ namespace Slicecam {
Point aimpoint,
Point &centroid,
double &peak_raw,
double &top10_mean ) {
double &top10_mean,
double &peak_snr ) {
if ( image.empty() || ncols <= 0 || nrows <= 0 ) return ERROR;

// Convert 1-based inclusive ROI to 0-based, clamped
Expand Down Expand Up @@ -379,6 +380,10 @@ namespace Slicecam {
// sensitive to seeing and intra-pixel position) that also tracks
// exposure time linearly, which is what the scaling relies on.
peak_raw = best_val + bkg;

// peak_snr is background-subtracted peak over the background noise sigma.
//
peak_snr = ( sigma > 0.0 ) ? best_val / sigma : 0.0;
{
const long bxp = static_cast<long>( std::floor( cx ) );
const long byp = static_cast<long>( std::floor( cy ) );
Expand Down
3 changes: 2 additions & 1 deletion slicecamd/slicecam_math.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@ namespace Slicecam {
Point aimpoint,
Point &centroid,
double &peak_raw, // raw ADU at source peak (saturation test)
double &top10_mean ); // mean of top 10% bkg-subtracted pixels (scaling)
double &top10_mean, // mean of top 10% bkg-subtracted pixels (scaling)
double &peak_snr ); // background-subtracted peak / background sigma
/**
* @brief convert pixel coordinates to sky coordinates using WCS keys
*/
Expand Down
Loading