diff --git a/include/aare/PixelMap.hpp b/include/aare/PixelMap.hpp index 41f0952..c14bd5b 100644 --- a/include/aare/PixelMap.hpp +++ b/include/aare/PixelMap.hpp @@ -10,11 +10,21 @@ NDArray GenerateMoench03PixelMap(); NDArray GenerateMoench05PixelMap(); NDArray GenerateMoench05PixelMap1g(); NDArray GenerateMoench05PixelMapOld(); +NDArray GenerateMoench04AnalogPixelMap(); // Matterhorn02 NDArray GenerateMH02SingleCounterPixelMap(); NDArray GenerateMH02FourCounterPixelMap(); +/** + * @brief Generate pixel map for Matterhorn10 detector + * @param dynamic_range Dynamic range of the detector (16, 8, or 4) + * @param n_counters Number of counters (1 to 4) + */ +NDArray +GenerateMatterhorn10PixelMap(const size_t dynamic_range = 16, + const size_t n_counters = 1); + // Eiger NDArray GenerateEigerFlipRowsPixelMap(); diff --git a/include/aare/RawMasterFile.hpp b/include/aare/RawMasterFile.hpp index 37a5a53..d7b13a4 100644 --- a/include/aare/RawMasterFile.hpp +++ b/include/aare/RawMasterFile.hpp @@ -97,9 +97,9 @@ class RawMasterFile { size_t m_frame_padding{}; // TODO! should these be bool? - uint8_t m_analog_flag{}; - uint8_t m_digital_flag{}; - uint8_t m_transceiver_flag{}; + bool m_analog_flag{}; + bool m_digital_flag{}; + bool m_transceiver_flag{}; ScanParameters m_scan_parameters; @@ -135,6 +135,8 @@ class RawMasterFile { size_t n_modules() const; uint8_t quad() const; + ReadoutMode get_reading_mode() const; + std::optional analog_samples() const; std::optional digital_samples() const; std::optional transceiver_samples() const; diff --git a/include/aare/decode.hpp b/include/aare/decode.hpp index baab789..c339709 100644 --- a/include/aare/decode.hpp +++ b/include/aare/decode.hpp @@ -38,6 +38,13 @@ uint32_t mask32to24bits(uint32_t input, BitOffset offset = {}); void expand24to32bit(NDView input, NDView output, BitOffset offset = {}); +/** + * @brief expands the two 4 bit values of an 8 bit buffer into two 8 bit values + * @param input input buffer with 4 bit values packed into 8 bit + * @param output output buffer with 8 bit values + */ +void expand4to8bit(NDView input, NDView output); + /** * @brief Apply custom weights to a 16-bit input value. Will sum up * weights[i]**i for each bit i that is set in the input value. diff --git a/include/aare/defs.hpp b/include/aare/defs.hpp index c0faedc..0236b45 100644 --- a/include/aare/defs.hpp +++ b/include/aare/defs.hpp @@ -188,6 +188,46 @@ struct ROI { } }; +/// @brief Chip specifications for Matterhorn1 +struct Matterhorn10 { + constexpr static size_t nRows = 256; + constexpr static size_t nCols = 256; +}; + +/// @brief Chip specifications for Matterhorn2 +struct Matterhorn02 { + constexpr static size_t nRows = 48; + constexpr static size_t nCols = 48; + constexpr static size_t nHalfCols = 24; +}; + +/// @brief Chip specifications for Moench04 +struct Moench04 { + constexpr static size_t nRows = 400; + constexpr static size_t nCols = 400; + constexpr static std::array + adcNumbers = + { + 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}; // TODO : should we + // only have chip + // specifications or + // also wiring in + // chiptestboard? + constexpr static size_t nPixelsPerSuperColumn = 5000; + constexpr static size_t superColumnWidth = 25; +}; + +enum ReadoutMode : uint8_t { + ANALOG_ONLY = 0, + DIGITAL_ONLY = 1, + ANALOG_AND_DIGITAL = 2, + TRANSCEIVER_ONLY = 3, + DIGITAL_AND_TRANSCEIVER = 4, + UNKNOWN = 5 +}; + using dynamic_shape = std::vector; // TODO! Can we uniform enums between the libraries? diff --git a/python/aare/CtbRawFile.py b/python/aare/CtbRawFile.py index cd4241d..561604b 100644 --- a/python/aare/CtbRawFile.py +++ b/python/aare/CtbRawFile.py @@ -18,6 +18,9 @@ class CtbRawFile(_aare.CtbRawFile): super().__init__(fname) self._chunk_size = chunk_size self._transform = transform + if self._transform: + if hasattr(self._transform, "compatibility") and callable(getattr(self._transform, "compatibility")): + self._transform.compatibility(self.master.reading_mode) def read_frame(self, frame_index: int | None = None ) -> tuple: @@ -45,7 +48,6 @@ class CtbRawFile(_aare.CtbRawFile): if header.shape == (1,): header = header[0] - if self._transform: res = self._transform(data) if isinstance(res, tuple): diff --git a/python/aare/__init__.py b/python/aare/__init__.py index b69d76e..ab3cd76 100644 --- a/python/aare/__init__.py +++ b/python/aare/__init__.py @@ -5,7 +5,7 @@ from . import _aare from ._aare import File, RawMasterFile, RawSubFile, JungfrauDataFile from ._aare import Pedestal_d, Pedestal_f, ClusterFinder_Cluster3x3i, VarClusterFinder -from ._aare import DetectorType +from ._aare import DetectorType, ReadoutMode from ._aare import hitmap from ._aare import ROI from ._aare import corner diff --git a/python/aare/transform.py b/python/aare/transform.py index d1492e4..e082bed 100644 --- a/python/aare/transform.py +++ b/python/aare/transform.py @@ -1,7 +1,8 @@ # SPDX-License-Identifier: MPL-2.0 import numpy as np from . import _aare - +from aare import ReadoutMode +from aare._aare import Matterhorn10 class AdcSar04Transform64to16: def __call__(self, data): @@ -24,6 +25,14 @@ class Moench05Transform: return np.take(data.view(np.uint16), self.pixel_map) +class Moench03Transform: + def __init__(self): + self.pixel_map = _aare.GenerateMoench03PixelMap() + + def __call__(self, data): + return np.take(data.view(np.uint16), self.pixel_map) + + class Moench05Transform1g: #Could be moved to C++ without changing the interface def __init__(self): @@ -40,19 +49,81 @@ class Moench05TransformOld: def __call__(self, data): return np.take(data.view(np.uint16), self.pixel_map) - - -class Matterhorn02Transform: + +class Moench04AnalogTransform: + #Could be moved to C++ without changing the interface def __init__(self): - self.pixel_map = _aare.GenerateMH02FourCounterPixelMap() + self.pixel_map = _aare.GenerateMoench04AnalogPixelMap() def __call__(self, data): - counters = int(data.size / 48**2 / 2) - if counters == 1: - return np.take(data.view(np.uint16), self.pixel_map[0]) - else: - return np.take(data.view(np.uint16), self.pixel_map[0:counters]) + return np.take(data.view(np.uint16), self.pixel_map) + +class Matterhorn02TransceiverTransform: + #Could be moved to C++ without changing the interface + def __init__(self): + self.pixel_map = _aare.GenerateMH02SingleCounterPixelMap() + def __call__(self, data): + return np.take(data.view(np.uint16), self.pixel_map) + +class Matterhorn10Transform: + """ + Transforms Matterhorn10 chip data from a buffer of bytes (uint8_t) + to a numpy array of uint8, uint16 depending on dynamic range. + Assumes data taken with transceiver samples only. + + :param dynamic_range: How many bits a pixel is encoded dynamic range (4, 8, or 16) + :type dynamic_range: int + :param num_counters: num counters used (1 to 4) + :type num_counters: int + + .. note:: + A matterhorn chip has 256 columns and 256 rows. + A matterhornchip with dynamic range 16 and 2 counters thus requires + 256*256*16*2/(2*64) = 1024 transceiver samples. (Per default 2 channels are enabled per transceiver sample, each channel storing 64 bits) + """ + def __init__(self, dynamic_range : int, num_counters : int): + self.pixel_map = _aare.GenerateMatterhorn10PixelMap(dynamic_range, num_counters) + self.dynamic_range = dynamic_range + self.num_counters = num_counters + + def compatibility(self, readingmode : ReadoutMode): + """ + checks if Matterhorn10Transform is compatible with given parameters + + :param readingmode: Reading mode set + :type readingmode: ReadoutMode + :raises ValueError: if not compatible + """ + if(readingmode != ReadoutMode.TRANSCEIVER_ONLY): + raise ValueError(f"Incompatible Transformation. Matterhorn10Transform only requires transceiver samples. However reading mode is {readingmode}.") + + pass + + def data_compatibility(self, data): + """ + checks if data is compatible for transformation + + :param data: data to be transformed, expected to be a 1D numpy array of uint8 + :type data: np.ndarray + :raises ValueError: if not compatible + """ + expected_size = (Matterhorn10.nRows*Matterhorn10.nCols*self.num_counters*self.dynamic_range)//8 # read_frame returns data in uint8_t + + if(data.size != expected_size): + raise ValueError(f"Data size {data.size} does not match expected size {expected_size} for Matterhorn10 with dynamic range {self.dynamic_range} and num_counters {self.num_counters}.") + + pass + + def __call__(self, data): + self.data_compatibility(data) + if self.dynamic_range == 16: + return np.take(data.view(np.uint16), self.pixel_map) + elif self.dynamic_range == 8: + return np.take(data.view(np.uint8), self.pixel_map) + else: #dynamic range 4 + return np.take(_aare.expand4to8bit(data.view(np.uint8)), self.pixel_map) + class Mythen302Transform: """ Transform Mythen 302 test chip data from a buffer of bytes (uint8_t) @@ -95,7 +166,7 @@ class Mythen302Transform: moench05 = Moench05Transform() moench05_1g = Moench05Transform1g() moench05_old = Moench05TransformOld() -matterhorn02 = Matterhorn02Transform() +matterhorn02 = Matterhorn02TransceiverTransform() adc_sar_04_64to16 = AdcSar04Transform64to16() adc_sar_05_64to16 = AdcSar05Transform64to16() adc_sar_05_06_07_08_64to16 = AdcSar05060708Transform64to16() \ No newline at end of file diff --git a/python/src/bind_Defs.hpp b/python/src/bind_Defs.hpp new file mode 100644 index 0000000..e048f1c --- /dev/null +++ b/python/src/bind_Defs.hpp @@ -0,0 +1,25 @@ +#include +#include + +#include "aare/defs.hpp" + +namespace py = pybind11; +using namespace aare; + +void define_defs_bindings(py::module &m) { + auto matterhorn10 = py::class_(m, "Matterhorn10"); + matterhorn10.attr("nRows") = Matterhorn10::nRows; + matterhorn10.attr("nCols") = Matterhorn10::nCols; + + auto matterhorn02 = py::class_(m, "Matterhorn02"); + matterhorn02.attr("nRows") = Matterhorn02::nRows; + matterhorn02.attr("nCols") = Matterhorn02::nCols; + matterhorn02.attr("nHalfCols") = Matterhorn02::nHalfCols; + + auto moench04 = py::class_(m, "Moench04"); + moench04.attr("nRows") = Moench04::nRows; + moench04.attr("nCols") = Moench04::nCols; + moench04.attr("nPixelsPerSuperColumn") = Moench04::nPixelsPerSuperColumn; + moench04.attr("superColumnWidth") = Moench04::superColumnWidth; + moench04.attr("adcNumbers") = Moench04::adcNumbers; +} diff --git a/python/src/pixel_map.hpp b/python/src/bind_PixelMap.hpp similarity index 59% rename from python/src/pixel_map.hpp rename to python/src/bind_PixelMap.hpp index ed5e550..05d683e 100644 --- a/python/src/pixel_map.hpp +++ b/python/src/bind_PixelMap.hpp @@ -33,15 +33,35 @@ void define_pixel_map_bindings(py::module &m) { new NDArray(GenerateMoench05PixelMapOld()); return return_image_data(ptr); }) + + .def("GenerateMoench04AnalogPixelMap", + []() { + auto ptr = + new NDArray(GenerateMoench04AnalogPixelMap()); + return return_image_data(ptr); + }) + .def("GenerateMH02SingleCounterPixelMap", []() { auto ptr = new NDArray( GenerateMH02SingleCounterPixelMap()); return return_image_data(ptr); }) - .def("GenerateMH02FourCounterPixelMap", []() { - auto ptr = - new NDArray(GenerateMH02FourCounterPixelMap()); - return return_image_data(ptr); - }); + .def("GenerateMH02FourCounterPixelMap", + []() { + auto ptr = + new NDArray(GenerateMH02FourCounterPixelMap()); + return return_image_data(ptr); + }) + + .def( + "GenerateMatterhorn10PixelMap", + [](const size_t dynamic_range, const size_t n_counters) { + auto ptr = new NDArray( + GenerateMatterhorn10PixelMap(dynamic_range, n_counters)); + return return_image_data(ptr); + }, + py::arg("dynamic_range") = 16, py::arg("n_counters") = 1, + R"( + Generate pixel map for Matterhorn02 detector)"); } \ No newline at end of file diff --git a/python/src/ctb_raw_file.hpp b/python/src/ctb_raw_file.hpp index a53712c..6deff4d 100644 --- a/python/src/ctb_raw_file.hpp +++ b/python/src/ctb_raw_file.hpp @@ -139,6 +139,22 @@ void define_ctb_raw_file_io_bindings(py::module &m) { return output; }); + m.def("expand4to8bit", + [](py::array_t + &input) { + py::buffer_info buf = input.request(); + + py::array_t output(buf.size * 2); + + NDView input_view(input.mutable_data(), + {input.size()}); + NDView output_view(output.mutable_data(), + {output.size()}); + + aare::expand4to8bit(input_view, output_view); + return output; + }); + m.def("decode_my302", [](py::array_t &input, diff --git a/python/src/module.cpp b/python/src/module.cpp index dfd25dc..19bbe29 100644 --- a/python/src/module.cpp +++ b/python/src/module.cpp @@ -9,8 +9,10 @@ #include "bind_ClusterFinder.hpp" #include "bind_ClusterFinderMT.hpp" #include "bind_ClusterVector.hpp" +#include "bind_Defs.hpp" #include "bind_Eta.hpp" #include "bind_Interpolator.hpp" +#include "bind_PixelMap.hpp" #include "bind_RawFile.hpp" #include "bind_calibration.hpp" @@ -20,7 +22,6 @@ #include "fit.hpp" #include "jungfrau_data_file.hpp" #include "pedestal.hpp" -#include "pixel_map.hpp" #include "raw_master_file.hpp" #include "raw_sub_file.hpp" #include "var_cluster.hpp" @@ -140,6 +141,8 @@ PYBIND11_MODULE(_aare, m) { register_calculate_3x3eta(m); register_calculate_3x3eta(m); + define_defs_bindings(m); + using Sum_index_pair_d = Sum_index_pair; PYBIND11_NUMPY_DTYPE(Sum_index_pair_d, sum, index); using Sum_index_pair_f = Sum_index_pair; diff --git a/python/src/raw_master_file.hpp b/python/src/raw_master_file.hpp index f8730d9..b67720b 100644 --- a/python/src/raw_master_file.hpp +++ b/python/src/raw_master_file.hpp @@ -23,6 +23,16 @@ namespace py = pybind11; using namespace ::aare; void define_raw_master_file_bindings(py::module &m) { + + py::enum_(m, "ReadoutMode") + .value("ANALOG_ONLY", ReadoutMode::ANALOG_ONLY) + .value("DIGITAL_ONLY", ReadoutMode::DIGITAL_ONLY) + .value("ANALOG_AND_DIGITAL", ReadoutMode::ANALOG_AND_DIGITAL) + .value("TRANSCEIVER_ONLY", ReadoutMode::TRANSCEIVER_ONLY) + .value("DIGITAL_AND_TRANSCEIVER", ReadoutMode::DIGITAL_AND_TRANSCEIVER) + .value("UNKNOWN", ReadoutMode::UNKNOWN) + .export_values(); + py::class_(m, "RawMasterFile") .def(py::init()) .def("data_fname", &RawMasterFile::data_fname, R"( @@ -81,6 +91,7 @@ void define_raw_master_file_bindings(py::module &m) { .def_property_readonly("transceiver_samples", &RawMasterFile::transceiver_samples) + .def_property_readonly("reading_mode", &RawMasterFile::get_reading_mode) .def_property_readonly("number_of_rows", &RawMasterFile::number_of_rows) .def_property_readonly("quad", &RawMasterFile::quad) .def_property_readonly("scan_parameters", diff --git a/python/tests/test_RawFile.py b/python/tests/test_RawFile.py index b9cad77..443c093 100644 --- a/python/tests/test_RawFile.py +++ b/python/tests/test_RawFile.py @@ -14,6 +14,9 @@ def test_read_rawfile_with_roi_spanning_over_one_module(test_data_path): assert headers.size == 10100 assert frames.shape == (10100, 256, 256) + assert headers.size == 10100 + assert frames.shape == (10100, 256, 256) + @pytest.mark.withdata def test_read_rawfile_with_multiple_rois(test_data_path): with RawFile(test_data_path / "raw/ROITestData/MultipleROIs/run_master_0.json") as f: @@ -44,6 +47,8 @@ def test_read_rawfile_with_multiple_rois(test_data_path): assert frame[0].shape == (301, 101) assert f.tell() == 2 + + @pytest.mark.withdata def test_read_rawfile_quad_eiger_and_compare_to_numpy(test_data_path): @@ -68,7 +73,6 @@ def test_read_rawfile_quad_eiger_and_compare_to_numpy(test_data_path): assert (image == image1).all() - @pytest.mark.withdata def test_read_rawfile_eiger_and_compare_to_numpy(test_data_path): d0 = test_data_path/'raw/eiger/Lab6_20500eV_2deg_20240629_d0_f0_7.raw' diff --git a/python/tests/test_RawMasterFile.py b/python/tests/test_RawMasterFile.py new file mode 100644 index 0000000..6ea9ff7 --- /dev/null +++ b/python/tests/test_RawMasterFile.py @@ -0,0 +1,14 @@ + + +import pytest +from aare import RawMasterFile, ReadoutMode, DetectorType + + +@pytest.mark.withdata +def test_read_rawfile_quad_eiger_and_compare_to_numpy(test_data_path): + + file_name = test_data_path/'raw/jungfrau/jungfrau_single_master_0.json' + + f = RawMasterFile(file_name) + assert(f.reading_mode == ReadoutMode.UNKNOWN) + assert(f.detector_type == DetectorType.Jungfrau) \ No newline at end of file diff --git a/src/PixelMap.cpp b/src/PixelMap.cpp index 1920b1f..000ad53 100644 --- a/src/PixelMap.cpp +++ b/src/PixelMap.cpp @@ -1,5 +1,6 @@ // SPDX-License-Identifier: MPL-2.0 #include "aare/PixelMap.hpp" +#include "aare/defs.hpp" #include @@ -31,6 +32,29 @@ NDArray GenerateMoench03PixelMap() { return order_map; } +NDArray GenerateMoench04AnalogPixelMap() { + std::array const adc_nr = Moench04::adcNumbers; + int const nadc = adc_nr.size(); + NDArray order_map({Moench04::nRows, Moench04::nCols}); + + int pixel = 0; + for (size_t i = 0; i != Moench04::nPixelsPerSuperColumn; ++i) { + for (size_t i_adc = 0; i_adc != nadc; ++i_adc) { + int const col = + (adc_nr[i_adc] % 16) * 25 + (i % Moench04::superColumnWidth); + int row = 0; + if (i_adc < 16) + row = 199 - (i / Moench04::superColumnWidth); + else + row = 200 + (i / Moench04::superColumnWidth); + + order_map(row, col) = pixel; + pixel++; + } + } + return order_map; +} + NDArray GenerateMoench05PixelMap() { std::array adc_numbers = {5, 9, 1}; NDArray order_map({160, 150}); @@ -104,16 +128,18 @@ NDArray GenerateEigerFlipRowsPixelMap() { return order_map; } +// transceiver pixel map for Matterhorn02 NDArray GenerateMH02SingleCounterPixelMap() { // This is the pixel map for a single counter Matterhorn02, i.e. 48x48 // pixels. Data is read from two transceivers in blocks of 4 pixels. - NDArray order_map({48, 48}); + NDArray order_map({Matterhorn02::nRows, Matterhorn02::nCols}); size_t offset = 0; size_t nSamples = 4; - for (int row = 0; row < 48; row++) { - for (int col = 0; col < 24; col++) { - for (int iTrans = 0; iTrans < 2; iTrans++) { - order_map(row, iTrans * 24 + col) = offset + nSamples * iTrans; + for (size_t row = 0; row < Matterhorn02::nRows; row++) { + for (size_t col = 0; col < Matterhorn02::nHalfCols; col++) { + for (size_t iTrans = 0; iTrans < 2; iTrans++) { + order_map(row, iTrans * Matterhorn02::nHalfCols + col) = + offset + nSamples * iTrans; } offset += 1; if ((col + 1) % nSamples == 0) { @@ -126,16 +152,64 @@ NDArray GenerateMH02SingleCounterPixelMap() { NDArray GenerateMH02FourCounterPixelMap() { auto single_counter_map = GenerateMH02SingleCounterPixelMap(); - NDArray order_map({4, 48, 48}); - for (int counter = 0; counter < 4; counter++) { - for (int row = 0; row < 48; row++) { - for (int col = 0; col < 48; col++) { + NDArray order_map( + {4, Matterhorn02::nRows, Matterhorn02::nCols}); + for (size_t counter = 0; counter < 4; counter++) { + for (size_t row = 0; row < Matterhorn02::nRows; row++) { + for (size_t col = 0; col < Matterhorn02::nCols; col++) { order_map(counter, row, col) = - single_counter_map(row, col) + counter * 48 * 48; + single_counter_map(row, col) + + counter * Matterhorn02::nRows * Matterhorn02::nCols; } } } return order_map; } +NDArray GenerateMatterhorn10PixelMap(const size_t dynamic_range, + const size_t n_counters) { + + // Matterhorn10 uses transceiver samples (each transceiver sample has 1-4 + // channels storing 8 bytes each) + constexpr size_t n_cols = Matterhorn10::nCols; + constexpr size_t n_rows = Matterhorn10::nRows; + NDArray pixel_map( + {static_cast(n_rows * n_counters), n_cols}); + + size_t num_consecutive_pixels{}; + switch (dynamic_range) { + case 16: + num_consecutive_pixels = 4; + break; + case 8: + num_consecutive_pixels = 8; + break; + case 4: + num_consecutive_pixels = 16; + break; + default: + throw std::runtime_error("Unsupported dynamic range for Matterhorn02"); + } + + for (size_t row = 0; row < n_rows; ++row) { + for (size_t counter = 0; counter < n_counters; ++counter) { + size_t col = 0; + for (size_t offset = 0; offset < 64; + offset += num_consecutive_pixels) { + for (size_t pkg = offset; pkg < Matterhorn10::nCols; + pkg += 64) { + for (size_t pixel = 0; pixel < num_consecutive_pixels; + ++pixel) { + pixel_map(row + counter * n_rows, col) = + pkg + pixel + row * n_cols * n_counters + + n_cols * counter; + ++col; + } + } + } + } + } + return pixel_map; +} + } // namespace aare \ No newline at end of file diff --git a/src/RawMasterFile.cpp b/src/RawMasterFile.cpp index 560449c..e7ef637 100644 --- a/src/RawMasterFile.cpp +++ b/src/RawMasterFile.cpp @@ -206,6 +206,30 @@ std::optional RawMasterFile::roi() const { std::optional> RawMasterFile::rois() const { return m_rois; } +ReadoutMode RawMasterFile::get_reading_mode() const { + + if (m_type != DetectorType::ChipTestBoard && + m_type != DetectorType::Xilinx_ChipTestBoard) { + LOG(TLogLevel::logINFO) + << "reading mode is only available for CTB detectors."; + return ReadoutMode::UNKNOWN; + } + + if (m_analog_flag && m_digital_flag) { + return ReadoutMode::ANALOG_AND_DIGITAL; + } else if (m_analog_flag) { + return ReadoutMode::ANALOG_ONLY; + } else if (m_digital_flag && m_transceiver_flag) { + return ReadoutMode::DIGITAL_AND_TRANSCEIVER; + } else if (m_digital_flag) { + return ReadoutMode::DIGITAL_ONLY; + } else if (m_transceiver_flag) { + return ReadoutMode::TRANSCEIVER_ONLY; + } else { + return ReadoutMode::UNKNOWN; + } +} + void RawMasterFile::parse_json(std::istream &is) { json j; is >> j; @@ -216,10 +240,10 @@ void RawMasterFile::parse_json(std::istream &is) { m_type = string_to(j["Detector Type"].get()); m_timing_mode = string_to(j["Timing Mode"].get()); - m_geometry = { - j["Geometry"]["y"], - j["Geometry"]["x"]}; // TODO: isnt it only available for version > 7.1? - // - try block default should be 1x1 + m_geometry = {j["Geometry"]["y"], + j["Geometry"]["x"]}; // TODO: isnt it only available for + // version > 7.1? + // - try block default should be 1x1 m_image_size_in_bytes = v < 8.0 ? j["Image Size in bytes"] : j["Image Size"]; @@ -272,22 +296,15 @@ void RawMasterFile::parse_json(std::istream &is) { // ---------------------------------------------------------------- // Special treatment of analog flag because of Moench03 + m_analog_flag = v < 8.0 && (m_type == DetectorType::Moench); + try { - m_analog_flag = j.at("Analog Flag"); - } catch (const json::out_of_range &e) { - // if it doesn't work still set it to one - // to try to decode analog samples (Old Moench03) - m_analog_flag = 1; - } - try { + m_analog_flag = static_cast(j.at("Analog Flag").get()); if (m_analog_flag) { m_analog_samples = j.at("Analog Samples"); } - } catch (const json::out_of_range &e) { // keep the optional empty - // and set analog flag to 0 - m_analog_flag = 0; } //----------------------------------------------------------------- try { @@ -302,7 +319,7 @@ void RawMasterFile::parse_json(std::istream &is) { // m_adc_mask = 0; // } try { - int digital_flag = j.at("Digital Flag"); + bool digital_flag = static_cast(j.at("Digital Flag").get()); if (digital_flag) { m_digital_samples = j.at("Digital Samples"); } @@ -310,7 +327,8 @@ void RawMasterFile::parse_json(std::istream &is) { // keep the optional empty } try { - m_transceiver_flag = j.at("Transceiver Flag"); + m_transceiver_flag = + static_cast(j.at("Transceiver Flag").get()); if (m_transceiver_flag) { m_transceiver_samples = j.at("Transceiver Samples"); } @@ -444,9 +462,9 @@ void RawMasterFile::parse_raw(std::istream &is) { // } else if (key == "Number of rows"){ // m_number_of_rows = std::stoi(value); } else if (key == "Analog Flag") { - m_analog_flag = std::stoi(value); + m_analog_flag = static_cast(std::stoi(value)); } else if (key == "Digital Flag") { - m_digital_flag = std::stoi(value); + m_digital_flag = static_cast(std::stoi(value)); } else if (key == "Analog Samples") { if (m_analog_flag == 1) { diff --git a/src/RawMasterFile.test.cpp b/src/RawMasterFile.test.cpp index 8173636..5d077ce 100644 --- a/src/RawMasterFile.test.cpp +++ b/src/RawMasterFile.test.cpp @@ -146,6 +146,8 @@ TEST_CASE("Parse a master file in .json format", "[.integration]") { REQUIRE_FALSE(f.analog_samples()); REQUIRE_FALSE(f.digital_samples()); + + REQUIRE(f.get_reading_mode() == ReadoutMode::UNKNOWN); } TEST_CASE("Parse a master file in old .raw format", @@ -211,6 +213,8 @@ TEST_CASE("Parse a master file in .raw format", "[.integration]") { // Frames in File : 100 REQUIRE(f.frames_in_file() == 100); + REQUIRE(f.get_reading_mode() == ReadoutMode::ANALOG_AND_DIGITAL); + // #Frame Header // Frame Number : 8 bytes // SubFrame Number/ExpLength : 4 bytes @@ -560,6 +564,7 @@ TEST_CASE("Parse a CTB file from stream") { REQUIRE(f.digital_samples() == std::nullopt); // Digital Flag is 0 REQUIRE(f.transceiver_samples() == 1152); REQUIRE(f.frames_in_file() == 40); + REQUIRE(f.get_reading_mode() == ReadoutMode::TRANSCEIVER_ONLY); } TEST_CASE("Parse v8.0 MYTHEN3 from stream") { diff --git a/src/decode.cpp b/src/decode.cpp index c1609be..157fed2 100644 --- a/src/decode.cpp +++ b/src/decode.cpp @@ -144,6 +144,24 @@ uint32_t mask32to24bits(uint32_t input, BitOffset offset) { return (input >> offset.value()) & mask24bits; } +void expand4to8bit(NDView input, NDView output) { + + if (2 * input.size() != output.size()) + throw std::runtime_error( + fmt::format("Mismatch between input and output size. Input " + "size of {} requires an output of at least {} " + "bytes. Called with input size: {} output size: {}", + LOCATION, input.size(), 2 * input.size(), input.size(), + output.size())); + + // assumes little-endian + for (ssize_t i = 0; i < input.size(); ++i) { + uint8_t val = input(i); + output[2 * i] = (val & 0x0F); + output[2 * i + 1] = (val & 0xF0) >> 4; + } +} + void expand24to32bit(NDView input, NDView output, BitOffset bit_offset) { diff --git a/src/decode.test.cpp b/src/decode.test.cpp index 5c4c392..d7f3901 100644 --- a/src/decode.test.cpp +++ b/src/decode.test.cpp @@ -152,4 +152,24 @@ TEST_CASE("Expand container with 24 bit data to 32") { CHECK(out(1) == 0xFFF); CHECK(out(2) == 0xFF0); } +} + +TEST_CASE("Expand 4 bit values packed into 8 bit to 8 bit values") { + { + uint8_t buffer[] = { + 0x00, 0xF0, 0xFF, 0x00, 0xF0, 0xFF, + }; + + aare::NDView input(&buffer[0], {6}); + aare::NDArray out({12}); + aare::expand4to8bit(input, out.view()); + + uint8_t expected_output[] = { + 0x0, 0x0, 0x0, 0xF, 0xF, 0xF, + 0x0, 0x0, 0x0, 0xF, 0xF, 0xF}; // assuming little endian + + for (size_t i = 0; i < 12; ++i) { + CHECK(out(i) == expected_output[i]); + } + } } \ No newline at end of file