From 45ec246672fe0dd7920a2a37152a1ca8d149ffc1 Mon Sep 17 00:00:00 2001 From: David Hale Date: Wed, 18 Sep 2024 11:36:47 -0700 Subject: [PATCH 1/5] Initial preparation for "next generation" camerad 2.0, adds source for Reed's FITS engine to repo but doesn't utilize it yet, cleans up and renames a few things but no functional changes. So far, this will compile and continue to function as the "main" branch does. --- camerad/CMakeLists.txt | 11 + camerad/archon.cpp | 87 +-- camerad/archon.h | 8 +- camerad/fits_file.h | 1160 ++++++++++++++++++++++++++++++++++++++++ common/common.h | 23 +- utils/network.cpp | 72 ++- utils/sendcmd.cpp | 2 +- utils/utilities.cpp | 22 +- utils/utilities.h | 3 + 9 files changed, 1315 insertions(+), 73 deletions(-) create mode 100644 camerad/fits_file.h diff --git a/camerad/CMakeLists.txt b/camerad/CMakeLists.txt index cbd40e25..28bdad7e 100644 --- a/camerad/CMakeLists.txt +++ b/camerad/CMakeLists.txt @@ -130,6 +130,9 @@ add_library(camera STATIC find_library(CCFITS_LIB CCfits NAMES libCCfits PATHS /usr/local/lib) find_library(CFITS_LIB cfitsio NAMES libcfitsio PATHS /usr/local/lib) +find_package( OpenCV REQUIRED ) +include_directories( ${OpenCV_INCLUDE_DIRS} ) + find_package(Threads) add_executable(camerad @@ -137,6 +140,9 @@ add_executable(camerad ${INTERFACE_INCLUDES} ) +find_package(Boost REQUIRED COMPONENTS thread chrono) +include_directories(${BOOST_INCLUDES}) + # ---------------------------------------------------------------------------- # Everyone gets this: # ---------------------------------------------------------------------------- @@ -151,6 +157,11 @@ target_link_libraries(camerad ${CMAKE_THREAD_LIBS_INIT} ${CCFITS_LIB} ${CFITS_LIB} + ${OpenCV_LIBS} + Boost::thread + Boost::chrono + pthread + ${BOOST_INCLUDES} ) target_link_libraries(camerad ${CARC_BASE}) diff --git a/camerad/archon.cpp b/camerad/archon.cpp index 0a691ad6..a2de7c70 100644 --- a/camerad/archon.cpp +++ b/camerad/archon.cpp @@ -33,9 +33,9 @@ namespace Archon { this->taplines = 0; this->configlines = 0; this->logwconfig = false; - this->image_data = nullptr; - this->image_data_bytes = 0; - this->image_data_allocated = 0; + this->archon_buf = nullptr; + this->archon_buf_bytes = 0; + this->archon_buf_allocated = 0; this->is_longexposure_set = false; this->is_window = false; this->is_autofetch = false; @@ -104,6 +104,7 @@ namespace Archon { } /**************** Archon::Interface::interface ******************************/ + /***** Archon::Interface::do_power ******************************************/ /** * @brief set/get the power state @@ -599,41 +600,41 @@ namespace Archon { /***** Archon::Interface::configure_controller ******************************/ - /**************** Archon::Interface::prepare_image_buffer *******************/ + /**************** Archon::Interface::prepare_archon_buffer ******************/ /** - * @fn prepare_image_buffer - * @brief prepare image_data buffer, allocating memory as needed + * @fn prepare_archon_buffer + * @brief prepare archon_buf buffer, allocating memory as needed * @param none * @return NO_ERROR if successful or ERROR on error * */ - long Interface::prepare_image_buffer() { - std::string function = "Archon::Interface::prepare_image_buffer"; + long Interface::prepare_archon_buffer() { + std::string function = "Archon::Interface::prepare_archon_buffer"; std::stringstream message; // If there is already a correctly-sized buffer allocated, // then don't do anything except initialize that space to zero. // - if ( (this->image_data != nullptr) && - (this->image_data_bytes != 0) && - (this->image_data_allocated == this->image_data_bytes) ) { - memset(this->image_data, 0, this->image_data_bytes); - message.str(""); message << "initialized " << this->image_data_bytes << " bytes of image_data memory"; + if ( (this->archon_buf != nullptr) && + (this->archon_buf_bytes != 0) && + (this->archon_buf_allocated == this->archon_buf_bytes) ) { + memset(this->archon_buf, 0, this->archon_buf_bytes); + message.str(""); message << "initialized " << this->archon_buf_bytes << " bytes of archon_buf memory"; logwrite(function, message.str()); } else { // If memory needs to be re-allocated, delete the old buffer - if (this->image_data != nullptr) { - logwrite(function, "deleting old image_data buffer"); - delete [] this->image_data; - this->image_data=nullptr; + if (this->archon_buf != nullptr) { + logwrite(function, "deleting old archon_buf buffer"); + delete [] this->archon_buf; + this->archon_buf=nullptr; } // Allocate new memory // - if (this->image_data_bytes != 0) { - this->image_data = new char[this->image_data_bytes]; - this->image_data_allocated=this->image_data_bytes; - message.str(""); message << "allocated " << this->image_data_bytes << " bytes for image_data"; + if (this->archon_buf_bytes != 0) { + this->archon_buf = new char[this->archon_buf_bytes]; + this->archon_buf_allocated=this->archon_buf_bytes; + message.str(""); message << "allocated " << this->archon_buf_bytes << " bytes for archon_buf"; logwrite(function, message.str()); } else { @@ -644,7 +645,7 @@ namespace Archon { return NO_ERROR; } - /**************** Archon::Interface::prepare_image_buffer *******************/ + /**************** Archon::Interface::prepare_archon_buffer ******************/ /**************** Archon::Interface::connect_controller *********************/ @@ -802,10 +803,10 @@ namespace Archon { // Free the memory // - if (this->image_data != nullptr) { + if (this->archon_buf != nullptr) { logwrite(function, "releasing allocated device memory"); - delete [] this->image_data; - this->image_data=nullptr; + delete [] this->archon_buf; + this->archon_buf=nullptr; } // On success, write the value to the log and return @@ -1941,11 +1942,11 @@ namespace Archon { return (ERROR); } - // allocate image_data in blocks because the controller outputs data in units of blocks + // allocate archon_buf in blocks because the controller outputs data in units of blocks // - this->image_data_bytes = (uint32_t) floor( ((this->camera_info.image_memory * num_detect) + BLOCK_LEN - 1 ) / BLOCK_LEN ) * BLOCK_LEN; + this->archon_buf_bytes = (uint32_t) floor( ((this->camera_info.image_memory * num_detect) + BLOCK_LEN - 1 ) / BLOCK_LEN ) * BLOCK_LEN; - if (this->image_data_bytes == 0) { + if (this->archon_buf_bytes == 0) { this->camera.log_error( function, "image data size is zero! check NUM_DETECT, HORI_AMPS, VERT_AMPS in .acf file" ); error = ERROR; } @@ -2812,7 +2813,7 @@ namespace Archon { // Read the data from the connected socket into memory, one block at a time // - ptr_image = this->image_data; + ptr_image = this->archon_buf; totalbytesread = 0; std::cerr << "reading bytes: "; for (block=0; blockcamera_info.frame_type = frame_type; /*** - // Check that image buffer is prepared //TODO should I call prepare_image_buffer() here, automatically? + // Check that image buffer is prepared //TODO should I call prepare_archon_buffer() here, automatically? // - if ( (this->image_data == nullptr) || - (this->image_data_bytes == 0) ) { + if ( (this->archon_buf == nullptr) || + (this->archon_buf_bytes == 0) ) { this->camera.log_error( function, "image buffer not ready" ); // return ERROR; } - if ( this->image_data_allocated != this->image_data_bytes ) { + if ( this->archon_buf_allocated != this->archon_buf_bytes ) { message.str(""); message << "incorrect image buffer size: " - << this->image_data_allocated << " bytes allocated but " << this->image_data_bytes << " needed"; + << this->archon_buf_allocated << " bytes allocated but " << this->archon_buf_bytes << " needed"; this->camera.log_error( function, message.str() ); // return ERROR; } ***/ - error = this->prepare_image_buffer(); + error = this->prepare_archon_buffer(); if (error == ERROR) { logwrite( function, "ERROR: unable to allocate an image buffer" ); return ERROR; @@ -3059,7 +3060,7 @@ namespace Archon { // Read the data from the connected socket into memory, one block at a time // - ptr_image = this->image_data; + ptr_image = this->archon_buf; totalbytesread = 0; std::cerr << "reading bytes: "; for (block=0; blockimage_data; // cast here to 32b + cbuf32 = (uint32_t *)this->archon_buf; // cast here to 32b // Write each amplifier as a separate extension // @@ -3337,13 +3338,13 @@ namespace Archon { // case 16: { if (this->camera_info.datatype == USHORT_IMG) { // raw - cbuf16 = (uint16_t *)this->image_data; // cast to 16b unsigned int + cbuf16 = (uint16_t *)this->archon_buf; // cast to 16b unsigned int // error = fits_file.write_image(cbuf16, this->fits_info); // write the image to disk //TODO error = this->fits_file.write_image(cbuf16, this->camera_info); // write the image to disk if ( error != NO_ERROR ) { this->camera.log_error( function, "writing 16-bit raw image to disk" ); } } else if (this->camera_info.datatype == SHORT_IMG) { - cbuf16s = (int16_t *)this->image_data; // cast to 16b signed int + cbuf16s = (int16_t *)this->archon_buf; // cast to 16b signed int int16_t *ibuf = nullptr; ibuf = new int16_t[ this->camera_info.section_size ]; for (long pix=0; pix < this->camera_info.section_size; pix++) { @@ -3408,7 +3409,7 @@ namespace Archon { // Cast the image buffer of chars into integers to convert four 8-bit values // into a 16-bit value // - cbuf16 = (unsigned short *)this->image_data; + cbuf16 = (unsigned short *)this->archon_buf; fitsfile *FP = nullptr; int status = 0; @@ -4655,9 +4656,9 @@ namespace Archon { // Allocate image buffer once this->camera_info.frame_type = Camera::FRAME_IMAGE; - error = this->prepare_image_buffer(); + error = this->prepare_archon_buffer(); if (error == ERROR) { - logwrite( function, "ERROR: unable to allocate an image buffer" ); + logwrite( function, "ERROR: unable to allocate archon buffer" ); return ERROR; } diff --git a/camerad/archon.h b/camerad/archon.h index 273104a3..50191b9f 100644 --- a/camerad/archon.h +++ b/camerad/archon.h @@ -146,9 +146,9 @@ namespace Archon { float heater_target_min; //!< minimum heater target temperature float heater_target_max; //!< maximum heater target temperature - char *image_data; //!< image data buffer - uint32_t image_data_bytes; //!< requested number of bytes allocated for image_data rounded up to block size - uint32_t image_data_allocated; //!< allocated number of bytes for image_data + char *archon_buf; //!< image data buffer holds FETCHed Archon data + uint32_t archon_buf_bytes; //!< requested number of bytes allocated for archon_buf rounded up to block size + uint32_t archon_buf_allocated; //!< allocated number of bytes for archon_buf std::atomic archon_busy; //!< indicates a thread is accessing Archon std::mutex archon_mutex; @@ -165,7 +165,7 @@ namespace Archon { // static long interface(std::string &iface); //!< get interface type long configure_controller(); //!< get configuration parameters - long prepare_image_buffer(); //!< prepare image_data, allocating memory as needed + long prepare_archon_buffer(); //!< prepare archon_buf, allocating memory as needed long connect_controller(const std::string &devices_in); //!< open connection to archon controller long disconnect_controller(); //!< disconnect from archon controller long load_timing(std::string acffile); //!< load specified ACF then LOADTIMING diff --git a/camerad/fits_file.h b/camerad/fits_file.h new file mode 100644 index 00000000..e97a11dc --- /dev/null +++ b/camerad/fits_file.h @@ -0,0 +1,1160 @@ +/** + \file fits_file.h + \brief FITS file handling engine + \details This header file contains the functions and variables used to create + and manage FITS files in the ROBO software package. The FITS files are + created with a threaded system that allows multiple files to be written + rapidly enough to be useful for telemetry of the fastest CCD cameras in the + instrument. A standard FITS header is also created, using both parameters + passed to the functions and files written into the status area of the disk + with information from subsystems that are necessary for inclusion in the FITS + headers. Supports writing single FITS images and data cubes, and compressed + images. + \author Dr. Reed L. Riddle riddle@caltech.edu + \note This is a slimmed down version of the FITS data management system from + the Robo-AO project. It is a method to write FITS data files from a system, + such as an AO system, that generates files at a high frequency. This package + has removed many features from the software that aren't necessary if you just + want to write a FITS file to keep this focused on only that operation. + + Modified (slightly) to fit into camerad (D.Hale) + */ + +#pragma once + +// System include files +# include +# include +# include +# include +# include +# include +# include +# include + +// Local include files +#include "utilities.h" +#include "camera.h" + +// Value flags for the supported FITS compression modes +constexpr int FITS_COMPRESSION_NONE=0; +constexpr int FITS_COMPRESSION_RICE=RICE_1; +constexpr int FITS_COMPRESSION_GZIP=GZIP_1; +constexpr int FITS_COMPRESSION_PLIO=PLIO_1; + +/// Maximum file size supported for image data cubes = 1GB +const unsigned long MAX_IMAGE_DATA_SIZE = 1073741824; + +/** \class FITS_cube_frame + \brief FITS cube frame object + \details This class contains the information that goes into a frame of a data + cube. These are put into a vector and written out by the FITS_file class in + its cube writing thread. + */ +template +class FITS_cube_frame +{ +public: + + /** \var std::valarray array + \details Array containing FITS frame data */ + std::valarray array; + + /** \var std::string timestamp + \details Timestamp when the frame was created */ + std::string timestamp; + + /** \var int sequence + \details Sequence number for the frame */ + int sequence; + + /** \var Camera::Information camera_info + \details Camera information for the frame */ + Camera::Information camera_info; + + + /**************** FITS_file::FITS_cube_frame ****************/ + /** + This function constructs the FITS_cube_frame class. Note that the array is + automatically filled by the valarray constructor when this class is + constructed. + */ + FITS_cube_frame(T * data, int size, std::string timestamp_in, int sequence_in, + Camera::Information camera_info_in): array(data, size) + { + // Copy the camera info from the input + this->camera_info = camera_info_in; + + // Copy the timestamp from the input + this->timestamp = timestamp_in; + + // Copy the sequence number from the input + this->sequence = sequence_in; + }; + /**************** FITS_file::FITS_cube_frame ****************/ + +}; + + +/** \class FITS_file + \brief FITS image file container + \details This class handles the interactions with data that are written into + the standard FITS format images. It handles single images and data cubes. Note + that it does not read data, only writes...instruments don't read data, but it + should be asimple matter to add reading to this class at some point. + \note This is a template class, based on the bit type of the data (int, float, + etc). + */ +template +class FITS_file +{ +private: + + /** \var mutable boost::mutex cache_mutex + \details Mutex to block the variables in threads */ + boost::timed_mutex cache_mutex; + + // Note that older C++ libraries use auto_ptr, so uncommend that line and + // comment out the unique_ptr line if your library doesn't support unique_ptr + /** \var std::auto_ptr pFits + \details Pointer to FITS data container. */ +// std::auto_ptr pFits; // used with older C++ versions + std::unique_ptr pFits; + + /** \var CCfits::ExtHDU* imageExt + \details FITS image extension header unit */ + CCfits::ExtHDU* imageExt; + + /** \var std::string fits_name + \details Name for the FITS file */ + std::string fits_name; + + /** \var bool file_open + \details Flag that the FITS file is open for writing */ + bool file_open; + + /** \var int num_frames + \details Number of frames in data cube */ + int num_frames; + + /** \var int compression + \details Flag for compresion type used in writing files */ + int compression; + + /** \var Camera::Information open_info + \details CAmera information object used to open a new file */ + Camera::Information open_info; + + /** \var int open_sequence + \details Sequence number submitted when opening a new file */ + int open_sequence; + + /** \var std::string open_timestamp + \details Timestamp submitted when opening a new file */ + std::string open_timestamp; + + /** \var float telrad + \details Reported telescope RA (in degrees), needed for WCS entry */ + float telrad; + + /** \var float teldecd + \details Reported telescope dec (in degrees), needed for WCS entry */ + float teldecd; + + /** \var boost::thread fits_cube_thread + \details Boost thread variable for the cube writing thread */ + boost::thread fits_cube_thread; + + /** \var bool run_cube_thread + \details Flag that the FITS file is open for writing */ + bool run_cube_thread; + + /** \var bool iscube + \details Flag that this FITS object writes data cubes */ + bool iscube; + + /** \var std::vector > cube_frames + \details Queue containing the information for each frame of the data cube */ + std::deque > cube_frames; + + /** \var std::vector > cube_cache + \details Cache of incoming frames that will be written to a data cube */ + std::deque > cube_cache; + + /** \var bool cube_thread_running + \details Flag the the thread writing the cubes is running */ + bool cube_thread_running; + + /** \var long cube_size + \details The size of the data cube in bytes (more or less) */ + long cube_size; + + /** \var bool last_image + \details Flag that the last image has been delivered to the FITS cube */ + bool last_image; + + /** \var long total_frames + \details Number of frames written for all data cubes processed */ + long total_frames; + + /** \var int max_size + \details This is the max allowed size for any single data cube */ + int max_size; + + /** \var int max_cube_frames + \details This is the max allowed number of frames that can be written to any + single data cube */ + int max_cube_frames; + + + /**************** FITS_file::swap ****************/ + /** + This is used to swap between two FITS_file class objects. This is used when + constructing class objects with assignment or copy construction. + \param [first] The first object to swap (swap into this) + \param [second] The second object to swap (swap from this) + */ + friend void swap(FITS_file & first, FITS_file & second) + { +// // Enable ADL (not necessary in our case, but good practice) +// using std::swap; + + // By swapping the members of two classes, the two classes are effectively + // swapped. + std::swap(first.log, second.log); + std::swap(first.pFits, second.pFits); + std::swap(first.imageExt, second.imageExt); + std::swap(first.fits_name, second.fits_name); + std::swap(first.file_open, second.file_open); + std::swap(first.num_frames, second.num_frames); + std::swap(first.compression, second.compression); + std::swap(first.open_info, second.open_info); + std::swap(first.open_sequence, second.open_sequence); + std::swap(first.open_timestamp, second.open_timestamp); + std::swap(first.telrad, second.telrad); + std::swap(first.teldecd, second.teldecd); + std::swap(first.fits_cube_thread, second.fits_cube_thread); + std::swap(first.run_cube_thread, second.run_cube_thread); + std::swap(first.iscube, second.iscube); + std::swap(first.cube_frames, second.cube_frames); + std::swap(first.cube_cache, second.cube_cache); + std::swap(first.cube_thread_running, second.cube_thread_running); + std::swap(first.cube_size, second.cube_size); + std::swap(first.last_image, second.last_image); + std::swap(first.total_frames, second.total_frames); + std::swap(first.max_size, second.max_size); + std::swap(first.max_cube_frames, second.max_cube_frames); + } + /**************** FITS_file::swap ****************/ + + + /**************** FITS_file::print_compression ****************/ + /** + Prints a message to show what compression is being used + */ + std::string print_compression(int compression) + { + std::stringstream message; // Temporary string for compression message + + if (compression == FITS_COMPRESSION_NONE){ + message << "(" << FITS_COMPRESSION_NONE << ":FITS_COMPRESSION_NONE)"; + } + else if (compression == FITS_COMPRESSION_RICE){ + message << "(" << FITS_COMPRESSION_RICE << ":FITS_COMPRESSION_RICE)"; + } + else if (compression == FITS_COMPRESSION_GZIP){ + message << "(" << FITS_COMPRESSION_GZIP << ":FITS_COMPRESSION_GZIP)"; + } + else if (compression == FITS_COMPRESSION_PLIO){ + message << "(" << FITS_COMPRESSION_PLIO << ":FITS_COMPRESSION_PLIO)"; + } + else { + message << "(" << 9999 << ":FITS_COMPRESSION_UNKNOWN)"; + } + return(message.str()); + } + /**************** FITS_file::print_compression ****************/ + + + /**************** FITS_file::initialize_class ****************/ + /** + Initializes values for the class variables + */ + void initialize_class() + { + this->file_open = false; + this->num_frames = 0; + this->compression = FITS_COMPRESSION_NONE; + this->telrad = 9999; + this->teldecd = 9999; + this->iscube = false; + this->run_cube_thread = false; + this->cube_thread_running = false; + this->cube_size = 0; + this->last_image = false; + this->total_frames = 0; + this->max_size = MAX_IMAGE_DATA_SIZE; + this->max_cube_frames = 10000; + } + /**************** FITS_file::initialize_class ****************/ + + + /**************** FITS_file::open_file ****************/ + /** + Open a FITS file to write data into it. This function is used for data cubes + but could be used for writing single files if you want to do it the hard way. + \param [camera_info] The camera_info class holds image parameters such as size, + pixel scales, detector information + \param [timestamp] The timestamp when the image was taken + \param [sequence] A sequence number if the image is one in a sequence + \param [compress] Compression to use on the image (NONE is default) + \note None. + */ + int open_file(Camera::Information & camera_info, + std::string & timestamp, int sequence, + int compress = FITS_COMPRESSION_NONE) + { + // Set the function information for logging + std::string function("FITS_file::open_file"); + std::stringstream message; + + message << "opening FITS file image for " << camera_info.image_name; + logwrite(function, message.str()); + message.str(""); + + try { + + // Set the class compression flag + this->compression = FITS_COMPRESSION_NONE; + if (compress != FITS_COMPRESSION_NONE){ + if (compress == FITS_COMPRESSION_RICE){ + this->compression = FITS_COMPRESSION_RICE; + } + else if (compress == FITS_COMPRESSION_GZIP){ + this->compression = FITS_COMPRESSION_GZIP; + } + else if (compress == FITS_COMPRESSION_PLIO){ + this->compression = FITS_COMPRESSION_PLIO; + } + else { + message << "ERROR unknown compression type: " << compress; + logwrite(function, message.str()); + message.str(""); + } + } + + // If the image is a data cube, we don't want to write anything into the + // primary image, so set the axes to 0 to avoid allocating the space. If + // this is a single frame image, then we need to allocate the space, + // unless it is compressed in which case we also set the primary image + // to a null image. + long naxes[2]; // Local variable of image axes size + int num_axis = 2; // Set the number of axes to 2, even on data cubes + if (this->iscube == true){ + naxes[0] = 0; + naxes[1] = 0; + num_axis = 0; + } + else { + naxes[0] = camera_info.naxes[0]; + naxes[1] = camera_info.naxes[1]; + num_axis = 2; + } + + this->fits_name = camera_info.fits_name; + + // Check that we can write the file, because CCFits will crash if not + std::ofstream checkfile (this->fits_name.c_str()); + if (checkfile.is_open()) { + checkfile.close(); + std::remove(this->fits_name.c_str()); + } + else { + message << "ERROR unable to create file " << this->fits_name; + logwrite(function, message.str()); + return(ERROR); + } + + // Allocate the FITS file container, which holds all of the information + // used by CCfits to write a file + this->pFits.reset( new CCfits::FITS(this->fits_name, camera_info.bitpix, + num_axis, naxes) ); + + // Create the primary image header + this->make_header(this->fits_name.substr(camera_info.directory.length()+1), + timestamp, sequence, camera_info); + + // Set the compression. You have to do this after allocating the FITS file + if (this->compression == FITS_COMPRESSION_RICE){ + this->pFits->setCompressionType(RICE_1); + } + else if (this->compression == FITS_COMPRESSION_GZIP){ + this->pFits->setCompressionType(GZIP_1); + } + else if (this->compression == FITS_COMPRESSION_PLIO){ + this->pFits->setCompressionType(PLIO_1); + } + } + // Catch any errors from creating the FITS file and log them + catch (CCfits::FitsException&){ + message << "ERROR unable to open FITS file " << this->fits_name; + logwrite(function, message.str()); + return(ERROR); + } + + // Set the flags for an open file without frames written to it + this->file_open = true; + this->num_frames = 0; + + // If this is a data cube, start the cube writing thread and set initial + // cube parameters + if (this->iscube == true){ + this->cube_size = 0; + if (this->cube_thread_running == false){ + this->open_info = camera_info; + this->open_sequence = sequence; + this->open_timestamp = timestamp; + this->run_cube_thread = true; + this->fits_cube_thread = boost::thread(&FITS_file::write_cube_thread, + this); + } + } + + // Write a log message and return a success value + message << "opened FITS file " << this->fits_name << " with compression " + << print_compression(this->compression); + logwrite(function, message.str()); + return(NO_ERROR); + } + /**************** FITS_file::open_file ****************/ + + + /**************** FITS_file::close_file ****************/ + /** + Closes a FITS image file; this version is meant to close a single frame file. + */ + int close_file() + { + // Set the function information for logging + std::string function("FITS_file::close_file"); + std::stringstream message; + + // Log what we're doing + message << "closing FITS file " << this->fits_name; + logwrite(function, message.str()); + message.str(""); + + // Add a header keyword for the time the file was written (right now!) + this->pFits->pHDU().addKey("DATE", get_timestamp(), "FITS file write date"); + + // Write the checksum + this->pFits->pHDU().writeChecksum(); + + // Deallocate the CCfits object and close the FITS file + this->pFits->destroy(); + + // Reset flags to prepare for the next file + this->file_open = false; + + // Log that the file closed successfully + message << "successfully closed FITS file " << this->fits_name; + logwrite(function, message.str()); + return(NO_ERROR); + } + /**************** FITS_file::close_file ****************/ + + + /**************** FITS_file::close_cube ****************/ + /** + Close a FITS cube file. + */ + int close_cube() + { + // Set the function information for logging + std::string function("FITS_file::close_file"); + std::stringstream message; + + if (this->file_open == false){ + logwrite(function, "FITS cube file already closed"); + return(NO_ERROR); + } + + // Log what we're doing + message << "closing FITS data cube " << this->fits_name; + logwrite(function, message.str()); + + // Stop the cube writing thread only after the final image is received and + // processed + if (this->last_image == true && this->cube_frames.size() == 0 && + this->cube_cache.size() == 0){ + logwrite(function, "closing the last cube file..."); + this->run_cube_thread = false; + } + + // Add a header keyword for the time the file was written (right now!), the + // number of frames written into the cube, and the time the system stopped + // taking cube data + this->pFits->pHDU().addKey("NFRAMES", this->num_frames, + "number of frames in FITS file"); + this->pFits->pHDU().addKey("DATE", get_timestamp(), "FITS file write date"); + + // Write the checksum + this->pFits->pHDU().writeChecksum(); + + // Deallocate the CCfits object and close the FITS file + this->pFits->destroy(); + + // Reset flags to prepare for the next file + this->file_open = false; + + // Log that the file closed successfully + message.str(""); + message << "successfully closed FITS data cube " << this->fits_name + << ", wrote " << this->num_frames << " cube frames and " + << this->cube_size << " image bytes, frames waiting: " + << this->cube_frames.size() << " " << this->cube_cache.size(); + logwrite(function, message.str()); + return(NO_ERROR); + } + /**************** FITS_file::close_cube ****************/ + + + /**************** FITS_file::write_single_image ****************/ + /** Writes the FITS image to a file container. This writes a single frame + FITS file. + \param [data] The image data array, a one dimensional array + \param [timestamp] The timestamp when the image was taken + \param [sequence] A sequence number if the image is one in a sequence + \param [camera_info] The camera_info class holds image parameters such as + size, pixel scales, detector information + \param [compress] Compression to use on the image (NONE is default) + */ + int write_single_image(T * data, std::string timestamp, int sequence, + Camera::Information camera_info, + int compress = FITS_COMPRESSION_NONE) + { + // Set the function information for logging + std::string function("FITS_file::write_single_image"); + std::stringstream message; + + // Set the FITS system to verbose mode so it writes error messages + CCfits::FITS::setVerboseMode(true); + + // Open the FITS file + if (this->open_file(camera_info, timestamp, sequence, compress) != NO_ERROR){ + message << "ERROR failed to open FITS file \"" << camera_info.image_name << "\", aborting"; + logwrite(function, message.str()); + return(ERROR); + } + + try { + // Move the data into a valarray, necessary to wite it using CCFITS + std::valarray array(data, camera_info.image_size); + + // Set the first pixel value to 1, this tells the FITS system where it + // starts writing (we don't start at anything but 1) + long fpixel = 1; + + // Write the primary image into the FITS file if compression is off + if (this->compression == FITS_COMPRESSION_NONE){ + this->pFits->pHDU().write(fpixel, camera_info.image_size, array); + } + // With compression, write to an extension + else { + CCfits::ExtHDU& pseudo_primary = this->pFits->extension(1); + pseudo_primary.write(fpixel, camera_info.image_size, array); + } + + // Flush the FITS container to make sure the image is written to disk + this->pFits->flush(); + } + // Catch any errors from the FITS system and log them + catch (CCfits::FitsError & error){ + message << "ERROR FITS file error thrown: " << error.message(); + logwrite(function, message.str()); + return(ERROR); + } + catch( std::exception &e ) { + message << "ERROR other exception thrown: " << e.what(); + logwrite(function, message.str()); + return(ERROR); + } + + // Close the FITS file + if (this->close_file() != NO_ERROR){ + message << "ERROR failed to close FITS file properly: " << this->fits_name; + logwrite(function, message.str()); + return(ERROR); + } + + return(NO_ERROR); + } + /**************** FITS_file::write_single_image ****************/ + + + /**************** FITS_file::write_cube_thread ****************/ + /** Writes FITS data cubes. This is run in a thread, started when the FITS + file is opened, and runs until the final image is received. If the data + cube goes over the maximum allowed size, the thread closed the cube and + creates a new one with the same name. + */ + void write_cube_thread() + { + std::string function("FITS_file::write_cube_thread"); + std::stringstream message; + int error; + + // If the thread is already flagged as running, stop here + if (this->cube_thread_running == true){ + logwrite(function, "thread is already running, stopping!"); + return; + } + + // Set the thread running flag + this->cube_thread_running = true; + + // Log that the thread is starting + logwrite(function, "starting thread to write cube frames..."); + + // Set some initial values for parameters used in writing the thread + bool finished = false; + this->cube_size = 0; + this->last_image = false; + this->total_frames = 0; + + // Loop until the flag to finish processing is set + while (this->run_cube_thread == true && finished == false){ + + // Pull data out of the cube cache if there is something in it. This is + // done here to avoid the writing process slowing down the process putting + // images in the cache. + if (this->cube_cache.size() > 0){ + + // Pull a maximum of 5 images over. This limits how much the system is + // accessing the cache to avoid slowing the writing process. + int size = this->cube_cache.size(); + if (size > 5){ + size = 5; + } + + // Get the front image out of the queue, then clear it. The mutex is + // locked while clearing. There are two options here, do it one image + // at a time, or shift all five and then delete all at once. This does + // the former to lock the mutex for as short of a time as possible for + // each image. + try { + for (int i = 0; i < size; i++) { + if (this->cache_mutex.try_lock_for(boost::chrono::milliseconds(1))){ + boost::lock_guard lock(this->cache_mutex, + boost::adopt_lock_t()); + this->cube_frames.push_back(this->cube_cache[0]); + this->cube_cache.pop_front(); + } + } + } + catch ( std::exception &e ) { + message.str(""); message << "ERROR exception occured: " << e.what(); + logwrite( function, message.str() ); + } + } + + // Don't do anything if there are no frames available + if (this->cube_frames.size() == 0){ + // Time out to save resources, this should be fast enough for data + // acquisition expected by this system + timeout(0.00001); + // Check flags and set an exit flag if the image process is complete and + // all images have been written out. + if (this->last_image == true && this->cube_frames.size() == 0 && + this->cube_cache.size() == 0){ + finished = true; + } + continue; + } + + // Open the cube file if it's not already open + if (this->file_open == false){ + logwrite(function, "opening a new cube file..."); + error = this->open_file(this->open_info, this->open_timestamp, + this->open_sequence, this->compression); + if (error != NO_ERROR){ + message << "ERROR failed to open FITS file \"" << this->fits_name + << "\", error code: " << error; + logwrite(function, message.str()); + message.str(""); + continue; + } + } + + // Write the image to the cube file + try { + // Set the FITS system to verbose mode so it writes error messages + CCfits::FITS::setVerboseMode(true); + + // Set the first pixel value to 1, this tells the FITS system where it + // starts writing (we don't start at anything but 1) + long fpixel = 1; + + // We set an image key to the frame number in the cube, just for tracking + std::stringstream image_key; + image_key << this->num_frames + 1; + + // Add the new image to the image extension + this->imageExt = this->pFits->addImage(image_key.str(), + this->cube_frames[0].camera_info.bitpix, + this->cube_frames[0].camera_info.naxes); + // Make the image cube header + this->make_cube_header(this->imageExt, this->cube_frames[0].timestamp, + this->cube_frames[0].camera_info); + + // Write the image extension into the cube + this->imageExt->write(fpixel, this->cube_frames[0].camera_info.image_size, + this->cube_frames[0].array); + + // Flush the FITS container to make sure the image is written to disk + this->pFits->flush(); + + #ifdef LOGLEVEL_DEBUG + message << "[DEBUG] wrote extension " << this->cube_frames[0].camera_info.extension; + logwrite( function, message.str() ); message.str(""); + #endif + + // Increment the frame counter + this->num_frames++; + this->total_frames++; + + // Add size of the new frame to the size of the cube + this->cube_size += this->cube_frames[0].camera_info.image_memory; + + this->cube_frames.pop_front(); + } + // Catch any errors from the FITS system and log them + catch (CCfits::FitsError & error){ + message << "ERROR FITS file error thrown: " << error.message(); + logwrite(function, message.str()); + message.str(""); + } + + // For cubes with lots of files, log the progress for writing the cube + if (this->num_frames % 1000 == 0){ + message << "number of frames written: " << this->num_frames << " size: " + << this->cube_size << " bytes, frames waiting: " + << this->cube_frames.size() << " " << this->cube_cache.size(); + logwrite(function, message.str()); + message.str(""); + } + +/******** temporarily (?) disable size and frame limit + + // If the cube has grown too large, close it + if (this->cube_size >= this->max_size || + this->num_frames >= this->max_cube_frames){ + logwrite(function, "closing the cube file..."); + #ifdef LOGLEVEL_DEBUG + message << "[DEBUG] cube_size=" << this->cube_size << " max_size=" << this->max_size + << " num_frames=" << this->num_frames << " max_frames=" << this->max_cube_frames; + logwrite( function, message.str() ); message.str(""); + #endif + error = this->close_cube(); + if (error != NO_ERROR){ + message << "ERROR there was a problem closing the FITS cube, error code: " + << error; + logwrite(function, message.str()); + message.str(""); + } + } +********/ + + // If the flag for a complete observation is set, and if the cache and + // cube queues are empty, flag that the system can finish. + if (this->last_image == true && this->cube_frames.size() == 0 && + this->cube_cache.size() == 0){ + finished = true; + logwrite(function, "flag set to finish writing the data cube"); + } + } + + // Close the final cube + error = this->close_cube(); + if (error != NO_ERROR){ + message << "ERROR there was a problem closing the FITS cube, error code: " + << error; + logwrite(function, message.str()); + message.str(""); + } + + // Log that the thread is complete and set the flag + message << "stopping cube frame writing thread, total frames written: " + << this->total_frames; + logwrite(function, message.str()); + this->cube_thread_running = false; + } + /**************** FITS_file::write_cube_thread ****************/ + + + /**************** FITS_file::make_header ****************/ + /** + Creates the primary FITS file header, which should have an extensive amount + of information about the file. The function looks for header file stubs that + are created in the common_info.status_dir directory by the various systems + and uses them to create the header file. + \param [filename] The base name of the image + \param [timestamp] The timestamp when the image was taken + \param [sequence] A sequence number if the image is one in a sequence + \param [camera_info] The camera_info class holds image parameters such as size, + pixel scales, detector information + \note None. + */ + void make_header(std::string filename, std::string timestamp, int sequence, + Camera::Information & camera_info) + { + // Set the function information for logging + std::string function("FITS_file::make_header"); + std::stringstream message; + + // Set these to the bad value, that'a default that will be changed if the + // telescope information is passed in when reading the header stub files + this->telrad = 9999; + this->teldecd = 9999; + + // Write the header keys to the FITS header + try { + // Add timestamp for the image + this->pFits->pHDU().addKey("DATE-OBS", timestamp, "Time of observation"); + + // Put the sequence into the header if this is one of multiple FITS files + // that are part of the same observation + if (sequence >= 0){ + this->pFits->pHDU().addKey("SEQUENCE", sequence, "Sequence number"); + } + + // Write information from camera_info structure into header + this->pFits->pHDU().addKey("DETECTOR", camera_info.detector, + "Detector controller"); + this->pFits->pHDU().addKey("DETSOFT", camera_info.detector_software, + "Detector software version"); + this->pFits->pHDU().addKey("DETFIRM", camera_info.detector_firmware, + "Detector firmware version"); + this->pFits->pHDU().addKey("EXPOSURE", camera_info.exposure_time.value(), + "Total Exposure Time ("+camera_info.exposure_time.unit()+")"); + this->pFits->pHDU().addKey("MODE_NUM", camera_info.current_observing_mode, + "Mode identifying key"); + message << camera_info.binning[0] << " " << camera_info.binning[1]; + this->pFits->pHDU().addKey("DETSUM", message.str(), "DET binning"); + message.str(""); + this->pFits->pHDU().addKey("DET_ID", camera_info.det_id, + "ID value of detector"); + this->pFits->pHDU().addKey("DETNAME", camera_info.det_name, + "Detector name or serial number"); + this->pFits->pHDU().addKey("PIXSCALE", camera_info.pixel_scale, + "Pixel scale, in arcsec per pixel"); + this->pFits->pHDU().addKey("FILENAME", filename, "File name"); + this->pFits->pHDU().addKey("ORIGNAME", filename, "Original file name"); + this->pFits->pHDU().addKey("FRAMENUM", camera_info.framenum, + "Detector frame number"); + } + // Catch any errors from the FITS system and log them + catch (CCfits::FitsError & err) { + message << "ERROR creating FITS header: " << err.message(); + logwrite(function, message.str()); + } + } + /**************** FITS_file::make_header ****************/ + + + /**************** FITS_file::make_cube_header ****************/ + /** + Writes the FITS header for image extensions in a data cube. Each cube should + have a header, which is limited to just the information required for the + individual cube images; information common to all of the images (such as + telescope coordinates or sensor data) should be written in the primary + header. + \param [imageExt] The FITS image extension being written + \param [timestamp] The timestamp when the image was taken + \param [camera_info] The camera_info class holds image parameters such as + size, pixel scales, detector information + \note None. + */ + void make_cube_header(CCfits::ExtHDU * imageExt, std::string & timestamp, + Camera::Information & camera_info) + { + // Set the function information for logging + std::string function("FITS_file::make_cube_header"); + std::stringstream message; + std::stringstream temp; + + try { + // Write the timestamp of observation for the image extension. + this->imageExt->addKey("UTC", timestamp, "Time of observation"); + + // Write information about the detector and amplifier + this->imageExt->addKey("DET_ID", camera_info.det_id, + "ID value of detector"); + this->imageExt->addKey("DETNAME", camera_info.det_name, + "Detector name or serial number"); + this->imageExt->addKey("AMP_ID", camera_info.amp_id, + "ID value of amplifier"); + this->imageExt->addKey("AMP_NAME", camera_info.amp_name, + "Name of amplifier"); + this->imageExt->addKey("GAIN", camera_info.det_gain, "Gain e-/adu"); + this->imageExt->addKey("READNOI", camera_info.read_noise, + "Read noise e-"); + this->imageExt->addKey("DARKCUR", camera_info.dark_current, + "Dark current e-/s @ 150 K"); + + // Write the detector information + temp << "[" << camera_info.detsize[0] << ":" + << camera_info.detsize[1] << "," + << camera_info.detsize[2] << ":" + << camera_info.detsize[3] << "]"; + this->imageExt->addKey("DETSIZE", temp.str(), "detector size (pixels)"); + temp.str(""); + + temp << "[" << camera_info.ccdsec[0] << ":" + << camera_info.ccdsec[1] << "," + << camera_info.ccdsec[2] << ":" + << camera_info.ccdsec[3] << "]"; + this->imageExt->addKey("CCDSEC", temp.str(), "Detector section"); + temp.str(""); + + temp << "[" << camera_info.region_of_interest[0] << ":" + << camera_info.region_of_interest[1] << "," + << camera_info.region_of_interest[2] << ":" + << camera_info.region_of_interest[3] << "]"; + this->imageExt->addKey("ROISEC", temp.str(), "Region of interest"); + temp.str(""); + + temp << "[" << camera_info.ampsec[0] << ":" + << camera_info.ampsec[1] << "," + << camera_info.ampsec[2] << ":" + << camera_info.ampsec[3] << "]"; + this->imageExt->addKey("AMPSEC", temp.str(), "Amplifier section"); + temp.str(""); + + temp << "[" << camera_info.trimsec[0] << ":" + << camera_info.trimsec[1] << "," + << camera_info.trimsec[2] << ":" + << camera_info.trimsec[3] << "]"; + this->imageExt->addKey("TRIMSEC", temp.str(), "Trim section"); + temp.str(""); + + temp << "[" << camera_info.datasec[0] << ":" + << camera_info.datasec[1] << "," + << camera_info.datasec[2] << ":" + << camera_info.datasec[3] << "]"; + this->imageExt->addKey("DATASEC", temp.str(), "Data section"); + temp.str(""); + + temp << "[" << camera_info.biassec[0] << ":" + << camera_info.biassec[1] << "," + << camera_info.biassec[2] << ":" + << camera_info.biassec[3] << "]"; + this->imageExt->addKey("BIASSEC", temp.str(), "Bias section"); + temp.str(""); + + temp << "[" << camera_info.detsec[0] << ":" + << camera_info.detsec[1] << "," + << camera_info.detsec[2] << ":" + << camera_info.detsec[3] << "]"; + this->imageExt->addKey("DETSEC", temp.str(), "Detector section"); + temp.str(""); + + // Add the time that the FITS frame was added to the cube + this->imageExt->addKey("DATE", get_timestamp(), "FITS frame write date"); + } + // Catch any errors from the FITS system and log them + catch (CCfits::FitsError & err) { + message << "ERROR creating FITS header: " << err.message(); + logwrite(function, message.str()); + } + } + /**************** FITS_file::make_cube_header ****************/ + + +public: + + + /**************** FITS_file::FITS_file ****************/ + /** + This function constructs the FITS file class + */ + FITS_file(bool cube_state_in = false) + { + // Initialize the class + this->initialize_class(); + + // Set flag to make this FITS object output cubes + this->iscube = cube_state_in; + logwrite("FITS_file::FITS_file","constructed"); + }; + /**************** FITS_file::FITS_file ****************/ + + + /**************** FITS_file::~FITS_file ****************/ + /** + This is the destructor for the FITS file class. + */ + ~FITS_file(){ + }; + /**************** FITS_file::~FITS_file ****************/ + + + /**************** FITS_file::FITS_file ****************/ + /** + This is the copy constructor for the FITS file class. + */ + FITS_file(const FITS_file & in_fitsfile) + { + this->swap(*this, in_fitsfile); + } + /**************** FITS_file::FITS_file ****************/ + + + /**************** FITS_file::FITS_file ****************/ + /** + This is the assignment constructor for the FITS file class. + */ + FITS_file & operator = (FITS_file & in_fitsfile) + { + this->swap(*this, in_fitsfile); + + return *this; + } + /**************** FITS_file::FITS_file ****************/ + + + /**************** FITS_file::add_user_key ***************/ + /** + Add a user-defined keyword to the primary header of an open FITS file. + This is invoked by a "FITS:" entry in the .acf file. All parameters + are passed in as strings. + \param [keyword] the keyword + \param [type] type of FITS key (INT, REAL, STRING) + \param [value] value of FITS key + \param [comment] comment + */ + void add_user_key(std::string keyword, std::string type, std::string value, + std::string comment) { + std::string function("FITS_file::add_user_key"); + std::stringstream message; + + // Add the type based on the kind of keyword sent, integer, float, string + if (type.compare("INT") == 0) { + this->pFits->pHDU().addKey(keyword, atoi(value.c_str()), comment); + } + else if (type.compare("REAL") == 0) { + this->pFits->pHDU().addKey(keyword, atof(value.c_str()), comment); + } + else if (type.compare("STRING") == 0) { + this->pFits->pHDU().addKey(keyword, value, comment); + } + // If the type us unknown, log an error and don't write the keyword + else { + message << "ERROR unknown type: " << type << " for user keyword: " << keyword + << "=" << value << " / " << comment; + logwrite(function, message.str()); + } + } + /**************** FITS_file::add_user_key ***************/ + + + /**************** FITS_file::write_image ****************/ + /** + Main image writing function. This gets called anytime that a FITS image is + written into the FITS file. It has options to write a full image, or to + add a frame to a data cube. + \param [data] The image data array (images are one dimensional) + \param [timestamp] The timestamp when the image was taken + \param [sequence] A sequence number if the image is one in a sequence + \param [camera_info] The camera_info class holds image parameters such as size, + pixel scales, detector information + \param [compress] FITS compression to use on the data file + \return ERROR if there is a failure, NO_ERROR on success + */ + int write_image(T * data, std::string timestamp, int sequence, + Camera::Information camera_info, + int compress = FITS_COMPRESSION_NONE) + { + std::string function("FITS_file::write_image"); + std::stringstream message; + int error; + + // Write into the data cube + if (this->iscube == true){ + + // Open the file if it's not already open + if (this->file_open == false && this->cube_thread_running == false){ + logwrite(function, "opening the cube file for writing..."); + error = this->open_file(camera_info, timestamp, sequence, compress); + if (error != NO_ERROR){ + message << "ERROR failed to open FITS file \"" << this->fits_name <<"\""; + logwrite(function, message.str()); + return(ERROR); + } + } + + // Create the FITS frame object from the input data + FITS_cube_frame frame(data, camera_info.image_size, timestamp, + sequence, camera_info); + + boost::unique_lock lock(this->cache_mutex); + this->cube_cache.push_back(frame); + boost::timed_mutex *m = lock.release(); + m->unlock(); + } + + // Write a single frame FITS image + else { + error = this->write_single_image(data, timestamp, sequence, camera_info, + compress); + if (error != NO_ERROR){ + message << "ERROR failed to write FITS file " << this->fits_name; + logwrite(function, message.str()); + return(ERROR); + } + } + + // Return success. Note there is no logging, data cubes would create a huge + // number of log files so only errors are written while writing FITS files. + return(NO_ERROR); + } + /**************** FITS_file::write_image ****************/ + + + /**************** FITS_file::complete ****************/ + /** + Completes a FITS data cube; this closes the file and shuts down processing + for the data cube. + */ + void complete() + { + std::string function("FITS_file::write_image"); + std::stringstream message; + + // Set the flag that tells the system the last image has been delivered + this->last_image = true; + + // Log the cube is flagged as complete + logwrite(function, "completion signal sent"); + + // Wait for the cube thread to finish + this->fits_cube_thread.join(); + + // Log that the cube is closed and everything is good + logwrite(function, "FITS cube processing complete"); + } + /**************** FITS_file::complete ****************/ + + + /**************** FITS_file::get_iscube ****************/ + /** + Return data cube flag value, true if this is FITS container is a data cube + and false if it is not. + */ + bool get_iscube() + { + return(this->iscube); + } + /**************** FITS_file::get_iscube ****************/ + +}; diff --git a/common/common.h b/common/common.h index 974ed3cd..bbbfceb5 100644 --- a/common/common.h +++ b/common/common.h @@ -24,24 +24,29 @@ constexpr long TIMEOUT = 3; constexpr long HELP = 4; namespace Common { - /**************** Common::FitsKeys ******************************************/ + /***** Common::FitsKeys ***************************************************/ /* * @class FitsKeys * @brief provides a user-defined keyword database * */ class FitsKeys { - private: - public: + public: FitsKeys() = default; + // copy constructor + // + FitsKeys( const FitsKeys &other ) { + this->keydb = other.keydb; + } + std::string get_keytype(std::string keyvalue); /// return type of keyword based on value long listkeys(); /// list FITS keys in the internal database long addkey(std::string arg); /// add FITS key to the internal database long delkey(std::string arg); /// delete FITS key from the internal database void erasedb() { this->keydb.clear(); }; /// erase the entire contents of the internal database - /***** Common::FitsKeys::addkey *****************************************/ + /***** Common::FitsKeys::addkey ***************************************/ /** * @brief template function adds FITS keyword to internal database * @details no parsing is done here @@ -121,7 +126,7 @@ namespace Common { return (NO_ERROR); } - /***** Common::FitsKeys::addkey *****************************************/ + /***** Common::FitsKeys::addkey ***************************************/ typedef struct { @@ -175,11 +180,10 @@ namespace Common { return; } }; - - /**************** Common::FitsKeys ******************************************/ + /***** Common::FitsKeys ***************************************************/ - /**************** Common::Queue *********************************************/ + /***** Common::Queue ******************************************************/ /** * @class Queue * @brief provides a thread-safe messaging queue @@ -202,6 +206,5 @@ namespace Common { void enqueue(std::string message); /// push an element into the queue. std::string dequeue(void); /// pop an element from the queue }; - - /**************** Common::Queue *********************************************/ + /***** Common::Queue ******************************************************/ } diff --git a/utils/network.cpp b/utils/network.cpp index 2b049b84..99867283 100644 --- a/utils/network.cpp +++ b/utils/network.cpp @@ -432,12 +432,10 @@ namespace Network { /**************** Network::TcpSocket::TcpSocket *****************************/ - /**************** Network::TcpSocket::TcpSocket *****************************/ + /***** Network::TcpSocket::TcpSocket ****************************************/ /** - * @fn TcpSocket * @brief TcpSocket class copy constructor - * @param[in] obj reference to class object - * @return none + * @param[in] obj reference to TcpSocket class object * */ TcpSocket::TcpSocket(const TcpSocket &obj) { @@ -448,24 +446,72 @@ namespace Network { fd = obj.fd; listenfd = obj.listenfd; host = obj.host; - addrs = obj.addrs; connection_open = obj.connection_open; + + // perform a deep copy of addrs + // + if ( obj.addrs != nullptr ) { + struct addrinfo *addrCopy = nullptr; + struct addrinfo *current = nullptr; + struct addrinfo *prev = nullptr; + try { + for ( struct addrinfo *p = obj.addrs; p != nullptr; p = p->ai_next ) { + current = new struct addrinfo; // freed in destructor + if ( current == nullptr ) { + // Clean up previous allocations before throwing an exception + while ( addrCopy != nullptr ) { + struct addrinfo *temp = addrCopy; + addrCopy = addrCopy->ai_next; + delete temp; + } + throw std::bad_alloc(); // or another appropriate exception + } + memcpy( current, p, sizeof( struct addrinfo ) ); + if ( prev != nullptr ) { + prev->ai_next = current; + } + else { + addrCopy = current; + } + prev = current; + } + } + catch ( ... ) { // clean up in case of exception + while ( addrCopy != nullptr ) { + struct addrinfo *temp = addrCopy; + addrCopy = addrCopy->ai_next; + delete temp; + } + throw; // re-throw the exception + } + addrs = addrCopy; + } + else { + addrs = nullptr; + } }; - /**************** Network::TcpSocket::TcpSocket *****************************/ + /***** Network::TcpSocket::TcpSocket ****************************************/ - /**************** Network::TcpSocket::~TcpSocket ****************************/ + /***** Network::TcpSocket::~TcpSocket ***************************************/ /** - * @fn ~TcpSocket - * @brief TcpSocket class deconstructor - * @param[in] none - * @return none + * @brief TcpSocket class destructor * */ TcpSocket::~TcpSocket() { this->Close(); - }; - /**************** Network::TcpSocket::~TcpSocket ****************************/ + // Clean up the addrs linked list if it was allocated + if (addrs != nullptr) { + struct addrinfo *current = addrs; + while (current != nullptr) { + struct addrinfo *next = current->ai_next; + delete current; // memory may have been allocated in copy constructor + current = next; + } + addrs = nullptr; + } + } + /***** Network::TcpSocket::~TcpSocket ***************************************/ /**************** Network::TcpSocket::Accept ********************************/ diff --git a/utils/sendcmd.cpp b/utils/sendcmd.cpp index 9f5187e9..8cb49444 100644 --- a/utils/sendcmd.cpp +++ b/utils/sendcmd.cpp @@ -32,7 +32,7 @@ int main(int argc, char *argv[]) { if ( argc==1 ) { std::cout << usage(); return 0; } try { - for ( size_t i=1; i #include #include +#include extern std::string tmzone_cfg; /// time zone if set in cfg file extern std::mutex generate_tmpfile_mtx; @@ -123,6 +124,8 @@ std::string generate_temp_filename(const std::string &prefix); void rtrim(std::string &s); +std::string demangle( const char* name ); + inline bool caseCompareChar(char a, char b) { return (std::toupper(a) == std::toupper(b)); } inline bool caseCompareString(const std::string &s1, const std::string &s2) { From 67173c243ec23c108b0d87da54ac6b8aa7e7c237 Mon Sep 17 00:00:00 2001 From: David Hale Date: Wed, 18 Sep 2024 16:40:59 -0700 Subject: [PATCH 2/5] updates the Camera::Information class for compatibility with Reed's FITS engine includes but doesn't yet utilize new FITS_file class still compiles and very well may still work as the main branch unless I missed something --- camerad/archon.cpp | 210 ++++----- camerad/archon.h | 71 ++-- camerad/astrocam.cpp | 2 +- camerad/astrocam.h | 6 +- camerad/camera.h | 993 +++++++++++++++++++++++++++---------------- camerad/fits.h | 20 +- 6 files changed, 789 insertions(+), 513 deletions(-) diff --git a/camerad/archon.cpp b/camerad/archon.cpp index a2de7c70..44004d46 100644 --- a/camerad/archon.cpp +++ b/camerad/archon.cpp @@ -108,43 +108,52 @@ namespace Archon { /***** Archon::Interface::do_power ******************************************/ /** * @brief set/get the power state - * @param[in] state_in input string contains requested power state - * @param[out] retstring return string contains the current power state - * @return ERROR or NO_ERROR + * @param[in] args input string contains requested power state + * @param[out] retstring return string contains the current power state + * @return ERROR | NO_ERROR | HELP * */ - long Interface::do_power(std::string state_in, std::string &retstring) { + long Interface::do_power(std::string args, std::string &retstring) { std::string function = "Archon::Interface::do_power"; std::stringstream message; long error = ERROR; + // Help + // + if ( args == "?" || args == "help" ) { + retstring="power"; + retstring.append( " [ on | off ]\n" ); + retstring.append( " Set Archon power on|off. If no arg supplied then return current state.\n" ); + return HELP; + } + if ( !this->archon.isconnected() ) { // nothing to do if no connection open to controller this->camera.log_error( function, "connection not open to controller" ); - return( ERROR ); + return ERROR; } // set the Archon power state as requested // - if ( !state_in.empty() ) { // received something - std::transform( state_in.begin(), state_in.end(), state_in.begin(), ::toupper ); // make uppercase - if ( state_in == "ON" ) { + if ( !args.empty() ) { // received something + std::transform( args.begin(), args.end(), args.begin(), ::toupper ); // make uppercase + if ( args == "ON" ) { error = this->archon_cmd( POWERON ); // send POWERON command to Archon if ( error == NO_ERROR ) std::this_thread::sleep_for( std::chrono::seconds(2) ); // wait 2s to ensure power is stable } else - if ( state_in == "OFF" ) { + if ( args == "OFF" ) { error = this->archon_cmd( POWEROFF ); // send POWEROFF command to Archon if ( error == NO_ERROR ) std::this_thread::sleep_for( std::chrono::milliseconds(200) ); // wait 200ms to ensure power is off } else { - message.str(""); message << "unrecognized argument " << state_in << ": expected {on|off}"; + message.str(""); message << "unrecognized argument " << args << ": expected {on|off}"; this->camera.log_error( function, message.str() ); - return( ERROR ); + return ERROR; } if ( error != NO_ERROR ) { - message.str(""); message << "setting Archon power " << state_in; + message.str(""); message << "setting Archon power " << args; this->camera.log_error( function, message.str() ); - return( ERROR ); + return ERROR; } } @@ -153,18 +162,18 @@ namespace Archon { std::string power; error = get_status_key( "POWER", power ); - if ( error != NO_ERROR ) return( ERROR ); + if ( error != NO_ERROR ) return ERROR; int status=-1; try { status = std::stoi( power ); } catch (std::invalid_argument &) { this->camera.log_error( function, "unable to convert power status message to integer" ); - return(ERROR); + return ERROR; } catch (std::out_of_range &) { this->camera.log_error( function, "power status out of range" ); - return(ERROR); + return ERROR; } // set the power status (or not) depending on the value extracted from the STATUS message @@ -174,35 +183,35 @@ namespace Archon { this->camera.log_error( function, "unable to find power in Archon status message" ); return( ERROR ); case 0: // usually an internal error - this->camera.power_status = "UNKNOWN"; + this->power_status = "UNKNOWN"; break; case 1: // no configuration applied - this->camera.power_status = "NOT_CONFIGURED"; + this->power_status = "NOT_CONFIGURED"; break; case 2: // power is off - this->camera.power_status = "OFF"; + this->power_status = "OFF"; break; case 3: // some modules powered, some not - this->camera.power_status = "INTERMEDIATE"; + this->power_status = "INTERMEDIATE"; break; case 4: // power is on - this->camera.power_status = "ON"; + this->power_status = "ON"; break; case 5: // system is in standby - this->camera.power_status = "STANDBY"; + this->power_status = "STANDBY"; break; default: // should be impossible - message.str(""); message << "unknown power status: " << status; + message.str(""); message << "unknown Archon power status: " << status; this->camera.log_error( function, message.str() ); return( ERROR ); } - message.str(""); message << "POWER:" << this->camera.power_status; + message.str(""); message << "POWER:" << this->power_status; this->camera.async.enqueue( message.str() ); - retstring = this->camera.power_status; + retstring = this->power_status; - return(NO_ERROR); + return NO_ERROR; } /***** Archon::Interface::do_power ******************************************/ @@ -226,20 +235,18 @@ namespace Archon { // and the new config file may not have everything defined. // This ensures nothing is carried over from any previous config. // - this->camera_info.hostname = ""; this->archon.sethost( "" ); - this->camera_info.port = -1; this->archon.setport( -1 ); this->n_hdrshift = 16; - this->camera.firmware[0] = ""; + this->camera.firmware[0].clear(); - this->exposeparam = ""; + this->exposeparam.clear(); this->longexposeparam.clear(); - this->trigin_exposeparam = ""; - this->trigin_untimedparam = ""; - this->trigin_readoutparam = ""; + this->trigin_exposeparam.clear(); + this->trigin_untimedparam.clear(); + this->trigin_readoutparam.clear(); this->trigin_expose_enable = DEF_TRIGIN_EXPOSE_ENABLE; this->trigin_expose_disable = DEF_TRIGIN_EXPOSE_DISABLE; @@ -255,8 +262,9 @@ namespace Archon { // for (int entry=0; entry < this->config.n_entries; entry++) { - if (config.param[entry].compare(0, 9, "ARCHON_IP")==0) { - this->camera_info.hostname = config.arg[entry]; + // ARCHON_IP is used to set the Archon host name in the Network::TcpSocket archon object + // + if (config.param[entry] == "ARCHON_IP") { this->archon.sethost( config.arg[entry] ); message.str(""); message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; logwrite( function, message.str() ); @@ -264,20 +272,18 @@ namespace Archon { applied++; } - if (config.param[entry].compare(0, 11, "ARCHON_PORT")==0) { // ARCHON_PORT + // ARCHON_PORT is used to set the Archon port in the Network::TcpSocket archon object + // + if (config.param[entry] == "ARCHON_PORT") { int port; try { port = std::stoi( config.arg[entry] ); - - } catch (std::invalid_argument &) { - this->camera.log_error( function, "unable to convert port number to integer" ); - return ERROR; - - } catch (std::out_of_range &) { - this->camera.log_error( function, "port number out of integer range" ); + } + catch (const std::exception &e) { + message.str(""); message << "parsing Archon port number: " << e.what(); + this->camera.log_error( function, message.str() ); return ERROR; } - this->camera_info.port = port; this->archon.setport(port); message.str(""); message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; logwrite( function, message.str() ); @@ -293,7 +299,9 @@ namespace Archon { } } - if (config.param[entry].compare(0, 12, "EXPOSE_PARAM")==0) { // EXPOSE_PARAM + // EXPOSE_PARAM is parameter used to trigger exposure + // + if (config.param[entry] == "EXPOSE_PARAM") { this->exposeparam = config.arg[entry]; message.str(""); message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; logwrite( function, message.str() ); @@ -301,7 +309,9 @@ namespace Archon { applied++; } - if ( config.param[entry] == "LONGEXPOSE_PARAM" ) { // LONGEXPOSE_PARAM + // LONGEXPOSE_PARAM is paramter to set long exposure mode (s instead of ms) + // + if ( config.param[entry] == "LONGEXPOSE_PARAM" ) { this->longexposeparam = config.arg[entry]; message.str(""); message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; logwrite( function, message.str() ); @@ -648,12 +658,11 @@ namespace Archon { /**************** Archon::Interface::prepare_archon_buffer ******************/ - /**************** Archon::Interface::connect_controller *********************/ + /***** Archon::Interface::connect_controller ********************************/ /** - * @fn connect_controller - * @brief - * @param none (devices_in here for future expansion) - * @return + * @brief connect to Archon controller + * @param[in] devices_in future expansion for multiple Archons + * @return ERROR | NO_ERROR * */ long Interface::connect_controller(const std::string& devices_in="") { @@ -672,13 +681,13 @@ namespace Archon { logwrite(function, "opening a connection to the camera system"); if ( this->archon.Connect() != 0 ) { - message.str(""); message << "connecting to " << this->camera_info.hostname << ":" << this->camera_info.port << ": " << strerror(errno); + message.str(""); message << "connecting to " << this->archon.gethost() << ":" << this->archon.getport() << ": " << strerror(errno); this->camera.log_error( function, message.str() ); return ERROR; } message.str(""); - message << "socket connection to " << this->camera_info.hostname << ":" << this->camera_info.port << " " + message << "socket connection to " << this->archon.gethost() << ":" << this->archon.getport() << " " << "established on fd " << this->archon.getfd(); logwrite(function, message.str()); @@ -779,15 +788,13 @@ namespace Archon { return error; } - /**************** Archon::Interface::connect_controller *********************/ + /***** Archon::Interface::connect_controller ********************************/ - /**************** Archon::Interface::disconnect_controller ******************/ + /***** Archon::Interface::disconnect_controller *****************************/ /** - * @fn disconnect_controller - * @brief - * @param none - * @return + * @brief close connection to Archon + * @return ERROR | NO_ERROR * */ long Interface::disconnect_controller() { @@ -821,7 +828,7 @@ namespace Archon { return error; } - /**************** Archon::Interface::disconnect_controller ******************/ + /***** Archon::Interface::disconnect_controller *****************************/ /**************** Archon::Interface::native *********************************/ @@ -1839,7 +1846,7 @@ namespace Archon { // int bigbuf=-1; if (error==NO_ERROR) error = get_configmap_value("BIGBUF", bigbuf); // get value of BIGBUF from loaded acf file - this->camera_info.activebufs = (bigbuf==1) ? 2 : 3; // set number of active buffers based on BIGBUF + this->activebufs = (bigbuf==1) ? 2 : 3; // set number of active buffers based on BIGBUF if ( error != NO_ERROR ) { logwrite( function, "ERROR: unable to read BIGBUF from ACF" ); return error; } // There is one special reserved mode name, "RAW" @@ -1903,7 +1910,6 @@ namespace Archon { if (error==NO_ERROR) error = get_configmap_value("SAMPLEMODE", samplemode); // SAMPLEMODE=0 for 16bpp, =1 for 32bpp if ( error != NO_ERROR ) { logwrite( function, "ERROR: unable to get SAMPLEMODE from ACF" ); return error; } if (samplemode < 0) { this->camera.log_error( function, "bad or missing SAMPLEMODE from ACF" ); return ERROR; } - this->camera_info.bitpix = (samplemode==0) ? 16 : 32; // Load parameters and Apply CDS/Deint configuration if any of them changed if ((error == NO_ERROR) && paramchanged) error = this->archon_cmd(LOADPARAMS); // TODO I think paramchanged is never set! @@ -1919,9 +1925,9 @@ namespace Archon { // Set axes, image dimensions, calculate image_memory, etc. // Raw will always be 16 bpp (USHORT). // Image can be 16 or 32 bpp depending on SAMPLEMODE setting in ACF. - // Call set_axes(datatype) with the FITS data type needed, which will set the info.datatype variable. + // Call set_axes(bitpix) where bitpix is the CCFits datatype (not literal bits per pixel) // - error = this->camera_info.set_axes(); // 16 bit raw is unsigned short int + error = this->camera_info.set_axes(samplemode==0 ? USHORT_IMG : ULONG_IMG); // 16 bit raw is unsigned short int /********* if (this->camera_info.frame_type == Camera::FRAME_RAW) { error = this->camera_info.set_axes(USHORT_IMG); // 16 bit raw is unsigned short int @@ -2028,47 +2034,38 @@ namespace Archon { std::stringstream message; long error=NO_ERROR; - cfg_map_t::iterator cfg_it; - param_map_t::iterator param_it; bool paramchanged = false; bool configchanged = false; std::stringstream errstr; - /** - * iterate through configmap, writing each config key in the map - */ - for (cfg_it = this->modemap[mode].configmap.begin(); - cfg_it != this->modemap[mode].configmap.end(); - cfg_it++) { - error = this->write_config_key( cfg_it->first.c_str(), cfg_it->second.value.c_str(), configchanged ); + // iterate through configmap, writing each config key in the map + // + for ( const auto &config : this->modemap[mode].configmap ) { + error = this->write_config_key( config.first.c_str(), config.second.value.c_str(), configchanged ); if (error != NO_ERROR) { - errstr << "ERROR: writing config key:" << cfg_it->first << " value:" << cfg_it->second.value << " for mode " << mode; + errstr << "ERROR writing config key:" << config.first << " value:" << config.second.value << " for mode " << mode; break; } } - /** - * if no errors from writing config keys, then - * iterate through the parammap, writing each parameter in the map - */ + // if no errors from writing config keys, then + // iterate through the parammap, writing each parameter in the map + // if (error == NO_ERROR) { - for (param_it = this->modemap[mode].parammap.begin(); - param_it != this->modemap[mode].parammap.end(); - param_it++) { - error = this->write_parameter( param_it->first.c_str(), param_it->second.value.c_str(), paramchanged ); + for ( const auto ¶m : this->modemap[mode].parammap ) { + error = this->write_parameter( param.first.c_str(), param.second.value.c_str(), paramchanged ); message.str(""); message << "paramchanged=" << (paramchanged?"true":"false"); logwrite(function, message.str()); if (error != NO_ERROR) { - errstr << "ERROR: writing parameter key:" << param_it->first << " value:" << param_it->second.value << " for mode " << mode; + errstr << "ERROR writing parameter key:" << param.first << " value:" << param.second.value << " for mode " << mode; break; } } } - /** - * apply the new settings to the system here, only if something changed - */ + // apply the new settings to the system here, only if something changed + // if ( (error == NO_ERROR) && paramchanged ) error = this->archon_cmd(LOADPARAMS); if ( (error == NO_ERROR) && configchanged ) error = this->archon_cmd(APPLYCDS); @@ -2113,9 +2110,8 @@ namespace Archon { if ( this->shutter( shuttenstr, dontcare ) != NO_ERROR ) { logwrite( function, "ERROR: setting shutter enable parameter" ); return ERROR; } } - /** - * read back some TAPLINE information - */ + // read back some TAPLINE information + // if (error==NO_ERROR) error = get_configmap_value("TAPLINES", this->taplines); // total number of taps std::vector tokens; @@ -2438,7 +2434,7 @@ namespace Archon { this->frame.next_index = this->frame.index + 1; // frame.next_index wraps to 0 when it reaches the maximum number of active buffers. - if (this->frame.next_index >= this->camera_info.activebufs) { + if (this->frame.next_index >= this->activebufs) { this->frame.next_index = 0; } } @@ -2596,7 +2592,7 @@ namespace Archon { long Interface::fetch(uint64_t bufaddr, uint32_t bufblocks) { std::string function = "Archon::Interface::fetch"; std::stringstream message; - uint32_t maxblocks = (uint32_t)(1.5E9 / this->camera_info.activebufs / 1024 ); + uint32_t maxblocks = (uint32_t)(1.5E9 / this->activebufs / 1024 ); uint64_t maxoffset = this->frame.bufbase[this->frame.index]; uint64_t maxaddr = maxoffset + maxblocks; @@ -2769,8 +2765,8 @@ namespace Archon { // Archon frame index is 1 biased so add 1 here bufready = this->frame.index + 1; - if (bufready < 1 || bufready > this->camera_info.activebufs) { - message.str(""); message << "invalid Archon buffer " << bufready << " requested. Expected {1:" << this->camera_info.activebufs << "}"; + if (bufready < 1 || bufready > this->activebufs) { + message.str(""); message << "invalid Archon buffer " << bufready << " requested. Expected {1:" << this->activebufs << "}"; this->camera.log_error( function, message.str() ); return ERROR; } @@ -2993,8 +2989,8 @@ namespace Archon { // bufready = this->frame.index + 1; - if (bufready < 1 || bufready > this->camera_info.activebufs) { - message.str(""); message << "invalid Archon buffer " << bufready << " requested. Expected {1:" << this->camera_info.activebufs << "}"; + if (bufready < 1 || bufready > this->activebufs) { + message.str(""); message << "invalid Archon buffer " << bufready << " requested. Expected {1:" << this->activebufs << "}"; this->camera.log_error( function, message.str() ); return ERROR; } @@ -3202,11 +3198,11 @@ namespace Archon { /**************** Archon::Interface::write_frame ****************************/ /** * @fn write_frame - * @brief creates a FITS_file object to write the archon_buf buffer to disk + * @brief creates a __FITS_file object to write the archon_buf buffer to disk * @param none * @return ERROR or NO_ERROR * - * A FITS_file object is created here to write the data. This object MUST remain + * A __FITS_file object is created here to write the data. This object MUST remain * valid while any (all) threads are writing data, so the write_data function * will keep track of threads so that it doesn't terminate until all of its * threads terminate. @@ -3266,7 +3262,7 @@ namespace Archon { // This call to set_axes() is to set the axis_pixels, axes, and section_size, // needed for the FITS writer // - error = this->camera_info.set_axes(); + error = this->camera_info.set_axes(ULONG_IMG); #ifdef LOGLEVEL_DEBUG message.str(""); message << "[DEBUG] x1=" << x1 << " x2=" << x2 << " y1=" << y1 << " y2=" << y2; @@ -4150,6 +4146,7 @@ namespace Archon { } /**************** Archon::Interface::hsetup *******************************/ + /**************** Archon::Interface::hroi ******************************/ /** * @fn hroi @@ -4273,7 +4270,12 @@ namespace Archon { this->camera_info.detector_pixels[0] = cols; this->camera_info.detector_pixels[1] = rows; - this->camera_info.set_axes(); + // need samplemode for bitpix + int samplemode=-1; + if (error==NO_ERROR) error = get_configmap_value("SAMPLEMODE", samplemode); + if ( error != NO_ERROR ) { logwrite( function, "ERROR: unable to get SAMPLEMODE from ACF" ); return error; } + if (samplemode < 0) { this->camera.log_error( function, "bad or missing SAMPLEMODE from ACF" ); return ERROR; } + this->camera_info.set_axes(samplemode==0 ? USHORT_IMG : ULONG_IMG); } } // end if geom passed in @@ -4293,6 +4295,7 @@ namespace Archon { } /**************** Archon::Interface::hroi *********************************/ + /**************** Archon::Interface::hwindow ******************************/ /** * @fn hwindow @@ -4411,7 +4414,12 @@ namespace Archon { this->camera_info.detector_pixels[0] = cols; this->camera_info.detector_pixels[1] = rows; - this->camera_info.set_axes(); + // need samplemode for bitpix + int samplemode=-1; + if (error==NO_ERROR) error = get_configmap_value("SAMPLEMODE", samplemode); + if ( error != NO_ERROR ) { logwrite( function, "ERROR: unable to get SAMPLEMODE from ACF" ); return error; } + if (samplemode < 0) { this->camera.log_error( function, "bad or missing SAMPLEMODE from ACF" ); return ERROR; } + this->camera_info.set_axes(samplemode==0 ? USHORT_IMG : ULONG_IMG); } else { message.str(""); message << "window state " << state_in << " is invalid. Expecting {true,false,0,1}"; @@ -4438,6 +4446,7 @@ namespace Archon { } /**************** Archon::Interface::hwindow *******************************/ + /**************** Archon::Interface::autofetch ******************************/ /** * @fn autofetch @@ -4526,6 +4535,7 @@ namespace Archon { } /**************** Archon::Interface::autofetch *******************************/ + /**************** Archon::Interface::hexpose ******************************/ /** * @fn hexpose diff --git a/camerad/archon.h b/camerad/archon.h index 50191b9f..783f2945 100644 --- a/camerad/archon.h +++ b/camerad/archon.h @@ -14,48 +14,51 @@ #include #include +#include "opencv2/opencv.hpp" /// OpenCV used for image manipulation #include "utilities.h" #include "common.h" #include "camera.h" #include "config.h" #include "logentry.h" #include "network.h" -#include "fits.h" +#include "fits.h" /// old version renames FITS_file to __FITS_file, will go away soon +#include "fits_file.h" /// new version implements FITS_file -#define MAXADCCHANS 16 //!< max number of ADC channels per controller (4 mod * 4 ch/mod) -#define MAXADMCHANS 72 //!< max number of ADM channels per controller (4 mod * 18 ch/mod) -#define BLOCK_LEN 1024 //!< Archon block size -#define REPLY_LEN 100 * BLOCK_LEN //!< Reply buffer size (over-estimate) + +constexpr int MAXADCCHANS = 16; //!< max number of ADC channels per controller (4 mod * 4 ch/mod) +constexpr int MAXADMCHANS = 72; //!< max number of ADM channels per controller (4 mod * 18 ch/mod) +constexpr int BLOCK_LEN = 1024; //!< Archon block size +constexpr int REPLY_LEN = 100 * BLOCK_LEN; //!< Reply buffer size (over-estimate) // Archon commands // -#define SYSTEM std::string("SYSTEM") -#define STATUS std::string("STATUS") -#define FRAME std::string("FRAME") -#define CLEARCONFIG std::string("CLEARCONFIG") -#define POLLOFF std::string("POLLOFF") -#define POLLON std::string("POLLON") -#define APPLYALL std::string("APPLYALL") -#define POWERON std::string("POWERON") -#define POWEROFF std::string("POWEROFF") -#define APPLYCDS std::string("APPLYCDS") -#define APPLYSYSTEM std::string("APPLYSYSTEM") -#define RESETTIMING std::string("RESETTIMING") -#define LOADTIMING std::string("LOADTIMING") -#define HOLDTIMING std::string("HOLDTIMING") -#define RELEASETIMING std::string("RELEASETIMING") -#define LOADPARAMS std::string("LOADPARAMS") -#define TIMER std::string("TIMER") -#define FETCHLOG std::string("FETCHLOG") -#define UNLOCK std::string("LOCK0") +const std::string SYSTEM = "SYSTEM"; +const std::string STATUS = "STATUS"; +const std::string FRAME = "FRAME"; +const std::string CLEARCONFIG = "CLEARCONFIG"; +const std::string POLLOFF = "POLLOFF"; +const std::string POLLON = "POLLON"; +const std::string APPLYALL = "APPLYALL"; +const std::string POWERON = "POWERON"; +const std::string POWEROFF = "POWEROFF"; +const std::string APPLYCDS = "APPLYCDS"; +const std::string APPLYSYSTEM = "APPLYSYSTEM"; +const std::string RESETTIMING = "RESETTIMING"; +const std::string LOADTIMING = "LOADTIMING"; +const std::string HOLDTIMING = "HOLDTIMING"; +const std::string RELEASETIMING = "RELEASETIMING"; +const std::string LOADPARAMS = "LOADPARAMS"; +const std::string TIMER = "TIMER"; +const std::string FETCHLOG = "FETCHLOG"; +const std::string UNLOCK = "LOCK0"; // Minimum required backplane revisions for certain features // -#define REV_RAMP std::string("1.0.548") -#define REV_SENSORCURRENT std::string("1.0.758") -#define REV_HEATERTARGET std::string("1.0.1087") -#define REV_FRACTIONALPID std::string("1.0.1054") -#define REV_VCPU std::string("1.0.784") +const std::string REV_RAMP = "1.0.548"; +const std::string REV_SENSORCURRENT = "1.0.758"; +const std::string REV_HEATERTARGET = "1.0.1087"; +const std::string REV_FRACTIONALPID = "1.0.1054"; +const std::string REV_VCPU = "1.0.784"; namespace Archon { @@ -102,8 +105,9 @@ namespace Archon { Config config; - FITS_file fits_file; //!< instantiate a FITS container object + __FITS_file fits_file; //!< instantiate a FITS container object "__" designates old version + int activebufs; //!< Archon controller number of active frame buffers int msgref; //!< Archon message reference identifier, matches reply to command bool abort; int taplines; @@ -122,6 +126,7 @@ namespace Archon { int win_vstop; int taplines_store; //!< int number of original taplines std::string tapline0_store; //!< store tapline0 for window mode so can restore later + std::string power_status; //!< archon power status bool lastcubeamps; @@ -226,10 +231,10 @@ namespace Archon { void add_filename_key(); - long get_status_key( std::string key, std::string &value ); /// get value for indicated key from STATUS string + long get_status_key( std::string key, std::string &value ); /// get value for indicated key from STATUS string - long power( std::string state_in, std::string &retstring ); /// wrapper for do_power - long do_power( std::string state_in, std::string &retstring ); /// set/get Archon power state + long power( std::string args, std::string &retstring ); /// wrapper for do_power + long do_power( std::string args, std::string &retstring ); /// set/get Archon power state long expose(std::string nseq_in); diff --git a/camerad/astrocam.cpp b/camerad/astrocam.cpp index 53caa8a5..e6d00f9f 100644 --- a/camerad/astrocam.cpp +++ b/camerad/astrocam.cpp @@ -262,7 +262,7 @@ namespace AstroCam { this->controller.at(dev).connected = false; // not yet connected this->controller.at(dev).firmwareloaded = false; // no firmware loaded - FITS_file* pFits = new FITS_file(); // create a pointer to a FITS_file class object + __FITS_file* pFits = new __FITS_file(); // create a pointer to a __FITS_file class object this->controller.at(dev).pFits = pFits; // set the pointer to this object in the public vector #ifdef LOGLEVEL_DEBUG diff --git a/camerad/astrocam.h b/camerad/astrocam.h index 1b2626db..383799fd 100644 --- a/camerad/astrocam.h +++ b/camerad/astrocam.h @@ -484,7 +484,7 @@ namespace AstroCam { // The Controller class is a sub-class of Interface and is here to contain - // the Camera::Information class and FITS_file class objects. + // the Camera::Information class and __FITS_file class objects. // There will be a vector of Controller class objects which matches the // vector of controller objects. // @@ -499,7 +499,7 @@ namespace AstroCam { Controller() = default; //!< class constructor Camera::Information info; //!< this is the main controller info object // Camera::FitsKeys userkeys; //!< create a FitsKeys object for FITS keys specified by the user - FITS_file *pFits; //!< FITS container object has to be a pointer here + __FITS_file *pFits; //!< FITS container object has to be a pointer here int error; @@ -562,7 +562,7 @@ namespace AstroCam { bool useframes; //!< Not all firmware supports frames. - FITS_file fits_file; //!< instantiate a FITS container object + __FITS_file fits_file; //!< instantiate a FITS container object typedef struct { ReadoutType readout_type; //!< enum for readout type diff --git a/camerad/camera.h b/camerad/camera.h index 2d44d4a2..38cee330 100644 --- a/camerad/camera.h +++ b/camerad/camera.h @@ -15,6 +15,8 @@ #include #include #include +#include +#include #include "common.h" #include "logentry.h" @@ -23,397 +25,656 @@ // handy snprintf shortcut #define SNPRINTF(VAR, ...) { snprintf(VAR, sizeof(VAR), __VA_ARGS__); } +/***** Camera *****************************************************************/ namespace Camera { - /**************** Camera::Camera ********************************************/ - // - class Camera { + + /** + * @brief forward declaration of Information class so Camera class can use it + */ + class Information; + + typedef enum { + FRAME_IMAGE, + FRAME_RAW, + NUM_FRAME_TYPES + } frame_type_t; + + + /***** Camera::Camera *******************************************************/ + /** + * @class Camera + * @brief this class describes some top-level settings + * + */ + class Camera { private: - std::string image_dir; - std::string base_name; - std::string fits_naming; + std::string image_dir; + std::string base_name; + std::string fits_naming; - std::string fitstime; //!< "YYYYMMDDHHMMSS" uesd for filename, set by get_fitsname() + std::string fitstime; //!< "YYYYMMDDHHMMSS" uesd for filename, set by get_fitsname() - mode_t dirmode; //!< user specified mode to OR with 0700 for imdir creation - int image_num; - bool is_datacube; - bool is_longerror; //!< set to return error message on command port - bool is_cubeamps; //!< should amplifiers be written as multi-extension data cubes? - std::atomic _abortstate;; + mode_t dirmode; //!< user specified mode to OR with 0700 for imdir creation + int image_num; + bool is_datacube; + bool is_longerror; //!< set to return error message on command port + bool is_cubeamps; //!< should amplifiers be written as multi-extension data cubes? + std::atomic _abortstate;; - std::mutex abort_mutex; - std::stringstream lasterrorstring; //!< a place to preserve an error message + std::mutex abort_mutex; + std::stringstream lasterrorstring; //!< a place to preserve an error message public: - Camera() : image_dir("/tmp"), base_name("image"), fits_naming("time"), - dirmode(0), image_num(0), is_datacube(false), is_longerror(false), is_cubeamps(false), - _abortstate(false), - autodir_state(true), abortstate(false), writekeys_when("before") { - } - - - bool autodir_state; - //!< if true then images are saved in a date subdir below image_dir, i.e. image_dir/YYYYMMDD/ - bool abortstate; //!< set true to abort the current operation (exposure, readout, etc.) - - std::string writekeys_when; //!< when to write fits keys "before" or "after" exposure - Common::Queue async; /// message queue object - - void set_abortstate(bool state); - - bool get_abortstate(); - - void set_dirmode(mode_t mode_in) { this->dirmode = mode_in; } - - std::map firmware; //!< firmware file for given controller device number, read from .cfg file - std::map readout_time; - //!< readout time in msec for given controller device number, read from .cfg file - - std::string power_status; //!< archon power status - - void log_error(std::string function, std::string message); - - std::string get_longerror(); - - long imdir(std::string dir_in); - - long imdir(std::string dir_in, std::string &dir_out); - - long autodir(std::string state_in, std::string &state_out); - - long basename(std::string name_in); - - long basename(std::string name_in, std::string &name_out); - - long imnum(std::string num_in, std::string &num_out); - - long writekeys(std::string writekeys_in, std::string &writekeys_out); - - long fitsnaming(std::string naming_in, std::string &naming_out); - - void increment_imnum() { if (this->fits_naming == "number") this->image_num++; }; - - void set_fitstime(std::string time_in); - - long get_fitsname(std::string &name_out); - - long get_fitsname(std::string controllerid, std::string &name_out); - - void abort(); - - void datacube(bool state_in); - - bool datacube(); - - long datacube(std::string state_in, std::string &state_out); - - void longerror(bool state_in); - - bool longerror(); - - long longerror(std::string state_in, std::string &state_out); - - void cubeamps(bool state_in); - - bool cubeamps(); - - long cubeamps(std::string state_in, std::string &state_out); - }; - - /**************** Camera::Camera ********************************************/ - - typedef enum { - FRAME_IMAGE, - FRAME_RAW, - NUM_FRAME_TYPES - } frame_type_t; - - const char *const frame_type_str[NUM_FRAME_TYPES] = { - "IMAGE", - "RAW" - }; - - - /***** Camera::ExposureTime ***********************************************/ - /** - * @class ExposureTime - * @brief creates object that encapsulates exposure time with its unit - * - * This class allows setting and getting an exposure time together with - * its unit. The default unit and value is msec and 0. Internally, the - * class stores the exposure time as an unsigned 32 bit value in the - * units of _unit but a specific unit can be requested with .s() and .ms() - * functions. Standard operators + - * / > < >= <= == are overloaded here - * to operate on scalars only. - * - */ - class ExposureTime { - private: - uint32_t _value; // exposure time in the current units - bool _is_set; // has it been set using the class value() function? - std::string _unit; // "s" for seconds, "ms" for milliseconds - bool _is_longexposure; // true for s, false for ms - - public: - /** - * @brief class constructor optionally sets exposure time and unit - * @param[in] time exposure time, defaults to 0 if not provided - * @param[in] u unit, "s" or "ms", defaults to ms if not provided - * - * Since this is a generic class, there is no range check on the - * exposure time value; that must be done by whatever instantiates - * an ExposureTime object. - * - */ - explicit ExposureTime( uint32_t time=0, const std::string &u="ms" ) - : _value(0), _is_set(false), _unit(u), _is_longexposure(u=="s") { - if ( u != "ms" && u != "s" ) throw std::invalid_argument("invalid unit, expected \"s\" or \"ms\""); - this->value(time); - } + Camera() : image_dir("/tmp"), base_name("image"), fits_naming("time"), + dirmode(0), image_num(0), is_datacube(false), is_longerror(false), is_cubeamps(false), + _abortstate(false), + autodir_state(true), abortstate(false), writekeys_when("before") { + } + + /// true saves image in date subdir below image_dir, i.e. image_dir/YYYYMMDD/ + bool autodir_state; + + bool abortstate; /// true aborts the current operation (exposure, readout, etc.) + + std::string writekeys_when; /// when to write fits keys "before" or "after" exposure + Common::Queue async; /// message queue object + + void set_abortstate(bool state); + + bool get_abortstate(); + + void set_dirmode(mode_t mode_in) { this->dirmode = mode_in; } + + std::map firmware; //!< firmware file for given controller device number, read from .cfg file + + /// readout time in msec for given controller device number, read from .cfg file + std::map readout_time; + + void log_error(std::string function, std::string message); + + std::string get_longerror(); + + long imdir(std::string dir_in); + long imdir(std::string dir_in, std::string &dir_out); + long autodir(std::string state_in, std::string &state_out); + long basename(std::string name_in); + long basename(std::string name_in, std::string &name_out); + long imnum(std::string num_in, std::string &num_out); + long writekeys(std::string writekeys_in, std::string &writekeys_out); + long fitsnaming(std::string naming_in, std::string &naming_out); + void increment_imnum() { if (this->fits_naming == "number") this->image_num++; }; + void set_fitstime(std::string time_in); + long get_fitsname(std::string &name_out); + long get_fitsname(std::string controllerid, std::string &name_out); + long set_fitsname(Information &camera_info); + void abort(); + void datacube(bool state_in); + bool datacube(); + long datacube(std::string state_in, std::string &state_out); + void longerror(bool state_in); + bool longerror(); + long longerror(std::string state_in, std::string &state_out); + void cubeamps(bool state_in); + bool cubeamps(); + long cubeamps(std::string state_in, std::string &state_out); + }; + /***** Camera::Camera *******************************************************/ + + + /***** Camera::ExposureTime *************************************************/ + /** + * @class ExposureTime + * @brief creates object that encapsulates exposure time with its unit + * + * This class allows setting and getting an exposure time together with + * its unit. The default unit and value is msec and 0. Internally, the + * class stores the exposure time as an unsigned 32 bit value in the + * units of _unit but a specific unit can be requested with .s() and .ms() + * functions. Standard operators + - * / > < >= <= == are overloaded here + * to operate on scalars only. + * + */ + class ExposureTime { + private: + uint32_t _value; // exposure time in the current units + bool _is_set; // has it been set using the class value() function? + std::string _unit; // "s" for seconds, "ms" for milliseconds + bool _is_longexposure; // true for s, false for ms - /** - * @brief set the unit - * @details this will modify the value as necessary - * @param[in] u "ms" or "s" for milliseconds or seconds - */ - void unit( const std::string &newunit ) { - if ( newunit == "s" || newunit == "ms" ) { - if ( _unit != newunit ) { - _value = ( newunit == "ms" ? _value*1000 : _value/1000 ); - _unit = newunit; - _is_longexposure = ( newunit == "s" ); - } + public: + /** + * @brief class constructor optionally sets exposure time and unit + * @param[in] time exposure time, defaults to 0 if not provided + * @param[in] u unit, "s" or "ms", defaults to ms if not provided + * + * Since this is a generic class, there is no range check on the + * exposure time value; that must be done by whatever instantiates + * an ExposureTime object. + * + */ + explicit ExposureTime( uint32_t time=0, const std::string &u="ms" ) + : _value(0), _is_set(false), _unit(u), _is_longexposure(u=="s") { + if ( u != "ms" && u != "s" ) throw std::invalid_argument("invalid unit, expected \"s\" or \"ms\""); + this->value(time); + } + + /** + * @brief copy constructor + */ + ExposureTime( const ExposureTime &other ) + : _value(other._value), + _is_set(other._is_set), + _unit(other._unit), + _is_longexposure(other._is_longexposure) { } + + /** + * @brief set the unit + * @details this will modify the value as necessary + * @param[in] u "ms" or "s" for milliseconds or seconds + */ + void unit( const std::string &newunit ) { + if ( newunit == "s" || newunit == "ms" ) { + if ( _unit != newunit ) { + _value = ( newunit == "ms" ? _value*1000 : _value/1000 ); + _unit = newunit; + _is_longexposure = ( newunit == "s" ); } - else throw std::invalid_argument("invalid unit, expected \"s\" or \"ms\""); } - - /** - * @brief return the current unit - * @return "s" | "ms" - */ - std::string unit() const { return _unit; } - - /** - * @brief set the longexposure state - * @details uses the class unit() function to both set the unit - * and modify the value as necessary. - * @param[in] true | false - */ - void longexposure( bool state ) { this->unit( state ? "s" : "ms" ); } - - /** - * @brief return the longexposure state - * @return true | false - */ - bool is_longexposure() const { return _is_longexposure; } - - /** - * @brief return the exposure time in units of milliseconds - * @return exposure time - */ - uint32_t ms() const { - return ( _unit == "ms" ? _value : _value * 1000 ); + else throw std::invalid_argument("invalid unit, expected \"s\" or \"ms\""); + } + + /** + * @brief return the current unit + * @return "s" | "ms" + */ + std::string unit() const { return _unit; } + + /** + * @brief set the longexposure state + * @details uses the class unit() function to both set the unit + * and modify the value as necessary. + * @param[in] true | false + */ + void longexposure( bool state ) { this->unit( state ? "s" : "ms" ); } + + /** + * @brief return the longexposure state + * @return true | false + */ + bool is_longexposure() const { return _is_longexposure; } + + /** + * @brief return the exposure time in units of milliseconds + * @return exposure time + */ + uint32_t ms() const { + return ( _unit == "ms" ? _value : _value * 1000 ); + } + + /** + * @brief return the exposure time in units of seconds + * @return exposure time + */ + uint32_t s() const { + return ( _unit == "s" ? _value : _value / 1000 ); + } + + /** + * @brief return the exposure time in the current units + * @return exposure time + */ + uint32_t value() const { + return _value; + } + + /** + * @brief set the exposure time + * @details There is no range check on the value; that must be done + * by whatever instantiates an ExposureTime object. + * @param[in] time exposure time in the current unit + */ + void value( uint32_t time ) { + _value = time; + _is_set = true; + } + + /** + * @brief return _is_set flag + * @details allows checking if a user has explicitly set the exposure time. + * @return true | false + */ + bool is_set() const { return _is_set; } + + /** + * @brief overload operators to perform in the correct units + * @details Use .s() or .ms() to operate on seconds or milliseconds. + * These only work on scalars (not other ExposureTime objects). + * + * E.G. .ms() +/- 1000 will add/subtract 1000 msec to/from the current value + * .s() +/- 1 will add/subtract 1 sec to/from the current value + * .s() == xxx will compare value in seconds to xxx + */ + ExposureTime operator+( uint32_t scalar ) const { + uint32_t val = ( _unit == "ms" ? ms() : s() ); + return ExposureTime( val + scalar, _unit ); + } + ExposureTime operator-( uint32_t scalar ) const { + uint32_t val = ( _unit == "ms" ? ms() : s() ); + return ExposureTime( val - scalar, _unit ); + } + ExposureTime operator*( uint32_t scalar ) const { + uint32_t val = ( _unit == "ms" ? ms() : s() * 1000 ); + return ExposureTime( val * scalar ); + } + ExposureTime operator/( uint32_t scalar ) const { + if ( scalar == 0 ) throw std::invalid_argument("division by zero"); + uint32_t val = ( _unit == "ms" ? ms() : s() * 1000 ); + return ExposureTime( val / scalar ); + } + bool operator<( uint32_t scalar ) const { + uint32_t val = ( _unit == "ms" ? ms() : s() * 1000 ); + return val < scalar; + } + bool operator>( uint32_t scalar ) const { + uint32_t val = ( _unit == "ms" ? ms() : s() * 1000 ); + return val > scalar; + } + bool operator==( uint32_t scalar ) const { + uint32_t val = ( _unit == "ms" ? ms() : s() * 1000 ); + return val == scalar; + } + bool operator<=( uint32_t scalar ) const { + uint32_t val = ( _unit == "ms" ? ms() : s() * 1000 ); + return val <= scalar; + } + bool operator>=( uint32_t scalar ) const { + uint32_t val = ( _unit == "ms" ? ms() : s() * 1000 ); + return val >= scalar; + } + }; + /***** Camera::ExposureTime *************************************************/ + + + /***** Camera::Information **************************************************/ + /** + * @class Information + * @brief holds necessary information for exposure + * @details This class contains all information needed for an exposure and + * resultant FITS file. Since the FITS object will make use of it, + * a copy can be made just for the FITS object. A copy constructor + * is provided to make this possible. + * + */ + class Information { + private: + public: + void swap( Information &other ) noexcept; + friend void swap( Information &first, Information &second ) noexcept { first.swap(second); } + + int det_id; //!< ID value of detector + int amp_id; //!< ID value of amplifier + int framenum; //!< Archon buffer frame number + + int serial_prescan; //!< Serial prescan number + int serial_overscan; //!< Serial overscan number + int parallel_overscan; //!< Parallel overscan number + int image_cols; //!< Number of columns in the image + int image_rows; //!< Number of rows in the image + std::string det_name; //!< name of detector + std::string amp_name; //!< name of amplifier + + std::string detector; //!< Detector name string + std::string detector_software; //!< Detector software version string + std::string detector_firmware; //!< Detector hardware version string + + float pixel_scale; //!< Image pixel scale, like arcsec per pixel + float det_gain; //!< Gain value for detector electronics + float read_noise; //!< Read noise value + float dark_current; //!< Dark current value + + size_t image_size; //!< pixels per CCD + + /** + * the following 4-element arrays are the components of FITS header + * keywords, [0:1,2:3], i.e CCDSEC[0:1,2:3] where 0,1,2,3 are the 4 + * elements of detsize[] + */ + int ccdsec[4]; //!< Detection section + int ampsec[4]; //!< amplifier section + int trimsec[4]; //!< trim section + int datasec[4]; //!< data section + int biassec[4]; //!< bias section (over-scan) + int detsec[4]; //!< detector section + int detsize[4]; //!< physical size of the CCD + + std::vector detid; //!< DETID = unique detector identifier + int bytes_per_pixel; //!< Bytes per pixle + int gain; //!< Image gain value + + int fits_compression_code; //!< cfitsio code for compression type + std::string fits_compression_type; //!< string representing FITS compression type + int fits_noisebits; //!< noisebits parameter used when compressing floating-point images + float frame_exposure_time; //!< Exposure time for individual frames + + std::string directory; //!< Data directory + std::string image_name; //!< Name of the image, includes all info + std::string basename; //!< Basic image name + + /** @var bitpix + * @brief FITS datatype (not literally bits per pixel) + * @details This is the datatype of the primary image. bitpix may be one + * of the following CFITSIO constants: BYTE_IMG, SHORT_IMG, + * LONG_IMG, FLOAT_IMG, DOUBLE_IMG, USHORT_IMG, ULONG_IMG, + * LONGLONG_IMG. Note that if you send in a bitpix of USHORT_IMG + * or ULONG_IMG, CCfits will set HDU::bitpix() to its signed + * equivalent (SHORT_IMG or LONG_IMG), and then set BZERO to 2^15 + * or 2^31. + */ + int bitpix; + + std::vector naxes; //!< array of axis lengths where element 0=cols, 1=rows, 2=cubedepth + frame_type_t frame_type; //!< frame_type is IMAGE or RAW + long detector_pixels[2]; //!< number of physical pixels. element 0=cols (pixels), 1=rows (lines) + long section_size; //!< pixels to write for this section (could be less than full sensor size) + uint32_t image_memory; //!< bytes per image sensor + std::string current_observing_mode; //!< the current mode + std::string readout_name; //!< name of the readout source + int readout_type; //!< type of the readout source is an enum + long naxis; //!< number of axes in the image (3 for data cube) + long axes[3]; //!< array of axis lengths where element 0=cols, 1=rows, 2=cubedepth <-- here for old fits.h + int binning[2]; //!< pixel binning, each axis + long axis_pixels[2]; //!< number of physical pixels used, before binning + long region_of_interest[4]; //!< region of interest + bool abortexposure; + int extension; //!< extension number for data cubes + bool shutterenable; //!< set true to allow the controller to open the shutter on expose, false to disable it + std::string shutteractivate; //!< shutter activation state + double exposure_progress; //!< exposure progress (fraction) + int num_pre_exposures; //!< pre-exposures are exposures taken but not saved + std::string fits_name; //!< contatenation of Camera's image_dir + image_name + image_num + std::string start_time; //!< system time when the exposure started (YYYY-MM-DDTHH:MM:SS.sss) + + bool iscube; // obsolete -- used for old fits.h only + int datatype; // obsolete -- used for old fits.h only + bool type_set; // obsolete -- used for old fits.h only + + std::vector > amp_section; + + ExposureTime exposure_time; //!< exposure time object, carries value and unit + + Common::FitsKeys userkeys; /// create a FitsKeys object for FITS keys specified by the user + Common::FitsKeys systemkeys; /// create a FitsKeys object for FITS keys imposed by the software + + long pre_exposures(std::string num_in, std::string &num_out); + + /***** Camera::Information (constructor) ********************************/ + /** + * @brief Camera::Information class constructor and initializer list + */ + Information() + : fits_compression_code(0), + fits_compression_type("none"), + naxes(2), +// axes{1, 1, 1}, // used by old fits.h system +// cubedepth(1), +// fitscubed(1), +// ncoadd(0), +// nslice(0), + binning{1, 1}, + region_of_interest{1, 1, 1, 1}, +// exposure_aborted(false), +// iscds(false), +// nmcds(0), +// ismex(false), + shutteractivate(""), +// requested_exptime(0), //!< default is undefined +// readouttime(-1), //!< default is undefined + num_pre_exposures(0) //!< default is no pre-exposures +// is_cds(false), +// nseq(1), +// nexp(-1), +// num_coadds(1), //!< default num of coadds +// sampmode(-1), +// sampmode_ext(-1), +// sampmode_frames(-1) + { + } + /***** Camera::Information (constructor) ********************************/ + + + /***** Camera::Information (copy constructor) ***************************/ + /** + * @brief Camera::Information copy constructor + */ + Information( const Information &other ) + : det_id(other.det_id), + amp_id(other.amp_id), + framenum(other.framenum), + serial_prescan(other.serial_prescan), + serial_overscan(other.serial_overscan), + parallel_overscan(other.parallel_overscan), + image_cols(other.image_cols), + image_rows(other.image_rows), + det_name(other.det_name), + amp_name(other.amp_name), + detector(other.detector), + detector_software(other.detector_software), + detector_firmware(other.detector_firmware), + pixel_scale(other.pixel_scale), + det_gain(other.det_gain), + read_noise(other.read_noise), + dark_current(other.dark_current), + image_size(other.image_size), + bytes_per_pixel(other.bytes_per_pixel), + gain(other.gain), + fits_compression_code(other.fits_compression_code), + fits_compression_type(other.fits_compression_type), + fits_noisebits(other.fits_noisebits), + frame_exposure_time(other.frame_exposure_time), + directory(other.directory), + image_name(other.image_name), + basename(other.basename), + bitpix(other.bitpix), + naxes(other.naxes), + frame_type(other.frame_type), + detector_pixels{other.detector_pixels[0], other.detector_pixels[1]}, + section_size(other.section_size), + image_memory(other.image_memory), + current_observing_mode(other.current_observing_mode), + readout_name(other.readout_name), + readout_type(other.readout_type), + naxis(other.naxis), + axes{other.axes[0], other.axes[1], other.axes[2]}, + binning{other.binning[0], other.binning[1]}, + axis_pixels{other.axis_pixels[0], other.axis_pixels[1]}, + region_of_interest{other.region_of_interest[0], other.region_of_interest[1], + other.region_of_interest[2], other.region_of_interest[3]}, + abortexposure(other.abortexposure), + extension(other.extension), + shutterenable(other.shutterenable), + shutteractivate(other.shutteractivate), + exposure_progress(other.exposure_progress), + num_pre_exposures(other.num_pre_exposures), + fits_name(other.fits_name), + start_time(other.start_time), + amp_section(other.amp_section), + exposure_time(other.exposure_time), + userkeys(other.userkeys), + systemkeys(other.systemkeys) + { + std::copy( std::begin(other.ccdsec), std::end(other.ccdsec), std::begin(ccdsec) ); + std::copy( std::begin(other.ampsec), std::end(other.ampsec), std::begin(ampsec) ); + std::copy( std::begin(other.trimsec), std::end(other.trimsec), std::begin(trimsec) ); + std::copy( std::begin(other.datasec), std::end(other.datasec), std::begin(datasec) ); + std::copy( std::begin(other.biassec), std::end(other.biassec), std::begin(biassec) ); + std::copy( std::begin(other.detsec), std::end(other.detsec), std::begin(detsec) ); + std::copy( std::begin(other.detsize), std::end(other.detsize), std::begin(detsize) ); } - - /** - * @brief return the exposure time in units of seconds - * @return exposure time - */ - uint32_t s() const { - return ( _unit == "s" ? _value : _value / 1000 ); + /***** Camera::Information (copy constructor) ***************************/ + + + /***** Camera::Information (copy assignment operator) *******************/ + /** + * @brief Camera::Information copy assignment operator + */ + Information &operator=(const Information &other) { + if ( this != &other ) { + det_id = other.det_id; + amp_id = other.amp_id; + framenum = other.framenum; + serial_prescan = other.serial_prescan; + serial_overscan = other.serial_overscan; + parallel_overscan = other.parallel_overscan; + image_cols = other.image_cols; + image_rows = other.image_rows; + det_name = other.det_name; + amp_name = other.amp_name; + detector = other.detector; + detector_software = other.detector_software; + detector_firmware = other.detector_firmware; + pixel_scale = other.pixel_scale; + det_gain = other.det_gain; + read_noise = other.read_noise; + dark_current = other.dark_current; + image_size = other.image_size; + bytes_per_pixel = other.bytes_per_pixel; + gain = other.gain; + fits_compression_code = other.fits_compression_code; + fits_compression_type = other.fits_compression_type; + fits_noisebits = other.fits_noisebits; + frame_exposure_time = other.frame_exposure_time; + directory = other.directory; + image_name = other.image_name; + basename = other.basename; + bitpix = other.bitpix; + naxes = other.naxes; + frame_type = other.frame_type; + detector_pixels[0] = other.detector_pixels[0]; + detector_pixels[1] = other.detector_pixels[1]; + section_size = other.section_size; + image_memory = other.image_memory; + current_observing_mode = other.current_observing_mode; + readout_name = other.readout_name; + readout_type = other.readout_type; + naxis = other.naxis; + axes[0] = other.axes[0]; + axes[1] = other.axes[1]; + axes[2] = other.axes[2]; + binning[0] = other.binning[0]; + binning[1] = other.binning[1]; + axis_pixels[0] = other.axis_pixels[0]; + axis_pixels[1] = other.axis_pixels[1]; + region_of_interest[0] = other.region_of_interest[0]; + region_of_interest[1] = other.region_of_interest[1]; + region_of_interest[2] = other.region_of_interest[2]; + region_of_interest[3] = other.region_of_interest[3]; + abortexposure = other.abortexposure; + extension = other.extension; + shutterenable = other.shutterenable; + shutteractivate = other.shutteractivate; + exposure_progress = other.exposure_progress; + num_pre_exposures = other.num_pre_exposures; + fits_name = other.fits_name; + start_time = other.start_time; + amp_section = other.amp_section; + exposure_time = other.exposure_time; + userkeys = other.userkeys; + systemkeys = other.systemkeys; + + std::copy( std::begin(other.ccdsec), std::end(other.ccdsec), std::begin(ccdsec) ); + std::copy( std::begin(other.ampsec), std::end(other.ampsec), std::begin(ampsec) ); + std::copy( std::begin(other.trimsec), std::end(other.trimsec), std::begin(trimsec) ); + std::copy( std::begin(other.datasec), std::end(other.datasec), std::begin(datasec) ); + std::copy( std::begin(other.biassec), std::end(other.biassec), std::begin(biassec) ); + std::copy( std::begin(other.detsec), std::end(other.detsec), std::begin(detsec) ); + std::copy( std::begin(other.detsize), std::end(other.detsize), std::begin(detsize) ); } - - /** - * @brief return the exposure time in the current units - * @return exposure time - */ - uint32_t value() const { - return _value; + return *this; + } + /***** Camera::Information (copy assignment operator) *******************/ + + + /***** Camera::Information::set_axes ************************************/ + /** + * @brief set image memory space + * @details Sets up the image memory space, including the byte size + * of the images and the number of pixels in each axis. This + * information has to be correct to match the output memory + * size of camera images in the current setup and write + * FITS files properly. + * @param[in] bitpix_in CCFits datatype, not the literal bits per pixel + * @return ERROR | NO_ERROR + * + */ + long set_axes( int bitpix_in ) { + std::string function = "Camera::Information::set_axes"; + std::stringstream message; + + this->bitpix = bitpix_in; + + if (this->frame_type == FRAME_RAW) { + this->bytes_per_pixel = 2; } - - /** - * @brief set the exposure time - * @details There is no range check on the value; that must be done - * by whatever instantiates an ExposureTime object. - * @param[in] time exposure time in the current unit - */ - void value( uint32_t time ) { - _value = time; - _is_set = true; + else { + switch (this->bitpix) { + case SHORT_IMG: + case USHORT_IMG: + this->bytes_per_pixel = 2; + break; + case LONG_IMG: + case ULONG_IMG: + case FLOAT_IMG: + this->bytes_per_pixel = 4; + break; + default: + message << "ERROR: unknown bitpix " << this->bitpix; + logwrite(function, message.str()); + return ERROR; + } } - /** - * @brief return _is_set flag - * @details allows checking if a user has explicitly set the exposure time. - * @return true | false - */ - bool is_set() const { return _is_set; } - - /** - * @brief overload operators to perform in the correct units - * @details Use .s() or .ms() to operate on seconds or milliseconds. - * These only work on scalars (not other ExposureTime objects). - * - * E.G. .ms() +/- 1000 will add/subtract 1000 msec to/from the current value - * .s() +/- 1 will add/subtract 1 sec to/from the current value - * .s() == xxx will compare value in seconds to xxx - */ - ExposureTime operator+( uint32_t scalar ) const { - uint32_t val = ( _unit == "ms" ? ms() : s() ); - return ExposureTime( val + scalar, _unit ); - } - ExposureTime operator-( uint32_t scalar ) const { - uint32_t val = ( _unit == "ms" ? ms() : s() ); - return ExposureTime( val - scalar, _unit ); - } - ExposureTime operator*( uint32_t scalar ) const { - uint32_t val = ( _unit == "ms" ? ms() : s() * 1000 ); - return ExposureTime( val * scalar ); - } - ExposureTime operator/( uint32_t scalar ) const { - if ( scalar == 0 ) throw std::invalid_argument("division by zero"); - uint32_t val = ( _unit == "ms" ? ms() : s() * 1000 ); - return ExposureTime( val / scalar ); - } - bool operator<( uint32_t scalar ) const { - uint32_t val = ( _unit == "ms" ? ms() : s() * 1000 ); - return val < scalar; - } - bool operator>( uint32_t scalar ) const { - uint32_t val = ( _unit == "ms" ? ms() : s() * 1000 ); - return val > scalar; - } - bool operator==( uint32_t scalar ) const { - uint32_t val = ( _unit == "ms" ? ms() : s() * 1000 ); - return val == scalar; - } - bool operator<=( uint32_t scalar ) const { - uint32_t val = ( _unit == "ms" ? ms() : s() * 1000 ); - return val <= scalar; - } - bool operator>=( uint32_t scalar ) const { - uint32_t val = ( _unit == "ms" ? ms() : s() * 1000 ); - return val >= scalar; - } - }; - /***** Camera::ExposureTime ***********************************************/ + this->naxis = 2; + this->axis_pixels[0] = this->region_of_interest[1] - + this->region_of_interest[0] + 1; + this->axis_pixels[1] = this->region_of_interest[3] - + this->region_of_interest[2] + 1; - /**************** Camera::Information ***************************************/ - // - class Information { - private: - public: - std::string hostname; //!< Archon controller hostname - int port; //!< Archon controller TPC/IP port number - int activebufs; //!< Archon controller number of active frame buffers - int bitpix; //!< Archon bits per pixel based on SAMPLEMODE - int datatype; //!< FITS data type (corresponding to bitpix) used in set_axes() - bool type_set; //!< set when FITS data type has been defined - frame_type_t frame_type; //!< frame_type is IMAGE or RAW - long detector_pixels[2]; //!< element 0=cols (pixels), 1=rows (lines) - long section_size; //!< pixels to write for this section (could be less than full sensor size) - long image_memory; //!< bytes per image sensor - std::string current_observing_mode; //!< the current mode - std::string readout_name; //!< name of the readout source - int readout_type; //!< type of the readout source is an enum - long naxis; - long axes[2]; - int binning[2]; - long axis_pixels[2]; - long region_of_interest[4]; - long image_center[2]; - bool abortexposure; - bool iscube; //!< the info object given to the FITS writer will need to know cube status - int extension; //!< extension number for data cubes - bool shutterenable; //!< set true to allow the controller to open the shutter on expose, false to disable it - std::string shutteractivate; //!< shutter activation state - double exposure_progress; //!< exposure progress (fraction) - int num_pre_exposures; //!< pre-exposures are exposures taken but not saved - std::string fits_name; //!< contatenation of Camera's image_dir + image_name + image_num - std::string start_time; //!< system time when the exposure started (YYYY-MM-DDTHH:MM:SS.sss) - - std::vector > amp_section; - - ExposureTime exposure_time; - - Common::FitsKeys userkeys; /// create a FitsKeys object for FITS keys specified by the user - Common::FitsKeys systemkeys; /// create a FitsKeys object for FITS keys imposed by the software - - - Information() { - this->axes[0] = 1; - this->axes[1] = 1; - this->binning[0] = 1; - this->binning[1] = 1; - this->region_of_interest[0] = 1; - this->region_of_interest[1] = 1; - this->region_of_interest[2] = 1; - this->region_of_interest[3] = 1; - this->image_center[0] = 1; - this->image_center[1] = 1; - this->iscube = false; - this->datatype = -1; - this->type_set = false; //!< set true when datatype has been defined - this->shutteractivate = ""; - this->num_pre_exposures = 0; //!< default is no pre-exposures - } + // set the array lengths of each axis in pixels + // + this->naxes[0] = this->axis_pixels[0] / this->binning[0]; + this->naxes[1] = this->axis_pixels[1] / this->binning[1]; + + this->section_size = this->naxes[0] * this->naxes[1]; // Pixels to write for this image section + this->image_size = this->naxes[0] * this->naxes[1]; // Pixels to write for this image section - long pre_exposures(std::string num_in, std::string &num_out); - - long set_axes() { - std::string function = "Camera::Information::set_axes"; - std::stringstream message; - long bytes_per_pixel; - - if (this->frame_type == FRAME_RAW) { - bytes_per_pixel = 2; - this->datatype = USHORT_IMG; - } else { - switch (this->bitpix) { - case 16: - bytes_per_pixel = 2; - this->datatype = SHORT_IMG; - break; - case 32: - bytes_per_pixel = 4; - this->datatype = FLOAT_IMG; - break; - default: - message << "ERROR: unknown bitpix " << this->bitpix << ": expected {16,32}"; - logwrite(function, message.str()); - return (ERROR); - } - } - this->type_set = true; // datatype has been set - - this->naxis = 2; - - this->axis_pixels[0] = this->region_of_interest[1] - - this->region_of_interest[0] + 1; - this->axis_pixels[1] = this->region_of_interest[3] - - this->region_of_interest[2] + 1; - - this->axes[0] = this->axis_pixels[0] / this->binning[0]; - this->axes[1] = this->axis_pixels[1] / this->binning[1]; - - this->section_size = this->axes[0] * this->axes[1]; // Pixels to write for this image section - this->image_memory = this->detector_pixels[0] - * this->detector_pixels[1] * bytes_per_pixel; // Bytes per detector + this->image_memory = this->detector_pixels[0] + * this->detector_pixels[1] * this->bytes_per_pixel; // Bytes per detector + + message << this->naxes[0] << " x " << this->naxes[1] << " pixels"; + logwrite( function, message.str() ); #ifdef LOGLEVEL_DEBUG - message << "[DEBUG] region_of_interest[1]=" << this->region_of_interest[1] + message.str(""); + message << "[DEBUG]" + << " region_of_interest[1]=" << this->region_of_interest[1] << " region_of_interest[0]=" << this->region_of_interest[0] << " region_of_interest[3]=" << this->region_of_interest[3] << " region_of_interest[2]=" << this->region_of_interest[2] - << " axes[0]=" << this->axes[0] - << " axes[1]=" << this->axes[1]; + << " naxes[0]=" << this->naxes[0] + << " naxes[1]=" << this->naxes[1] + << " detector_pixels[0]=" << this->detector_pixels[0] + << " detector_pixels[1]=" << this->detector_pixels[1] + << " section_size=" << this->section_size << "pix" + << " image_size=" << this->image_size << "pix" + << " image_memory=" << this->image_memory << "Bytes"; logwrite( function, message.str() ); #endif - return (NO_ERROR); - } + return NO_ERROR; + } + /***** Camera::Information::set_axes ************************************/ }; - - /**************** Camera::Information ***************************************/ + /***** Camera::Information ************************************************/ } +/***** Camera *****************************************************************/ diff --git a/camerad/fits.h b/camerad/fits.h index ec4f11a9..b47f18e5 100644 --- a/camerad/fits.h +++ b/camerad/fits.h @@ -24,7 +24,7 @@ const int FITS_WRITE_WAIT = 5000; /// approx time (in msec) to wait for a frame to be written -class FITS_file { +class __FITS_file { // prepended "__" to designate old version private: std::atomic threadcount; /// keep track of number of write_image_thread threads std::atomic framen; /// internal frame counter for data cubes @@ -41,7 +41,7 @@ class FITS_file { bool iserror() { return this->error; }; /// allows outsiders access to errors that occurred in a fits writing thread bool isopen() { return this->file_open; }; /// allows outsiders access file open status - FITS_file() : threadcount(0), framen(0), writing_file(false), error(false), file_open(false) { + __FITS_file() : threadcount(0), framen(0), writing_file(false), error(false), file_open(false) { } /**************** FITS_file::open_file ************************************/ @@ -56,7 +56,7 @@ class FITS_file { * */ long open_file(bool writekeys, Camera::Information &info) { - std::string function = "FITS_file::open_file"; + std::string function = "__FITS_file::open_file"; std::stringstream message; long axes[2]; // local variable of image axes size @@ -177,7 +177,7 @@ class FITS_file { * */ void close_file(bool writekeys, Camera::Information &info) { - std::string function = "FITS_file::close_file"; + std::string function = "__FITS_file::close_file"; std::stringstream message; // Nothing to do if not open @@ -357,8 +357,8 @@ class FITS_file { * */ template - void write_image_thread(std::valarray &data, Camera::Information &info, FITS_file *self) { - std::string function = "FITS_file::write_image_thread"; + void write_image_thread(std::valarray &data, Camera::Information &info, __FITS_file *self) { + std::string function = "__FITS_file::write_image_thread"; std::stringstream message; // This makes the thread wait while another thread is writing images. This @@ -422,8 +422,8 @@ class FITS_file { * */ template - void write_cube_thread(std::valarray &data, Camera::Information &info, FITS_file *self) { - std::string function = "FITS_file::write_cube_thread"; + void write_cube_thread(std::valarray &data, Camera::Information &info, __FITS_file *self) { + std::string function = "__FITS_file::write_cube_thread"; std::stringstream message; #ifdef LOGLEVEL_DEBUG @@ -552,7 +552,7 @@ class FITS_file { * */ void make_camera_header(Camera::Information &info) { - std::string function = "FITS_file::make_camera_header"; + std::string function = "__FITS_file::make_camera_header"; std::stringstream message; try { // To put just the filename into the header (and not the path), find the last slash @@ -581,7 +581,7 @@ class FITS_file { * Uses CCFits */ void add_key(std::string keyword, std::string type, std::string value, std::string comment) { - std::string function = "FITS_file::add_key"; + std::string function = "__FITS_file::add_key"; std::stringstream message; // The file must have been opened first From 84aa24c123838a64da2165cba88f1cc9ddc98372 Mon Sep 17 00:00:00 2001 From: David Hale Date: Wed, 25 Sep 2024 15:05:01 -0700 Subject: [PATCH 3/5] implements ExposureMode base and derived classes, enables dynamic selection of appropriate expose function. --- camerad/archon.cpp | 353 +++++++++++++++++++++++++++++++++------ camerad/archon.h | 56 ++++++- camerad/camera.cpp | 49 +++--- camerad/camera.h | 8 +- camerad/camerad.cpp | 146 +++++----------- camerad/camerad.h | 3 +- camerad/exposure_modes.h | 131 +++++++++++++++ camerad/fits_file.h | 2 +- common/common.cpp | 108 +++++++----- common/common.h | 45 ++--- 10 files changed, 650 insertions(+), 251 deletions(-) create mode 100644 camerad/exposure_modes.h diff --git a/camerad/archon.cpp b/camerad/archon.cpp index 44004d46..8380f868 100644 --- a/camerad/archon.cpp +++ b/camerad/archon.cpp @@ -6,6 +6,7 @@ * */ #include "archon.h" +#include "exposure_modes.h" #include // for std::stringstream #include // for setfil, setw, etc. @@ -22,8 +23,10 @@ namespace Archon { // Archon::Interface constructor // Interface::Interface() { + this->pExposureMode=nullptr; + this->exposure_mode_str="not_set"; this->archon_busy = false; - this->modeselected = false; + this->is_camera_mode = false; this->firmwareloaded = false; this->msgref = 0; this->lastframe = 0; @@ -95,6 +98,35 @@ namespace Archon { Interface::~Interface() = default; + /***** Archon::ExposureBase::expose *****************************************/ + /** + * @brief perform an exposure + * @details This is the main entry point for the expose command. Things + * common to all exposure modes are done here, and things unique + * to a specialization are handled by calling expose_for_mode(). + * @param[in] mode mode in + * @return ERROR | NO_ERROR + * + */ + long ExposureBase::expose( const std::string &nseq_in ) { + std::string function="Archon::ExposureBase::expose"; + std::stringstream message; + long error; + + message << "nseq_in=" << nseq_in; + logwrite( function, message.str() ); + + std::string mode = interface->camera_info.camera_mode; + + // Calls the specific expose for the selected ExposureMode + // + error = expose_for_mode(); + + return error; + } + /***** Archon::ExposureBase::expose *****************************************/ + + /**************** Archon::Interface::interface ******************************/ long Interface::interface(std::string &iface) { std::string function = "Archon::Interface::interface"; @@ -105,6 +137,49 @@ namespace Archon { /**************** Archon::Interface::interface ******************************/ + /***** Archon::Interface::select_expose_mode ********************************/ + /** + * @brief select the expose mode + * @details This dynamically constructs an appropriate exposure mode + * handler with a pointer to the Interface class so that the + * ExposureMode specialization has access to the interface.. + * @param[in] mode input exposure mode + * @return ERROR | NO_ERROR + * + */ + void Interface::select_exposure_mode( ExposureMode mode ) { + std::string function="Archon::Interface::select_expose_mode"; + switch ( mode ) { + case Archon::ExposureMode::EXPOSUREMODE_CCD: + this->pExposureMode = std::make_unique(this); + this->exposure_mode_str = "CCD"; + logwrite( function, "selected Expose_CCD" ); + break; + case Archon::ExposureMode::EXPOSUREMODE_FOWLER: + this->pExposureMode = std::make_unique(this); + this->exposure_mode_str = "Fowler"; + logwrite( function, "selected Expose_Fowler" ); + break; + case Archon::ExposureMode::EXPOSUREMODE_RXRVIDEO: + this->pExposureMode = std::make_unique(this); + this->exposure_mode_str = "RXRV"; + logwrite( function, "selected Expose_RXRV" ); + break; + case Archon::ExposureMode::EXPOSUREMODE_UTR: + this->pExposureMode = std::make_unique(this); + this->exposure_mode_str = "UTR"; + logwrite( function, "selected Expose_UTR" ); + break; + default: + this->exposure_mode_str = "not_set"; + logwrite( function, "ERROR unknown exposure mode" ); + this->pExposureMode = nullptr; + break; + } + } + /***** Archon::Interface::select_expose_mode ********************************/ + + /***** Archon::Interface::do_power ******************************************/ /** * @brief set/get the power state @@ -1769,7 +1844,7 @@ namespace Archon { // if (error != NO_ERROR) error = this->fetchlog(); - this->modeselected = false; // require that a mode be selected after loading new firmware + this->is_camera_mode = false; // require that a mode be selected after loading new firmware return error; } @@ -1957,8 +2032,8 @@ namespace Archon { error = ERROR; } - this->camera_info.current_observing_mode = mode; // identify the newly selected mode in the camera_info class object - this->modeselected = true; // a valid mode has been selected + this->camera_info.camera_mode = mode; // identify the newly selected mode in the camera_info class object + this->is_camera_mode = true; // a valid mode has been selected message.str(""); message << "new mode: " << mode << " will use " << this->camera_info.bitpix << " bits per pixel"; logwrite(function, message.str()); @@ -2650,7 +2725,7 @@ namespace Archon { * * This version, with no parameter, is the one that is called by the server. * The decision is made here if the frame to be read is a RAW or an IMAGE - * frame based on this->camera_info.current_observing_mode, then the + * frame based on this->camera_info.camera_mode, then the * overloaded version of read_frame(frame_type) is called with the appropriate * frame type of IMAGE or RAW. * @@ -2662,12 +2737,12 @@ namespace Archon { std::stringstream message; long error = NO_ERROR; - if ( ! this->modeselected ) { + if ( ! this->is_camera_mode ) { this->camera.log_error( function, "no mode selected" ); return ERROR; } - int rawenable = this->modemap[this->camera_info.current_observing_mode].rawenable; + int rawenable = this->modemap[this->camera_info.camera_mode].rawenable; if (rawenable == -1) { this->camera.log_error( function, "RAWENABLE is undefined" ); @@ -2676,7 +2751,7 @@ namespace Archon { // RAW-only // - if (this->camera_info.current_observing_mode == "RAW") { // "RAW" is the only reserved mode name + if (this->camera_info.camera_mode == "RAW") { // "RAW" is the only reserved mode name // the RAWENABLE parameter must be set in the ACF file, in order to read RAW data // @@ -2709,7 +2784,7 @@ namespace Archon { logwrite(function, "[DEBUG] rawenable is set -- IMAGE+RAW file will be saved"); logwrite(function, "[DEBUG] switching to mode=RAW"); #endif - std::string orig_mode = this->camera_info.current_observing_mode; // save the original mode, so we can come back to it + std::string orig_mode = this->camera_info.camera_mode; // save the original mode, so we can come back to it error = this->set_camera_mode("raw"); // switch to raw mode if ( error != NO_ERROR ) { logwrite( function, "ERROR: switching to raw mode" ); return error; } @@ -2759,7 +2834,7 @@ namespace Archon { uint64_t bufaddr; unsigned int block, bufblocks=0; long error = ERROR; - int num_detect = this->modemap[this->camera_info.current_observing_mode].geometry.num_detect; + int num_detect = this->modemap[this->camera_info.camera_mode].geometry.num_detect; // Archon buffer number of the last frame read into memory // Archon frame index is 1 biased so add 1 here @@ -2947,7 +3022,7 @@ namespace Archon { uint64_t bufaddr; unsigned int block, bufblocks=0; long error = ERROR; - int num_detect = this->modemap[this->camera_info.current_observing_mode].geometry.num_detect; + int num_detect = this->modemap[this->camera_info.camera_mode].geometry.num_detect; this->camera_info.frame_type = frame_type; @@ -3219,7 +3294,7 @@ namespace Archon { int16_t *cbuf16s; //!< used to cast char buf into 16 bit int long error=NO_ERROR; - if ( ! this->modeselected ) { + if ( ! this->is_camera_mode ) { this->camera.log_error( function, "no mode selected" ); return ERROR; } @@ -3461,7 +3536,7 @@ namespace Archon { // supplemental header keywords // - fits_write_key( FP, TSTRING, "MODE", &this->camera_info.current_observing_mode, "observing mode", &status ); + fits_write_key( FP, TSTRING, "MODE", &this->camera_info.camera_mode, "observing mode", &status ); // write HDU // @@ -3732,10 +3807,128 @@ namespace Archon { /***** Archon::Interface::get_status_key ************************************/ - /**************** Archon::Interface::expose *********************************/ + /***** Archon::Interface::expose ********************************************/ /** - * @fn expose - * @brief initiate an exposure + * @brief initiate an exposure (NEW VERSION) + * @details This calls the expose_for_mode() function which is overridden + * in each specialized Expose class. All of the exposure details + * are handled by each specialized class for the current mode. + * @param[in] nseq_in + * @return ERROR | NO_ERROR | HELP + * + */ + long Interface::expose(std::string nseq_in) { + std::string function = "Archon::Interface::expose"; + std::stringstream message; + long error; + + // must have defined an exposure mode + // + if ( ! pExposureMode ) { + this->camera.log_error( function, "exposure mode pointer not initialized" ); + return ERROR; + } + + // must have selected a camera mode xxx from a valid ACF MODE_xxx + // + if ( ! this->is_camera_mode ) { + this->camera.log_error( function, "no camera mode selected" ); + return ERROR; + } + + std::string mode = this->camera_info.camera_mode; // local short copy for convenience + + // exposeparam is set by the configuration file + // check to make sure it was set or else expose won't work + // + if ( this->exposeparam.empty() ) { + message.str(""); message << "EXPOSE_PARAM not defined in configuration file " << this->config.filename; + this->camera.log_error( function, message.str() ); + return ERROR; + } + + // If the exposure time or longexposure mode were never set then read them from the Archon. + // This ensures that, if the client doesn't set these values then the server will have the + // same default values that the ACF has, rather than hope that the ACF programmer picks + // their defaults to match mine. + // + if ( ! this->camera_info.exposure_time.is_set() ) { + this->camera.async.enqueue_and_log( "NOTICE", function, "exptime has not been set--will read from Archon" ); + + // read the Archon configuration memory + // + std::string etime; + if ( read_parameter( "exptime", etime ) != NO_ERROR ) { + logwrite( function, "ERROR: reading \"exptime\" parameter from Archon" ); + return ERROR; + } + + // Tell the server these values + // + std::string retval; + if ( this->exptime( etime, retval ) != NO_ERROR ) { logwrite( function, "ERROR: setting exptime" ); return ERROR; } + } + + if ( ! this->is_longexposure_set && ! this->longexposeparam.empty() ) { + logwrite( function, "NOTICE:longexposure has not been set--will read from Archon" ); + this->camera.async.enqueue_and_log( "NOTICE", function, "longexposure has not been set--will read from Archon" ); + + // read the Archon configuration memory + // + std::string lexp; + if ( read_parameter( this->longexposeparam, lexp ) != NO_ERROR ) { + logwrite( function, "ERROR reading \""+this->longexposeparam+"\" parameter from Archon" ); + return ERROR; + } + + // Tell the server these values + // + std::string retval; + if ( this->longexposure( lexp, retval ) != NO_ERROR ) { + logwrite( function, "ERROR: setting longexposure" ); + return ERROR; + } + } + + // If nseq_in is not supplied then set nseq to 1. + // Add any pre-exposures onto the number of sequences. + // + int nseq; // numerical value of Nseq used for arithmetic + std::string nseqstr; // string of Nseq used for setting Archon parameter + if ( nseq_in.empty() ) { + nseq = 1 + this->camera_info.num_pre_exposures; + nseqstr = std::to_string( nseq ); + } + else { + // sequence argument passed in + try { + nseq = std::stoi( nseq_in ) + this->camera_info.num_pre_exposures; + nseqstr = std::to_string( nseq ); + } + catch (const std::exception &e) { + message.str(""); message << "parsing nseq " << nseq_in << ": " << e.what(); + this->camera.log_error( function, message.str() ); + return ERROR; + } + } + + // call the expose() in the ExposureBase class which will call the + // correct expose function for the current Exposure Mode + // + if ( this->pExposureMode ) error = pExposureMode->expose(nseq_in); + else { + this->camera.log_error( function, "exposure mode pointer not initialized" ); + error = ERROR; + } + + return error; + } + /***** Archon::Interface::expose ********************************************/ + + + /***** Archon::Interface::__expose ******************************************/ + /** + * @brief initiate an exposure (OLD VERSION) * @param nseq_in string, if set becomes the number of sequences * @return ERROR or NO_ERROR * @@ -3750,16 +3943,16 @@ namespace Archon { * read out the detector into the frame buffer after an exposure. * */ - long Interface::expose(std::string nseq_in) { - std::string function = "Archon::Interface::expose"; + long Interface::__expose(std::string nseq_in) { + std::string function = "Archon::Interface::__expose"; std::stringstream message; long error = NO_ERROR; std::string nseqstr; int nseq; - std::string mode = this->camera_info.current_observing_mode; // local copy for convenience + std::string mode = this->camera_info.camera_mode; // local copy for convenience - if ( ! this->modeselected ) { + if ( ! this->is_camera_mode ) { this->camera.log_error( function, "no mode selected" ); return ERROR; } @@ -4106,7 +4299,7 @@ namespace Archon { return (error); } - /**************** Archon::Interface::expose *********************************/ + /***** Archon::Interface::__expose ******************************************/ /**************** Archon::Interface::hsetup ********************************/ @@ -4259,7 +4452,7 @@ namespace Archon { error = this->cds(cmd.str(), dontcare); // update modemap, in case someone asks again - std::string mode = this->camera_info.current_observing_mode; + std::string mode = this->camera_info.camera_mode; this->modemap[mode].geometry.linecount = rows; this->modemap[mode].geometry.pixelcount = cols; @@ -4402,7 +4595,7 @@ namespace Archon { error = this->cds( cmd.str(), dontcare ); // update modemap, in case someone asks again - std::string mode = this->camera_info.current_observing_mode; + std::string mode = this->camera_info.camera_mode; // Adjust geometry parameters and camera_info this->modemap[mode].geometry.linecount = rows; @@ -4561,9 +4754,9 @@ namespace Archon { std::string nseqstr; int nseq, finalframe, nread, currentindex; - std::string mode = this->camera_info.current_observing_mode; // local copy for convenience + std::string mode = this->camera_info.camera_mode; // local copy for convenience - if ( ! this->modeselected ) { + if ( ! this->is_camera_mode ) { this->camera.log_error( function, "no mode selected" ); return ERROR; } @@ -4787,9 +4980,9 @@ namespace Archon { std::string nseqstr; int nseq; - std::string mode = this->camera_info.current_observing_mode; // local copy for convenience + std::string mode = this->camera_info.camera_mode; // local copy for convenience - if ( ! this->modeselected ) { + if ( ! this->is_camera_mode ) { this->camera.log_error( function, "no mode selected" ); return ERROR; } @@ -5860,9 +6053,9 @@ namespace Archon { std::vector tokens; long error = NO_ERROR; - std::string mode = this->camera_info.current_observing_mode; // local copy for convenience + std::string mode = this->camera_info.camera_mode; // local copy for convenience - if ( ! this->modeselected ) { + if ( ! this->is_camera_mode ) { this->camera.log_error( function, "no mode selected" ); return ERROR; } @@ -6271,7 +6464,7 @@ namespace Archon { // add any keys from the ACF file (from modemap[mode].acfkeys) into the // camera_info.userkeys object // - std::string mode = this->camera_info.current_observing_mode; + std::string mode = this->camera_info.camera_mode; Common::FitsKeys::fits_key_t::iterator keyit; for ( keyit = this->modemap[mode].acfkeys.keydb.begin(); keyit != this->modemap[mode].acfkeys.keydb.end(); @@ -7655,13 +7848,13 @@ namespace Archon { } std::string testname; - /* the first token is the test name */ try { - testname = tokens.at(0); - - } catch ( std::out_of_range & ) { - this->camera.log_error( function, "testname token out of range" ); - return ERROR; + testname = tokens.at(0); // the first token is the testname + } + catch ( const std::exception &e ) { + message.str(""); message << "parsing testname token: " << e.what(); + this->camera.log_error( function, message.str() ); + return ERROR; } // ---------------------------------------------------- @@ -7671,7 +7864,7 @@ namespace Archon { // if (testname == "ampinfo") { - std::string mode = this->camera_info.current_observing_mode; + std::string mode = this->camera_info.camera_mode; int framemode = this->modemap[mode].geometry.framemode; message.str(""); message << "[ampinfo] observing mode=" << mode; @@ -7716,7 +7909,9 @@ namespace Archon { } error = NO_ERROR; - } else if (testname == "busy") { + } + else + if (testname == "busy") { // ---------------------------------------------------- // busy // ---------------------------------------------------- @@ -7746,7 +7941,38 @@ namespace Archon { } retstring = this->archon_busy ? "true" : "false"; - } else if (testname == "fitsname") { + } + else + if (testname == "expmode") { + // ---------------------------------------------------- + // expmode + // ---------------------------------------------------- + // set exposure mode + if ( tokens.size() > 1 ) { + if ( tokens[1] == "?" || tokens[1] == "help" ) { + retstring="test expmode"; + retstring.append( " ccd | fowler | rxrv | utr\n" ); + retstring.append( " Sets the expose mode.\n" ); + return HELP; + } + error = NO_ERROR; // assume success but change on error + if ( caseCompareString(tokens[1], "ccd") ) this->select_exposure_mode( Archon::ExposureMode::EXPOSUREMODE_CCD ); + else + if ( caseCompareString(tokens[1], "fowler") ) this->select_exposure_mode( Archon::ExposureMode::EXPOSUREMODE_FOWLER ); + else + if ( caseCompareString(tokens[1], "rxrv") ) this->select_exposure_mode( Archon::ExposureMode::EXPOSUREMODE_RXRVIDEO ); + else + if ( caseCompareString(tokens[1], "utr") ) this->select_exposure_mode( Archon::ExposureMode::EXPOSUREMODE_UTR ); + else { + logwrite( function, "ERROR unknown mode" ); + retstring="invalid_argument"; + error = ERROR; + } + } + retstring = this->get_exposure_mode(); + } + else + if (testname == "fitsname") { // ---------------------------------------------------- // fitsname // ---------------------------------------------------- @@ -7766,7 +7992,9 @@ namespace Archon { } // end if (testname == fitsname) - } else if ( testname == "builddate" ) { + } + else + if ( testname == "builddate" ) { // ---------------------------------------------------- // builddate // ---------------------------------------------------- @@ -7777,7 +8005,9 @@ namespace Archon { logwrite( function, build ); // end if ( testname == builddate ) - } else if (testname == "async") { + } + else + if (testname == "async") { // ---------------------------------------------------- // async [message] // ---------------------------------------------------- @@ -7803,7 +8033,9 @@ namespace Archon { error = NO_ERROR; // end if (testname == async) - } else if (testname == "modules") { + } + else + if (testname == "modules") { // ---------------------------------------------------- // modules // ---------------------------------------------------- @@ -7817,7 +8049,9 @@ namespace Archon { retstring = message.str(); error = NO_ERROR; - } else if (testname == "parammap") { + } + else + if (testname == "parammap") { // ---------------------------------------------------- // parammap // ---------------------------------------------------- @@ -7849,7 +8083,9 @@ namespace Archon { error = NO_ERROR; // end if (testname == parammap) - } else if (testname == "configmap") { + } + else + if (testname == "configmap") { // ---------------------------------------------------- // configmap // ---------------------------------------------------- @@ -7903,7 +8139,9 @@ namespace Archon { logwrite(function, message.str()); // end if (testname == configmap) - } else if (testname == "bw") { + } + else + if (testname == "bw") { // ---------------------------------------------------- // bw // ---------------------------------------------------- @@ -7912,7 +8150,7 @@ namespace Archon { // of exposures, including reading the frame buffer -- everything except // for the fits file writing. - if ( ! this->modeselected ) { + if ( ! this->is_camera_mode ) { this->camera.log_error( function, "no mode selected" ); return ERROR; } @@ -8003,8 +8241,8 @@ namespace Archon { } this->add_filename_key(); // add filename to system keys database Common::FitsKeys::fits_key_t::iterator keyit; // add keys from the ACF file - for (keyit = this->modemap[this->camera_info.current_observing_mode].acfkeys.keydb.begin(); - keyit != this->modemap[this->camera_info.current_observing_mode].acfkeys.keydb.end(); + for (keyit = this->modemap[this->camera_info.camera_mode].acfkeys.keydb.begin(); + keyit != this->modemap[this->camera_info.camera_mode].acfkeys.keydb.end(); keyit++) { this->camera_info.userkeys.keydb[keyit->second.keyword].keyword = keyit->second.keyword; this->camera_info.userkeys.keydb[keyit->second.keyword].keytype = keyit->second.keytype; @@ -8097,7 +8335,9 @@ namespace Archon { logwrite(function, message.str()); // end if (testname==bw) - } else if (testname == "timer") { + } + else + if (testname == "timer") { // ---------------------------------------------------- // timer // ---------------------------------------------------- @@ -8187,7 +8427,9 @@ namespace Archon { retstring = "delta=" + std::to_string( m ) + " stddev=" + std::to_string( stdev ); // end if (testname==timer) - } else if (testname == "rconfigmap") { + } + else + if (testname == "rconfigmap") { // ---------------------------------------------------- // rconfigmap // reports the configmap (what should have been written) @@ -8213,7 +8455,9 @@ namespace Archon { } } error = NO_ERROR; - } else if (testname == "rconfig") { + } + else + if (testname == "rconfig") { // ---------------------------------------------------- // rconfig // reads config directly from Archon @@ -8247,7 +8491,9 @@ namespace Archon { } } error = NO_ERROR; - } else if (testname == "logwconfig") { + } + else + if (testname == "logwconfig") { // ---------------------------------------------------- // logwconfig [ ] // set/get state of logwconfig, to optionally log WCONFIG commands @@ -8261,7 +8507,8 @@ namespace Archon { message.str(""); message << "logwconfig " << retstring; logwrite( function, message.str() ); error = NO_ERROR; - } else { + } + else { // ---------------------------------------------------- // invalid test name // ---------------------------------------------------- diff --git a/camerad/archon.h b/camerad/archon.h index 783f2945..010ed495 100644 --- a/camerad/archon.h +++ b/camerad/archon.h @@ -84,16 +84,62 @@ namespace Archon { const int DEF_SHUTENABLE_ENABLE = 1; const int DEF_SHUTENABLE_DISABLE = 0; + /** + * @brief exposure modes + */ + enum class ExposureMode { + EXPOSUREMODE_CCD, + EXPOSUREMODE_FOWLER, + EXPOSUREMODE_RXRVIDEO, + EXPOSUREMODE_UTR + }; + + class Interface; + + /***** Archon::ExposureBase ***********************************************/ + /** + * @class Archon::ExposureBase + * @brief exposure base class + * + */ + class ExposureBase { + protected: + Archon::Interface* interface; // pointer to the Archon::Interface class + + public: + ExposureBase(Interface* interface) : interface(interface) { } + + virtual ~ExposureBase() = default; + + virtual long expose_for_mode() = 0; + + long expose( const std::string &nseq_in ); + }; + /***** Archon::ExposureBase ***********************************************/ + + + /***** Archon::Interface **************************************************/ + /** + * @class Archon::Interface + * @brief describes the interface to an Archon + * + */ class Interface { - private: + private: unsigned long int start_timer, finish_timer; //!< Archon internal timer, start and end of exposure int n_hdrshift; //!< number of right-shift bits for Archon buffer in HDR mode - public: + std::unique_ptr pExposureMode; /// pointer to ExposureBase class + std::string exposure_mode_str; /// human readable representation of exposure mode + + public: Interface(); ~Interface(); + void select_exposure_mode( ExposureMode mode ); /// select exposure mode + const std::string get_exposure_mode() { return this->exposure_mode_str; } + // Class Objects // Network::TcpSocket archon; @@ -115,7 +161,7 @@ namespace Archon { bool logwconfig; //!< optionally log WCONFIG commands std::vector gain; //!< digital CDS gain (from TAPLINE definition) std::vector offset; //!< digital CDS offset (from TAPLINE definition) - bool modeselected; //!< true if a valid mode has been selected, false otherwise + bool is_camera_mode; //!< true if a valid camera mode has been selected, false otherwise bool firmwareloaded; //!< true if firmware is loaded, false otherwise bool is_longexposure_set; //!< true for long exposure mode (exptime in sec), false for exptime in msec bool is_window; //!< true if in window mode for h2rg, false if not @@ -236,7 +282,8 @@ namespace Archon { long power( std::string args, std::string &retstring ); /// wrapper for do_power long do_power( std::string args, std::string &retstring ); /// set/get Archon power state - long expose(std::string nseq_in); + long expose(std::string nseq_in); /// new version + long __expose(std::string nseq_in); /// old version long hexpose(std::string nseq_in); @@ -426,4 +473,5 @@ namespace Archon { */ map_t statusmap; }; + /***** Archon::Interface **************************************************/ } diff --git a/camerad/camera.cpp b/camerad/camera.cpp index 9674b679..392a0c04 100644 --- a/camerad/camera.cpp +++ b/camerad/camera.cpp @@ -57,32 +57,37 @@ namespace Camera { } - /** Camera::Camera::log_error ***********************************************/ + /***** Camera::Camera::log_error ******************************************/ /** - * @fn log_error - * @brief logs the error and saves the message to be returned on the command port - * @param std::string function name - * @param std::string message (error) - * @return ERROR or NO_ERROR + * @brief logs and saves an error message + * @details This expands the functionality of simply logwrite-ing an + * error message by saving the message to the class, which + * enables returning that message string to the command port + * if "is_longerror" is set. Do not include the string "ERROR" + * in the supplied message because that will be added here + * automatically. + * @param[in] function calling function + * @param[in] message error message * */ - void Camera::log_error(std::string function, std::string message) { - std::stringstream err; - - // Save this message in class variable - this->lasterrorstring.str(""); - this->lasterrorstring << message; - - // Form an error string as "ERROR: " - err << "ERROR: " << this->lasterrorstring.str(); - - // Log and send to async port in the usual ways - // - logwrite(function, err.str()); - this->async.enqueue(err.str()); + void Camera::log_error( const std::string &function, const std::string &message) { + std::stringstream err; + + // Save this message to the class + // + this->lasterrorstring.str(""); + this->lasterrorstring << message; + + // Form an error string as "ERROR: " + // + err << "ERROR: " << this->lasterrorstring.str(); + + // Log and send to async port in the usual ways + // + logwrite(function, err.str()); + this->async.enqueue(err.str()); } - - /** Camera::Camera::log_error ***********************************************/ + /***** Camera::Camera::log_error ******************************************/ /** Camera::Camera::get_longerror *******************************************/ diff --git a/camerad/camera.h b/camerad/camera.h index 38cee330..eb2f18c6 100644 --- a/camerad/camera.h +++ b/camerad/camera.h @@ -90,7 +90,7 @@ namespace Camera { /// readout time in msec for given controller device number, read from .cfg file std::map readout_time; - void log_error(std::string function, std::string message); + void log_error( const std::string &function, const std::string &message ); std::string get_longerror(); @@ -377,7 +377,7 @@ namespace Camera { long detector_pixels[2]; //!< number of physical pixels. element 0=cols (pixels), 1=rows (lines) long section_size; //!< pixels to write for this section (could be less than full sensor size) uint32_t image_memory; //!< bytes per image sensor - std::string current_observing_mode; //!< the current mode + std::string camera_mode; //!< the current camera mode xxxx from ACF MODE_xxxx std::string readout_name; //!< name of the readout source int readout_type; //!< type of the readout source is an enum long naxis; //!< number of axes in the image (3 for data cube) @@ -480,7 +480,7 @@ namespace Camera { detector_pixels{other.detector_pixels[0], other.detector_pixels[1]}, section_size(other.section_size), image_memory(other.image_memory), - current_observing_mode(other.current_observing_mode), + camera_mode(other.camera_mode), readout_name(other.readout_name), readout_type(other.readout_type), naxis(other.naxis), @@ -553,7 +553,7 @@ namespace Camera { detector_pixels[1] = other.detector_pixels[1]; section_size = other.section_size; image_memory = other.image_memory; - current_observing_mode = other.current_observing_mode; + camera_mode = other.camera_mode; readout_name = other.readout_name; readout_type = other.readout_type; naxis = other.naxis; diff --git a/camerad/camerad.cpp b/camerad/camerad.cpp index 3e0c4c72..8902eb70 100644 --- a/camerad/camerad.cpp +++ b/camerad/camerad.cpp @@ -356,11 +356,12 @@ void block_main(Network::TcpSocket sock) { */ void thread_main(Network::TcpSocket sock) { while (true) { - server.conn_mutex.lock(); - sock.Accept(); - server.conn_mutex.unlock(); - doit(sock); // call function to do the work - sock.Close(); + { + std::lock_guard lock(server.conn_mutex); + sock.Accept(); + } + doit(sock); // call function to do the work + sock.Close(); } return; } @@ -467,19 +468,17 @@ void doit(Network::TcpSocket sock) { if ((ret = sock.Read(sbuf, delim)) <= 0) { if (ret < 0) { // could be an actual read error - message.str(""); - message << "Read error on fd " << sock.getfd() << ": " << strerror(errno); + message.str(""); message << "Read error on fd " << sock.getfd() << ": " << strerror(errno); logwrite(function, message.str()); } - if (ret == 0) { - message.str(""); - message << "timeout reading from fd " << sock.getfd(); + if (ret == -2) { + message.str(""); message << "timeout reading from fd " << sock.getfd(); logwrite(function, message.str()); } break; // Breaking out of the while loop will close the connection. - // This probably means that the client has terminated abruptly, - // having sent FIN but not stuck around long enough - // to accept CLOSE and give the LAST_ACK. + // This probably means that the client has terminated abruptly, + // having sent FIN but not stuck around long enough + // to accept CLOSE and give the LAST_ACK. } // convert the input buffer into a string and remove any trailing linefeed @@ -488,14 +487,11 @@ void doit(Network::TcpSocket sock) { sbuf.erase(std::remove(sbuf.begin(), sbuf.end(), '\r'), sbuf.end()); sbuf.erase(std::remove(sbuf.begin(), sbuf.end(), '\n'), sbuf.end()); - if (sbuf.empty()) { - sock.Write("\n"); - continue; - } // acknowledge empty command so client doesn't time out + if (sbuf.empty()) { sock.Write("\n"); continue; } // acknowledge empty command so client doesn't time out try { - std::size_t cmd_sep = sbuf.find_first_of(" "); // find the first space, which separates command from argument list + std::size_t cmd_sep = sbuf.find_first_of(" "); cmd = sbuf.substr(0, cmd_sep); // cmd is everything up until that space @@ -504,17 +500,20 @@ void doit(Network::TcpSocket sock) { continue; } // acknowledge empty command so client doesn't time out - if (cmd_sep == std::string::npos) { - // If no space was found, - args = ""; // then the arg list is empty, + if (cmd_sep == std::string::npos) { // If no space was found, + args = ""; // then the arg list is empty, } else { - args = sbuf.substr(cmd_sep + 1); // otherwise args is everything after that space. + args = sbuf.substr(cmd_sep + 1); // otherwise args is everything after that space. } - message.str(""); - message << "thread " << sock.id << " received command on fd " << sock.getfd() << ": " << cmd << " " << args; + // command number counter helps pair the response with the command in the logs + // + if ( ++server.cmd_num == INT_MAX ) server.cmd_num=0; + + message.str(""); message << "thread " << sock.id << " received command on fd " << sock.getfd() + << " (" << server.cmd_num << "): " << cmd << " " << args; logwrite(function, message.str()); - } catch (std::runtime_error &e) { + } catch (const std::runtime_error &e) { std::stringstream errstream; errstream << e.what(); message.str(""); @@ -629,28 +628,22 @@ void doit(Network::TcpSocket sock) { else if (cmd=="isopen") { ret = server.is_connected( retstring ); - sock.Write(retstring); - sock.Write(" "); } else if (cmd=="useframes") { ret = server.access_useframes(args); - if (!args.empty()) { sock.Write(args); sock.Write(" "); } } else if (cmd=="geometry") { ret = server.geometry(args, retstring); - if (!retstring.empty()) { sock.Write(retstring); sock.Write(" "); } } else if (cmd=="buffer") { ret = server.buffer(args, retstring); - if (!retstring.empty()) { sock.Write(retstring); sock.Write(" "); } } else if (cmd=="readout") { ret = server.readout(args, retstring); - if (!retstring.empty()) { sock.Write(retstring); sock.Write(" "); } } #endif #ifdef STA_ARCHON @@ -670,49 +663,32 @@ void doit(Network::TcpSocket sock) { else if (cmd=="hroi") { ret = server.hroi( args, retstring ); - if (!retstring.empty()) { sock.Write(retstring); sock.Write(" "); } } else if (cmd=="hwindow") { ret = server.hwindow( args, retstring ); - if (!retstring.empty()) { sock.Write(retstring); sock.Write(" "); } } #endif else if (cmd == "roi") { ret = server.region_of_interest(args, retstring); - if (!retstring.empty()) { - sock.Write(retstring); - sock.Write(" "); - } } else if (cmd == "isloaded") { retstring = server.firmwareloaded ? "true" : "false"; - sock.Write(retstring); - sock.Write(" "); ret = NO_ERROR; } else if (cmd == "mode") { if (args.empty()) { // no argument means asking for current mode - if (server.modeselected) { + if (server.is_camera_mode) { + retstring=server.camera_info.camera_mode; ret = NO_ERROR; - sock.Write(server.camera_info.current_observing_mode); - sock.Write(" "); } else ret = ERROR; // no mode selected returns an error } else ret = server.set_camera_mode(args); } else if (cmd == "getp") { ret = server.get_parameter(args, retstring); - if (!retstring.empty()) { - sock.Write(retstring); - sock.Write(" "); - } } else if (cmd == "setp") { ret = server.set_parameter(args); } else if (cmd == "loadtiming") { if (args.empty()) ret = server.load_timing(retstring); else ret = server.load_timing(args, retstring); - if (!retstring.empty()) { - sock.Write(retstring); - sock.Write(" "); - } } else if (cmd == "inreg") { ret = server.inreg(args); } else if (cmd == "printstatus") { @@ -724,43 +700,19 @@ void doit(Network::TcpSocket sock) { ret = server.write_frame(); } else if (cmd == "cds") { ret = server.cds(args, retstring); - if (!retstring.empty()) { - sock.Write(retstring); - sock.Write(" "); - } } else if (cmd == "heater") { ret = server.heater(args, retstring); - if (!retstring.empty()) { - sock.Write(retstring); - sock.Write(" "); - } } else if (cmd == "sensor") { ret = server.sensor(args, retstring); - if (!retstring.empty()) { - sock.Write(retstring); - sock.Write(" "); - } } else if (cmd == "longexposure") { ret = server.longexposure(args, retstring); - if (!retstring.empty()) { - sock.Write(retstring); - sock.Write(" "); - } } else if (cmd == "hdrshift") { ret = server.hdrshift(args, retstring); - if (!retstring.empty()) { - sock.Write(retstring); - sock.Write(" "); - } } else if (cmd == "trigin") { ret = server.trigin(args); } else if (cmd=="autofetch") { ret = server.autofetch(args, retstring); - if (!retstring.empty()) { - sock.Write(retstring); - sock.Write( " "); - } } else if ( cmd == "fetchlog" ) { ret = server.fetchlog(); @@ -771,39 +723,18 @@ void doit(Network::TcpSocket sock) { } else if (cmd == "exptime") { ret = server.exptime(args, retstring); - if (!retstring.empty()) { - sock.Write(retstring); - sock.Write(" "); - } } - else if (!retstring.empty()) { - sock.Write(retstring); - sock.Write(" "); - } else if (cmd == "bias") { + else if (cmd == "bias") { ret = server.bias(args, retstring); - if (!retstring.empty()) { - sock.Write(retstring); - sock.Write(" "); - } } else if (cmd == "echo") { sock.Write(args); sock.Write("\n"); } else if (cmd == "interface") { ret = server.interface(retstring); - sock.Write(retstring); - sock.Write(" "); } else if (cmd =="power") { ret = server.power( args, retstring ); - if (!retstring.empty()) { - sock.Write(retstring); - sock.Write(" "); - } } else if (cmd == "test") { ret = server.test(args, retstring); - if (!retstring.empty()) { - sock.Write(retstring); - sock.Write(" "); - } } else if (cmd == "native") { try { std::transform(args.begin(), args.end(), args.begin(), ::toupper); // make uppercase @@ -813,7 +744,6 @@ void doit(Network::TcpSocket sock) { } #ifdef ASTROCAM ret = server.native(args, retstring); - if (!retstring.empty()) { sock.Write(retstring); sock.Write(" "); } #endif #ifdef STA_ARCHON ret = server.native(args); @@ -823,24 +753,28 @@ void doit(Network::TcpSocket sock) { // if no matching command found // else { - message.str(""); - message << "ERROR unrecognized command: " << cmd; + message.str(""); message << "ERROR unrecognized command: " << cmd; logwrite(function, message.str()); ret = ERROR; } if (ret != NOTHING) { - std::string retstr = (ret == 0 ? "DONE\n" : "ERROR\n"); - if (ret == 0) retstr = "DONE\n"; - else retstr = "ERROR" + server.camera.get_longerror() + "\n"; - if (sock.Write(retstr) < 0) connection_open = false; + if ( !retstring.empty() ) retstring.append(" "); + if ( ret != HELP ) retstring.append( ret==NO_ERROR ? "DONE" : "ERROR" ); + + if ( !retstring.empty() && ret != HELP ) { + message.str(""); message << "command (" << server.cmd_num << ") reply: " << retstring; + logwrite( function, message.str() ); + } + + retstring.append("\n"); + if ( sock.Write( retstring ) < 0 ) connection_open = false; } - if (!sock.isblocking()) break; // Non-blocking connection exits immediately. - // Keep blocking connection open for interactive session. + if (!sock.isblocking()) break; // Non-blocking connection exits immediately. + // Keep blocking connection open for interactive session. } - sock.Close(); return; } diff --git a/camerad/camerad.h b/camerad/camerad.h index a1e6a693..648b6e08 100644 --- a/camerad/camerad.h +++ b/camerad/camerad.h @@ -51,8 +51,9 @@ namespace Camera { int nbport; //!< non-blocking port int blkport; //!< blocking port int asyncport; //!< asynchronous message port + int cmd_num; - Server() : nbport(-1), blkport(-1), asyncport(-1) { + Server() : nbport(-1), blkport(-1), asyncport(-1), cmd_num(0) { } ~Server() { diff --git a/camerad/exposure_modes.h b/camerad/exposure_modes.h new file mode 100644 index 00000000..33207130 --- /dev/null +++ b/camerad/exposure_modes.h @@ -0,0 +1,131 @@ +/** + * @file exposure_modes.h + * @brief describes the supported exposure modes + * @details This contains derived classes for each exposure mode, + * selected by Archon::Interface::select_expose_mode(). + * These inherit from the ExposureBase class and override + * the expose_for_mode() function. + * + */ +#pragma once + +#include "archon.h" + +namespace Archon { + + + /***** Archon::Expose_CCD ***************************************************/ + class Expose_CCD : public ExposureBase { + public: + Expose_CCD(Interface* interface) : ExposureBase(interface) { } + + long expose_for_mode() override { + const std::string function="Archon::Expose_CCD::expose_for_mode"; + logwrite( "Archon::Expose_CCD::expose_for_mode", "CCD" ); + return NO_ERROR; + } + }; + /***** Archon::Expose_CCD ***************************************************/ + + + /***** Archon::Expose_UTR ***************************************************/ + class Expose_UTR : public ExposureBase { + public: + Expose_UTR(Interface* interface) : ExposureBase(interface) { } + + long expose_for_mode() override { + const std::string function="Archon::Expose_UTR::expose_for_mode"; + logwrite( "Archon::Expose_UTR::expose_for_mode", "UTR" ); + return NO_ERROR; + } + }; + /***** Archon::Expose_UTR ***************************************************/ + + + /***** Archon::Expose_Fowler ************************************************/ + class Expose_Fowler : public ExposureBase { + public: + Expose_Fowler(Interface* interface) : ExposureBase(interface) { } + + long expose_for_mode() override { + const std::string function="Archon::Expose_Fowler::expose_for_mode"; + logwrite( "Archon::Expose_Fowler::expose_for_mode", "Fowler" ); + return NO_ERROR; + } + }; + /***** Archon::Expose_Fowler ************************************************/ + + + /***** Archon::Expose_RXRV **************************************************/ + /** + * @class Archon::Expose_RXRV + * @brief derived exposure mode class for RXR video + * @details This is constructed with a pointer to the Archon::Interface + * class so that member functions have access to Interface, + * using the "this->interface->" pointer dereference. + * + */ + class Expose_RXRV : public ExposureBase { + public: + Expose_RXRV(Interface* interface) : ExposureBase(interface) { } + + /***** Archon::Expose_RXRV::expose_for_mode *****************************/ + /** + * @brief this is the exposure sequence for RXR Video + * + */ + long expose_for_mode() override { + const std::string function="Archon::Expose_RXRV::expose_for_mode"; + std::stringstream message; + + logwrite( function, "RXRV" ); + logwrite( "Archon::Expose_RXRV::isconnected", std::to_string(this->interface->archon.isconnected()) ); + + // ********************************** + // ******** first frame here ******** + // ********************************** + + // Read the first frame buffer from Archon to host and decrement my local + // frame counter. This call to read_frame() reads into this->archon_buf. + // + + // Always deinterlace first frame. + // Write here only if is_unp set to write unprocessed images. + // + + // + // -- MAIN SEQUENCE LOOP -- + // + + // increment ring index + // + + // Wait for detector readout into Archon internal frame buffer + // + + // ********************************** + // ******** next frames here ******** + // ********************************** + + // Read the next (and subsequent) frame buffers from Archon to host and + // decrement my local frame counter. This reads into this->archon_buf. + // + + // De-interlace each subsequent frame + // + + // for multi-exposure (non-cubeamp) cubes, close the FITS file now that they've all been written + // + + // Complete the FITS file after processing all frames. + // This closes the file(s) for any defined FITS_file object(s), and + // shuts down the FITS engine, waiting for the queue to empty if needed. + // + + return NO_ERROR; + } + /***** Archon::Expose_RXRV::expose_for_mode *****************************/ + }; + /***** Archon::Expose_RXRV **************************************************/ + +} diff --git a/camerad/fits_file.h b/camerad/fits_file.h index e97a11dc..b1444dfe 100644 --- a/camerad/fits_file.h +++ b/camerad/fits_file.h @@ -837,7 +837,7 @@ class FITS_file "Detector firmware version"); this->pFits->pHDU().addKey("EXPOSURE", camera_info.exposure_time.value(), "Total Exposure Time ("+camera_info.exposure_time.unit()+")"); - this->pFits->pHDU().addKey("MODE_NUM", camera_info.current_observing_mode, + this->pFits->pHDU().addKey("MODE_NUM", camera_info.camera_mode, "Mode identifying key"); message << camera_info.binning[0] << " " << camera_info.binning[1]; this->pFits->pHDU().addKey("DETSUM", message.str(), "DET binning"); diff --git a/common/common.cpp b/common/common.cpp index 31805902..5a0832ed 100644 --- a/common/common.cpp +++ b/common/common.cpp @@ -8,46 +8,78 @@ #include "common.h" namespace Common { - /** Common::Queue::enqueue **************************************************/ - /** - * @fn enqueue - * @brief puts a message into the queue - * @param std::string message - * @return none - * - */ - void Queue::enqueue(std::string message) { - std::lock_guard lock(queue_mutex); - message_queue.push(message); - notifier.notify_one(); - return; - } - /** Common::Queue::enqueue **************************************************/ - - - /** Common::Queue::dequeue **************************************************/ - /** - * @fn dequeue - * @brief pops the first message off the queue - * @param none - * @return std::string message - * - * Get the "front"-element. - * If the queue is empty, wait untill an element is avaiable. - * - */ - std::string Queue::dequeue(void) { - std::unique_lock lock(queue_mutex); - while (message_queue.empty()) { - notifier.wait(lock); // release lock as long as the wait and reaquire it afterwards. - } - std::string message = message_queue.front(); - message_queue.pop(); - return message; + /***** Common::Queue::enqueue ***********************************************/ + /** + * @brief puts a message into the queue + * @param[in] message string to write + * + */ + void Queue::enqueue(std::string message) { + std::lock_guard lock(queue_mutex); + message_queue.push(message); + notifier.notify_one(); + return; + } + /***** Common::Queue::enqueue ***********************************************/ + + + /***** Common::Queue::enqueue_and_log ***************************************/ + /** + * @brief puts a message into the queue and writes it to the log + * @param[in] function name of function for logging purposes + * @param[in] message string to write + * + */ + void Queue::enqueue_and_log(std::string function, std::string message) { + std::lock_guard lock(queue_mutex); + message_queue.push(message); + notifier.notify_one(); + logwrite( function, message ); + return; + } + /***** Common::Queue::enqueue_and_log ***************************************/ + + + /***** Common::Queue::enqueue_and_log ***************************************/ + /** + * @brief puts a message into the queue and writes it to the log + * @param[in] tag tag for broadcast message + * @param[in] function name of function for logging purposes + * @param[in] message string to write + * + */ + void Queue::enqueue_and_log( std::string tag, std::string function, std::string message ) { + std::lock_guard lock(queue_mutex); + std::stringstream qmessage; + qmessage << tag << ":" << message; + message_queue.push(qmessage.str()); + notifier.notify_one(); + logwrite( function, message ); + return; + } + /***** Common::Queue::enqueue_and_log ***************************************/ + + + /***** Common::Queue::dequeue ***********************************************/ + /** + * @brief pops the first message off the queue + * @return message string read from the queue + * + * Get the "front"-element. + * If the queue is empty, wait untill an element is avaiable. + * + */ + std::string Queue::dequeue(void) { + std::unique_lock lock(queue_mutex); + while(message_queue.empty()) { + notifier.wait(lock); // release lock as long as the wait and reaquire it afterwards. } - - /** Common::Queue::dequeue **************************************************/ + std::string message = message_queue.front(); + message_queue.pop(); + return message; + } + /***** Common::Queue::dequeue ***********************************************/ /** Common::FitsKeys::get_keytype *******************************************/ diff --git a/common/common.h b/common/common.h index bbbfceb5..d29ba5b2 100644 --- a/common/common.h +++ b/common/common.h @@ -183,28 +183,29 @@ namespace Common { /***** Common::FitsKeys ***************************************************/ - /***** Common::Queue ******************************************************/ - /** - * @class Queue - * @brief provides a thread-safe messaging queue - * - */ - class Queue { + /***** Common::Queue ********************************************************/ + /** + * @class Queue + * @brief provides a thread-safe messaging queue + * + */ + class Queue { private: - std::queue message_queue; - mutable std::mutex queue_mutex; - std::condition_variable notifier; - bool is_running; - + std::queue message_queue; + mutable std::mutex queue_mutex; + std::condition_variable notifier; + bool is_running; public: - Queue(void) : is_running(false) { - } - - void service_running(bool state) { this->is_running = state; }; /// set service running - bool service_running() { return this->is_running; }; /// is the service running? - - void enqueue(std::string message); /// push an element into the queue. - std::string dequeue(void); /// pop an element from the queue - }; - /***** Common::Queue ******************************************************/ + Queue(void) : message_queue(), queue_mutex(), notifier(), is_running(false) { }; + ~Queue(void) {} + + void service_running(bool state) { is_running = state; }; ///< set service running + bool service_running() { return is_running; }; ///< is the service running? + + void enqueue_and_log(std::string function, std::string message); + void enqueue_and_log(std::string tag, std::string function, std::string message); + void enqueue(std::string message); ///< push an element into the queue. + std::string dequeue(void); ///< pop an element from the queue + }; + /***** Common::Queue ********************************************************/ } From d01a44fbb21231fdb2d3e06fe992293dfee6dbeb Mon Sep 17 00:00:00 2001 From: David Hale Date: Thu, 26 Sep 2024 15:55:04 -0700 Subject: [PATCH 4/5] builds more functionality into Expose_RXRV --- camerad/archon.cpp | 163 ++++++++++++++++++++++++++++++++++++-- camerad/archon.h | 29 +++++-- camerad/camera.h | 1 + camerad/camerad.cpp | 13 +++ camerad/exposure_modes.h | 118 ++++++++++++++++++++------- common/camerad_commands.h | 22 +++++ 6 files changed, 305 insertions(+), 41 deletions(-) create mode 100644 common/camerad_commands.h diff --git a/camerad/archon.cpp b/camerad/archon.cpp index 8380f868..35bf7080 100644 --- a/camerad/archon.cpp +++ b/camerad/archon.cpp @@ -104,17 +104,30 @@ namespace Archon { * @details This is the main entry point for the expose command. Things * common to all exposure modes are done here, and things unique * to a specialization are handled by calling expose_for_mode(). - * @param[in] mode mode in + * @param[in] nseq_in * @return ERROR | NO_ERROR * */ - long ExposureBase::expose( const std::string &nseq_in ) { + long ExposureBase::expose( const int &nseq_in ) { std::string function="Archon::ExposureBase::expose"; std::stringstream message; long error; - message << "nseq_in=" << nseq_in; - logwrite( function, message.str() ); + nseq = nseq_in; + + // Copy the camera info class to use here for FITS file writing + // of processed images. + // + fits_info = interface->camera_info; + + // If unprocessed images are being written then also make a copy + // for that FITS writer, inserting "_unp" at the end of the filename. + // + if ( interface->is_unp ) { + unp_info = interface->camera_info; + size_t pos = unp_info.fits_name.find(".fits"); + if ( pos != std::string::npos ) unp_info.fits_name.insert( pos, "_unp" ); + } std::string mode = interface->camera_info.camera_mode; @@ -150,6 +163,11 @@ namespace Archon { void Interface::select_exposure_mode( ExposureMode mode ) { std::string function="Archon::Interface::select_expose_mode"; switch ( mode ) { + case Archon::ExposureMode::EXPOSUREMODE_RAW: + this->pExposureMode = std::make_unique(this); + this->exposure_mode_str = "Raw"; + logwrite( function, "selected Expose_Raw" ); + break; case Archon::ExposureMode::EXPOSUREMODE_CCD: this->pExposureMode = std::make_unique(this); this->exposure_mode_str = "CCD"; @@ -180,6 +198,117 @@ namespace Archon { /***** Archon::Interface::select_expose_mode ********************************/ + /***** Archon::Interface::save_unp ******************************************/ + /** + * @brief set/get the state of saving unprocessed images + * @param[in] args input string contains requested state + * @param[out] retstring return string contains the current state + * @return ERROR | NO_ERROR | HELP + * + */ + long Interface::save_unp(std::string args, std::string &retstring) { + std::string function = "Archon::Interface::save_unp"; + std::stringstream message; + + // Help + // + if ( args == "?" || args == "help" ) { + retstring=CAMERAD_SAVEUNP; + retstring.append( " [ true | false ]\n" ); + retstring.append( " Set state of saving unprocessed images. If no argument provided\n" ); + retstring.append( " then the current state is returned.\n" ); + return HELP; + } + + // Accept the strings "true" or "false" without regards to case + // and set the class variable. + // + if ( caseCompareString( args, "true" ) ) { + this->is_unp = true; + } + else + if ( caseCompareString( args, "false" ) ) { + this->is_unp = false; + } + else if ( !args.empty() ) { + message.str(""); message << "ERROR invalid argument " << args << ": expected { true false }"; + logwrite( function, message.str() ); + retstring="invalid_argument"; + return ERROR; + } + + // returns the state of the class variable as a string + // + retstring = ( this->is_unp ? "true" : "false" ); + + message << "will" << ( this->is_unp ? " " : " not " ) << "save unprocessed images"; + logwrite( function, message.str() ); + + return NO_ERROR; + } + /***** Archon::Interface::save_unp ******************************************/ + + + /***** Archon::Interface::fits_compression **********************************/ + /** + * @brief set/get FITS compression + * @param[in] args input string contains requested compression + * @param[out] retstring return string contains the current compression + * @return ERROR | NO_ERROR | HELP + * + */ + long Interface::fits_compression(std::string args, std::string &retstring) { + std::string function = "Archon::Interface::fits_compression"; + std::stringstream message; + + // Help + // + if ( args == "?" || args == "help" ) { + retstring=CAMERAD_COMPRESSION; + retstring.append( " [ none | rice | gzip | plio ]\n" ); + retstring.append( " Set the FITS compression type. No argument returns the current type.\n" ); + return HELP; + } + + // Accept string representation of the compression type without regards to + // case, saving the the class a FITS-friendly code and a human-friendly + // string which represents the compression type. + // + if ( caseCompareString( args, "none" ) ) { + this->camera_info.fits_compression_code = 0; + this->camera_info.fits_compression_type = "none"; + } + else + if ( caseCompareString( args, "rice" ) ) { + this->camera_info.fits_compression_code = RICE_1; + this->camera_info.fits_compression_type = "rice"; + } + else + if ( caseCompareString( args, "gzip" ) ) { + this->camera_info.fits_compression_code = GZIP_1; + this->camera_info.fits_compression_type = "gzip"; + } + else + if ( caseCompareString( args, "plio" ) ) { + this->camera_info.fits_compression_code = PLIO_1; + this->camera_info.fits_compression_type = "plio"; + } + else if ( !args.empty() ) { + message.str(""); message << "ERROR invalid argument " << args << ": expected { none rice zip plio }"; + logwrite( function, message.str() ); + retstring="invalid_argument"; + return ERROR; + } + + // returns the human-friendly string + // + retstring=this->camera_info.fits_compression_type; + + return NO_ERROR; + } + /***** Archon::Interface::fits_compression **********************************/ + + /***** Archon::Interface::do_power ******************************************/ /** * @brief set/get the power state @@ -3915,7 +4044,7 @@ namespace Archon { // call the expose() in the ExposureBase class which will call the // correct expose function for the current Exposure Mode // - if ( this->pExposureMode ) error = pExposureMode->expose(nseq_in); + if ( this->pExposureMode ) error = pExposureMode->expose(nseq); else { this->camera.log_error( function, "exposure mode pointer not initialized" ); error = ERROR; @@ -7840,6 +7969,28 @@ namespace Archon { std::vector tokens; long error; + // Help + // + if ( args == "?" || args == "help" ) { + retstring = CAMERAD_TEST; + retstring.append( " ...\n" ); + retstring.append( " ampinfo\n" ); + retstring.append( " async [ ? | ]\n" ); + retstring.append( " builddate [ ? ]\n" ); + retstring.append( " busy [ ? | yes ]\n" ); + retstring.append( " bw ? | \n" ); + retstring.append( " configmap [ ? ]\n" ); + retstring.append( " expmode [ ? | ccd | fowler | rxrv | utr ]\n" ); + retstring.append( " fitsname [ ? ]\n" ); + retstring.append( " logwconfig [ ? | ]\n" ); + retstring.append( " modules [ ? ]\n" ); + retstring.append( " parammap [ ? ]\n" ); + retstring.append( " rconfig [ ? | ]\n" ); + retstring.append( " rconfigmap [ ? | ]\n" ); + retstring.append( " timer [ ? ]\n" ); + return HELP; + } + Tokenize(args, tokens, " "); if (tokens.empty()) { @@ -7952,7 +8103,7 @@ namespace Archon { if ( tokens[1] == "?" || tokens[1] == "help" ) { retstring="test expmode"; retstring.append( " ccd | fowler | rxrv | utr\n" ); - retstring.append( " Sets the expose mode.\n" ); + retstring.append( " Sets the expose mode. No argument returns the current mode.\n" ); return HELP; } error = NO_ERROR; // assume success but change on error diff --git a/camerad/archon.h b/camerad/archon.h index 010ed495..f1c80298 100644 --- a/camerad/archon.h +++ b/camerad/archon.h @@ -88,6 +88,7 @@ namespace Archon { * @brief exposure modes */ enum class ExposureMode { + EXPOSUREMODE_RAW, EXPOSUREMODE_CCD, EXPOSUREMODE_FOWLER, EXPOSUREMODE_RXRVIDEO, @@ -105,15 +106,22 @@ namespace Archon { class ExposureBase { protected: Archon::Interface* interface; // pointer to the Archon::Interface class + int nseq; + + // Each exposure gets its own copy of the Camera::Information class. + // There is one each for processed and unprocessed images. + // + Camera::Information fits_info; /// processed images + Camera::Information unp_info; /// un-processed images public: - ExposureBase(Interface* interface) : interface(interface) { } + ExposureBase(Interface* interface) : interface(interface), nseq(1) { } virtual ~ExposureBase() = default; virtual long expose_for_mode() = 0; - long expose( const std::string &nseq_in ); + long expose( const int &nseq_in ); }; /***** Archon::ExposureBase ***********************************************/ @@ -166,6 +174,7 @@ namespace Archon { bool is_longexposure_set; //!< true for long exposure mode (exptime in sec), false for exptime in msec bool is_window; //!< true if in window mode for h2rg, false if not bool is_autofetch; + bool is_unp; //!< should I write unprocessed files? int win_hstart; int win_hstop; int win_vstart; @@ -176,6 +185,13 @@ namespace Archon { bool lastcubeamps; + int ring_index; //!< index into ring buffer, counts 0,1,0,1,... + std::vector signal_buf; //!< signal frame data ring buffer + std::vector reset_buf; //!< reset frame data ring buffer + char *archon_buf; //!< image data buffer as FETCH-ed from Archon + uint32_t archon_buf_bytes; //!< requested number of bytes allocated for archon_buf rounded up to block size + uint32_t archon_buf_allocated; //!< allocated number of bytes for archon_buf + std::string trigin_state; //!< for external triggering of exposures int trigin_expose; //!< current value of trigin expose @@ -197,10 +213,6 @@ namespace Archon { float heater_target_min; //!< minimum heater target temperature float heater_target_max; //!< maximum heater target temperature - char *archon_buf; //!< image data buffer holds FETCHed Archon data - uint32_t archon_buf_bytes; //!< requested number of bytes allocated for archon_buf rounded up to block size - uint32_t archon_buf_allocated; //!< allocated number of bytes for archon_buf - std::atomic archon_busy; //!< indicates a thread is accessing Archon std::mutex archon_mutex; //!< protects Archon from being accessed by multiple threads, @@ -214,6 +226,11 @@ namespace Archon { // Functions // + void ring_index_inc() { if (++this->ring_index==2) this->ring_index=0; } + int prev_ring_index() { int i=this->ring_index-1; return( i<0 ? 1 : i ); } + long save_unp(std::string args, std::string &retstring); + long fits_compression(std::string args, std::string &retstring); + static long interface(std::string &iface); //!< get interface type long configure_controller(); //!< get configuration parameters long prepare_archon_buffer(); //!< prepare archon_buf, allocating memory as needed diff --git a/camerad/camera.h b/camerad/camera.h index eb2f18c6..d7f6b3d4 100644 --- a/camerad/camera.h +++ b/camerad/camera.h @@ -21,6 +21,7 @@ #include "common.h" #include "logentry.h" #include "utilities.h" +#include "camerad_commands.h" /// commands accepted by camerad // handy snprintf shortcut #define SNPRINTF(VAR, ...) { snprintf(VAR, sizeof(VAR), __VA_ARGS__); } diff --git a/camerad/camerad.cpp b/camerad/camerad.cpp index 8902eb70..c6911e89 100644 --- a/camerad/camerad.cpp +++ b/camerad/camerad.cpp @@ -534,6 +534,11 @@ void doit(Network::TcpSocket sock) { ret = NOTHING; std::string retstring; // string for return the value (where needed) + if (cmd == "help" || cmd == "?" ) { + for ( const auto &syntax : CAMERAD_SYNTAX ) { retstring.append( syntax+"\n" ); } + ret = HELP; + } + else if (cmd == "exit") { server.camera.async.enqueue("exit"); // shutdown the async message thread if running server.exit_cleanly(); // shutdown the server @@ -717,6 +722,14 @@ void doit(Network::TcpSocket sock) { else if ( cmd == "fetchlog" ) { ret = server.fetchlog(); } + else + if ( cmd == CAMERAD_COMPRESSION ) { + ret = server.fits_compression(args, retstring); + } + else + if ( cmd == CAMERAD_SAVEUNP ) { + ret = server.save_unp(args, retstring); + } #endif else if (cmd == "expose") { ret = server.expose(args); diff --git a/camerad/exposure_modes.h b/camerad/exposure_modes.h index 33207130..957aec7b 100644 --- a/camerad/exposure_modes.h +++ b/camerad/exposure_modes.h @@ -1,10 +1,15 @@ /** * @file exposure_modes.h + * * @brief describes the supported exposure modes - * @details This contains derived classes for each exposure mode, - * selected by Archon::Interface::select_expose_mode(). - * These inherit from the ExposureBase class and override - * the expose_for_mode() function. + * + * @details This contains derived classes for each exposure mode, selected + * by Archon::Interface::select_expose_mode(). These inherit from + * the ExposureBase class and override the expose_for_mode function. + * + * Each class is constructed with a pointer to the Archon::Interface + * class so that member functions have access to Interface, using the + * "this->interface->" pointer dereference. * */ #pragma once @@ -14,6 +19,20 @@ namespace Archon { + /***** Archon::Expose_Raw ***************************************************/ + class Expose_Raw : public ExposureBase { + public: + Expose_Raw(Interface* interface) : ExposureBase(interface) { } + + long expose_for_mode() override { + const std::string function="Archon::Expose_Raw::expose_for_mode"; + logwrite( "Archon::Expose_Raw::expose_for_mode", "Raw" ); + return NO_ERROR; + } + }; + /***** Archon::Expose_CCD ***************************************************/ + + /***** Archon::Expose_CCD ***************************************************/ class Expose_CCD : public ExposureBase { public: @@ -60,9 +79,10 @@ namespace Archon { /** * @class Archon::Expose_RXRV * @brief derived exposure mode class for RXR video - * @details This is constructed with a pointer to the Archon::Interface - * class so that member functions have access to Interface, - * using the "this->interface->" pointer dereference. + * @details Each Archon frame buffer contains a pair of frames, a read and + * a reset frame. The reset frame belongs to the next read, so + * the read of the first pair and the reset of the last pair are + * not used. * */ class Expose_RXRV : public ExposureBase { @@ -77,52 +97,92 @@ namespace Archon { long expose_for_mode() override { const std::string function="Archon::Expose_RXRV::expose_for_mode"; std::stringstream message; + long error = ERROR; + + // Create FITS_file pointers for the files used in this function, + // for CDS and unprocessed images. + // + auto file_cds = std::make_unique>(interface->camera.datacube() ? true : false); + auto file_unp = std::make_unique>(interface->camera.datacube() ? true : false); logwrite( function, "RXRV" ); - logwrite( "Archon::Expose_RXRV::isconnected", std::to_string(this->interface->archon.isconnected()) ); + logwrite( "Archon::Expose_RXRV::isconnected", std::to_string(interface->archon.isconnected()) ); - // ********************************** - // ******** first frame here ******** - // ********************************** + // *************************************** + // ******** first frame pair here ******** + // *************************************** // Read the first frame buffer from Archon to host and decrement my local - // frame counter. This call to read_frame() reads into this->archon_buf. + // frame counter. This call to read_frame() reads into interface->archon_buf. // + error = interface->read_frame(); + + if ( error != NO_ERROR ) { + logwrite( function, "ERROR reading frame buffer" ); + return ERROR; + } // Always deinterlace first frame. // Write here only if is_unp set to write unprocessed images. // + // + // -- MAIN SEQUENCE LOOP -- + // + while ( nseq > 0 ) { + + // increment ring index // - // -- MAIN SEQUENCE LOOP -- - // + interface->ring_index_inc(); - // increment ring index - // + if ( interface->camera_info.exposure_time.value() != 0 ) { // wait for the exposure delay to complete (if there is one) + error = interface->wait_for_exposure(); + if ( error != NO_ERROR ) { + logwrite( function, "ERROR: waiting for exposure" ); + break; + } + } - // Wait for detector readout into Archon internal frame buffer - // + // Wait for detector readout into Archon internal frame buffer + // + error = interface->wait_for_readout(); + if ( error != NO_ERROR ) { + logwrite( function, "ERROR waiting for readout" ); + break; + } + + // ********************************************* + // ******** subsequent frame pairs here ******** + // ********************************************* + + // Read the next (and subsequent) frame buffers from Archon to host and + // decrement my local frame counter. This reads into interface->archon_buf. + // + error = interface->read_frame(); + nseq--; - // ********************************** - // ******** next frames here ******** - // ********************************** + if ( error != NO_ERROR ) { + logwrite( function, "ERROR reading frame buffer" ); + break; + } - // Read the next (and subsequent) frame buffers from Archon to host and - // decrement my local frame counter. This reads into this->archon_buf. - // + // De-interlace each subsequent frame + // - // De-interlace each subsequent frame - // + // Write CDS frame + // - // for multi-exposure (non-cubeamp) cubes, close the FITS file now that they've all been written - // + if ( error != NO_ERROR ) break; + } // Complete the FITS file after processing all frames. // This closes the file(s) for any defined FITS_file object(s), and // shuts down the FITS engine, waiting for the queue to empty if needed. // + if ( file_cds ) file_cds->complete(); + if ( file_unp ) file_unp->complete(); - return NO_ERROR; + return error; } /***** Archon::Expose_RXRV::expose_for_mode *****************************/ }; diff --git a/common/camerad_commands.h b/common/camerad_commands.h new file mode 100644 index 00000000..f723939d --- /dev/null +++ b/common/camerad_commands.h @@ -0,0 +1,22 @@ +/** + * @file camerad_commands.h + * @brief the doit() function listens for these commands. + * @author David Hale + * + */ + +#pragma once + +const std::string CAMERAD_COMPRESSION = "compress"; ///< FITS compression type +const std::string CAMERAD_EXPOSE = "expose"; ///< initiate an exposure +const std::string CAMERAD_POWER = "power"; ///< controller power state +const std::string CAMERAD_SAVEUNP = "saveunp"; ///< save unprocessed images +const std::string CAMERAD_TEST = "test"; ///< test routines + +const std::vector CAMERAD_SYNTAX = { + CAMERAD_COMPRESSION+" [ ? | none | rice | gzip | plio ]", + CAMERAD_EXPOSE, + CAMERAD_POWER+" [ ? | on | off ]", + CAMERAD_SAVEUNP+" [ ? | true | false ]", + CAMERAD_TEST+" ? | ..." + }; From 671ee687c43ef7731c88bad8e072c1d0dfc031cc Mon Sep 17 00:00:00 2001 From: David Hale Date: Tue, 1 Oct 2024 23:17:24 -0700 Subject: [PATCH 5/5] adds a deinterlacing class This technically builds but it isn't going to work as it is, there are some scope problems. Still working out how to best do this. --- camerad/archon.cpp | 8 +- camerad/archon.h | 99 +++++++++++++++++------ camerad/deinterlace_modes.h | 156 ++++++++++++++++++++++++++++++++++++ camerad/exposure_modes.h | 92 ++++++++++++++++++++- 4 files changed, 327 insertions(+), 28 deletions(-) create mode 100644 camerad/deinterlace_modes.h diff --git a/camerad/archon.cpp b/camerad/archon.cpp index 35bf7080..c44f4956 100644 --- a/camerad/archon.cpp +++ b/camerad/archon.cpp @@ -7,6 +7,7 @@ */ #include "archon.h" #include "exposure_modes.h" +#include "deinterlace_modes.h" #include // for std::stringstream #include // for setfil, setw, etc. @@ -4041,14 +4042,17 @@ namespace Archon { } } - // call the expose() in the ExposureBase class which will call the - // correct expose function for the current Exposure Mode + // ***************************************************************** + // * call the expose() in the ExposureBase class which will call the + // * correct expose function for the current Exposure Mode + // ***************************************************************** // if ( this->pExposureMode ) error = pExposureMode->expose(nseq); else { this->camera.log_error( function, "exposure mode pointer not initialized" ); error = ERROR; } + // ***************************************************************** return error; } diff --git a/camerad/archon.h b/camerad/archon.h index f1c80298..39ab7b23 100644 --- a/camerad/archon.h +++ b/camerad/archon.h @@ -23,7 +23,7 @@ #include "network.h" #include "fits.h" /// old version renames FITS_file to __FITS_file, will go away soon #include "fits_file.h" /// new version implements FITS_file - +#include "deinterlace_modes.h" constexpr int MAXADCCHANS = 16; //!< max number of ADC channels per controller (4 mod * 4 ch/mod) constexpr int MAXADMCHANS = 72; //!< max number of ADM channels per controller (4 mod * 18 ch/mod) @@ -95,35 +95,84 @@ namespace Archon { EXPOSUREMODE_UTR }; - class Interface; - - /***** Archon::ExposureBase ***********************************************/ /** - * @class Archon::ExposureBase - * @brief exposure base class - * + * @brief deinterlace modes */ - class ExposureBase { - protected: - Archon::Interface* interface; // pointer to the Archon::Interface class - int nseq; - - // Each exposure gets its own copy of the Camera::Information class. - // There is one each for processed and unprocessed images. - // - Camera::Information fits_info; /// processed images - Camera::Information unp_info; /// un-processed images - - public: - ExposureBase(Interface* interface) : interface(interface), nseq(1) { } + enum class DeInterlaceMode { + DEINTERLACE_NONE, + DEINTERLACE_RXRVIDEO + }; - virtual ~ExposureBase() = default; + class Interface; //!< forward declaration + class ExposureBase; //!< forward declaration - virtual long expose_for_mode() = 0; - long expose( const int &nseq_in ); - }; - /***** Archon::ExposureBase ***********************************************/ + /***** Archon::convert_archon_buffer **************************************/ + /** + * @brief convert Archon char* buffer to the correct type + * @details The Archon transmits 8-bit Bytes which must be organized as + * either 16-bit words or 32-bit words, depending on whether + * the Archon is using HDR mode. This function casts the Archon + * buffer to the appropriate type as well as performs optional + * right-shifting. + * @param[in] bufin Archon buffer + * @param[in] imgsz image size, i.e. number of pixels in buffer + * @param[in] hdrshift number of right-shift bits in HDR mode + * @return newly allocated and converted buffer of type U + * + */ + template + std::vector convert_archon_buffer( const char* bufin, size_t imgsz, int hdrshift=0 ) { + // cast the char* buffer from Archon to the requested type + const T* typecast_bufin = reinterpret_cast(bufin); + + // return buffer can be of a different type + std::vector bufout(imgsz); + + for ( size_t pix=0; pix::value ) { + bufout[pix] = static_cast(typecast_bufin[pix] >> hdrshift); + } + else + // for signed 16-bit subtract 2^15 from every pixel + if constexpr ( std::is_same::value ) { + bufout[pix] = typecast_bufin[pix] - 32768; + } + // for all others it's a straight copy + else { + bufout[pix] = typecast_bufin[pix]; + } + } + return bufout; + } + /***** Archon::convert_archon_buffer **************************************/ + + + /***** Archon::createDeInterlacer *******************************************/ + /** + * @brief factory function for creating a deinterlacer object + * @details This is the main entry point for the expose command. Things + * common to all exposure modes are done here, and things unique + * to a specialization are handled by calling expose_for_mode(). + * @param[in] nseq_in + * @return ERROR | NO_ERROR + * + */ + template + std::unique_ptr createDeInterlacer( const std::vector &buffer, + const std::string &mode ) { + if ( mode == "none" ) { + return std::make_unique>(buffer); + } + else + if ( mode == "rxrv" ) { + return std::make_unique>(buffer); + } + else + return nullptr; + } + /***** Archon::createDeInterlacer *******************************************/ /***** Archon::Interface **************************************************/ diff --git a/camerad/deinterlace_modes.h b/camerad/deinterlace_modes.h new file mode 100644 index 00000000..07c24201 --- /dev/null +++ b/camerad/deinterlace_modes.h @@ -0,0 +1,156 @@ +#pragma once + +namespace Archon { + + /***** Archon::DeInterlaceBase **********************************************/ + /** + * @class Archon::DeInterlaceBase + * @brief deinterlacing base class + * + */ + class DeInterlaceBase { + public: + virtual ~DeInterlaceBase() = default; + virtual void deinterlace() = 0; + }; + /***** Archon::DeInterlaceBase **********************************************/ + + + /***** Archon::DeInterlace **************************************************/ + /** + * @class Archon::DeInterlace + * @brief template class + * + */ + template + class DeInterlace : public DeInterlaceBase { + protected: + std::vector input_buffer; + size_t imgsz; + + public: + DeInterlace(std::vector bufin, size_t imgszin) : input_buffer(std::move(bufin)), imgsz(imgszin) { } + + virtual void do_deinterlace( std::vector &buffer ) = 0; + + void deinterlace() override { + do_deinterlace(input_buffer); + } + }; + /***** Archon::DeInterlace **************************************************/ + + + /***** Archon::DeInterlace_RXRVideo *****************************************/ + /** + * @class Archon::DeInterlace_RXRVideo + * @brief derived class for deinterlace mode RXRVIDEO + * + */ + template + class DeInterlace_RXRVideo : public DeInterlace { + private: + std::vector> _sigbuf; + std::vector> _resbuf; + + public: + DeInterlace_RXRVideo( const std::vector &bufin, const size_t imgsz ) + : DeInterlace(bufin,imgsz), + _sigbuf(2, std::vector(imgsz)), + _resbuf(2, std::vector(imgsz)) { } + + DeInterlace_RXRVideo( const char* bufin, size_t bufsz, int hdrshift=0 ) + : DeInterlace(bufin, bufsz,hdrshift), + _sigbuf(2, std::vector(bufsz)), + _resbuf(2, std::vector(bufsz)) { } + + /** + * @brief returns one of the requested _sigbufs by index + * @param[in] idx index of which buffer to return + * @return reference to _sigbuf[idx] + */ + const std::vector &get_sigbuf(size_t idx) const { + if ( idx > _sigbuf.size() ) { + std::stringstream message; + message << "DeInterlace_RXRVideo::get_sigbuf index " << idx << " out of range: " << _sigbuf.size(); + throw std::out_of_range( message.str() ); + } + else return _sigbuf[idx]; + } + + /** + * @brief returns one of the requested _resbufs by index + * @param[in] idx index of which buffer to return + * @return reference to _resbuf[idx] + */ + const std::vector &get_resbuf(size_t idx) const { + if ( idx > _resbuf.size() ) { + std::stringstream message; + message << "DeInterlace_RXRVideo::get_resbuf index " << idx << " out of range: " << _resbuf.size(); + throw std::out_of_range( message.str() ); + } + else return _resbuf[idx]; + } + + // implement specific logic for mode "RXRVIDEO" + void do_deinterlace( std::vector &buffer ) override { + } + }; + /***** Archon::DeInterlace_RXRVideo *****************************************/ + + + /***** Archon::DeInterlace_None *********************************************/ + /** + * @class Archon::DeInterlace_None + * @brief derived class for deinterlace mode NONE + * @details This class inherits from the DeInterlace template class, + * which inherits from the DeInterlaceBase class. + * + */ + template + class DeInterlace_None : public DeInterlace { + public: + // inherit constructor + using DeInterlace::DeInterlace; + + // implement specific logic for mode "NONE" + void do_deinterlace( std::vector &buffer ) override { + } + }; + /***** Archon::DeInterlace_None *********************************************/ + + + /*** + template + std::unique_ptr createDeInterlacer( const std::vector &buffer, + const std::string &mode ) { + if ( mode == "none" ) { + return std::make_unique>(buffer); + } + return nullptr; + } + ***/ + + template + std::unique_ptr deinterlace_factory( const std::string &mode, + const std::vector buf, + const size_t imgsz ) { + std::stringstream message; + if ( mode == "none" ) { + message << "[DEBUG] created deinterlacer for datatype " << demangle(typeid(T).name()); + logwrite( "deinterlacer_factory", message.str() ); + return std::make_unique>(buf, imgsz); + } + else + if ( mode == "rxrv" ) { + message << "[DEBUG] created deinterlacer for datatype " << demangle(typeid(T).name()); + logwrite( "deinterlacer_factory", message.str() ); + return std::make_unique>(buf, imgsz); + } + else { + logwrite( "deinterlacer_factory", "ERROR unknown mode: "+mode ); + return nullptr; + } + } + + +} diff --git a/camerad/exposure_modes.h b/camerad/exposure_modes.h index 957aec7b..075b6cd4 100644 --- a/camerad/exposure_modes.h +++ b/camerad/exposure_modes.h @@ -18,8 +18,51 @@ namespace Archon { + /***** Archon::ExposureBase *************************************************/ + /** + * @class Archon::ExposureBase + * @brief exposure base class + * @details This class gets inherited by a derived Exposure_xxx class and + * contains a virtual declaration for expose_for_mode() which is + * overridden in each derived class and contains the exposure + * sequence for that exposure mode. + * + */ + class ExposureBase { + protected: + Archon::Interface* interface; // pointer to the Archon::Interface class + std::unique_ptr deinterlacer; + int nseq; + + // Each exposure gets its own copy of the Camera::Information class. + // There is one each for processed and unprocessed images. + // + Camera::Information fits_info; /// processed images + Camera::Information unp_info; /// un-processed images + + public: + ExposureBase(Interface* interface) : interface(interface), deinterlacer(nullptr), nseq(1) { } + + virtual ~ExposureBase() = default; + + virtual long expose_for_mode() = 0; + + long expose( const int &nseq_in ); + + template + void create_deinterlacer( const std::string &mode, const std::vector &buf, const size_t imgsz ) { + this->deinterlacer = deinterlacer_factory(mode, buf, imgsz); + } + }; + /***** Archon::ExposureBase *************************************************/ + /***** Archon::Expose_Raw ***************************************************/ + /** + * @class Archon::Expose_Raw + * @brief derived exposure mode class for raw sampling, oscilloscope mode + * + */ class Expose_Raw : public ExposureBase { public: Expose_Raw(Interface* interface) : ExposureBase(interface) { } @@ -30,10 +73,15 @@ namespace Archon { return NO_ERROR; } }; - /***** Archon::Expose_CCD ***************************************************/ + /***** Archon::Expose_Raw ***************************************************/ /***** Archon::Expose_CCD ***************************************************/ + /** + * @class Archon::Expose_CCD + * @brief derived exposure mode class for CCDs + * + */ class Expose_CCD : public ExposureBase { public: Expose_CCD(Interface* interface) : ExposureBase(interface) { } @@ -48,6 +96,11 @@ namespace Archon { /***** Archon::Expose_UTR ***************************************************/ + /** + * @class Archon::Expose_UTR + * @brief derived exposure mode class for Up The Ramp + * + */ class Expose_UTR : public ExposureBase { public: Expose_UTR(Interface* interface) : ExposureBase(interface) { } @@ -62,6 +115,11 @@ namespace Archon { /***** Archon::Expose_Fowler ************************************************/ + /** + * @class Archon::Expose_Fowler + * @brief derived exposure mode class for Fowler sampling + * + */ class Expose_Fowler : public ExposureBase { public: Expose_Fowler(Interface* interface) : ExposureBase(interface) { } @@ -86,6 +144,22 @@ namespace Archon { * */ class Expose_RXRV : public ExposureBase { + private: + template + void process_frame() { + std::vector converted_buf = convert_archon_buffer(interface->archon_buf, interface->camera_info.image_size); + this->deinterlace(converted_buf, interface->camera_info.image_size); + } + + template + void deinterlace( const std::vector &converted_buf, const size_t imgsz ) { + auto deinterlacer = deinterlace_factory("rxrv", converted_buf, imgsz); + if ( deinterlacer ) deinterlacer->deinterlace(); + else { + throw std::runtime_error("Archon::Expose_RXRV::deinterlace failed to create deinterlacer"); + } + } + public: Expose_RXRV(Interface* interface) : ExposureBase(interface) { } @@ -122,9 +196,25 @@ namespace Archon { return ERROR; } + // Processing the first frame pair will convert the Archon char* buffer + // and deinterlace, producing a pair of properly typed and deinterlaced + // frames in _sigbuf[i] and _resbuf[i]. + // + switch (interface->camera_info.bitpix) { + case ULONG_IMG: process_frame(); + break; + case USHORT_IMG: process_frame(); + break; + } + // Always deinterlace first frame. // Write here only if is_unp set to write unprocessed images. // + if ( deinterlacer ) deinterlacer->deinterlace(); + else { + logwrite( function, "ERROR no deinterlacer" ); + return ERROR; + } // // -- MAIN SEQUENCE LOOP --