Closed JamesBremner closed 8 months ago
I have added a user option to set the frame length. Minimum length is 8 bytes, so as to hold a 64 bit floating point number.
To make this more relevant to your work I need details of your frames. In particular, the offset to the data you want to plot.
Perhaps you have never before had to document a frame design? Here is an example of how I do it:
Field | Length bits | Description |
---|---|---|
Header | 16 | 0xF00D |
Length | 16 | frame length in bytes |
Blast | 8 | blast number |
ID | 8 | message type |
repeat | 4 | repeat number, max 0xF. Packed into hi order bits of third message byte |
number | 4 | packet number, max 0xF. Packed into lo order bits of third message byte |
Data0 | 8 | |
Data1 | 8 | |
CRC | 8 |
The frame itself is flat, it is simply
double frame[NPIXELS].
There is a ring of struct, with points to preallocated frame buffers. Each frame is preceded by a counter and elapsed time.
To be completely, unambiguous, here is the code (work in progress) for the dispatcher. It is intended to run in a thread. I have not tried to run it yet.
typedef struct {
unsigned int elapsed;
unsigned int counter;
unsigned int shutter;
unsigned int length;
unsigned int accumulate;
size_t nsize;
double *data;
} Frame_t;
`Frame_t frames[FRAME_RING_SIZE] = { { 0}}; unsigned int frame_in = FRAME_RING_SIZE_SIZE-1;
uint16_t rawbuffer[8192] = { 0 }; `
bool mallocFrames(unsigned int npixels)
{
for (int n = 0; n < FRAME_RING_SIZE ; n++)
{
if (!mallocFrame_( &frames[n], npixels)) {
return false;
}
}
return true;
}
`DWORD WINAPI dispatcher( LPVOID lpParam )
{
char pc, pc0;
int n = 0;
unsigned int npixels, nbytes;
unsigned long int nread;
double dark = 0.;
Frame_t *fp = &frames[0];
frame_in = 0;
clearFrame_(fp);
while (true) {
// Text logging ring buffer
pc = pc0 = textring[textring_index];
// Read one line from the spectrometer
if ((n=readLine(pc0,TEXT_RING_BUFSIZ)))
{
textring_index = (textring_index + 1)%TEXT_RING;
// First thing, check for data coming in the next packet
if ((pc=stringMatch(pc0,"BINARY16"))&&(stringUint(pc,&npixels)))
{
nbytes = 2*npixels; // actually, superferlous, it is not going to change
nread = 0;
// Read the data packet
if ( ReadFile(hSerial,rawbuffer,nbytes,&nread, NULL) ) {
if (nread == nbytes)
{
// We have the packet, check for the end of packet message
nread = readLine(buff, sizeof(buff) - 1);
if (stringMatch(buff, "END DATA"))
{
// Scale to volts
for (n = 0; n < npixels; n++)
{
fp->data[n] = rawbuffer[n] * SpectrometerCtl.vperbit;
}
fp->length = npixels;
// Subtract dark level from the masked pixels
if (SpectrometerCtl.do_dark && SpectrometerCtl.darkpixels)
{
dark = avg(fp->data, SpectrometerCtl.darkpixels);
for (n = 0; n < SpectrometerCtl.darkpixels; n++)
{
fp->data[n] -= dark;
}
}
// Enqueue to graphics here
// Next pointer for the next data frame
frame_in = (frame_in + 1) % FRAME_RING_SIZE;
fp = &frames[frame_in];
}
}
}
}
// Frame header values
else if ((pc=stringMatch(pc0,"ELAPSED"))) {
stringUint(pc,&(fp->elapsed));
}
else if ((pc=stringMatch(pc0,"COUNTER"))) {
stringUint(pc,&(fp->counter));
}
else if ((pc=stringMatch(pc0,"SHUTTER"))) {
stringUint(pc,&(fp->shutter));
}
// Starts
else if ((pc=stringMatch(pc0,"START"))) {
frame_in = 0;
fp = &frames[0];
clearFrame_(fp);
}
else if ((pc=stringMatch(pc0,"TRIGGERED SINGLES START"))) {
strcpy( SpectrometerCtl.mode, "TRIGGER" );
frame_in = 0;
fp = &frames[0];
clearFrame_(fp);
else if ((pc=stringMatch(pc0,"TRIGGERED SETS START"))) {
strcpy( SpectrometerCtl.mode, "TRIGGERSETS" );
frame_in = 0;
fp = &frames[0];
clearFrame_(fp);
}
else if ((pc=stringMatch(pc0,"CLOCKED START"))) {
strcpy( SpectrometerCtl.mode, "CLOCKED" );
frame_in = 0;
fp = &frames[0];
clearFrame_(fp);
}
else if ((pc=stringMatch(pc0,"GATE START"))) {
strcpy( SpectrometerCtl.mode, "GATE" );
frame_in = 0;
fp = &frames[0];
clearFrame_(fp);
}
// Controller settings
else if ((pc=stringMatch(pc0,"CLOCK"))) {
stringUint(pc,&(SpectrometerCtl.clock));
}
else if ((pc=stringMatch(pc0,"OUTER CLOCK"))) {
stringUint(pc,&(SpectrometerCtl.outerclock));
}
else if ((pc=stringMatch(pc0,"INTERVAL"))) {
stringUint(pc,&(SpectrometerCtl.interval));
}
else if ((pc=stringMatch(pc0,"FRAMES"))) {
stringUint(pc,&(SpectrometerCtl.frames));
}
else if ((pc=stringMatch(pc0,"SETS"))) {
stringUint(pc,&(SpectrometerCtl.sets));
}
else if ((pc=stringMatch(pc0,"EVERY"))) {
stringUint(pc,&(SpectrometerCtl.every));
}
else {
// Screen for special configuration values
}
}
}
}
`
TLDR
Please provide a succinct description of your frame, preferably in a format similar to the one I use, see previous post.
In the above, mallocFrames() is run once, after getting the frame size from the spectrometer. After that everything is reused as is, as you see there.
Normally when I graph a frame, I update a text in a corner of the display to show the counters for the frame and frameset (i.e. for a kinetic series).
In case it is of interest, here is the spectrometer control structure.
typedef struct {
char name[64];
char version[64];
unsigned int pixels;
unsigned int darkpixels;
unsigned int bits;
double vfs;
double vperbit;
double coefficients[8];
unsigned int ncoeffs;
double *wavelengths;
unsigned int frameset;
unsigned int clock;
unsigned int outerclock;
unsigned int interval;
unsigned int frames;
unsigned int sets;
unsigned int every;
double chiptemperature;
char mode[32];
bool do_dark;
} SpectrometerCtl_t;
Field Length
data npixel * sizof(double)
Field Length Content
data npixel*sizeof(double) voltages, one for each pixel.
OK, let's do Q & A.
How long are your frames? Constant or variable? Do you have a CRC check? What is the header ID? What is the bit offset from start of header to start of data point to be plotted? What is the width and format of the data point?
Frames are npixels. The value npixels is set at startup (by query to the spectrometer) and after that it is constant forever. No, there is no CRC There is no header Bit offset is zero, 0. Each data point is a c language double, whatever that is on a windows PC.
Frame length is npixels.
No, there is no CRC There is no header
What a terrible, terrible design! No error checking at all?
Npixels is usually in the range of 2,000 to 4,000, depending on the sensor.
The instrument we want to provide for the undefunded school to teach science to impoverished kids, is 2048. The one I use in my lab is 3694.
It is not necessary. The data comes over USB, I think there is a crc there, it is not needed anywhere else.
There is a super high end front circuit for the CCD, all differential to a 16bit ADC, whatever error appears there is nature. After that it is shipped over the USB and then it is digital again.
Each data point is a c language double, whatever that is on a windows PC.
Usually 4 bytes or 8 bytes. You will have to let me know which? Alternatively I can make it a user option.
Bit offset is zero, 0.
What does this mean? Does it mean that the 4 or 8 byte data point is the first 4 or 8 bytes of the frame? If so, then the current complot code does what you need!
I.e. digital data again. It is up to the pc to deal with memory errors. If I ever see one, I will be motivated to do something about a crc
I imagine a c++ double on a PC is at least 8 bytes.
Easy to check, just a moment.
Bit offset 0, the data begins at the beggining. No header, no padding, nothing. It is just a double array in C.
But the argument for trace.set() is a std::vector, yes? Did you change it already?
Bit offset 0, the data begins at the beggining. No header, no padding, nothing. It is just a double array in C.
OK, then the current code does what you need. It assumes a 8 byte floating point number. You can specify the total frame length at startup.
Oh, okay, thank you, let me take a look. You updated this, yes?
Here is the code that extracts the data point from the frame
Can you give me a line number? I am looking at the complot/src/complotter.cpp. I think I am missing it It looks like it adds points one at a time.
It looks like it adds points one at a time.
Exactly. One data point per frame.
Oh, but a frame is a few thousand points.
It is a spectrum, from 200 nm to 1100nm, one point every 1/2 or 1/10 nm or so.
Would std::array be a better container for this than vector?
(Sorry, I know where are talking about doubles now. But one point at a time hints that the guts of it are the vector).
It is a spectrum,
But only one point is plotted. You have to choose which frequency you want to plot. This is why I keep asking what the offest is to the point you want to plot.
What frequency? You meant the increment along the x axis? That is a fair point.
When I have written low level graphics (many years ago), I usually create a map from index to screen coordinate, if I am passing y values only. And if I am passing x and y, I create a map from x coordiantes to screen coordinates.
And then there is another cute step where you check that your point is actually a different pixel from the previous point. That saves you a lot of overhead when the data is much denser than the screen
You mean the offset from the screen left side? I though you meant the offset into the data packet.
What frequency? You meant the increment along the x axis? That is a fair point.
No. The x axis is time.
The Y axis is the value for the frequency recorded at that time.
That is not what I am plotting. I am plotting intensity versus wavelength, It is a spectrum.
That is not a real time plot. That is a static plot.
We need an x-y plot.
Okay, it is a static plot.
Do you have that?
How often do you need an update of your spectrum plot? Each time a new packet arrives?
Yes, that is it! Eureka. The fastest is 100 times a second, but I routinely fall behind and catch up through the ring buffer.
Suggestion: A static spectrum plot updated at 1 HZ, with whatever data arrived most recently.
I usuall add buttions forward and back, so that after a burst we can step through the set of spectra.
I would set the update loop at whatever it can bear without bogging down the machine overall. But yes I do that somettimes, graph the most recent and step back after
It is impossible to update a spectrum plot with a significant number of values at 100Hz. No-one could see such a thing and get anything meaningful out of it.
I can do a static spectrum plot updated at 1 HZ, with whatever data arrived most recently ( data arriving at up to 100Hz )
Of course, I agree 100% and more. What you are describing is close to what I usually do.
graph the most recent and step back after
I can log the data as it arrives, but not at 100Hz. Maybe somewhere in range 1 - 10 Hz, with a timestamp on each record, so you can do a replay.
Except I think I set the update at 10/sec.
Since I discovered python, I have been doing it with matplotlib animation, It does 10Hz very nicely. In the days when I progammed this in X, it was not that much faster.
I am going offline for dinner. Chat tomorrow
Yes, of course, absolutely. But, checking a buffer or a pointer for a new graph once per loop would be enough.
Okay, thank you. Same here, I need to cook for myself, stir fry tofu tonight
In the python system, in the animation, I check a queue. If empty, do nothing. Else push everything onto the history list and plot the last item off the queue.
Really, the timer event structure you have already, is almost everything I need.
It needs to be a static plot - which I think we can do easily. And, I need to copy the double *data to a vector
For a general purpose sort of 2d graphics, it would be good to be able to plot x,y, rather than just y with an assumed constant spacing.
Maybe something like: set(vector,vector), and, set(vector), where the first takes both x and y.
Are there calls to set labels and tics and font sizes?
(After it is working I might work on some of the dress up for the graphs. If I do, I am happy to contribute that work.)
Currently, the data arrives from the device simulator as one double per frame.
This issue is for discussing how best to handle larger and more complex frames.
Motivation in https://github.com/JamesBremner/windex/issues/9
Question: Please describe your frames.