mirror of
https://github.com/slsdetectorgroup/aare.git
synced 2025-07-14 04:41:50 +02:00
docs and python tests
This commit is contained in:
8
docs/src/JungfrauDataFile.rst
Normal file
8
docs/src/JungfrauDataFile.rst
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
JungfrauDataFile
|
||||||
|
==================
|
||||||
|
|
||||||
|
|
||||||
|
.. doxygenclass:: aare::JungfrauDataFile
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:private-members:
|
47
docs/src/Tests.rst
Normal file
47
docs/src/Tests.rst
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
****************
|
||||||
|
Tests
|
||||||
|
****************
|
||||||
|
|
||||||
|
We test the code both from the C++ and Python API. By default only tests that does not require image data is run.
|
||||||
|
|
||||||
|
C++
|
||||||
|
~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
mkdir build
|
||||||
|
cd build
|
||||||
|
cmake .. -DAARE_TESTS=ON
|
||||||
|
make -j 4
|
||||||
|
|
||||||
|
export AARE_TEST_DATA_DIR=/path/to/test/data
|
||||||
|
./run_test [.files] #or using ctest, [.files] is the option to include tests needing data
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Python
|
||||||
|
~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
#From the root dir of the library
|
||||||
|
python -m pytest python/tests --files # passing --files will run the tests needing data
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Getting the test data
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. attention ::
|
||||||
|
|
||||||
|
The tests needing the test data are not run by default. To make the data available, you need to set the environment variable
|
||||||
|
AARE_TEST_DATA to the path of the test data directory. Then pass either [.files] for the C++ tests or --files for Python
|
||||||
|
|
||||||
|
The image files needed for the test are large and are not included in the repository. They are stored
|
||||||
|
using GIT LFS in a separate repository. To get the test data, you need to clone the repository.
|
||||||
|
To do this, you need to have GIT LFS installed. You can find instructions on how to install it here: https://git-lfs.github.com/
|
||||||
|
Once you have GIT LFS installed, you can clone the repository like any normal repo using:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
git clone https://gitea.psi.ch/detectors/aare-test-data.git
|
@ -20,9 +20,6 @@ AARE
|
|||||||
Requirements
|
Requirements
|
||||||
Consume
|
Consume
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:caption: Python API
|
:caption: Python API
|
||||||
:maxdepth: 1
|
:maxdepth: 1
|
||||||
@ -31,6 +28,7 @@ AARE
|
|||||||
pyCtbRawFile
|
pyCtbRawFile
|
||||||
pyClusterFile
|
pyClusterFile
|
||||||
pyClusterVector
|
pyClusterVector
|
||||||
|
pyJungfrauDataFile
|
||||||
pyRawFile
|
pyRawFile
|
||||||
pyRawMasterFile
|
pyRawMasterFile
|
||||||
pyVarClusterFinder
|
pyVarClusterFinder
|
||||||
@ -51,6 +49,7 @@ AARE
|
|||||||
ClusterFinderMT
|
ClusterFinderMT
|
||||||
ClusterFile
|
ClusterFile
|
||||||
ClusterVector
|
ClusterVector
|
||||||
|
JungfrauDataFile
|
||||||
Pedestal
|
Pedestal
|
||||||
RawFile
|
RawFile
|
||||||
RawSubFile
|
RawSubFile
|
||||||
@ -59,4 +58,8 @@ AARE
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:caption: Developer
|
||||||
|
:maxdepth: 3
|
||||||
|
|
||||||
|
Tests
|
10
docs/src/pyJungfrauDataFile.rst
Normal file
10
docs/src/pyJungfrauDataFile.rst
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
JungfrauDataFile
|
||||||
|
===================
|
||||||
|
|
||||||
|
.. py:currentmodule:: aare
|
||||||
|
|
||||||
|
.. autoclass:: JungfrauDataFile
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
:inherited-members:
|
@ -15,4 +15,9 @@ cmake.verbose = true
|
|||||||
[tool.scikit-build.cmake.define]
|
[tool.scikit-build.cmake.define]
|
||||||
AARE_PYTHON_BINDINGS = "ON"
|
AARE_PYTHON_BINDINGS = "ON"
|
||||||
AARE_SYSTEM_LIBRARIES = "ON"
|
AARE_SYSTEM_LIBRARIES = "ON"
|
||||||
AARE_INSTALL_PYTHONEXT = "ON"
|
AARE_INSTALL_PYTHONEXT = "ON"
|
||||||
|
|
||||||
|
[tool.pytest.ini_options]
|
||||||
|
markers = [
|
||||||
|
"files: marks tests that need additional data (deselect with '-m \"not files\"')",
|
||||||
|
]
|
@ -2,7 +2,6 @@
|
|||||||
#include "aare/JungfrauDataFile.hpp"
|
#include "aare/JungfrauDataFile.hpp"
|
||||||
#include "aare/defs.hpp"
|
#include "aare/defs.hpp"
|
||||||
|
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <pybind11/iostream.h>
|
#include <pybind11/iostream.h>
|
||||||
@ -15,8 +14,48 @@
|
|||||||
namespace py = pybind11;
|
namespace py = pybind11;
|
||||||
using namespace ::aare;
|
using namespace ::aare;
|
||||||
|
|
||||||
|
// Disable warnings for unused parameters, as we ignore some
|
||||||
|
// in the __exit__ method
|
||||||
|
#pragma GCC diagnostic push
|
||||||
|
#pragma GCC diagnostic ignored "-Wunused-parameter"
|
||||||
|
|
||||||
|
auto read_dat_frame(JungfrauDataFile &self) {
|
||||||
|
std::vector<ssize_t> shape;
|
||||||
|
shape.reserve(2);
|
||||||
|
shape.push_back(self.rows());
|
||||||
|
shape.push_back(self.cols());
|
||||||
|
|
||||||
|
// return headers from all subfiles
|
||||||
|
py::array_t<JungfrauDataHeader> header(1);
|
||||||
|
py::array_t<uint16_t> image(shape);
|
||||||
|
|
||||||
|
self.read_into(reinterpret_cast<std::byte *>(image.mutable_data()),
|
||||||
|
header.mutable_data());
|
||||||
|
|
||||||
|
return py::make_tuple(header, image);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto read_n_dat_frames(JungfrauDataFile &self, size_t n_frames) {
|
||||||
|
// adjust for actual frames left in the file
|
||||||
|
n_frames = std::min(n_frames, self.total_frames() - self.tell());
|
||||||
|
if (n_frames == 0) {
|
||||||
|
throw std::runtime_error("No frames left in file");
|
||||||
|
}
|
||||||
|
std::vector<size_t> shape{n_frames, self.rows(), self.cols()};
|
||||||
|
|
||||||
|
// return headers from all subfiles
|
||||||
|
py::array_t<JungfrauDataHeader> header(n_frames);
|
||||||
|
|
||||||
|
py::array_t<uint16_t> image(shape);
|
||||||
|
|
||||||
|
self.read_into(reinterpret_cast<std::byte *>(image.mutable_data()),
|
||||||
|
n_frames, header.mutable_data());
|
||||||
|
|
||||||
|
return py::make_tuple(header, image);
|
||||||
|
}
|
||||||
|
|
||||||
void define_jungfrau_data_file_io_bindings(py::module &m) {
|
void define_jungfrau_data_file_io_bindings(py::module &m) {
|
||||||
//Make the JungfrauDataHeader usable from numpy
|
// Make the JungfrauDataHeader usable from numpy
|
||||||
PYBIND11_NUMPY_DTYPE(JungfrauDataHeader, framenum, bunchid);
|
PYBIND11_NUMPY_DTYPE(JungfrauDataHeader, framenum, bunchid);
|
||||||
|
|
||||||
py::class_<JungfrauDataFile>(m, "JungfrauDataFile")
|
py::class_<JungfrauDataFile>(m, "JungfrauDataFile")
|
||||||
@ -33,50 +72,44 @@ void define_jungfrau_data_file_io_bindings(py::module &m) {
|
|||||||
.def_property_readonly("bytes_per_pixel",
|
.def_property_readonly("bytes_per_pixel",
|
||||||
&JungfrauDataFile::bytes_per_pixel)
|
&JungfrauDataFile::bytes_per_pixel)
|
||||||
.def_property_readonly("bitdepth", &JungfrauDataFile::bitdepth)
|
.def_property_readonly("bitdepth", &JungfrauDataFile::bitdepth)
|
||||||
.def_property_readonly("current_file",
|
.def_property_readonly("current_file", &JungfrauDataFile::current_file)
|
||||||
&JungfrauDataFile::current_file)
|
.def_property_readonly("total_frames", &JungfrauDataFile::total_frames)
|
||||||
.def_property_readonly("total_frames",
|
|
||||||
&JungfrauDataFile::total_frames)
|
|
||||||
.def_property_readonly("n_files", &JungfrauDataFile::n_files)
|
.def_property_readonly("n_files", &JungfrauDataFile::n_files)
|
||||||
.def("read_frame",
|
.def("read_frame",
|
||||||
[](JungfrauDataFile &self) {
|
[](JungfrauDataFile &self) { return read_dat_frame(self); })
|
||||||
|
|
||||||
std::vector<ssize_t> shape;
|
|
||||||
shape.reserve(2);
|
|
||||||
shape.push_back(self.rows());
|
|
||||||
shape.push_back(self.cols());
|
|
||||||
|
|
||||||
// return headers from all subfiles
|
|
||||||
py::array_t<JungfrauDataHeader> header(1);
|
|
||||||
py::array_t<uint16_t> image(shape);
|
|
||||||
|
|
||||||
self.read_into(
|
|
||||||
reinterpret_cast<std::byte *>(image.mutable_data()),
|
|
||||||
header.mutable_data());
|
|
||||||
|
|
||||||
return py::make_tuple(header, image);
|
|
||||||
})
|
|
||||||
.def("read_n",
|
.def("read_n",
|
||||||
[](JungfrauDataFile &self, size_t n_frames) {
|
[](JungfrauDataFile &self, size_t n_frames) {
|
||||||
// adjust for actual frames left in the file
|
return read_n_dat_frames(self, n_frames);
|
||||||
n_frames =
|
},
|
||||||
std::min(n_frames, self.total_frames() - self.tell());
|
R"(
|
||||||
if (n_frames == 0) {
|
Read maximum n_frames frames from the file.
|
||||||
throw std::runtime_error("No frames left in file");
|
)")
|
||||||
}
|
.def(
|
||||||
std::vector<size_t> shape{n_frames, self.rows(), self.cols()};
|
"read",
|
||||||
|
[](JungfrauDataFile &self) {
|
||||||
|
self.seek(0);
|
||||||
|
auto n_frames = self.total_frames();
|
||||||
|
return read_n_dat_frames(self, n_frames);
|
||||||
|
},
|
||||||
|
R"(
|
||||||
|
Read all frames from the file. Seeks to the beginning before reading.
|
||||||
|
)")
|
||||||
|
.def("__enter__", [](JungfrauDataFile &self) { return &self; })
|
||||||
|
.def("__exit__",
|
||||||
|
[](JungfrauDataFile &self,
|
||||||
|
const std::optional<pybind11::type> &exc_type,
|
||||||
|
const std::optional<pybind11::object> &exc_value,
|
||||||
|
const std::optional<pybind11::object> &traceback) {
|
||||||
|
// self.close();
|
||||||
|
})
|
||||||
|
.def("__iter__", [](JungfrauDataFile &self) { return &self; })
|
||||||
|
.def("__next__", [](JungfrauDataFile &self) {
|
||||||
|
try {
|
||||||
|
return read_dat_frame(self);
|
||||||
|
} catch (std::runtime_error &e) {
|
||||||
|
throw py::stop_iteration();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// return headers from all subfiles
|
#pragma GCC diagnostic pop
|
||||||
py::array_t<JungfrauDataHeader> header(n_frames);
|
|
||||||
|
|
||||||
py::array_t<uint16_t> image(shape);
|
|
||||||
|
|
||||||
self.read_into(
|
|
||||||
reinterpret_cast<std::byte *>(image.mutable_data()),
|
|
||||||
n_frames,
|
|
||||||
header.mutable_data());
|
|
||||||
|
|
||||||
return py::make_tuple(header, image);
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
29
python/tests/conftest.py
Normal file
29
python/tests/conftest.py
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def pytest_addoption(parser):
|
||||||
|
parser.addoption(
|
||||||
|
"--files", action="store_true", default=False, help="run slow tests"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def pytest_configure(config):
|
||||||
|
config.addinivalue_line("markers", "files: mark test as needing image fiels to run")
|
||||||
|
|
||||||
|
|
||||||
|
def pytest_collection_modifyitems(config, items):
|
||||||
|
if config.getoption("--files"):
|
||||||
|
return
|
||||||
|
skip = pytest.mark.skip(reason="need --files option to run")
|
||||||
|
for item in items:
|
||||||
|
if "files" in item.keywords:
|
||||||
|
item.add_marker(skip)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def test_data_path():
|
||||||
|
return Path(os.environ["AARE_TEST_DATA"])
|
||||||
|
|
92
python/tests/test_jungfrau_dat_files.py
Normal file
92
python/tests/test_jungfrau_dat_files.py
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
import pytest
|
||||||
|
import numpy as np
|
||||||
|
from aare import JungfrauDataFile
|
||||||
|
|
||||||
|
@pytest.mark.files
|
||||||
|
def test_jfungfrau_dat_read_number_of_frames(test_data_path):
|
||||||
|
with JungfrauDataFile(test_data_path / "dat/AldoJF500k_000000.dat") as dat_file:
|
||||||
|
assert dat_file.total_frames == 24
|
||||||
|
|
||||||
|
with JungfrauDataFile(test_data_path / "dat/AldoJF250k_000000.dat") as dat_file:
|
||||||
|
assert dat_file.total_frames == 53
|
||||||
|
|
||||||
|
with JungfrauDataFile(test_data_path / "dat/AldoJF65k_000000.dat") as dat_file:
|
||||||
|
assert dat_file.total_frames == 113
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.files
|
||||||
|
def test_jfungfrau_dat_read_number_of_file(test_data_path):
|
||||||
|
with JungfrauDataFile(test_data_path / "dat/AldoJF500k_000000.dat") as dat_file:
|
||||||
|
assert dat_file.n_files == 4
|
||||||
|
|
||||||
|
with JungfrauDataFile(test_data_path / "dat/AldoJF250k_000000.dat") as dat_file:
|
||||||
|
assert dat_file.n_files == 7
|
||||||
|
|
||||||
|
with JungfrauDataFile(test_data_path / "dat/AldoJF65k_000000.dat") as dat_file:
|
||||||
|
assert dat_file.n_files == 7
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.files
|
||||||
|
def test_read_module(test_data_path):
|
||||||
|
"""
|
||||||
|
Read all frames from the series of .dat files. Compare to canned data in npz format.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Read all frames from the .dat file
|
||||||
|
with JungfrauDataFile(test_data_path / "dat/AldoJF500k_000000.dat") as f:
|
||||||
|
header, data = f.read()
|
||||||
|
|
||||||
|
#Sanity check
|
||||||
|
n_frames = 24
|
||||||
|
assert header.size == n_frames
|
||||||
|
assert data.shape == (n_frames, 512, 1024)
|
||||||
|
|
||||||
|
# Read reference data using numpy
|
||||||
|
with np.load(test_data_path / "dat/AldoJF500k.npz") as f:
|
||||||
|
ref_header = f["headers"]
|
||||||
|
ref_data = f["frames"]
|
||||||
|
|
||||||
|
# Check that the data is the same
|
||||||
|
assert np.all(ref_header == header)
|
||||||
|
assert np.all(ref_data == data)
|
||||||
|
|
||||||
|
@pytest.mark.files
|
||||||
|
def test_read_half_module(test_data_path):
|
||||||
|
|
||||||
|
# Read all frames from the .dat file
|
||||||
|
with JungfrauDataFile(test_data_path / "dat/AldoJF250k_000000.dat") as f:
|
||||||
|
header, data = f.read()
|
||||||
|
|
||||||
|
n_frames = 53
|
||||||
|
assert header.size == n_frames
|
||||||
|
assert data.shape == (n_frames, 256, 1024)
|
||||||
|
|
||||||
|
# Read reference data using numpy
|
||||||
|
with np.load(test_data_path / "dat/AldoJF250k.npz") as f:
|
||||||
|
ref_header = f["headers"]
|
||||||
|
ref_data = f["frames"]
|
||||||
|
|
||||||
|
# Check that the data is the same
|
||||||
|
assert np.all(ref_header == header)
|
||||||
|
assert np.all(ref_data == data)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.files
|
||||||
|
def test_read_single_chip(test_data_path):
|
||||||
|
|
||||||
|
# Read all frames from the .dat file
|
||||||
|
with JungfrauDataFile(test_data_path / "dat/AldoJF65k_000000.dat") as f:
|
||||||
|
header, data = f.read()
|
||||||
|
|
||||||
|
n_frames = 113
|
||||||
|
assert header.size == n_frames
|
||||||
|
assert data.shape == (n_frames, 256, 256)
|
||||||
|
|
||||||
|
# Read reference data using numpy
|
||||||
|
with np.load(test_data_path / "dat/AldoJF65k.npz") as f:
|
||||||
|
ref_header = f["headers"]
|
||||||
|
ref_data = f["frames"]
|
||||||
|
|
||||||
|
# Check that the data is the same
|
||||||
|
assert np.all(ref_header == header)
|
||||||
|
assert np.all(ref_data == data)
|
Reference in New Issue
Block a user