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
45 changes: 3 additions & 42 deletions .github/workflows/cmake-workflow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ on:
workflow_dispatch:
inputs:
target:
description: "Optional build target or instrument name (leave blank for default build)"
description: "Optional build target, such as emulator (leave blank for default build)"
required: false
default: ""
type: string
Expand Down Expand Up @@ -52,35 +52,6 @@ jobs:
sudo cmake --install /tmp/zmqpp/build
sudo ldconfig

- name: Rewrite GitHub SSH URLs for submodules
shell: bash
run: |
git config --global url."https://github.com/".insteadOf "[email protected]:"
git submodule sync --recursive

- name: Update matching submodule
if: env.TARGET_NAME != '' && env.TARGET_NAME != 'emulator'
shell: bash
run: |
if [[ ! -f .gitmodules ]]; then
echo "No .gitmodules file found; skipping submodule update."
exit 0
fi

MATCH_PATH=$(git config -f .gitmodules --get-regexp '^submodule\..*\.path$' | awk -v target="$TARGET_NAME" '
$2 == target { print $2; exit }
$2 ~ "/" target "$" { print $2; exit }
')

if [[ -z "$MATCH_PATH" ]]; then
echo "No submodule path matched '$TARGET_NAME'; continuing without submodule update."
exit 0
fi

echo "Matched submodule path: $MATCH_PATH"
git submodule sync -- "$MATCH_PATH"
git submodule update --init --recursive --remote -- "$MATCH_PATH"

- name: Configure CMake
shell: bash
run: |
Expand All @@ -93,10 +64,6 @@ jobs:
-DCONTROLLER=archon
)

if [[ -n "$TARGET_NAME" && "$TARGET_NAME" != "emulator" ]]; then
CMAKE_ARGS+=("-DINSTRUMENT=${TARGET_NAME}")
fi

cmake "${CMAKE_ARGS[@]}" ..

- name: Build default
Expand All @@ -110,17 +77,11 @@ jobs:
echo "run_unit_tests target not available; skipping unit tests."
fi

- name: Build emulator target
if: env.TARGET_NAME == 'emulator'
shell: bash
run: |
cmake --build build --target emulator -j"$(nproc)"

- name: Build named target
if: env.TARGET_NAME != '' && env.TARGET_NAME != 'emulator'
if: env.TARGET_NAME != ''
shell: bash
run: |
cmake --build build -j"$(nproc)"
cmake --build build --target "$TARGET_NAME" -j"$(nproc)"

cmake-3-24-subproject:
runs-on: ubuntu-latest
Expand Down
12 changes: 0 additions & 12 deletions .gitmodules

This file was deleted.

12 changes: 0 additions & 12 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -99,18 +99,6 @@ endif()
option(CAMERAD_BUILD_TOOLS "Build the emulator, listener, socksend and shm_reader" ${PROJECT_IS_TOP_LEVEL})
option(CAMERAD_INSTALL "Generate install rules" ${PROJECT_IS_TOP_LEVEL})

# This lets an instrument package share an environment with the generic package
# and other instruments. The module installs into a Python package of this name,
# and camerad and the emulator are named for it. The name is supplied by
# whoever builds the instrument package.
#
# This is temporary and I'll remove it once each instrument's
# repository declares its own module and writes its own install rules
set(CAMERAD_PYTHON_PACKAGE "" CACHE STRING "Python package an instrument package installs its module into")
if (CAMERAD_PYTHON_PACKAGE)
string(REPLACE "_" "-" CAMERAD_PACKAGE_SUFFIX ${CAMERAD_PYTHON_PACKAGE})
endif()

add_subdirectory(${PROJECT_BASE_DIR}/utils)
add_subdirectory(${PROJECT_BASE_DIR}/common)
add_subdirectory(${PROJECT_BASE_DIR}/camerad)
Expand Down
42 changes: 8 additions & 34 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,7 @@ If you encounter any problems or have questions about this project, please open
|-----------------------------------|--------------------------------------|
| `$ cmake -DCONTROLLER=archon ..` | `$ cmake -DCONTROLLER=astrocam ..` |

Add `-DINSTRUMENT=` to build an instrument module, whose sources come from `camerad/Instruments/<name>`:

