added moench04 support

This commit is contained in:
Erik Frojdh 2023-06-05 17:40:58 +02:00
parent 7fbd308c7d
commit 43f0f0e43a
10 changed files with 616 additions and 81 deletions

View File

@ -12,6 +12,7 @@ make
And then have a look at the examples:
- [Reading cluster files](examples/ClusterFile.ipynb)
- [Reading Moench03/04 raw files](examples/RawFile.ipynb)
## Build instructions

View File

@ -1,29 +1,45 @@
import json
from pathlib import Path
import numpy as np
import re
from . import RawFileReader
from .enums import DetectorType
class RawFile:
"""
Generic Raw File reader. Picks up settings from .json master file
Currently supports: Moench03 =)
"""
def __init__(self, fname):
def __init__(self, fname, header = False):
self.findex = 0
self.fname = fname
self.json = False #Master file data comes from json
fname = Path(fname)
if fname.suffix != '.json':
raise ValueError("Need a master file in json format")
with open(fname) as f:
self.master = json.load(f)
if fname.suffix == '.json':
with open(fname) as f:
self.master = json.load(f)
self.json = True
elif fname.suffix == '.raw':
with open(fname) as f:
self._load_raw_master(f)
else:
raise ValueError(f'{fname.suffix} not supported')
#Figure out which file to open
if self.master['Detector Type'] == 'Moench' and self.master['Analog Samples'] == 5000:
#TODO! pass settings to reader
self._parse_fname()
self.reader = RawFileReader(self.data_fname(0,0))
self.reader = RawFileReader(self.data_fname(0,0), DetectorType.MOENCH_03, header = header)
elif self.master['Detector Type'] == 'ChipTestBoard' and self.master['Analog Samples'] == 5000:
self._parse_fname()
#Do we read analog or analog+digital
if self.master['Digital Flag'] == 1:
dt = DetectorType.MOENCH_04_AD
else:
dt = DetectorType.MOENCH_04_AD
self.reader = RawFileReader(self.data_fname(0,0), dt, header = header)
else:
raise ValueError('unsupported file')
@ -35,20 +51,128 @@ class RawFile:
except:
raise ValueError(f"Could not parse master file name: {self.fname}")
def _load_raw_master(self, f):
self.master = {}
lines = f.readlines()
it = iter(lines)
for line in it:
if line.startswith("#Frame"):
break
if line == "\n":
continue
if line.startswith("Scan Parameters"):
while not line.endswith("]\n"):
line += next(it)
field, value = line.split(":", 1)
self.master[field.strip(" ")] = value.strip(" \n")
frame_header = {}
for line in it:
field, value = line.split(":", 1)
frame_header[field.strip()] = value.strip(" \n")
self.master["Frame Header"] = frame_header
self.master["Version"] = float(self.master["Version"])
self._parse_values()
def _parse_values(self):
int_fields = set(
(
"Analog Samples",
"Analog Flag",
"Digital Flag",
"Digital Samples",
"Max Frames Per File",
"Image Size",
"Frame Padding",
"Total Frames",
"Dynamic Range",
"Ten Giga",
"Quad",
"Number of Lines read out",
"Number of UDP Interfaces"
)
)
time_fields = set((
"Exptime",
"Exptime1",
"Exptime2",
"Exptime3",
"GateDelay1",
"GateDelay2",
"GateDelay3",
"SubExptime",#Eiger
"SubPeriod", #Eiger
"Period"
))
#some fields might not exist for all detectors
#hence using intersection
for field in time_fields.intersection(self.master.keys()):
self.master[field] = self.to_nanoseconds(self.master[field])
#Parse bothx .json and .raw master files
if self.json:
self.master['Image Size'] = self.master["Image Size in bytes"]
self.master['Pixels'] = (self.master['Pixels']['x'], self.master['Pixels']['y'])
self.master['nmod'] = int(self.master['Geometry']['x']*self.master['Geometry']['y'] )#ports not modules
if self.master['Detector Type'] == 'Eiger':
self.master['nmod'] = self.master['nmod'] // 2
else:
for field in int_fields.intersection(self.master.keys()):
self.master[field] = int(self.master[field].split()[0])
self.master["Pixels"] = tuple(
int(i) for i in self.master["Pixels"].strip("[]").split(",")
)
if "Rate Corrections" in self.master:
self.master["Rate Corrections"] = (
self.master["Rate Corrections"].strip("[]").split(",")
)
n = len(self.master["Rate Corrections"])
assert (
self.master["nmod"] == n
), f'nmod from Rate Corrections {n} differs from nmod {self.master["nmod"]}'
#Parse threshold for Mythen3 (if needed)
if "Threshold Energies" in self.master.keys():
th = self.master["Threshold Energies"]
if isinstance(th, str):
th = [int(i) for i in th.strip('[]').split(',')]
self.master["Threshold Energies"] = th
@staticmethod
def to_nanoseconds(t):
nanoseconds = {"s": 1000 * 1000 * 1000, "ms": 1000 * 1000, "us": 1000, "ns": 1}
try:
value = re.match(r"(\d+(?:\.\d+)?)", t).group()
unit = t[len(value) :]
value = int(float(value) * nanoseconds[unit])
value = np.timedelta64(value, 'ns')
except:
raise ValueError(f"Could not convert: {t} to nanoseconds")
return value
def data_fname(self, i, findex=0):
return Path(f"{self.base}_d{i}_f{findex}_{self.run_id}.raw")
def read(self):
return self.reader.read()
def read(self, *args):
return self.reader.read(*args)
# Support iteration
def __iter__(self):
return self
def __next__(self):
frame = self.reader.read()
if frame.shape[0] == 0:
res = self.reader.read()
if res.shape[0] == 0:
raise StopIteration
# Support with statement

