From 5690a6128487dea2303ed5e7476727ca0cf6a39b Mon Sep 17 00:00:00 2001 From: Bechir Date: Thu, 7 Mar 2024 03:09:43 +0100 Subject: [PATCH] add python bindings --- CMakeLists.txt | 49 +++++++++++------------------- examples/CMakeLists.txt | 1 - python/CMakeLists.txt | 1 + python/aare/File.py | 57 +++++++++++++++++++++++++++++++++++ python/aare/Frame.py | 16 ++++++++++ python/aare/__init__.py | 3 ++ python/example/__init__.py | 0 python/example/read_frame.py | 7 +++++ python/src/bindings.cpp | 41 +++++++++++++++++++++++++ src/file_io/file/JsonFile.cpp | 18 +++-------- 10 files changed, 147 insertions(+), 46 deletions(-) create mode 100644 python/CMakeLists.txt create mode 100644 python/aare/File.py create mode 100644 python/aare/Frame.py create mode 100644 python/aare/__init__.py create mode 100644 python/example/__init__.py create mode 100644 python/example/read_frame.py create mode 100644 python/src/bindings.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 1632e8e..2b769b9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -35,6 +35,7 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON) option(USE_SANITIZER "Sanitizers for debugging" ON) option(DISABLE_WARNINGS "Disbale compilation warnings" OFF) +option(USE_PYTHON "Build python bindings" ON) set(OPTIONAL_FLAGS "") @@ -46,49 +47,33 @@ if(USE_SANITIZER) set(OPTIONAL_FLAGS "${OPTIONAL_FLAGS} -fdiagnostics-parseable-fixits -fdiagnostics-generate-patch -fdiagnostics-show-template-tree -fsanitize=address,undefined,pointer-compare -fno-sanitize-recover -D_FORTIFY_SOURCE=2 -fstack-protector -fno-omit-frame-pointer ") endif() -# if(TUNE_LOCAL) -# if(UNIX AND NOT APPLE) -# message(STATUS "unix") -# set(ARCH_FLAGS ) -# target_compile_options(project_options INTERFACE -mtune=native -march=native ) -# elseif(APPLE) -# message(STATUS "compiling for apple") -# target_compile_options(project_options INTERFACE -mtune=apple-m1 -mcpu=apple-m1 ) -# endif() -# # -# endif() - -include(CheckIPOSupported) -check_ipo_supported(RESULT LTO_AVAILABLE) -if((CMAKE_BUILD_TYPE STREQUAL "Release") AND LTO_AVAILABLE) - message(STATUS "Building with link time optimization") -else() - message(STATUS "Building without link time optimization") -endif() - set(SUPPRESSED_WARNINGS "-Wno-return-type") -set(CMAKE_CXX_FLAGS "${OPTIONAL_FLAGS} ${OPTIMIZATION_FLAGS} ${SUPPRESSED_WARNINGS}") +set(CMAKE_CXX_FLAGS "${OPTIMIZATION_FLAGS} ${SUPPRESSED_WARNINGS}") + +if(USE_PYTHON) +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC") +endif(USE_PYTHON) + + + include_directories(include) add_subdirectory(src) - - - add_library(aare INTERFACE) - -# target_link_libraries( aare -# PRIVATE -# nlohmann_json::nlohmann_json -# PUBLIC -# fmt::fmt -# ) - target_link_libraries(aare INTERFACE common core file_io) add_subdirectory(examples) +target_link_libraries(example PUBLIC aare) + +if(USE_PYTHON) + find_package (Python 3.11 COMPONENTS Interpreter Development) + find_package(pybind11 2.11 REQUIRED) + add_subdirectory(python) + target_link_libraries(_aare PRIVATE aare nlohmann_json::nlohmann_json fmt::fmt) +endif() \ No newline at end of file diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 6088677..b2954b2 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -2,6 +2,5 @@ add_executable(example "${CMAKE_CURRENT_SOURCE_DIR}/main.cpp") target_include_directories(example PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}") -target_link_libraries(example PUBLIC aare) diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt new file mode 100644 index 0000000..3327273 --- /dev/null +++ b/python/CMakeLists.txt @@ -0,0 +1 @@ +pybind11_add_module(_aare src/bindings.cpp) diff --git a/python/aare/File.py b/python/aare/File.py new file mode 100644 index 0000000..da454e9 --- /dev/null +++ b/python/aare/File.py @@ -0,0 +1,57 @@ +import json +from typing import Any +import _aare +import os + + +class File: + """ + File class. uses proxy pattern to wrap around the pybinding class + abstracts the python binding class that is requires type and detector information + (e.g. _FileHandler_Jungfrau_16) + """ + def __init__(self, path): + """ + opens the master file and checks the dynamic range and detector + + """ + self.path = path + # check if file exists + if not os.path.exists(path): + raise FileNotFoundError(f"File not found: {path}") + ext = os.path.splitext(path)[1] + + if ext not in (".raw", ".json"): + raise ValueError(f"Invalid file extension: {ext}") + + if ext == ".json": + # read the master file and get the detector and bitdepth + master_data = json.load(open(path)) + detector = master_data["Detector Type"] + bitdepth = None + if 'Dynamic Range' not in master_data and detector == "Jungfrau": + bitdepth = 16 + else: + bitdepth = master_data["Dynamic Range"] + else: + NotImplementedError("Raw file not implemented yet") + + # class_name is of the form _FileHandler_Jungfrau_16... + class_name = f"_FileHandler_{detector}_{bitdepth}" + + # this line is the equivalent of: + # self._file = _FileHandler_Jungfrau_16(path) + self._file = getattr(_aare, class_name)(path) + + + def __getattribute__(self, __name: str) -> Any: + """ + Proxy pattern to call the methods of the _file + """ + return getattr(object.__getattribute__(self, "_file"), __name) + + + + + + \ No newline at end of file diff --git a/python/aare/Frame.py b/python/aare/Frame.py new file mode 100644 index 0000000..46e91ec --- /dev/null +++ b/python/aare/Frame.py @@ -0,0 +1,16 @@ +from typing import Any + + +class Frame: + """ + Frame class. uses proxy pattern to wrap around the pybinding class + the intention behind it is to only use one class for frames in python (not Frame_8, Frame_16, etc) + """ + def __init__(self, frameImpl): + self._frameImpl = frameImpl + def __getattribute__(self, __name: str) -> Any: + """ + Proxy pattern to call the methods of the frameImpl + """ + return getattr(object.__getattribute__(self, "_frameImpl"), __name) + \ No newline at end of file diff --git a/python/aare/__init__.py b/python/aare/__init__.py new file mode 100644 index 0000000..77273c6 --- /dev/null +++ b/python/aare/__init__.py @@ -0,0 +1,3 @@ +from _aare import * +from .Frame import Frame +from .File import File \ No newline at end of file diff --git a/python/example/__init__.py b/python/example/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/python/example/read_frame.py b/python/example/read_frame.py new file mode 100644 index 0000000..457a4ef --- /dev/null +++ b/python/example/read_frame.py @@ -0,0 +1,7 @@ +from aare import File, Frame + +if __name__ == "__main__": + file = File("/home/bb/github/aare/data/jungfrau_single_master_0.json") + frame = file.get_frame(0) + print(frame.rows, frame.cols) + print(frame.get(0,0)) \ No newline at end of file diff --git a/python/src/bindings.cpp b/python/src/bindings.cpp new file mode 100644 index 0000000..249323d --- /dev/null +++ b/python/src/bindings.cpp @@ -0,0 +1,41 @@ +#include +#include +#include +#include + +#include "common/defs.hpp" +#include "core/Frame.hpp" +#include "file_io/FileHandler.hpp" + +namespace py = pybind11; + + +PYBIND11_MODULE(_aare, m) { + // helps to convert from std::string to std::filesystem::path + py::class_(m, "Path") + .def(py::init()); + py::implicitly_convertible(); + + //TODO: find a solution to avoid code duplication and include other detectors + py::class_>(m, "_FileHandler_Jungfrau_16") + .def(py::init()) + .def("get_frame", &FileHandler::get_frame); + + + py::enum_(m, "DetectorType"); + + py::class_>(m, "_Frame16") + .def(py::init()) + .def("get", &Frame::get) + .def_readonly("rows", &Frame::rows) + .def_readonly("cols", &Frame::cols) + .def_readonly("bitdepth", &Frame::bitdepth); + + + + + + +} + + diff --git a/src/file_io/file/JsonFile.cpp b/src/file_io/file/JsonFile.cpp index b203387..7c0a31e 100644 --- a/src/file_io/file/JsonFile.cpp +++ b/src/file_io/file/JsonFile.cpp @@ -1,27 +1,19 @@ #include "file_io/JsonFile.hpp" #include -template -Frame* JsonFile::get_frame(int frame_number){ - int subfile_id=frame_number/this->max_frames_per_file; - std::byte* buffer; +template +Frame *JsonFile::get_frame(int frame_number) { + int subfile_id = frame_number / this->max_frames_per_file; + std::byte *buffer; size_t frame_size = this->subfiles[subfile_id]->bytes_per_frame(); buffer = new std::byte[frame_size]; - this->subfiles[subfile_id]->get_frame(buffer, frame_number%this->max_frames_per_file); - - + this->subfiles[subfile_id]->get_frame(buffer, frame_number % this->max_frames_per_file); auto f = new Frame(buffer, this->rows, this->cols); delete[] buffer; return f; - - - - - } template class JsonFile; -