RaptorEagleV/raptor_eagle_acqproc.cpp

495 lines
21 KiB
C++

#include <fitsio.h>
#include <cmath>
#include <filesystem>
#include "raptor_eagle_ccd.h"
/*******************************************************************************/
/* ======= RaptorEagleCCD::AcquisitionProcess CLASS IMPLEMENTATION ======= */
/*******************************************************************************/
namespace details
{
double JulianDay(const std::chrono::utc_clock::time_point& tm)
{
auto stp = std::chrono::time_point{std::chrono::utc_clock::to_sys(tm)};
const auto stp_days = std::chrono::floor<std::chrono::days>(stp);
const std::chrono::year_month_day ymd{stp_days};
int y = (int)ymd.year();
unsigned m = (unsigned)ymd.month();
unsigned D = (unsigned)ymd.day();
if (m < 3) {
m += 12;
y -= 1;
}
// fraction of day
double df = std::chrono::duration<double, std::ratio<86400>>(stp - stp_days).count();
int C = static_cast<int>(2.0 - y / 100.0 + y / 400.0);
int E = static_cast<int>(365.25 * (y + 4716.0)), F = static_cast<int>(30.6001 * (m + 1.0));
return C + D + E + F - 1524.5 + df;
}
} // namespace details
RaptorEagleCCD::AcquisitionProcess::AcquisitionProcess(RaptorEagleCCD* manager) : _manager(manager)
{
std::stringstream st;
st << std::this_thread::get_id();
_manager->logDebug("Create acquisition process (thread id: {})", st.str());
}
RaptorEagleCCD::AcquisitionProcess::~AcquisitionProcess()
{
std::stringstream st;
st << std::this_thread::get_id();
_manager->logDebug("Delete acquisition process (thread id: {})", st.str());
}
void RaptorEagleCCD::AcquisitionProcess::start(const std::shared_ptr<acq_params_t>& params)
{
// if (isAcqInProgress) {
// throw std::system_error(RaptorEagleCCDError::ERROR_ACQUISITION_IN_PROGRESS);
// }
isAcqInProgress = true;
// _acqParams = std::move(params);
_acqParams = params;
// _acqParams->abortTime = std::chrono::utc_clock::time_point(); // to ensure the time point is in past
_snapAndCopyFuture = std::async(std::launch::async, [self = shared_from_this(), this]() {
std::chrono::milliseconds timeout =
CAMERA_CAPTURE_TIMEOUT_ADD_CONSTANT +
std::chrono::milliseconds(static_cast<std::chrono::milliseconds::rep>(_acqParams->expTime * 1000));
static char color_space[] = "Gray";
std::stringstream st;
st << std::this_thread::get_id();
_manager->logDebug("Arm grabber and wait for acquisition starting trigger (thread id: {}) ...", st.str());
_manager->xclibApiCall(pxd_doSnap(_manager->_cameraUnitmap, 1, timeout.count()),
std::format("pxd_doSnap({}, 1, {})", _manager->_cameraUnitmap, timeout.count()));
_manager->logDebug("Capture is finished (thread id: {})!", st.str());
if ((_acqParams->abortTime > _acqParams->startTime) &&
!_acqParams->saveInAbort) { // abort acquisition was occured! just exit
_manager->logInfo("It seems the acquisition was aborted! Do not save acquired image!");
isAcqInProgress = false;
_status = STATUS_IDLE;
// _status = std::string(CAMERA_ATTR_CAMERA_STATUS_IDLE.begin(), CAMERA_ATTR_CAMERA_STATUS_IDLE.end());
// _manager->_cameraStatus = CAMERA_ATTR_CAMERA_STATUS_IDLE;
return;
}
_manager->logDebug("Image ROI [{}, {}, {}, {}] (binned {}x{})", _acqParams->roiStartX, _acqParams->roiStartY,
_acqParams->roiWidth, _acqParams->roiHeight, _acqParams->binX, _acqParams->binY);
_manager->logDebug("Copy image from grabber to buffer (thread id: {}) ...", st.str());
// compute image dimension
// NOTE: _acqParams->roiWidth and _acqParams->roiHeight are expected in CCD pixels (not binned)!!!
// auto div = std::div(_acqParams->roiWidth - _acqParams->roiStartX, _acqParams->binX);
auto div = std::div(_acqParams->roiWidth, _acqParams->binX);
auto dimx = div.quot + (div.rem ? 1 : 0);
// div = std::div(_acqParams->roiHeight - _acqParams->roiStartY, _acqParams->binY);
div = std::div(_acqParams->roiHeight, _acqParams->binY);
auto dimy = div.quot + (div.rem ? 1 : 0);
// LONGLONG npix = _acqParams->roiWidth * _acqParams->roiHeight;
LONGLONG npix = dimx * dimy;
// NOTE:
auto ldiv = std::lldiv(npix, _manager->_dimCCD[0]);
_imageBufferRows = ldiv.quot + (ldiv.rem ? 1 : 0);
bool gap = false;
if ((_acqParams->roiWidth + _acqParams->roiStartX) < _manager->_dimCCD[0]) {
/* IT SEEMS IF MAX ROI X-COORDINATE IS LESS THAN CCD DIM
* EAGLE CAMERA CONTROLLER SETUP READING FOR WIDTH+1 ROI!!! */
gap = true;
++_imageBufferRows;
}
// _imageBufferRows = static_cast<size_t>(std::ceil(npix / _manager->_dimCCD[0]));
// read size
size_t sz = _imageBufferRows * _manager->_dimCCD[0];
try {
// sz = _manager->_dimCCD[0] * _manager->_dimCCD[1];
if (_imageBufferSize < sz) {
_manager->logDebug("Reallocate image buffer to {} elements", sz);
_imageBufferSize = sz;
_imageBuffer.reset(new ushort[sz]); // may thow std::bad_alloc here!
}
auto log_str = std::format("pxd_readushort({}, 1, 0, 0, -1, {}, {}, {}, \"{}\")", _manager->_cameraUnitmap,
_imageBufferRows, (void*)_imageBuffer.get(), _imageBufferSize, color_space);
// _manager->xclibApiCall(pxd_readushort(_manager->_cameraUnitmap, 1, 0, 0, -1, -1,
// (ushort*)_imageBuffer.get(), _imageBufferSize, (char*)color_space),
// log_str);
_manager->xclibApiCall(pxd_readushort(_manager->_cameraUnitmap, 1, 0, 0, -1, _imageBufferRows,
(ushort*)_imageBuffer.get(), _imageBufferSize, (char*)color_space),
log_str);
isAcqInProgress = false;
} catch (...) {
isAcqInProgress = false;
_status = STATUS_IDLE;
// _status = std::string(CAMERA_ATTR_CAMERA_STATUS_IDLE.begin(), CAMERA_ATTR_CAMERA_STATUS_IDLE.end());
// _manager->_cameraStatus = CAMERA_ATTR_CAMERA_STATUS_IDLE;
throw;
}
// ------- save to FITS file --------
if (_acqParams->filename.empty()) {
_manager->logWarn("An empty FITS filename is given! Do not save acquired image!");
_status = STATUS_IDLE;
// _status = std::string(CAMERA_ATTR_CAMERA_STATUS_IDLE.begin(), CAMERA_ATTR_CAMERA_STATUS_IDLE.end());
// _manager->_cameraStatus = CAMERA_ATTR_CAMERA_STATUS_IDLE;
return;
}
_status = STATUS_SAVE;
// _status = std::string(CAMERA_ATTR_CAMERA_STATUS_IDLE.begin(), CAMERA_ATTR_CAMERA_STATUS_IDLE.end()) +
// std::format(" {}", _acqParams->filename);
// _manager->_cameraStatus = CAMERA_ATTR_CAMERA_STATUS_SAVE;
std::string fname{"!"}; // to overwrite existing file
fname += _acqParams->filename;
_manager->logInfo("Try to save FITS file with name '{}'", _acqParams->filename);
_manager->logTrace("Actual saving filename: {}", fname);
fitsfile* fitsFilePtr;
int status = 0;
const int naxis = 2;
long naxes[naxis] = {dimx, dimy};
// long naxes[2];
// naxes[0] = _acqParams->roiWidth;
// naxes[1] = _acqParams->roiHeight;
char err_str[100];
fits_clear_errmsg();
_manager->logDebug("Create an empty FITS file ...");
fits_create_file(&fitsFilePtr, fname.c_str(), &status);
_manager->logDebug("Create primary FITS HDU (dim = [{}, {}])", naxes[0], naxes[1]);
fits_create_img(fitsFilePtr, USHORT_IMG, 2, naxes, &status);
_manager->logDebug("Write {} pixels to the HDU ...", npix);
if (gap) {
/* IT SEEMS IF MAX ROI X-COORDINATE IS LESS THAN CCD DIM
* EAGLE CAMERA CONTROLLER SETUP READING FOR WIDTH+1 ROI!!!
* SO ONE NEEDS TO WRITE IMAGE PER ROW WITH SKIPPING EXTRA PIXEL
* AT THE END OF EACH ROW
*/
auto ptr = _imageBuffer.get();
for (size_t i = 0; i < dimy; ++i) {
fits_write_img(fitsFilePtr, TUSHORT, i * dimx + 1, dimx, _imageBuffer.get(), &status);
}
} else {
fits_write_img(fitsFilePtr, TUSHORT, 1, npix, (void*)_imageBuffer.get(), &status);
}
// helper to convert std::string_view to C-lang null-terminated string
auto sv2cstr = [](const std::string_view& sv) {
static char buffer[72];
size_t i = 0;
for (const char& el : sv) {
buffer[i++] = el;
}
buffer[i] = '\0';
return (const char*)buffer;
};
// the hardcoded camera setup-related keywords
fits_update_key_str(fitsFilePtr, "ORIGIN", "SAO RAS", NULL, &status);
fits_update_key_str(fitsFilePtr, "CREATOR", "RaptorEagleV control software", NULL, &status);
// fits_update_key_str(fitsFilePtr, "FILE", _acqParams->filename.c_str(), "Original filename", &status);
std::filesystem::path pt = _acqParams->filename;
fits_update_key_str(fitsFilePtr, "FILE", pt.filename().c_str(), "Original filename", &status);
fits_write_date(fitsFilePtr, &status);
std::string comm;
std::string str = std::format("{0:%F}T{0:%H}:{0:%M}:{0:%S}", _acqParams->startTime);
double jd = details::JulianDay(_acqParams->startTime);
fits_update_key_str(fitsFilePtr, "DATE-OBS", str.c_str(), "Start of the exposure in UTC", &status);
fits_update_key_dbl(fitsFilePtr, "JD", jd, -15, "Julian day of exposure start time", &status);
fits_update_key_dbl(fitsFilePtr, "MJD", jd - 2400000.5, -15, "Modified Julian day of exposure start time",
&status);
if (_acqParams->startTime < _acqParams->abortTime) { // acquisition was aborted
std::chrono::duration<double> real_exp = _acqParams->abortTime - _acqParams->startTime;
_manager->logTrace("Acq. start time: {}; acq. abort time: {}", _acqParams->startTime,
_acqParams->abortTime);
_manager->logDebug("Exposure was stopped! Recompute the exposure duration to {} secs", real_exp.count());
_acqParams->expTime = real_exp.count();
}
fits_update_key_dbl(fitsFilePtr, "EXPTIME", _acqParams->expTime, -3, "Integration time in seconds", &status);
fits_update_key_lng(fitsFilePtr, "CRPIX1", 1, "Reference pixel along X-axis", &status);
fits_update_key_lng(fitsFilePtr, "CRVAL1", 1, "Physical value of the reference pixel", &status);
fits_update_key_lng(fitsFilePtr, "STARTX", _acqParams->roiStartX + 1, // "+1" to convert to FITS notation
"Image area start pixel along X-axis [CCD pixel]", &status);
fits_update_key_lng(fitsFilePtr, "CRPIX2", 1, "Reference pixel along Y-axis", &status);
fits_update_key_lng(fitsFilePtr, "CRVAL2", 1, "Physical value of the reference pixel", &status);
fits_update_key_lng(fitsFilePtr, "STARTY", _acqParams->roiStartY + 1, // "+1" to convert to FITS notation
"Image area start pixel along Y-axis [CCD pixel]", &status);
fits_update_key_lng(fitsFilePtr, "XBIN", _acqParams->binX, "Horizontal binning", &status);
fits_update_key_lng(fitsFilePtr, "YBIN", _acqParams->binY, "Vertical binning", &status);
str = std::format("{}x{}", _acqParams->binX, _acqParams->binY);
fits_update_key_str(fitsFilePtr, "BINNING", str.c_str(), "Binning mode (XBINxYBIN)", &status);
comm = "Shutter state: ";
comm += _acqParams->shutterState == CAMERA_ATTR_SHUTTER_STATE_CLOSED ? "always closed"
: _acqParams->shutterState == CAMERA_ATTR_SHUTTER_STATE_OPEN ? "always open"
: _acqParams->shutterState == CAMERA_ATTR_SHUTTER_STATE_EXP ? "open for exposure duration time"
: "unknown";
fits_update_key_str(fitsFilePtr, "SHUTTER", sv2cstr(_acqParams->shutterState), comm.c_str(), &status);
comm = "Camera readout rate ";
comm += _acqParams->readRate == CAMERA_ATTR_READ_RATE_FAST ? "(2 MHz)"
: _acqParams->readRate == CAMERA_ATTR_READ_RATE_SLOW ? "(75 kHz)"
: "unknown";
fits_update_key_str(fitsFilePtr, "READRATE", sv2cstr(_acqParams->readRate), comm.c_str(), &status);
fits_update_key_str(fitsFilePtr, "READMODE", sv2cstr(_acqParams->readMode), "Camera readout mode", &status);
fits_update_key_str(fitsFilePtr, "GAIN", sv2cstr(_acqParams->gain), "Preamp gain", &status);
str = _acqParams->tecState ? "ON" : "OFF";
fits_update_key_str(fitsFilePtr, "TECSTATE", str.c_str(), "Thermoelectrical cooler state", &status);
fits_update_key_dbl(fitsFilePtr, "CCDTEMP", _acqParams->ccdTemp, -2, "CCD chip temperature in Celsius",
&status);
fits_update_key_dbl(fitsFilePtr, "PCBTEMP", _acqParams->pcbTemp, -2, "PCB temperature in Celsius", &status);
fits_update_key_dbl(fitsFilePtr, "TECSETPT", _acqParams->tecSetPoint, -5,
"TEC set point temperature in Celsius", &status);
if (!status) {
// keywords from user template file
if (_acqParams->templateFilename.size() && !status) {
_manager->logDebug("Copy keywords from '{}' template file", _acqParams->templateFilename);
fits_write_key_template(fitsFilePtr, _acqParams->templateFilename.c_str(), &status);
if (status) { // ignore possible errors
fits_get_errstatus(status, err_str);
_manager->logWarn(
"An error occured while copy keywords from the template file '{}' (err = {}, msg = {})! "
"Ignore!",
_acqParams->templateFilename, status, err_str);
status = 0;
}
}
// permanent keywords (may update keywords from template file!)
char card[81];
char kname[9] = " ";
int k_type;
if (_acqParams->permanentKeywords.size()) {
_manager->logDebug("Copy {} permanent keywords", _acqParams->permanentKeywords.size());
} else {
_manager->logDebug("There is no one permanent keyword! Skip!");
}
for (auto& s : _acqParams->permanentKeywords) {
fits_parse_template(s.data(), card, &k_type, &status);
if (status) { // ignore possible errors
fits_get_errstatus(status, err_str);
_manager->logWarn(
"An error occured while writing permanent keyword card [{}] (err = {}, msg = {})! "
"Ignore!",
s, status, err_str);
status = 0;
} else {
for (int i = 0; i < 8; ++i) {
kname[i] = card[i];
}
}
_manager->logTrace("Try to update [{}] FITS card (name = '{}')", card, kname);
fits_update_card(fitsFilePtr, kname, card, &status);
if (status) {
fits_get_errstatus(status, err_str);
_manager->logWarn(
"An error occured while updating FITS card (name = '{}') (err = {}, msg = {})! Skip!", kname,
status, err_str);
} else {
_manager->logTrace("The FITS card (name = '{}') was updated successfully", kname);
}
}
// keyword from user (may update template file and permanent keywords!)
if (_acqParams->currentKeywords.size()) {
_manager->logDebug("Copy {} current keywords", _acqParams->currentKeywords.size());
} else {
_manager->logDebug("There is no one current keyword! Skip!");
}
for (auto& s : _acqParams->currentKeywords) {
fits_parse_template(s.data(), card, &k_type, &status);
if (status) { // ignore possible errors
fits_get_errstatus(status, err_str);
_manager->logWarn(
"An error occured while writing user keyword card [{}] (err = {}, msg = {})! "
"Ignore!",
s, status, err_str);
status = 0;
} else {
for (int i = 0; i < 8; ++i) {
kname[i] = card[i];
}
}
_manager->logTrace("Try to update [{}] FITS card (name = '{}')", card, kname);
fits_update_card(fitsFilePtr, kname, card, &status);
if (status) {
fits_get_errstatus(status, err_str);
_manager->logWarn(
"An error occured while updating FITS card (name = '{}') (err = {}, msg = {})! Skip!", kname,
status, err_str);
} else {
_manager->logTrace("The FITS card (name = '{}') was updated successfully", kname);
}
}
}
// hardcoded camera hardware version info keywords
fits_update_key_ulng(fitsFilePtr, "SERNUM", (ULONGLONG)_manager->_cameraSerialNumber, "Camera serial number",
&status);
str = std::format("{}.{}", _manager->_microVersion[0], _manager->_microVersion[1]);
fits_update_key_str(fitsFilePtr, "MICROVER", str.c_str(), "Camera microcontroller version", &status);
str = std::format("{}.{}", _manager->_FPGAVersion[0], _manager->_FPGAVersion[1]);
fits_update_key_str(fitsFilePtr, "FPGAVER", str.c_str(), "Camera FPGA version", &status);
str = std::format("{}", _manager->_buildDate);
fits_update_key_str(fitsFilePtr, "BUILDDAT", str.c_str(), "Camera build date, YY-MM-DD", &status);
fits_update_key_str(fitsFilePtr, "BUILDCOD", sv2cstr(_manager->_buildCode), "Camera build code", &status);
fits_close_file(fitsFilePtr, &status);
_status = STATUS_IDLE;
// _status = std::string(CAMERA_ATTR_CAMERA_STATUS_IDLE.begin(), CAMERA_ATTR_CAMERA_STATUS_IDLE.end());
// _manager->_cameraStatus = CAMERA_ATTR_CAMERA_STATUS_IDLE;
fits_get_errstatus(status, err_str);
if (status) {
_manager->logError("An error occured while writing FITS file '{}'! FITS status = {} ({})",
_acqParams->filename, status, err_str);
} else {
_manager->logInfo("FITS file '{}' is saved", _acqParams->filename);
}
_imageBuffer.release();
});
}
void RaptorEagleCCD::AcquisitionProcess::stop(bool save)
{
if (isAcqInProgress) {
_manager->logInfo("Abort current acquisition process!");
_acqParams->saveInAbort = save;
auto bytes = _manager->readRegisters({0xD4}); // curent trigger mode register
bytes[0] |= CL_ABORT_CURRENT_EXP; // set abort acquisition bit
_acqParams->abortTime = std::chrono::utc_clock::now();
_manager->writeRegisters({0xD4}, bytes);
} else {
_manager->logWarn("There was no active acquisition process! Ignore!");
}
}
std::string RaptorEagleCCD::AcquisitionProcess::status()
{
std::lock_guard lock(_statusMutex);
switch (_status) {
case STATUS_IDLE:
_statusString = std::format("{}", CAMERA_ATTR_CAMERA_STATUS_IDLE);
break;
case STATUS_ACQ: {
std::chrono::duration<double> curr_exp = std::chrono::utc_clock::now() - _acqParams->startTime;
auto remain_exp = _acqParams->expTime - curr_exp.count();
if (remain_exp < 0) {
_status = STATUS_READ;
_statusString = std::format("{} {}", CAMERA_ATTR_CAMERA_STATUS_READ, _acqParams->filename);
} else {
_statusString = std::format("{} {}", CAMERA_ATTR_CAMERA_STATUS_ACQ, remain_exp);
}
break;
}
case STATUS_READ:
_statusString = std::format("{} {}", CAMERA_ATTR_CAMERA_STATUS_READ, _acqParams->filename);
break;
case STATUS_SAVE:
_statusString = std::format("{} {}", CAMERA_ATTR_CAMERA_STATUS_SAVE, _acqParams->filename);
break;
default:
_statusString = "UNKNOWN"; // is should not be!!!
}
return _statusString;
}