Skip to content
Closed
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
21 changes: 18 additions & 3 deletions Config/slicecamd.cfg.in
Original file line number Diff line number Diff line change
Expand Up @@ -84,17 +84,32 @@ FINE_ACQUIRE_BACKGROUND=(80 165 30 210)
#
FINE_ACQUIRE_MIN_SAMPLES=3

# FINE_ACQUIRE_PREC=<arcsec>
# MAD scatter threshold per axis (arcsec). A correction is only commanded when
# the per-axis scatter of the collected samples is within this; a noisier batch
# is discarded and re-gathered rather than moving on an untrustworthy median.
#
FINE_ACQUIRE_PREC=0.4

# FINE_ACQUIRE_SETTLE_FRAMES=<n>
# <n> frames discarded after each telescope move to allow settling before
# centroid measurements resume.
#
FINE_ACQUIRE_SETTLE_FRAMES=2

# FINE_ACQUIRE_SETTLE_SEC=<sec>
# Seconds to wait after a commanded telescope move before evaluating the next
# frame. The move plus CCD readout takes time; acting on a frame grabbed mid-
# move/mid-settle gives a wrong correction. Time-based, independent of cadence.
#
FINE_ACQUIRE_SETTLE_SEC=4

# FINE_ACQUIRE_GAIN=<g>
# Proportional gain <g> = {0..1} applied to the commanded offset when the residual
# is at or below FINE_ACQUIRE_GAIN_THRESHOLD arcsec.
# Proportional gain <g> applied to the commanded offset. Use 1.0: a fine-tune
# step should apply the full measured offset -- deliberately under-correcting a
# known offset is never beneficial here.
#
FINE_ACQUIRE_GAIN=0.7
FINE_ACQUIRE_GAIN=1.0