```bash
$ cmake -DCONTROLLER=archon -DINSTRUMENT=hispec_tracking_camera ..
```
This builds the core with no instrument. An instrument builds from its own repository, which pulls in this one; see [Composing the core](#composing-the-core).

`-DINTERFACE_TYPE=` is separate from `-DCONTROLLER=` and selects only which emulator is built. It defaults to `Archon`; `-DINTERFACE_TYPE=AstroCam` skips the emulator, since none is implemented for ARC.

Expand Down Expand Up @@ -205,51 +201,29 @@ An instrument repository that installs its module puts it inside a Python packag
`pip install` builds the same artifacts and places them in the target environment, so `import camera_interface` needs no `PYTHONPATH` and `camerad` is on `PATH` whenever the environment is active:

```bash
$ pip install ./camera-interface \
--config-settings=cmake.define.INSTRUMENT=hispec_tracking_camera
$ pip install ./camera-interface
```

Any CMake option can be passed the same way, so `--config-settings=cmake.define.ENABLE_SHM_OUTPUT=ON` works as well. `CONTROLLER` defaults to `archon` and `BUILD_PYTHON_MODULE` is forced on.

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, so a caller should assert which build it loaded:
The controller is fixed when the wheel is built, so a caller should assert which build it loaded:

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

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

### Instrument packages

An instrument package can share an environment with the generic package and with other instruments. Its build sets `CAMERAD_PYTHON_PACKAGE` to the package's name, e.g. in a `pyproject.toml` in the instrument's own repository that builds the core from its checkout:

```toml
[tool.scikit-build]
cmake.source-dir = "external/camera-interface"
wheel.packages = []
cmake.define.BUILD_PYTHON_MODULE = "ON"
cmake.define.CONTROLLER = "archon"
cmake.define.INSTRUMENT = "myinst"
cmake.define.CAMERAD_PYTHON_PACKAGE = "myinst_camera"
```

That package installs:

- `myinst_camera/camera_interface…so` and `myinst_camera/__init__.py`, imported as `import myinst_camera` or `import myinst_camera.camera_interface`;
- `camerad-myinst-camera` and `camerad-emulator-myinst-camera`, named for the package with `_` replaced by `-`.

It does not install `camerad-socksend` or `camerad-shm-reader`, which come with the generic package. The generic package is unchanged: `import camera_interface`, plus `camerad`, `camerad-emulator` and `camerad-socksend`. Uninstalling one package leaves the others' files in place.

## 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:

```python
import camera_interface

camera = camera_interface.Camera("hispecatc.cfg")
camera = camera_interface.Camera("reference.cfg")
camera.open()
camera.load()
camera.power("on")
Expand All @@ -265,7 +239,7 @@ 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`.

Every build's module is named `camera_interface`. The generic build installs it at the top level, and an instrument package installs it inside a package of its own ([Instrument packages](#instrument-packages)), so several instruments can share one environment. Importing several instruments into one process is intended to be supported but is not yet tested; for now, use one instrument per process.
Every build's module is named `camera_interface`. The generic build installs it at the top level, and an instrument repository installs it inside a package of its own ([Composing the core](#composing-the-core)), so several instruments can share one environment. Importing several instruments into one process is intended to be supported but is not yet tested; for now, use one instrument per process.

`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.

Expand All @@ -281,7 +255,7 @@ $ python python/examples/raw_channel_sweep.py --config lris2.cfg --fits-dir /tmp

## Frame Outputs

Every instrument publishes each acquired frame to one or more outputs, configured entirely via `.cfg` file keys (`Camera::Interface::configure_frame_outputs()` builds them from `Camera::apply_config_overrides()`, called once at startup for every instrument, not just HISPEC). Both outputs are independent; either, both, or neither can be enabled per instrument.
Every instrument publishes each acquired frame to one or more outputs, configured entirely via `.cfg` file keys (`Camera::Interface::configure_frame_outputs()` builds them from `Camera::apply_config_overrides()`, called once at startup for every instrument). Both outputs are independent; either, both, or neither can be enabled per instrument.

### Streams

Expand Down Expand Up @@ -316,7 +290,7 @@ Frame geometry (width/height/pixel depth) isn't a config key: it's fixed for an
Two readers ship with the repo. `camerad-shm-reader` prints geometry, keywords and pixel statistics once, for diagnostics. `python/examples/shm_read_frames.py` is a sample streaming consumer: it blocks on the stream's semaphore and reports every frame as it arrives, flagging any it missed.

```bash
$ python python/examples/shm_read_frames.py --segment hispec_tracking_camera --count 10
$ python python/examples/shm_read_frames.py --segment camera --count 10
```

It needs numpy and `ImageStreamIOWrap`, the Python wrapper from the ImageStreamIO source tree, built with `-DPYTHON_WRAPPER=ON`; the wrapper is not on PyPI.
Expand Down
55 changes: 6 additions & 49 deletions camerad/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ set( CAMERAD_DIR ${PROJECT_BASE_DIR}/camerad )
set( CMAKE_CXX_STANDARD 17 )

include_directories(${CAMERAD_DIR}
${CAMERAD_DIR}/Instruments
${PROJECT_BASE_DIR}/utils
${PROJECT_BASE_DIR}/common
)
Expand Down Expand Up @@ -103,34 +102,10 @@ else()
endif()

# ----------------------------------------------------------------------------
# default instrument is none
# the controller's own interface factory, compiled into this project's final
# targets. An instrument builds from its own repository and supplies its own
# ----------------------------------------------------------------------------
option(INSTRUMENT "Choose instrument { generic ... }")
if (INSTRUMENT)
message(STATUS "instrument: ${INSTRUMENT}")
include(Instruments/${INSTRUMENT}/${INSTRUMENT}.cmake)
else()
message(STATUS "instrument: none")
set (INSTRUMENT_SOURCES "")
# If no instrument is defined then include the factory for the specified controller
if (CONTROLLER STREQUAL "archon")
set (INSTRUMENT_SOURCES ${CAMERAD_DIR}/archon_interface_factory.cpp)
elseif (CONTROLLER STREQUAL "astrocam")
set (INSTRUMENT_SOURCES ${CAMERAD_DIR}/astrocam_interface_factory.cpp)
elseif (CONTROLLER STREQUAL "bob")
set (INSTRUMENT_SOURCES ${CAMERAD_DIR}/bob_interface_factory.cpp)
elseif (CONTROLLER STREQUAL "joe")
set (INSTRUMENT_SOURCES ${CAMERAD_DIR}/joe_interface_factory.cpp)
endif()
endif()

# ----------------------------------------------------------------------------
# Separate the registration TU from the instrument implementation so that
# the registration is compiled into the final target.
# ----------------------------------------------------------------------------
set (INSTRUMENT_REGISTRATION ${INSTRUMENT_SOURCES})
list (FILTER INSTRUMENT_REGISTRATION INCLUDE REGEX "_interface_factory\\.cpp$")
list (FILTER INSTRUMENT_SOURCES EXCLUDE REGEX "_interface_factory\\.cpp$")
set (INTERFACE_FACTORY ${CAMERAD_DIR}/${CONTROLLER}_interface_factory.cpp)

# ----------------------------------------------------------------------------
# Camera Interface Base
Expand Down Expand Up @@ -170,18 +145,6 @@ target_include_directories(${INTERFACE_TARGET} PUBLIC ${INTERFACE_INCLUDES})
target_compile_definitions(${INTERFACE_TARGET} INTERFACE ${INTERFACE_DEFINITIONS})
add_library(camerad::${INTERFACE_TARGET} ALIAS ${INTERFACE_TARGET})

# ----------------------------------------------------------------------------
# instrument implementation, if any.
# objects shared by the final targets, not depended on as a library
# ----------------------------------------------------------------------------
if (INSTRUMENT_SOURCES)
add_library(instrument_obj OBJECT ${INSTRUMENT_SOURCES})
target_link_libraries(instrument_obj ${INTERFACE_TARGET})
set (INSTRUMENT_OBJECTS $<TARGET_OBJECTS:instrument_obj>)
else()
set (INSTRUMENT_OBJECTS "")
endif()

# ----------------------------------------------------------------------------
# External libraries, such as FITS, etc.
# ----------------------------------------------------------------------------
Expand Down Expand Up @@ -237,24 +200,18 @@ endif()
# composes this one declares its own, linking the libraries above
# ----------------------------------------------------------------------------
if (PROJECT_IS_TOP_LEVEL)
add_executable(camerad
${INSTRUMENT_REGISTRATION}
${INSTRUMENT_OBJECTS}
)
add_executable(camerad ${INTERFACE_FACTORY})
target_link_libraries(camerad
camerad_daemon
${INTERFACE_TARGET}
)
if (CAMERAD_PYTHON_PACKAGE)
set_target_properties(camerad PROPERTIES OUTPUT_NAME camerad-${CAMERAD_PACKAGE_SUFFIX})
endif()

if (CAMERAD_INSTALL)
install(TARGETS camerad RUNTIME DESTINATION ${CAMERAD_INSTALL_BINDIR})
endif()

# Added here, not at the top level, to inherit the controller and instrument
# selection above. The option itself is declared at the top level
# Added here, not at the top level, to inherit the controller selection
# above. The option itself is declared at the top level
if (BUILD_PYTHON_MODULE)
add_subdirectory(${PROJECT_BASE_DIR}/python ${CMAKE_BINARY_DIR}/python)
endif()
Expand Down
1 change: 0 additions & 1 deletion camerad/Instruments/cryoscope
Submodule cryoscope deleted from 35dd0e
1 change: 0 additions & 1 deletion camerad/Instruments/deimos
Submodule deimos deleted from d39ee9
1 change: 0 additions & 1 deletion camerad/Instruments/hispec
Submodule hispec deleted from 1be486
1 change: 0 additions & 1 deletion camerad/Instruments/hispec_tracking_camera
Submodule hispec_tracking_camera deleted from 60382f
Loading
Loading