#include #include #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(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>(stp - stp_days).count(); int C = static_cast(2.0 - y / 100.0 + y / 400.0); int E = static_cast(365.25 * (y + 4716.0)), F = static_cast(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& 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(_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; 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()); size_t npix = _acqParams->roiWidth * _acqParams->roiHeight; _imageBufferRows = static_cast(std::ceil(npix / _manager->_dimCCD[0])); size_t sz = _imageBufferRows * _manager->_dimCCD[0]; try { 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, _imageBufferRows, _imageBuffer.get(), _imageBufferSize, (char*)color_space), log_str); isAcqInProgress = false; } catch (...) { isAcqInProgress = false; throw; } // ------- save to FITS file -------- if (_acqParams->filename.empty()) { _manager->logWarn("An empty FITS filename is given! Do not save acquired image!"); return; } 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; // int naxis = 2; 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); fits_write_img(fitsFilePtr, TUSHORT, 1, (LONGLONG)npix, _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); 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, -12, "Julian day of exposure start time", &status); fits_update_key_dbl(fitsFilePtr, "MJD", jd - 2400000.5, -12, "Modified Julian day of exposure start time", &status); if (_acqParams->startTime < _acqParams->abortTime) { // acquisition was aborted std::chrono::duration real_exp = _acqParams->abortTime - _acqParams->startTime; _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, -2, "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[80]; char kname[8]; int k_type; for (auto& s : _manager->_permanentFitsKeywords) { 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]; } } fits_update_card(fitsFilePtr, kname, card, &status); } // keyword from user (may update template file and permanent keywords!) for (auto& s : _manager->_currentFitsKeywords) { 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]; } } fits_update_card(fitsFilePtr, kname, card, &status); } } // 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); 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); } }); } 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!"); } }