# FINE_ACQUIRE_GAIN_LARGE=<g>
# Proportional gain <g> applied when the residual exceeds FINE_ACQUIRE_GAIN_THRESHOLD.
Expand Down
69 changes: 68 additions & 1 deletion slicecamd/slicecam_interface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,13 @@ namespace Slicecam {
void Interface::do_fineacquire() {
const char* function = "Slicecam::Interface::do_fineacquire";

// After commanding a TCS move, wait settle_sec before evaluating another
// frame. The move plus CCD readout takes time; acting on an image grabbed
// mid-move or mid-settle yields a wrong correction. Time-based so it does
// not depend on the frame cadence.
//
if ( std::chrono::steady_clock::now() < this->fineacquire_state.settle_until ) return;

// skip frames if we are waiting for the telescope to settle after a move
//
if (this->fineacquire_state.settle_frames > 0) {
Expand Down Expand Up @@ -302,6 +309,21 @@ namespace Slicecam {
return;
}

// Reject a duplicate frame. A commanded framegrab does not guarantee the
// image has refreshed; processing the same frame twice double-counts
// samples and can command a second move from a pre-move image (overshoot).
// Skip the frame if a cheap subsample signature matches the last one.
//
{
uint64_t sig = 1469598103934665603ULL; // FNV-1a offset basis
const size_t step = img_data.size() > 4096 ? img_data.size() / 4096 : 1;
for ( size_t i = 0; i < img_data.size(); i += step ) {
sig = ( sig ^ static_cast<uint64_t>( static_cast<int64_t>( img_data[i] ) ) ) * 1099511628211ULL;
}
if ( sig == this->fineacquire_state.last_frame_sig ) return; // identical frame; wait for a fresh one
Comment on lines +319 to +323

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Hash the acquisition ROI instead of fixed strides

When frames are larger than 4096 pixels, this duplicate check hashes only a fixed stride of pixels. For a 1024x1024 frame that samples columns 0/256/512/768, while the configured fine-acquire target is around x=150 (FINE_ACQUIRE_AIMPOINT/ROI), so in emulated or low-noise averaged runs a fresh frame where only the star region changes can produce the same signature and be skipped indefinitely; do_fineacquire() then stops accumulating samples after the first matching signature. Include the centroid ROI/full frame or a real frame counter/timestamp in the signature so legitimate new frames cannot be mistaken for stale ones.

Useful? React with 👍 / 👎.

this->fineacquire_state.last_frame_sig = sig;
}

// find the star centroid near the aim point
//
Point centroid;
Expand Down Expand Up @@ -514,6 +536,21 @@ namespace Slicecam {
}
}

// Never command a move from a noisy solution. We reach here either with the
// scatter within tolerance, or because we hit max_samples. In the latter
// case, if the scatter is still too high the median is not trustworthy:
// discard the batch and re-gather rather than applying a full-gain move to
// an unreliable position (which causes wrong/overshooting moves). This
// matches the reference engine, which refuses to move until precision is met.
//
if ( !scatter_ok ) {
logwrite( function, "fine acquisition: scatter too high to move safely after "
+std::to_string(n)+" samples (scatter=("+std::to_string(sig_dra)+","
+std::to_string(sig_ddec)+") > "+std::to_string(prec)+" arcsec); re-gathering" );
this->fineacquire_state.reset();
return;
}

// select gain: use gain_large when offset is well above the goal threshold
//
const double effective_gain = ( offset_arcsec > this->fineacquire_state.gain_threshold_arcsec )
Expand Down Expand Up @@ -542,9 +579,13 @@ namespace Slicecam {
return;
}

// reset samples and discard settle_count frames for telescope settling
// reset samples; ignore frames for settle_count frames AND for settle_sec
// seconds so the telescope move + readout completes before we measure again
this->fineacquire_state.reset();
this->fineacquire_state.settle_frames = this->fineacquire_state.settle_count;
this->fineacquire_state.settle_until = std::chrono::steady_clock::now()
+ std::chrono::duration_cast<std::chrono::steady_clock::duration>(
std::chrono::duration<double>( this->fineacquire_state.settle_sec ) );
}
/***** Slicecam::Interface::do_fineacquire **********************************/

Expand Down Expand Up @@ -1371,6 +1412,32 @@ namespace Slicecam {
applied++;
}

if ( config.param[entry] == "FINE_ACQUIRE_PREC" ) {
try { this->fineacquire_state.prec_arcsec = std::stod( config.arg[entry] ); }
catch ( const std::exception &e ) {
message.str(""); message << "ERROR invalid FINE_ACQUIRE_PREC "
<< 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_SETTLE_SEC" ) {
try { this->fineacquire_state.settle_sec = std::stod( config.arg[entry] ); }
catch ( const std::exception &e ) {
message.str(""); message << "ERROR invalid FINE_ACQUIRE_SETTLE_SEC "
<< 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_EXPTIME_MIN" ) {
try { this->fineacquire_state.exptime_min = std::stod( config.arg[entry] ); }
catch ( const std::exception &e ) {
Expand Down
7 changes: 5 additions & 2 deletions slicecamd/slicecam_interface.h
Original file line number Diff line number Diff line change
Expand Up @@ -87,13 +87,16 @@ namespace Slicecam {
std::vector<double> ddec_samp; ///< dDEC samples, degrees
int max_samples = 10; ///< samples before evaluating a move
int min_samples = 3; ///< minimum samples before scatter-gated early exit
double prec_arcsec = 0.1; ///< MAD scatter threshold per axis for early exit (arcsec)
double prec_arcsec = 0.4; ///< MAD scatter threshold per axis; never command a move unless scatter is within this (arcsec)
double goal_arcsec = 0.3; ///< convergence threshold, arcsec
double gain = 0.7; ///< gain applied when offset <= gain_threshold_arcsec
double gain = 1.0; ///< gain applied when offset <= gain_threshold_arcsec (full correction; never under-correct a known offset)
double gain_large = 1.0; ///< gain applied when offset > gain_threshold_arcsec
double gain_threshold_arcsec = 2.0; ///< offset above which gain_large is used
int settle_frames = 0; ///< countdown of frames to discard while telescope settles
int settle_count = 2; ///< configured: frames to discard after each move
double settle_sec = 4.0; ///< configured: seconds to wait after a TCS move before evaluating the next frame
std::chrono::steady_clock::time_point settle_until{}; ///< frames are ignored until this time (post-move settle)
uint64_t last_frame_sig = 0; ///< signature of the last processed frame, to reject duplicate (unrefreshed) frames
int consecutive_centroid_failures = 0; ///< counts consecutive centroid failures
// exposure compensation (shared by the reactive trim and, later, autoexpose)
double exptime_min = 0.1; ///< clamp: minimum auto-adjusted exposure (sec)
Expand Down
6 changes: 3 additions & 3 deletions slicecamd/slicecam_math.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ namespace Slicecam {
* Among all qualifying candidates, select the brightest.
*
* Step 4: refine to sub-pixel centroid via iterative Gaussian-
* windowed first-moment (centroid_sigma_pix = 2.0, 12 iterations,
* windowed first-moment (centroid_sigma_pix = 1.2, 12 iterations,
* eps = 0.01 px).
*
* All pixel coordinates are FITS 1-based on input and output.
Expand Down Expand Up @@ -324,12 +324,12 @@ namespace Slicecam {
// --- Step 4: iterative Gaussian-windowed first-moment centroid ---
//
// centroid_halfwin = 4 (CF's --centroid-hw default)
// centroid_sigma_pix = 2.0 (CF's default, NOTE: different from filt_sigma)
// centroid_sigma_pix = 1.2 (matches auto-acq-clean --centroid-sigma 1.2)
// centroid_maxiter = 12 (CF's default)
// centroid_eps_pix = 0.01
//
const int hw = 4;
const double s2 = 2.0 * 2.0; // centroid_sigma_pix^2
const double s2 = 1.2 * 1.2; // centroid_sigma_pix^2

double cx = static_cast<double>( best_x );
double cy = static_cast<double>( best_y );
Expand Down
Loading