diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..3539207 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,54 @@ +cmake_minimum_required(VERSION 3.15) + +project( + jungfraucalibration + DESCRIPTION "helper functions for Jungfrau calibration software" + HOMEPAGE_URL "https://gitea.psi.ch/detectors/JFCalibration2" + LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +include(FetchContent) + +option(JF_FETCH_AARE "Fetch aare library from github" ON) + +if(JF_FETCH_AARE) + FetchContent_Declare(aare GIT_REPOSITORY https://github.com/slsdetectorgroup/aare + GIT_TAG dev/jungfraucalibration) #use newest version - change to main once merged + FetchContent_MakeAvailable(aare) + install( + TARGETS aare_core + EXPORT ${TARGETS_EXPORT_NAME} + ) + install(TARGETS aare_compiler_flags + EXPORT ${TARGETS_EXPORT_NAME} + ) #mmh is this the way to go I think I will define the same compiler options twice now? can directly use aare_compiler_flags instead of compiler_flags + message(STATUS "target: aare") +else() + #set(AARE_INSTALL_PATH "/usr/local/aare" CACHE PATH "Installation directory for AARE") + list(APPEND CMAKE_PREFIX_PATH ${AARE_INSTALL_PATH}) + message(STATUS "looking for aare in: ${CMAKE_PREFIX_PATH}") + find_package(aare REQUIRED) + + if(TARGET aare_core) + message(STATUS "found aare_core target") + else() + message(FATAL_ERROR "aare_core target was not found!") + endif() + #message(STATUS "found aare: ${AARE_INCLUDE_DIRS}") +endif() + +set(SourceFiles + ${CMAKE_CURRENT_SOURCE_DIR}/src/BadChannels.cpp) + +add_library(jungfraucalibration STATIC ${SourceFiles} ${PUBLICHEADERS}) +target_include_directories( + jungfraucalibration PUBLIC "$") + +target_link_libraries(jungfraucalibration PRIVATE aare_core) # TODO: should it be public? + +# TODO: add other compile options? +target_compile_features(jungfraucalibration PRIVATE cxx_std_17) + diff --git a/include/BadChannels.hpp b/include/BadChannels.hpp new file mode 100644 index 0000000..3e0f5f3 --- /dev/null +++ b/include/BadChannels.hpp @@ -0,0 +1,14 @@ + +#include "aare/JungfrauDataFile.hpp" +#include "aare/utils/SparseMask.hpp" + +using namespace aare; + +namespace jungfraucalibration +{ + + SparseMask CreateBadChannelPixelMask(JungfrauDataFile &pedestal_file, const size_t num_pedestals_g0, const size_t num_pedestals_g1, const size_t num_pedestals_g2); + + SparseMask CreateBadChannelPixelMask(JungfrauDataFile &pedestals_g0_file, const JungfrauDataFile &pedestals_g1_file, const JungfrauDataFile &pedestal_g2_file); + +} // namespace jungfraucalibration \ No newline at end of file diff --git a/jfcal/helpers.py b/jfcal/helpers.py index 6173614..1c2865e 100644 --- a/jfcal/helpers.py +++ b/jfcal/helpers.py @@ -47,4 +47,11 @@ def get_fname(path, gain, label = 'CuFluo', index = -1): path = Path(path) file_sets = [fname for fname in path.glob(f'{label}{gain}*000000.dat')] file_sets.sort(key = lambda f: str(f).rsplit('_',2)[1]) - return file_sets[index] # \ No newline at end of file + return file_sets[index] # + + +def get_fname(path : Path, file_prefix : str): + first_file = min(path.glob(str(Path)+f'{file_prefix}_*', default=None)) + if first_file is None: + raise ValueError(f"No files found in {path} with prefix {file_prefix}") + return first_file diff --git a/python/jungfrau.config b/python/jungfrau.config new file mode 100644 index 0000000..e69de29 diff --git a/python/src/JungfrauCalibration.py b/python/src/JungfrauCalibration.py new file mode 100644 index 0000000..f24d28d --- /dev/null +++ b/python/src/JungfrauCalibration.py @@ -0,0 +1,124 @@ +from pathlib import Path +from aare import JungfrauDataFile +from helpers import get_first_file + +class JungfrauCalibrationParameters: + """ + A class to hold calibration parameters for the Jungfrau detector. + """ + + @property + def pedestal_file_dir(self) -> Path: + return self._pedestal_file_dir + + @pedestal_file_dir.setter + def pedestal_file_dir(self, filepath : Path): + if not filepath.exists(): + raise ValueError(f"Pedestal file directory {filepath} does not exist.") + self._pedestal_file_dir = filepath + + @property + def pedestal_g0_file_prefix(self) -> str: + return self._pedestal_g0_file_prefix + + @pedestal_g0_file_prefix.setter + def pedestal_g0_file_prefix(self, file_prefix : str): + self._pedestal_g0_file_prefix = file_prefix + + # TODO: add deprecated decorator + @property + def pedestal_file_prefix(self) -> str: + return self._pedestal_file_prefix + @pedestal_file_prefix.setter + def pedestal_file_prefix(self, file_prefix : str): + self._pedestal_file_prefix = file_prefix + + @property + def num_pedestals_g0(self) -> int: + return self._num_pedestals_g0 + + @num_pedestals_g0.setter + def num_pedestals_g0(self, num_pedestals : int): + self._num_pedestals_g0 = num_pedestals + + @property + def num_pedestals_g1(self) -> int: + return self._num_pedestals_g1 + + @num_pedestals_g1.setter + def num_pedestals_g1(self, num_pedestals : int): + self._num_pedestals_g1 = num_pedestals + + @property + def num_pedestals_g2(self) -> int: + return self._num_pedestals_g2 + + @num_pedestals_g2.setter + def num_pedestals_g2(self, num_pedestals : int): + self._num_pedestals_g2 = num_pedestals + + @property + def raw_file_dir(self) -> Path: + return self._raw_file_dir + + @raw_file_dir.setter + def raw_file_dir(self, filepath : Path): + if not filepath.exists(): + raise ValueError(f"Raw file directory {filepath} does not exist.") + self._raw_file_dir = filepath + + @property + def raw_file_prefix(self) -> str: + return self._raw_file_prefix + + @raw_file_prefix.setter + def raw_file_prefix(self, file_prefix : str): + self._raw_file_prefix = file_prefix + + + # TODO add a read_config method + + +def calculate_bad_pixels_mask(calibration_params: JungfrauCalibrationParameters): + """ + Calculate the bad pixels mask for the Jungfrau detector. + + Returns: + np.ndarray: A boolean array where True indicates a bad pixel. + """ + + if(calibration_params.num_pedestals_g0 is not None and calibration_params.num_pedestals_g1 is not None and calibration_params.num_pedestals_g2 is not None): + + jungfrau_file = JungfrauDataFile(get_first_file(calibration_params.pedestal_file_dir, calibration_params.pedestal_file_prefix)) + + g0_pedestal_frames = jungfrau_file.read_n(calibration_params.num_pedestals_g0) # TODO: option to only read gain? - mmh reading things twice from filesystem also bad + + g1_pedestal_frames = jungfrau_file.read_n(calibration_params.num_pedestals_g1) + + g2_pedestal_frames = jungfrau_file.read_n(calibration_params.num_pedestals_g2) + + + + # Placeholder for actual implementation + # This function should analyze the detector data and identify bad pixels + pass + + +def main(): + calibration_params = JungfrauCalibrationParameters() + + calibration_params.pedestal_file_dir = Path("/mnt/sls_det_storage/jungfrau_calib/data/Module_708_Calib") + + calibration_params.pedestal_file_prefix = "pedeG0_M708_2025-12-09_" + + calibration_params.num_pedestals_g0 = 1000 + calibration_params.num_pedestals_g1 = 1000 + calibration_params.num_pedestals_g2 = 1000 + + + + + + + + diff --git a/python/src/helpers.py b/python/src/helpers.py new file mode 100644 index 0000000..4326c1f --- /dev/null +++ b/python/src/helpers.py @@ -0,0 +1,11 @@ +import glob +from pathlib import Path + +def get_first_file(path : Path, file_prefix : str): + """ + Get the first file with lowest index in directory that matches the given prefix. + """ + first_file = min(path.glob(str(Path)+f'{file_prefix}*', default=None)) + if first_file is None: + raise ValueError(f"No files found in {path} with prefix {file_prefix}") + return first_file \ No newline at end of file diff --git a/src/BadChannels.cpp b/src/BadChannels.cpp new file mode 100644 index 0000000..a2daac7 --- /dev/null +++ b/src/BadChannels.cpp @@ -0,0 +1,52 @@ + +#include "BadChannels.hpp" +#include "aare/calibration.hpp" + +using namespace aare; + +namespace jungfraucalibration +{ + + SparseMask CreateBadChannelPixelMask(JungfrauDataFile &pedestal_file, const size_t num_pedestals_g0, const size_t num_pedestals_g1, const size_t num_pedestals_g2) + { + auto pedestals_g0 = pedestal_file.read_n(num_pedestals_g0); + auto pedestals_g1 = pedestal_file.read_n(num_pedestals_g1); + auto pedestals_g2 = pedestal_file.read_n(num_pedestals_g2); + + const size_t rows = pedestal_file.rows(); + const size_t cols = pedestal_file.cols(); + + // get gain from each pixel - update mask + SparseMask bad_channel_mask(STORAGEFORMAT::ROWMAJOR, rows, cols); // bad channels pixel mask + + size_t max_frames = std::max({num_pedestals_g0, num_pedestals_g1, num_pedestals_g2}); + + // TODO is this more efficient e.g. all three frames fit into cache instead of doing one file at the time? + for (size_t frame_idx = 0; frame_idx < max_frames; ++frame_idx) + { + + for (size_t row = 0; row < rows; ++row) + { + for (size_t col = 0; col < cols; ++col) + { + // TODO: nicer to access element from frame directly instead of view()? What is the type? + if (frame_idx < num_pedestals_g0 && get_gain(pedestals_g0[frame_idx].view()(row, col)) != 0) + { + bad_channel_mask.insert(row, col); + } + if (frame_idx < num_pedestals_g1 && get_gain(pedestals_g1[frame_idx].view()(row, col)) != 1) + { + bad_channel_mask.insert(row, col); + } + if (frame_idx < num_pedestals_g2 && get_gain(pedestals_g2[frame_idx].view()(row, col)) != 2) + { + bad_channel_mask.insert(row, col); + } + } + } + } + + return bad_channel_mask; + } + +} // namespace jungfraucalibration