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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions camerad/archon_controller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1324,11 +1324,13 @@ namespace Camera {
char message[256];
auto index = this->frameinfo.index.load();
uint32_t maxblocks = (uint32_t)(1.5E9 / this->activebufs / 1024 );
uint64_t maxoffset = this->frameinfo.bufbase[index];
uint64_t maxaddress = maxoffset + maxblocks;
uint64_t minaddress = this->frameinfo.bufbase[index]; // start of the buffer being read
uint64_t maxaddress = minaddress + (uint64_t)maxblocks * BLOCK_LEN; // end of that buffer
uint64_t endaddress = bufferaddress + (uint64_t)bufferblocks * BLOCK_LEN; // end of the request

if (bufferaddress > maxaddress) {
SNPRINTF(message, "fetch Archon buffer requested address 0x%0lX exceeds 0x%0lX", bufferaddress, maxaddress);
if (bufferaddress < minaddress || endaddress > maxaddress) {
SNPRINTF(message, "fetch Archon buffer requested 0x%0lX:0x%0lX outside buffer 0x%0lX:0x%0lX",
bufferaddress, endaddress, minaddress, maxaddress);
logwrite(function, std::string(message));
return ERROR;
}
Expand Down
71 changes: 61 additions & 10 deletions emulator/emulator-archon.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ namespace Archon {
: instr( instr ),
poweron( false ),
bigbuf( false ),
samplemode( 0 ),
frame{ 0, // index
0, // frame
"", // timer
Expand Down Expand Up @@ -433,23 +434,28 @@ namespace Archon {
return ERROR;
}

// Find which buffer matches the requested base address
// Find the frame buffer containing the requested address. The buffers are
// equally spaced, and an address may point anywhere inside one, such as at
// its raw data. Bytes past what the buffer holds are sent as zeros.
const uint64_t bufspan = this->frame.bufbase.at(1) - this->frame.bufbase.at(0);
int bufidx = -1;
for ( int i = 0; i < this->image->activebufs; i++ ) {
if ( this->frame.bufbase.at(i) == bufaddr ) { bufidx = i; break; }
if ( bufaddr >= this->frame.bufbase.at(i) && bufaddr < this->frame.bufbase.at(i) + bufspan ) { bufidx = i; break; }
}

if ( bufidx < 0 || this->frame.bufdata.at(bufidx).empty() ) {
std::cerr << get_timestamp() << function << "ERROR: no frame data for address 0x"
<< std::hex << bufaddr << "\n";
if ( bufidx < 0 ) {
std::cerr << get_timestamp() << function << "ERROR: address 0x"
<< std::hex << bufaddr << " is not in a frame buffer\n";
return ERROR;
}

const auto &data = this->frame.bufdata.at(bufidx);
size_t data_size = data.size();
size_t data_offset = bufaddr - this->frame.bufbase.at(bufidx);

std::cout << get_timestamp() << function << "sending " << std::dec << reqblocks
<< " blocks (" << data_size << " bytes available) from buffer " << bufidx+1 << "\n";
<< " blocks from offset " << data_offset << " of buffer " << bufidx+1
<< " (" << data_size << " bytes held)\n";

// Disable Nagle's algorithm for low-latency block transfer
int flag = 1;
Expand All @@ -459,7 +465,6 @@ namespace Archon {
size_t header_len = header.size();
size_t block_with_header = header_len + BLOCKLEN;
int totalbyteswritten = 0;
size_t data_offset = 0;

// Assemble header + one block into a single buffer per write
std::vector<char> sendbuf(block_with_header);
Expand Down Expand Up @@ -488,6 +493,12 @@ namespace Archon {
if ( retval > 0 ) written += retval;
else break;
}
// Wait for this block to drain from the socket send queue before writing
// the next one. A host that waits for a full block to be available before
// reading can stall with a partial block buffered.
//
int outq;
while ( ioctl( sock.getfd(), TIOCOUTQ, &outq ) == 0 && outq > 0 ) usleep( 10 );
totalbyteswritten += BLOCKLEN;
}
std::cout << get_timestamp() << function << "wrote " << std::dec << totalbyteswritten << " bytes\n";
Expand Down Expand Up @@ -634,6 +645,26 @@ namespace Archon {
this->image->linecount = std::stoi(value);
}

else if ( key == "SAMPLEMODE" ) {
this->samplemode = std::stoi(value);
}

else if ( key == "RAWENABLE" ) {
this->rawinfo.enable = std::stoi(value);
}

else if ( key == "RAWSAMPLES" ) {
this->rawinfo.samples = std::stoi(value);
}

else if ( key == "RAWSTARTLINE" ) {
this->rawinfo.startline = std::stoi(value);
}

else if ( key == "RAWENDLINE" ) {
this->rawinfo.endline = std::stoi(value);
}

} // end else
}
catch( std::invalid_argument & ) {
Expand Down Expand Up @@ -892,15 +923,35 @@ namespace Archon {

int width = iface.image->pixelcount * iface.image->taplines;
int height = iface.image->linecount;
size_t frame_bytes = static_cast<size_t>(width) * height * sizeof(uint16_t);
int bytes_per_pixel = ( iface.samplemode == 1 ) ? 4 : 2;
size_t frame_bytes = static_cast<size_t>(width) * height * bytes_per_pixel;

// With RAWENABLE=1, raw samples for the captured lines are stored after
// the image, starting at the next whole block. Each line holds RAWSAMPLES
// rounded up to a multiple of 1024 samples.
int raw_lines = 0;
if ( iface.rawinfo.enable == 1 && iface.rawinfo.samples > 0 ) {
raw_lines = std::max( std::min( iface.rawinfo.endline, height - 1 ) - iface.rawinfo.startline + 1, 0 );
}
int raw_samples = ( ( iface.rawinfo.samples + 1023 ) / 1024 ) * 1024;
int raw_blocks = ( raw_lines > 0 ) ? raw_samples * 2 / BLOCKLEN : 0;
size_t raw_offset = ( raw_lines > 0 ) ? ( ( frame_bytes + BLOCKLEN - 1 ) / BLOCKLEN ) * BLOCKLEN : 0;
size_t raw_line_bytes = static_cast<size_t>(raw_blocks) * BLOCKLEN;

iface.frame.bufdata.at(idx).resize(frame_bytes);
iface.frame.bufdata.at(idx).resize( raw_lines > 0 ? raw_offset + raw_line_bytes * raw_lines : frame_bytes );
if (iface.frame_source) {
iface.frame_source->fill_frame(iface.frame.bufdata.at(idx).data(), width, height);
iface.frame_source->fill_frame(iface.frame.bufdata.at(idx).data(), width, height, bytes_per_pixel);
}
if ( raw_lines > 0 ) {
iface.raw_source.fill_raw( iface.frame.bufdata.at(idx).data() + raw_offset, raw_samples, raw_lines, raw_line_bytes );
}

iface.frame.bufwidth.at(idx) = width;
iface.frame.bufheight.at(idx) = height;
iface.frame.bufsample.at(idx) = iface.samplemode;
iface.frame.bufrawblocks.at(idx) = raw_blocks;
iface.frame.bufrawlines.at(idx) = raw_lines;
iface.frame.bufrawoffset.at(idx) = static_cast<int>(raw_offset);

double rowtime = iface.image->calc_rowtime();

Expand Down
11 changes: 6 additions & 5 deletions emulator/emulator-archon.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ namespace Archon {

std::unique_ptr<ImageInfoBase> image; ///!< smart pointer to the base class
std::unique_ptr<Emulator::FrameSource> frame_source;
Emulator::SyntheticSource raw_source; ///!< raw samples are synthetic whatever the frame source

Interface( const std::string &instr );

Expand All @@ -63,6 +64,7 @@ namespace Archon {
unsigned long long init_time;
bool poweron; //!< is the power on?
bool bigbuf; //!< is BIGBUF==1 in ACF file?
int samplemode; //!< from "SAMPLEMODE=" in ACF file, 0=16 bit, 1=32 bit
std::string exposeparam; //!< param name to trigger exposure when set =1

std::string active_mode; //!< currently active mode detected from ACF parameters
Expand Down Expand Up @@ -197,11 +199,10 @@ namespace Archon {
* rawinfo_t is a struct which contains variables specific to raw data functions
*/
struct rawinfo_t {
int adchan; // selected A/D channels
int rawsamples; // number of raw samples per line
int rawlines; // number of raw lines
int iteration; // iteration number
int iterations; // number of iterations
int enable = 0; // from "RAWENABLE=" in ACF file
int samples = 0; // from "RAWSAMPLES=", raw samples per line
int startline = 0; // from "RAWSTARTLINE=", first line captured
int endline = 0; // from "RAWENDLINE=", last line captured
} rawinfo;

/**
Expand Down
1 change: 1 addition & 0 deletions emulator/emulator-server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,7 @@ void doit(Network::TcpSocket sock) {
}
else
if (cmd.compare("APPLYALL")==0) {
server->image->readoutlines = server->image->linecount; // READOUT_TIME is for the frame as loaded
retstream << "<" << ref;
}
else
Expand Down
81 changes: 60 additions & 21 deletions emulator/frame_source.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,45 +10,47 @@
#include <algorithm>
#include <filesystem>
#include <iostream>
#include <limits>
#include <fitsio.h>

#include "utilities.h"

namespace Emulator {

// Abstract interface for filling frame buffers with pixel data
//
class FrameSource {

public:
virtual ~FrameSource() = default;

// Fill buffer with one frame of pixel data (uint16 pixels)
/// @param buffer destination buffer, must be at least width*height*2 bytes
/// @param width frame width in pixels
/// @param height frame height in pixels
/// @return true on success
virtual bool fill_frame(char* buffer, int width, int height) = 0;
/** @brief Fill buffer with one frame of pixel data
* @param buffer destination buffer, must be at least width*height*bytes_per_pixel bytes
* @param width frame width in pixels
* @param height frame height in pixels
* @param bytes_per_pixel 2 for 16-bit pixels, 4 for 32-bit pixels
* @return true on success
*/
virtual bool fill_frame(char* buffer, int width, int height, int bytes_per_pixel) = 0;
};


// Generate synthetic frames with bias + Gaussian noise
// Mode-aware: RXR generates signal/reset halves with correlated noise
//
class SyntheticSource : public FrameSource {

private:

std::mt19937 rng{std::random_device{}()};
double noise_stddev = 50.0;
std::string* active_mode; // non-owning pointer to emulator's active mode
int taplines = 0;

public:
// @param mode pointer to the active mode string (owned by Interface)
// @param taps number of taplines for RXR half-width calculation
SyntheticSource(std::string* mode = nullptr, int taps = 0)
: active_mode(mode), taplines(taps) {}

void set_taplines(int taps) { taplines = taps; }

bool fill_frame(char* buffer, int width, int height) override {
auto* pixels = reinterpret_cast<uint16_t*>(buffer);
// Pixel values are the same at either depth; T sets the pixel width and the clamp
template <typename T>
void fill_pixels(T* pixels, int width, int height) {
const double maxval = static_cast<double>(std::numeric_limits<T>::max());
std::normal_distribution<double> noise(0.0, noise_stddev);
size_t npixels = static_cast<size_t>(width) * height;

Expand All @@ -71,8 +73,8 @@ namespace Emulator {
double common_noise = noise(rng);
double sig_val = 10000.0 + common_noise + noise(rng) * 0.3;
double res_val = 5000.0 + common_noise + noise(rng) * 0.3;
pixels[tap_offset + col] = static_cast<uint16_t>(std::clamp(sig_val, 0.0, 65535.0));
pixels[tap_offset + col + half] = static_cast<uint16_t>(std::clamp(res_val, 0.0, 65535.0));
pixels[tap_offset + col] = static_cast<T>(std::clamp(sig_val, 0.0, maxval));
pixels[tap_offset + col + half] = static_cast<T>(std::clamp(res_val, 0.0, maxval));
}
}
}
Expand All @@ -82,16 +84,51 @@ namespace Emulator {
for (size_t i = 0; i < npixels; i++) {
int col = i % width;
double val = 5000.0 + noise(rng) + static_cast<double>(col) * 100.0 / width;
pixels[i] = static_cast<uint16_t>(std::clamp(val, 0.0, 65535.0));
pixels[i] = static_cast<T>(std::clamp(val, 0.0, maxval));
}
}
}

public:

// @param mode pointer to the active mode string (owned by Interface)
// @param taps number of taplines for RXR half-width calculation
//
SyntheticSource(std::string* mode = nullptr, int taps = 0)
: active_mode(mode), taplines(taps) {}

void set_taplines(int taps) { taplines = taps; }

/** @brief Fill raw ADC samples with bias + noise, zero-padding each line out to line_bytes
* @param buffer destination buffer, must be at least lines*line_bytes bytes
* @param samples 16-bit samples per line
* @param lines number of lines
* @param line_bytes bytes occupied by each line
*/
void fill_raw(char* buffer, int samples, int lines, size_t line_bytes) {
const double maxval = static_cast<double>(std::numeric_limits<uint16_t>::max());
std::normal_distribution<double> noise(0.0, noise_stddev);
for (int line = 0; line < lines; line++) {
char* linestart = buffer + line * line_bytes;
std::memset(linestart, 0, line_bytes);
auto* raw = reinterpret_cast<uint16_t*>(linestart);
for (int sample = 0; sample < samples; sample++) {
raw[sample] = static_cast<uint16_t>(std::clamp(32768.0 + noise(rng), 0.0, maxval));
}
}
}

bool fill_frame(char* buffer, int width, int height, int bytes_per_pixel) override {
if (bytes_per_pixel == 4) fill_pixels(reinterpret_cast<uint32_t*>(buffer), width, height);
else fill_pixels(reinterpret_cast<uint16_t*>(buffer), width, height);
return true;
}
};


// Read FITS files from folder, serve them sequentially, cycling
// Not mode-aware: FITS data is already in the correct format for its mode
//
class FitsFileSource : public FrameSource {
private:
std::vector<std::string> files;
Expand All @@ -112,7 +149,7 @@ namespace Emulator {
<< " FITS files found in " << datadir << "\n";
}

bool fill_frame(char* buffer, int width, int height) override {
bool fill_frame(char* buffer, int width, int height, int bytes_per_pixel) override {
std::string function = "(Emulator::FitsFileSource::fill_frame) ";
if (files.empty()) {
std::cerr << get_timestamp() << function << "ERROR: no FITS files\n";
Expand Down Expand Up @@ -153,7 +190,8 @@ namespace Emulator {

long fpixel[2] = {1, 1};
long nelements = static_cast<long>(width) * height;
fits_read_pix(fptr, TUSHORT, fpixel, nelements, nullptr, buffer, nullptr, &status);
int datatype = (bytes_per_pixel == 4) ? TUINT : TUSHORT;
fits_read_pix(fptr, datatype, fpixel, nelements, nullptr, buffer, nullptr, &status);
fits_close_file(fptr, &status);

if (status) {
Expand All @@ -168,6 +206,7 @@ namespace Emulator {


// Create the appropriate FrameSource based on config
//
inline std::unique_ptr<FrameSource> make_frame_source(
const std::string &datadir,
std::string* active_mode = nullptr,
Expand Down
3 changes: 2 additions & 1 deletion emulator/generic.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ namespace Archon {
*
*/
virtual double calc_rowtime() override {
double rowtime = std::floor( 10 * this->readtime / this->linecount ); // row time in 10 msec
int lines = ( this->readoutlines > 0 ) ? this->readoutlines : this->linecount; // lines READOUT_TIME applies to
double rowtime = std::floor( 10 * this->readtime / lines ); // row time in 10 msec
rowtime *= 90.0; // 90% of row time in usec
return rowtime;
}
Expand Down
3 changes: 2 additions & 1 deletion emulator/image_info_base.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,15 @@ namespace Archon {
int pixelcount; //!< from "PIXELCOUNT=" in ACF file
int readtime; //!< from "READOUT_TIME=" in configuration file
int readouttime; //!<
int readoutlines; //!< LINECOUNT that READOUT_TIME applies to, latched at APPLYALL
int imwidth; //!<
int imheight; //!<
int exptime; //!< requested exposure time in msec from WCONFIG
int exposure_factor; //!< multiplier for exptime relative to 1 sec (=1 for sec, =1000 for msec, etc.)

ImageInfoBase() : framen(0), activebufs(-1), taplines(-1),
linecount(-1), pixelcount(-1),
readtime(-1), readouttime(0),
readtime(-1), readouttime(0), readoutlines(-1),
imwidth(0), imheight(0),
exptime(0), exposure_factor(1000) { }

Expand Down
Loading