Detector reference
Detector sends two separate ZMQ messages for each acquired frame coming after each other.
JSON header¶
The first one is a JSON header where all necessary information is stored.
This is the JSON header signature for the firmware version 6.1.2 for a MOENCH detector:
{
"jsonversion": unsigned int,
"bitmode": unsigned int,
"fileIndex": unsigned long int,
"detshape": [
unsigned int,
unsigned int
],
"shape": [
unsigned int,
unsigned int
],
"size": unsigned int,
"acqIndex": unsigned long int,
"frameIndex": unsigned long int,
"progress": double,
"fname": string,
"data": unsigned int,
"completeImage": unsigned int,
"frameNumber": unsigned long long int,
"expLength": unsigned int,
"packetNumber": unsigned int,
"detSpec1": unsigned long int,
"timestamp": unsigned long int,
"modId": unsigned int,
"row": unsigned int,
"column": unsigned int,
"detSpec2": unsigned int,
"detSpec3": unsigned int,
"detSpec4": unsigned int,
"detType": unsigned int,
"version": unsigned int,
"flipRows": unsigned int,
"quad": unsigned int,
"addJsonHeader": {
string : string
}
}
| Field | Description |
|---|---|
| jsonversion | Version of the json header. Value at 4 for v6.x.x and v7.x.x |
| bitmode | Bits per pixel [4|8|16|32] |
| fileIndex | Current file acquisition index |
| detshape | Geometry of the entire detector |
| shape | Geometry of the current port streamed out |
| size | Size of image of current port in bytesout |
| acqIndex | Frame number from the detector (redundant) |
| frameIndex | Frame number of current acquisition (Starting at 0) |
| progress | Progress of current acquisition in % |
| fname | Current file name |
| data | 1 if there is data following 0 if dummy header |
| completeImage | 1 if no missing packets for this frame in this port, else 0 |
| frameNumber | Frame number [From detector udp header] |
| expLength | subframe number (32 bit eiger) or real time exposure time in 100ns (others) [From detector udp header] |
| packetNumber | Number of packets caught for that frame |
| detSpec1 | See here [From detector udp header] |
| timestamp | Timestamp with 10 MHz clock [From detector udp header] |
| modId | Module Id [From detector udp header] |
| row | Row number in detector [From detector udp header] |
| column | Column number in detector [From detector udp header] |
| detSpec2 | See here [From detector udp header] |
| detSpec3 | See here [From detector udp header] |
| detSpec4 | See here [From detector udp header] |
| detType detSpec3 | Detector type enum See Detector enum [From detector udp header] |
| version | Detector header version. At 2 [From detector udp header] |
| flipRows | 1 if rows should be flipped. Usually for Eiger bottom. |
| quad | 1 if its an Eiger quad. |
| addJsonHeader | Optional custom parameters that is required for processing code. |
Here is an example of the received message from MOENCH detector:
{
"jsonversion": 4,
"bitmode": 16,
"fileIndex": 6,
"detshape": [1, 1],
"shape": [400, 400],
"size": 320000,
"acqIndex": 1,
"frameIndex": 0,
"progress": 100.0,
"fname": "/mnt/LocalData/DATA/MOENCH/20230128_run/230128",
"data": 1,
"completeImage": 1,
"frameNumber": 1,
"expLength": 0,
"packetNumber": 40,
"bunchId": 0,
"timestamp": 0,
"modId": 0,
"row": 0,
"column": 0,
"reserved": 0,
"debug": 0,
"roundRNumber": 0,
"detType": 5,
"version": 1,
"flipRows": 0,
"quad": 0,
"addJsonHeader": {"detectorMode": "analog", "frameMode": "raw"},
}
"data" : 0) are zeros or empty. After a dummy JSON header no frame of the detector will be transmitted and should not be awaited by the server.
Changes between the firmware versions¶
Changes from 6.x.x to 7.0.0: 4 field names have changed from 6.x.x to 7.x.x because they have also been changed in the detector udp header. Since the meaning has not changed, the udp header version stays the same as well as the zmq header version. detSpec1 <- bunchId detSpec2 <- reserved detSpec3 <- debug detSpec4 <- roundRNumber
Payload¶
The second ZMQ packet consists of the acquired capture represented as a 1D array of bytes where 160000 values are stored in a unsigned 16-bit integer (np.uint16) data type. Nevertheless, it is not a just flatten 400x400 2D array where the pixels line up one by one but in a strange order defined (as I understand) by reading from the ADC on the detector's FPGA board.
Frame remapping¶
In the main repository slsDetectorPackage we can find a reference moench03T1ReceiverDataNew.h#66:110 which reoders the pixels in a frame:
int nadc = 32;
int sc_width = 25;
int sc_height = 200;
int adc_nr[32] = {300, 325, 350, 375, 300, 325, 350, 375, 200, 225, 250,
275, 200, 225, 250, 275, 100, 125, 150, 175, 100, 125,
150, 175, 0, 25, 50, 75, 0, 25, 50, 75};
int row, col;
int isample;
int iadc;
int ix, iy;
int npackets = 40;
int i;
int adc4(0);
int off=sizeof(header);
for (int ip = 0; ip < npackets; ip++) {
for (int is = 0; is < 128; is++) {
for (iadc = 0; iadc < nadc; iadc++) {
i = 128 * ip + is;
adc4 = (int)iadc / 4;
if (i < sc_width * sc_height) {
// for (int i=0; i<sc_width*sc_height; i++) {
col = adc_nr[iadc] + (i % sc_width);
if (adc4 % 2 == 0) {
row = 199 - i / sc_width;
} else {
row = 200 + i / sc_width;
}
dataMap[row][col] = off +
(nadc * i + iadc) * 2; //+16*(ip+1);
#ifdef HIGHZ
dataMask[row][col] = 0x3fff; // invert data
#endif
if (dataMap[row][col] < 0 ||
dataMap[row][col] >= off + nSamples * 2 * 32)
std::cout << "Error: pointer " << dataMap[row][col]
<< " out of range " << std::endl;
}
}
}
}
So I just rewrite the upper C++ code in python and saved the reorder_map which is basically a 400x400 numpy array which consists of indexes.
import numpy as np
# 32 numbers
nadc = 32
adc_nr = [300, 325, 350, 375, 300, 325, 350, 375,
200, 225, 250, 275, 200, 225, 250, 275,
100, 125, 150, 175, 100, 125, 150, 175,
0, 25, 50, 75, 0, 25, 50, 75,
]
npackets = 40
sc_width = 25
sc_height = 200
# indexes need to be integer
ind = np.zeros([400, 400], dtype=np.int32)
for ip in range(npackets):
for iss in range(128):
for iadc in range(32):
i = 128 * ip + iss
adc4 = iadc // 4
if i < sc_width * sc_height:
col = adc_nr[iadc] + (i % sc_width)
if adc4 % 2 == 0:
row = 199 - i // sc_width
else:
row = 200 + i // sc_width
ind[row, col] = 32 * i + iadc
np.save("reorder_map", ind)
numpy feature, we can easily rearrange the 1D array coming from the detector into 2D with the right order:
# load the map once
reorder_map = np.load("reorder_map.npy")
# obtain the 1D array represents frame from ZMQ
msg = socket.recv()
# saving a raw 1D array
raw_frame = np.frombuffer(msg, dtype=np.uint16)
# rearrange the frame in the right order
frame = raw_frame[reorder_map]