View File

@ -3,3 +3,5 @@ from _creader import *
from .file_utils import open_file
from .ClusterFile import ClusterFile
from .enums import DetectorType
from .RawFile import RawFile

8
creader/enums.py Normal file
View File

@ -0,0 +1,8 @@
from enum import IntEnum
class DetectorType(IntEnum):
#TODO! Move defenition to C to avoid mismatch
GENERIC = 0
MOENCH_03 = 3
MOENCH_04_A = 4
MOENCH_04_AD = 5

205
examples/RawFile.ipynb Normal file

File diff suppressed because one or more lines are too long

View File

@ -1,7 +1,7 @@
#include "RawFileReader.h"
#include "arr_desc.h"
#include "data_types.h"
#include "raw_reader.h"
#include "arr_desc.h"
#include <stdbool.h>
@ -11,6 +11,7 @@ typedef struct {
// additional fields for size and decoder?
int dtype;
bool read_header;
int detector;
} RawFileReader;
//clang-format on
@ -28,11 +29,12 @@ static int RawFileReader_init(RawFileReader *self, PyObject *args,
// Should we read the header
self->read_header = false;
self->detector = DT_GENERIC;
static char *kwlist[] = {"fname", "header", NULL};
static char *kwlist[] = {"fname", "detector_type", "header", NULL};
if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|p", kwlist, &fname_obj,
&self->read_header)) {
if (!PyArg_ParseTupleAndKeywords(args, kwds, "Oi|p", kwlist, &fname_obj,
&self->detector, &self->read_header)) {
return -1;
}
@ -41,12 +43,13 @@ static int RawFileReader_init(RawFileReader *self, PyObject *args,
return -1;
PyBytes_AsStringAndSize(fname_bytes, &fname, &len);
#ifdef CR_VERBOSE
printf("fname: %s\n read_header: %d\n", fname, self->read_header);
printf("fname: %s\n read_header: %d detector type: %d\n", fname,
self->read_header, self->detector);
#endif
self->fp = fopen(fname, "rb");
// Keep the return code to not return before releasing buffer
int rc = 0;
@ -77,27 +80,46 @@ static PyObject *RawFileReader_read(RawFileReader *self, PyObject *args) {
self->dtype = NPY_UINT16;
const int ndim = 3;
Py_ssize_t n_frames = 1; // default number of frames to read
// Py_ssize_t moench_version = 3;
// Py_ssize_t analog_digital = 0;
if (!PyArg_ParseTuple(args, "|n", &n_frames))
return NULL;
npy_intp dims[3] = {n_frames, 400, 400};
PyObject *frames = PyArray_SimpleNew(ndim, dims, self->dtype);
char *out_buf = PyArray_DATA((PyArrayObject *)frames);
PyObject *digital_frames = NULL;
char *digital_out = NULL;
PyArray_FILLWBYTE((PyArrayObject *)frames, 0);
//Optional return the header
// Optional return the header
PyObject *header = NULL;
char* header_out = NULL;
if(self->read_header){
char *header_out = NULL;
if (self->read_header) {
header = PyArray_SimpleNewFromDescr(1, dims, frame_header_dt());
header_out = PyArray_DATA((PyArrayObject *)header);
}
const size_t frame_size = 400 * 400 * 2;
char *out_buf = PyArray_DATA((PyArrayObject *)frames);
int64_t n_read =
read_raw(self->fp, n_frames, frame_size, out_buf, header_out);
// Both analog and digital data
if (self->detector == DT_MOENCH_04_AD) {
digital_frames = PyArray_SimpleNew(ndim, dims, NPY_UINT8);
PyArray_FILLWBYTE((PyArrayObject *)digital_frames, 0);
digital_out = PyArray_DATA((PyArrayObject *)digital_frames);
}
int64_t n_read = 0;
switch (self->detector) {
case DT_MOENCH_03:
n_read = read_raw_m03(self->fp, n_frames, out_buf, header_out);
break;
case DT_MOENCH_04_A:
case DT_MOENCH_04_AD:
n_read =
read_raw_m04(self->fp, n_frames, out_buf, digital_out, header_out);
break;
default:
break;
}
if (n_read != n_frames) {
// resize the array to match the number of read photons
@ -114,20 +136,35 @@ static PyObject *RawFileReader_read(RawFileReader *self, PyObject *args) {
// resize the array to match the number of clusters read
PyArray_Resize((PyArrayObject *)frames, &new_shape, 1, NPY_ANYORDER);
//if we also read header we need to reshape the header
if(self->read_header){
// if we also read header we need to reshape the header
if (self->read_header) {
new_shape.len = 1;
PyArray_Resize((PyArrayObject *)header, &new_shape, 1, NPY_ANYORDER);
PyArray_Resize((PyArrayObject *)header, &new_shape, 1,
NPY_ANYORDER);
}
}
// Build up a tuple with the return values
PyObject *ret = PyTuple_Pack(1, frames);
if (self->detector == DT_MOENCH_04_AD){
_PyTuple_Resize(&ret, 2);
PyTuple_SET_ITEM(ret, 1, digital_frames);
}
PyObject *ret = frames;
if(self->read_header){
ret = PyTuple_Pack(2, frames, header);
Py_DECREF(header);
if (self->read_header){
Py_ssize_t old_size = PyTuple_GET_SIZE(ret);
_PyTuple_Resize(&ret, old_size+1);
PyTuple_SET_ITEM(ret, old_size, header);
}
// if we only have one item in the tuple lets return it instead of the tuple
if(PyTuple_GET_SIZE(ret) == 1){
Py_DECREF(ret);
return frames;
}else{
Py_DECREF(frames);
return ret;
}
return ret;
}

View File

@ -34,7 +34,12 @@ typedef struct {
uint32_t c;
} ClusterAnalysis;
enum Decoder { MOENCH_03 = 3 };
enum DetectorType {
DT_GENERIC = 0,
DT_MOENCH_03 = 3,
DT_MOENCH_04_A = 4,
DT_MOENCH_04_AD = 5
};
typedef struct
{
@ -55,4 +60,5 @@ typedef struct
} Header;
#endif

View File

@ -1,13 +1,14 @@
#include "raw_reader.h"
#include "data_types.h"
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
int64_t read_raw(FILE *fp, int64_t n_frames, size_t frame_size, char* out_buf, Header* header_out) {
int64_t read_raw_m03(FILE *fp, int64_t n_frames, char *frame_out,
Header *header_out) {
const size_t frame_size = 400 * 400 * 2;
char *tmp = malloc(frame_size);
Header h;
@ -17,9 +18,9 @@ int64_t read_raw(FILE *fp, int64_t n_frames, size_t frame_size, char* out_buf, H
if (fread(&h, sizeof(Header), 1, fp) != 1) {
break;
}
if(header_out){
memcpy(header_out, &h, sizeof(Header));
header_out++;
if (header_out) {
memcpy(header_out, &h, sizeof(Header));
header_out++;
}
// Read frame to temporary buffer
@ -28,9 +29,9 @@ int64_t read_raw(FILE *fp, int64_t n_frames, size_t frame_size, char* out_buf, H
}
// Decode frame and write to numpy output array
decode_moench03((uint16_t *)tmp, (uint16_t *)out_buf);
decode_moench03((uint16_t *)tmp, (uint16_t *)frame_out);
out_buf += frame_size;
frame_out += frame_size;
frames_read++;
}
@ -38,29 +39,123 @@ int64_t read_raw(FILE *fp, int64_t n_frames, size_t frame_size, char* out_buf, H
return frames_read;
}
void decode_moench03(const uint16_t *buf, uint16_t *out_buf)
{
int adc_numbers[32] = {12, 13, 14, 15, 12, 13, 14, 15, 8, 9, 10, 11, 8, 9, 10, 11,
4, 5, 6, 7, 4, 5, 6, 7, 0, 1, 2, 3, 0, 1, 2, 3};
for (int n_pixel = 0; n_pixel < 5000; n_pixel++)
{
int64_t read_raw_m04(FILE *fp, int64_t n_frames, char *frame_out,
char *digital_out, Header *header_out) {
for (int i_sc = 0; i_sc < 32; i_sc++)
{
// analog part
int adc_nr = adc_numbers[i_sc];
int col = ((adc_nr * 25) + (n_pixel % 25));
int row = 0;
if (i_sc / 4 % 2 == 0)
{
row = 199 - (n_pixel / 25);
}
else
{
row = 200 + (n_pixel / 25);
}
int i_analog = n_pixel * 32 + i_sc;
out_buf[row * 400 + col] = buf[i_analog] & 0x3FFF;
const size_t frame_size = 400 * 400 * 2;
const size_t digital_frame_size = 5000 * 8;
char *tmp = malloc(frame_size);
char *digital_tmp = malloc(digital_frame_size);
Header h;
int64_t frames_read = 0;
#ifdef CR_VERBOSE
printf("MOENCH04: digital_out: %p\n", digital_out);
#endif
while (frames_read < n_frames) {
// Read header to temp buffer, return on fail
if (fread(&h, sizeof(Header), 1, fp) != 1) {
break;
}
if (header_out) {
memcpy(header_out, &h, sizeof(Header));
header_out++;
}
// Read frame to temporary buffer
if (fread(tmp, frame_size, 1, fp) != 1) {
break;
}
if (digital_out) {
if (fread(digital_tmp, digital_frame_size, 1, fp) != 1) {
break;
}
}
// Decode frame and write to numpy output array
// decode_moench03((uint16_t *)tmp, (uint16_t *)frame_out);
decode_moench04(tmp, digital_tmp, frame_out, digital_out);
frame_out += frame_size;
if (digital_out)
digital_out += 400*400; //one byte per pixel
frames_read++;
}
free(tmp);
free(digital_tmp);
return frames_read;
}
void decode_moench03(const uint16_t *buf, uint16_t *out_buf) {
int adc_numbers[32] = {12, 13, 14, 15, 12, 13, 14, 15, 8, 9, 10,
11, 8, 9, 10, 11, 4, 5, 6, 7, 4, 5,
6, 7, 0, 1, 2, 3, 0, 1, 2, 3};
for (int n_pixel = 0; n_pixel < 5000; n_pixel++) {
for (int i_sc = 0; i_sc < 32; i_sc++) {
// analog part
int adc_nr = adc_numbers[i_sc];
int col = ((adc_nr * 25) + (n_pixel % 25));
int row = 0;
if (i_sc / 4 % 2 == 0) {
row = 199 - (n_pixel / 25);
} else {
row = 200 + (n_pixel / 25);
}
int i_analog = n_pixel * 32 + i_sc;
out_buf[row * 400 + col] = buf[i_analog] & 0x3FFF;
}
}
}
void decode_moench04(const uint16_t *analog_data, const uint64_t *digital_data,
uint16_t *analog_frame, uint8_t *digital_frame) {
int adc_numbers[32] = {9, 8, 11, 10, 13, 12, 15, 14, 1, 0, 3,
2, 5, 4, 7, 6, 23, 22, 21, 20, 19, 18,
17, 16, 31, 30, 29, 28, 27, 26, 25, 24};
int dbits_bit[32] = {-1, -1, -1, -1, -1, -1, 1, 3, 5, 7, -1,
-1, -1, -1, -1, -1, 62, 60, 58, 56, 54, 52,
50, 48, 63, 61, 59, 57, 55, 53, 51, 49};
for (int n_pixel = 0; n_pixel < 5000; n_pixel++) {
uint64_t current_dbits = digital_data[n_pixel];
for (int i_sc = 0; i_sc < 32; i_sc++) {
// analog part
int adc_nr = adc_numbers[i_sc];
int col = ((adc_nr % 16) * 25) + (n_pixel % 25);
int row = 0;
if (i_sc < 16) {
row = 199 - (n_pixel / 25);
} else {
row = 200 + (n_pixel / 25);
}
int i_analog = n_pixel * 32 + i_sc;
analog_frame[row * 400 + col] = analog_data[i_analog] & 0x3FFF;
// if we pass digital data to be decoded
if (digital_frame) {
int bit_loc = dbits_bit[adc_nr];
if (bit_loc < 0) {
digital_frame[row * 400 + col] = 2;
} else {
uint8_t dbit = (current_dbits >> bit_loc) & 1U;
if ((6 <= adc_nr) & (adc_nr <= 9)) {
digital_frame[row * 400 + col] = dbit;
} else {
digital_frame[row * 400 + col] = 1 - dbit;
}
}
} else {
// digital_frame[row * 400 + col] = 2;
}
}
}
}
}

View File

@ -1,8 +1,27 @@
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include "data_types.h"
int64_t read_raw(FILE *fp, int64_t n_frames, size_t frame_size, char* out_buf, Header* header_out);
int64_t read_raw_m03(
FILE *fp,
int64_t n_frames,
char* frame_out,
Header* header_out
);
int64_t read_raw_m04(
FILE *fp,
int64_t n_frames,
char* frame_out,
char* digital_out,
Header* header_out
);
void decode_moench03(const uint16_t *buf, uint16_t *out_buf);
void decode_moench04(const uint16_t *analog_data,
const uint64_t *digital_data,
uint16_t *analog_frame,
uint8_t *digital_frame);

View File

@ -1,31 +1,69 @@
import pytest
import os, sys
from creader import RawFileReader
from creader import RawFileReader, DetectorType
from fixtures import data_path
import numpy as np
m03 = DetectorType.MOENCH_03
m04a= DetectorType.MOENCH_04_A
m04ad= DetectorType.MOENCH_04_AD
def test_references_on_read(data_path):
fname= data_path/'test_d0_f0_0.raw'
r = RawFileReader(fname)
fname= data_path/'Moench03_d0_f0_0.raw'
r = RawFileReader(fname, m03)
frames = r.read(10)
assert sys.getrefcount(frames) == 2 #Over counts by one due to call by reference
def test_references_on_read_with_header(data_path):
fname= data_path/'test_d0_f0_0.raw'
r = RawFileReader(fname, header = True)
fname= data_path/'Moench03_d0_f0_0.raw'
r = RawFileReader(fname, m03, header = True)
frames, header = r.read(100)
assert sys.getrefcount(frames) == 2 #Over counts by one due to call by reference
assert sys.getrefcount(header) == 2
def test_reading_frame_numbers(data_path):
fname= data_path/'test_d0_f0_0.raw'
r = RawFileReader(fname, header = True)
fname= data_path/'Moench03_d0_f0_0.raw'
r = RawFileReader(fname, header = True, detector_type = m03)
frames, header = r.read(1000)
assert (header['Frame Number'] == np.arange(201,1201, dtype = np.uint64)).all()
def test_reading_more_files_than_available(data_path):
fname= data_path/'test_d0_f0_0.raw'
r = RawFileReader(fname, header = True)
fname= data_path/'Moench03_d0_f0_0.raw'
r = RawFileReader(fname, m03, header = True)
frames, header = r.read(1500)
assert frames.shape == (1000,400, 400)
assert header.size == 1000
def test_references_on_m04_a(data_path):
fname= data_path/'Moench04_no_digital_d0_f0_0.raw'
r = RawFileReader(fname, m04a)
frames = r.read(3)
assert sys.getrefcount(frames) == 2 #Over counts by one due to call by reference
assert frames.shape == (3,400,400)
def test_references_on_m04_a_with_heaer(data_path):
fname= data_path/'Moench04_no_digital_d0_f0_0.raw'
r = RawFileReader(fname, m04a, header = True)
frames, header = r.read(7)
assert sys.getrefcount(frames) == 2 #Over counts by one due to call by reference
assert frames.shape == (7,400,400)
assert sys.getrefcount(header) == 2
assert header.size == 7
def test_references_on_m04_ad(data_path):
fname= data_path/'Moench04_digital_d0_f0_0.raw'
r = RawFileReader(fname, m04ad)
analog, digital = r.read(3)
assert sys.getrefcount(analog) == 2 #Over counts by one due to call by reference
assert analog.shape == (3,400,400)
assert sys.getrefcount(digital) == 2
def test_references_on_m04_ad_with_header(data_path):
fname= data_path/'Moench04_digital_d0_f0_0.raw'
r = RawFileReader(fname, m04ad, header = True)
analog, digital, header = r.read(3)
assert sys.getrefcount(analog) == 2 #Over counts by one due to call by reference
assert analog.shape == (3,400,400)
assert sys.getrefcount(digital) == 2
assert sys.getrefcount(header) == 2