Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,14 +160,26 @@ Any CMake option can be passed the same way, so `--config-settings=cmake.define.

pybind11 comes from `[build-system] requires`, so pip fetches it into an isolated build environment. It is never installed into the environment being built for.

The controller and instrument are fixed when the wheel is built, and the module is always named `camera_interface`, so one environment holds one instrument's build. Install into a separate environment per instrument and have the caller assert which one it loaded:
The controller and instrument are fixed when the wheel is built, so a caller should assert which build it loaded:

```python
assert camera_interface.instrument_name() == "hispec_tracking_camera"
```

A compiler and the full dependency set have to be present wherever `pip install` runs, since it compiles camerad and the module from source.

### Per-instrument packages

Installing this way twice replaces the first build, since both wheels are called `camera-interface` and both modules `camera_interface`. `packaging/` holds one directory per instrument that needs its own package, each fixing the instrument and the module name so neither is the caller's to pass:

```bash
$ pip install ./camera-interface/packaging/tracking
```

That installs `camera-interface-tracking`, providing the module `camera_interface_tracking`. Such packages are independent of each other and of the plain `camera-interface` above, so any combination can share one environment.

To add one, copy a `packaging/*/pyproject.toml` and change `name`, `INSTRUMENT` and `CAMERAD_MODULE_NAME`.

## Python Module

Built with `-DBUILD_PYTHON_MODULE=ON`, `camera_interface` lets a Python process own a camera directly, with no `camerad` process and no text protocol in between. It performs the same startup `camerad` does, then exposes the interface as methods:
Expand All @@ -191,6 +203,15 @@ Every command `camerad` accepts is reachable: the base commands are bound as met

The controller and instrument are fixed at CMake configure time, so `instrument_name()` and `controller_name()` report which build was loaded. A failed command raises `RuntimeError`.

`-DCAMERAD_MODULE_NAME=` renames the module and its file together, so per-instrument builds can be imported side by side. [Per-instrument packages](#per-instrument-packages) is the packaged form of the same thing:

```bash
$ cmake -DBUILD_PYTHON_MODULE=ON -DINSTRUMENT=hispec_tracking_camera \
-DCAMERAD_MODULE_NAME=camera_interface_tracking ..
```

It defaults to `camera_interface`, so a build that does not set it is unaffected.

`output_status()` is a snapshot, never a barrier: the FITS writer queues and drops frames by design because disk is slower than acquisition can be, so nothing here lets a caller stall acquisition by waiting on an output. Anything needing to be woken per frame should attach to the shared-memory segment, which posts semaphores.

Commands release the GIL while they run, so a blocking `expose()` leaves the rest of the process responsive.
Expand Down
19 changes: 19 additions & 0 deletions packaging/tracking/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[build-system]
requires = ["scikit-build-core>=0.10", "pybind11>=2.12"]
build-backend = "scikit_build_core.build"

[project]
name = "camera-interface-tracking"
version = "2.0.0"
description = "camera_interface built for the HISPEC tracking camera"
requires-python = ">=3.10"

[tool.scikit-build]
cmake.version = ">=3.15"
cmake.source-dir = "../.."
wheel.packages = []

cmake.define.BUILD_PYTHON_MODULE = "ON"
cmake.define.CONTROLLER = "archon"
cmake.define.INSTRUMENT = "hispec_tracking_camera"
cmake.define.CAMERAD_MODULE_NAME = "camera_interface_tracking"
7 changes: 7 additions & 0 deletions python/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,15 @@ target_include_directories(camera_interface PRIVATE
${INTERFACE_INCLUDES}
)

set(CAMERAD_MODULE_NAME "camera_interface" CACHE STRING
"Import name of the Python module")

# Filename and import name must agree, so both come from CAMERAD_MODULE_NAME
set_target_properties(camera_interface PROPERTIES OUTPUT_NAME ${CAMERAD_MODULE_NAME})

target_compile_definitions(camera_interface PRIVATE
CAMERAD_INSTRUMENT_NAME="${INSTRUMENT}"
CAMERAD_MODULE_NAME=${CAMERAD_MODULE_NAME}
)

# Same link set as camerad, minus the server sources it does not use
Expand Down
10 changes: 8 additions & 2 deletions python/camera_interface_module.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -118,15 +118,21 @@ namespace {

}

PYBIND11_MODULE(camera_interface, module) {
#ifndef CAMERAD_MODULE_NAME
#define CAMERAD_MODULE_NAME camera_interface
#endif

PYBIND11_MODULE(CAMERAD_MODULE_NAME, module) {
module.doc() = "Direct control of a camera-interface camera, without camerad";

module.def("instrument_name", [] { return std::string(INSTRUMENT_NAME); },
"Return the instrument this module was built for");
module.def("controller_name", [] { return std::string(CONTROLLER_NAME); },
"Return the controller this module was built for");

py::class_<CameraSession>(module, "Camera",
// module_local keeps this out of pybind11's process-wide type registry, so
// two per-instrument builds can be imported together
py::class_<CameraSession>(module, "Camera", py::module_local(),
"One camera, configured from a camerad .cfg file.\n\n"
"Construction performs the same setup camerad does at startup: read the\n"
"config, initialize logging, then configure the controller, interface,\n"
Expand Down
Loading