Skip to content

Ros2 port - #18

Open
romleiaj wants to merge 52 commits into
mainfrom
ros2-port
Open

romleiaj wants to merge 52 commits into
mainfrom
ros2-port

Conversation

@romleiaj

Copy link
Copy Markdown
Collaborator

No description provided.

romleiaj and others added 30 commits September 23, 2026 09:30
Convert pyproject.toml to a standard [project] table with the hatchling
build backend, raising the supported Python floor to 3.10 (the version
the postproc image runs) and dropping the per-dependency version markers
that floor made necessary. ipdb moves to the dev dependency group.
Remove the now-redundant poetry.lock, setup.py, and requirements.txt.
Replace the pip editable install in kamerapy.dockerfile with the
standard uv Docker pattern: the uv binary is copied from the official
distroless image, dependencies are synced from the lockfile in a
cached layer before the source copy, and the project venv is put on
PATH. Add a .dockerignore so a local .venv can't leak into the build
context, and gitignore .venv.
The gui image installs kamera editable with --no-deps under ROS
Noetic's Python 3.8, so pip's metadata check would reject the new
requires-python >= 3.10. Bypass it with --ignore-requires-python;
the modules the GUI imports still run on 3.8.
…ively

The conda env (environment.yml) supplies python + GDAL + uv; `make install`
then builds .venv on top of it with uv venv --system-site-packages and
uv sync --frozen. The kamerapy docker image now uses micromamba and the same
make install as the native setup scripts, replacing the Linux-only GDAL
wheel extra, and setup_postproc.{sh,ps1} are thin wrappers around the same
steps for Linux/macOS and Windows.

Pin python-preference = "only-system" in [tool.uv]: uv's default otherwise
substitutes a managed standalone interpreter for the conda one, leaving the
conda GDAL invisible through --system-site-packages. Commit uv.lock, which
--frozen requires, and fix bookworm-slim apt deps (libgl1 + libglib2.0-0;
libgl1-mesa-glx no longer exists in bookworm).
Verified py310 builds exist on conda-forge for linux-64, win-64, and
osx-arm64. Provided via conda like GDAL, so the --system-site-packages venv
sees it with no pyproject/lock change.
…teps

The scripts were wrappers around three commands; the README now gives those
directly for Linux/macOS and Windows (which runs the two uv commands from
`make install`, since make usually isn't available there). Use explicit
`conda env create` rather than update-as-create: micromamba's `env update`
errors on a missing env.

Also note GPU pycolmap selection: CUDA builds need driver 575+ (CUDA 12.9),
older drivers silently fall back to CPU, and GPU only matters for full
camera model calibration.
pip install -e . into the conda env is the simplest Windows path: one env,
one activation, no uv.lock but pyproject floors keep it sane. Trim install
docs and packaging comments to the essentials.
Rewrite the calibration pipeline as the kamera.calibration package. One COLMAP
model holds all nine cameras: trigger-synchronized images form rig frames, INS
positions are pose priors, and a prior-anchored rig bundle adjustment refines
sensor_from_rig and intrinsics. Outputs per flight: camera model yamls in the
INS frame, rig.yaml with the INS boresight and lever arm, DIVE v2 registration
JSON and GIFs for every modality pair per channel, and a PDF report.

- pass 1 maps every camera independently with position priors; pass 2 puts
  the rig from pass 1 onto the largest model, adds the IR images to their
  frames, triangulates and bundle adjusts twice
- UV and IR frames are contrast-normalized; thermal-to-visible SIFT pairs are
  pruned; rig extrinsics are seeded from the densest cluster of per-frame
  estimates so a folded sub-model cannot bias them
- environment moves to Python 3.13 and the conda-forge CUDA pycolmap 4.2;
  make install recreates .venv
- remove the superseded per-camera calibration scripts
Cameras whose exposure midpoint does not coincide with the trigger sit an
effective metre or so along track in the rig model (bundle adjustment cannot
tell a delay from a lever arm on a translating rig), and a homography fit at
infinity drops that baseline. Fit each pair for a nominal ground range instead
(--registration_range_m, default the calibration flight's median scene range),
read each camera's forward offset back into an exposure delay in rig.yaml and
the report, and only write GIFs for frames the rig model registered.
Only the relative exposure midpoints are observable: the position priors absorb
any delay common to the rig. Rename the field and fix the report text.
Configure ruff in pyproject.toml (the classic E4/E7/E9/F rule set, pinned so
results do not depend on the ruff version), add it to the dev dependency group,
and run ruff format over the calibration package and camera_models.py. No code
changes beyond formatting.
- calibrate_rig builds the per-camera entries and counts frames in one pass,
  sorts the per-frame arrays once, and reads scene ranges through a named
  helper instead of a nested comprehension
- RigCalibration gains center_in_ins_body and rotation_from_reference, which
  the yaml writer, the report and the delay estimate all used to spell out
- InsTrajectory shares the segment lookup between pose and sample_gap
- build_image_tree uses a plain if/else instead of a side-effect conditional
  expression and only starts a process pool when there is work
- cli hoists the camera set and GIF frame list out of the loops they were
  recomputed in, and helpers come before main
- derive_rig builds the translation array once; the triangulator's
  refine_intrinsics=False gets a comment explaining why it stays off
- report drops semicolon-joined statements
- docstrings, comments and long strings wrapped to 88 columns, which the
  formatter leaves alone
save_to_krtd had no callers. unproject_to_depth and save_depth_viz were
defined on the base Camera class, where they shadowed the abstract
unproject_to_depth and referenced a depth_map only DepthCamera has; they now
live on DepthCamera. The bare except in Camera.__str__ catches Exception.
Creates or updates the conda env from environment.yml and builds .venv on top
of it with uv from the lockfile, via conda run so nothing needs activating
first. Windows previously used pip install -e . and skipped the lockfile.
make install now calls the script; the Python version is read from
environment.yml instead of being duplicated in the Makefile. README recommends
Miniforge, with Miniconda/Anaconda as alternatives.
Pass 2 refines each camera's rig offset only through tracks that survive
COLMAP's 4 px triangulation filter, so a seed a degree off silently stalls
near the seed while the reprojection RMS still looks fine. Print how many of
the shared frames fell in the seed cluster, warn when the scatter is over half
a degree or under half the frames made the cluster, and carry the observation
count per camera into the terminal summary, the camera and rig yamls, and the
report table, since that count collapses when the IR tracks were dropped.
Refining the distortion coefficients from the initial pair is degenerate on
flat ground. On a 250-frame subset of the May 2025 flight it drove L_ir to a
30% focal error and k2 of -3, so every L_ir model died at three images and
derive_rig failed with no frames shared with the reference; C_ir and R_ir
only survived by luck. With distortion frozen, L_ir alone builds an 83-image
model. Focal length stays free, and pass 2 refines the full intrinsics once
the whole rig is posed.
- bootstrap.py falls back to micromamba, which is all the kamerapy image
  has; micromamba needs -y and rejects conda's --no-capture-output.
- Stages build into <path>.partial and are moved into place when they
  finish, so an interrupted run redoes the stage instead of skipping it.
  Matching moves into the database stage for the same reason.
- Registration GIFs read both images from the normalized tree; the raw UV
  frames are nearly black.
- The report keeps a homography page when there are no GIF images
  (--gif_frames 0, or no frame with both cameras) instead of crashing.
The four save_to_file copies had drifted: DepthCamera wrote
camera_quaternion with a leading space (invalid yaml) and its depth
visualization into a directory that does not exist. model_type becomes a
class attribute so it is right for every subclass, and the dead
dist = "None" branch is dropped. Output is unchanged for the other three
classes.
The script passed output_dir as save_shapefile_per_image, turning on
per-image shapefiles and ignoring the directory. The function now takes
output_dir (default <flight_dir>/processed_results) for all of its
outputs, and the script checks flight_dir before using it.
Postflight finds camera models through <sys_cfg>/sys_config.json. The
pipeline now writes a copy of the flight's file with the yaml paths
pointed at the calibrated models, and --install_sys_config puts it in
place, keeping the original as sys_config.json.orig.
Holding distortion at zero registers L_ir but puts the RGB corners about
25 px off, the mapper drops those observations, and the rig seed comes out
several times looser (L_rgb scatter 0.05 -> 0.94 deg on the 250-frame
subset). The config now carries k1, k2 per modality next to focal_px,
rounded from the May 2025 calibration, and pass 1 keeps them fixed with only
the focal length free. On the same subset that puts all three IR cameras in
one 369-image model with every seed under 0.3 deg. Pass 2 refines the full
intrinsics once the whole rig is posed, as before.
DIVE only uses ir->rgb and uv->rgb; the ir->uv files and GIFs follow from
those two and only add noise to the outputs and the report.
Outputs now land in <flight>/calibration/camera_models/ so they stop colliding
with the SfM models in pass1/ and rig/.

The report is rebuilt page by page:
- a flight summary page: dates and duration, triggers on disk versus complete,
  selected and registered frames, what a frame is, images per camera, the
  flight track zoomed to the registered frames, and the INS altitude profile
- the intrinsics table ordered by modality so focal lengths compare at a
  glance, with distortion at three decimals and the per-pixel angle replaced
  by the full field of view and the ground sample distance at scene range
- the rig geometry table grouped by swathe with rotation and lever arm split
  into x, y, z columns, and the optical-axes sketch drawn in aircraft body
  axes via the boresight, hanging from the mount plate, seen from behind
- one full-page overlay per homography pair: the RGB frame in colour with the
  warped camera blended magenta over green inside its footprint, as DIVE
  shows a registration; the GIFs flip the same way
- the boresight residual page and the error notes page are dropped
romleiaj and others added 22 commits September 23, 2026 09:30
Format geo_conversions, nav_conversions, postflight/utilities and
create_flight_summary with ruff, drop unused imports and dead assignments,
narrow a bare except, and rename single-letter variables. No behaviour
change apart from one real fix: enu_to_llh built its ECEF x and y as
one-element tuples (trailing commas), which numpy 2 refuses to convert to
scalars, so every scalar call raised a TypeError. The round trip
llh -> enu -> llh now closes to a micro-degree.
- Rename messages to ROS2-legal CamelCase: GSOF_INS->GsofIns,
  GSOF_EVT->GsofEvt, EVT->Evt, PASHR->Pashr
- Replace removed 'time' builtin with builtin_interfaces/Time
- Rename tPvErr srv field to pv_err (ROS2 requires snake_case fields)
- Drop unused interfaces: ImageWithMask, POSAVX, CamControl, DiskUsage,
  EraseDataDisk, SetUInt
- msgdispatch: publishers now require an rclpy node; fix add_publisher
  storing every publisher under the literal key 'name'
- Replace catkin build files with ament_cmake + rosidl_default_generators;
  install msgdispatch via ament_python_install_package
roskv:
- ament_cmake build; C++ lib was already ROS-free, now exported via
  modern CMake targets instead of catkin_LIBRARIES
- Drop ROS1-only modules: reactor.py (relied on rosservice runtime
  introspection which has no ROS2 equivalent), rosparam_kv.py (global
  param server is gone in ROS2), and the rospy test nodes
- roskv CLI and python lib are now fully ROS-independent

kamcore:
- Convert to ament_python; monitor scripts become console_script
  entry points (fps_monitor, cam_param_monitor, shapefile_monitor,
  seed_redis_config)
- fps/cam_param/shapefile monitor nodes ported rospy -> rclpy;
  fps_monitor FPS math deduplicated; update loop is now a ROS timer
- phase_one srv import is optional so non-PhaseOne systems (nayak,
  taiga prosilica RGB) don't need that package installed
- Drop no-op kamcore_node.py keepalive node + kamcore.launch (existed
  to babysit roscore, which no longer exists) and unused
  diagnostics_to_influxdb.py
- Launch files converted to ROS2 XML (.launch.xml)
nexus:
- ArchiverBase/ArchiveManager now take an rclpy node; subscriptions,
  publishers, and services hang off it. Service callbacks use the
  ROS2 request/response signature
- ROS1 global params replaced: /cfg/hosts fov lookup dropped (was
  unused), /system_name -> SYSTEM_NAME env, /cfg/file_formats ->
  /sys/arch/ext_* in Redis (matching what img_nexus already used)
- msg_as_dict rewritten from genpy introspection to rosidl
  get_fields_and_field_types; header.seq removed (gone in ROS2)
- /rawmsg error publisher created once instead of per-publish
- Package becomes a pure ament_python library: dropped duplicate
  nexus_node/img_nexus/evt_listener/simulate_heading nodes (nayak and
  taiga run the copy embedded in view_server; nothing launches these)

view_server:
- image_view_server ported to rclpy with a MultiThreadedExecutor and
  reentrant callback group so blocking image requests don't starve
  image callbacks
- Nexus sync epochs keyed on (sec, nanosec) tuples since ROS2 message
  stamps are unhashable; event logging keyed on event_num since
  header.seq no longer exists
- Dropped dead sync_queue_callback2 path and unused web_server node
- roskv: hash_genpy_msg -> hash_ros_msg using rclpy serialize_message
- ins_socket_driver and spoof_events become rclpy console scripts;
  a background executor thread services the archiver's ROS interfaces
  while the main thread runs the blocking GSOF socket loop
- gsof.py no longer needs rospy: dispatches build builtin_interfaces
  Time stamps from GPS time floats; module-level rclpy logger.
  header.seq assignments dropped (removed in ROS2); event identity
  lives in event_num / the frame_id query string
- Global rosparams (/data_mount_point, spoof params) were already
  redundant with env vars + Redis; node params reduced to
  ip/port/replay/retry
- Drop the legacy upstream NMEA driver stack (driver.py, parser.py,
  nmea_class, checksum_utils, four nmea_* scripts, ins_spoof_driver,
  nmea.launch): nayak/taiga only ever launch ins_socket_driver, which
  explicitly disabled the NMEA path
- Launch files converted to ROS2 XML; ament_python build
mcc_daq:
- daq_node rewritten roscpp -> rclcpp: TriggerTimer/AsyncTriggerTimer/
  OneShotManager now use rclcpp timers and Time/Duration; one-shot
  timers are wall timers cancelled+erased on fire (uint64 ids replace
  boost uuids, dropping the boost dependency); AsyncSpinner replaced
  by a MultiThreadedExecutor
- ROS1-only TimerEvent replaced with a minimal local struct carrying
  current_real
- Global params /spoof_rate & /spoof_daq (loaded via the old roscore
  container) replaced with the SPOOF_RATE env var; node params
  declared explicitly
- Dropped the unused UsbDaqDummy class, the interactive chatter test
  node, and chatter/testusb launch files
- utils.h provides rclcpp logging shims so the vendored MCC hardware
  code keeps its ROS_INFO-style call sites

ser_daq:
- ser_daq_driver ported to rclpy (ament_python console script);
  pulse timing uses threading.Timer instead of rospy one-shot timers;
  unused params/spoof plumbing removed
cam_utils (new package):
- ROS2 port of phase_one's EventCache/parseParams/loadFile helpers,
  which both camera drivers previously compiled via a hardcoded
  /root/kamera/... path into the phase_one tree. EventCache::search
  now reports event_num explicitly since header.seq is gone in ROS2

kw_genicam_driver:
- driver_a6750 rewritten roscpp -> rclcpp. Active path (EventHandler +
  CamParamHandler + executor) preserved; the unreachable legacy main
  loop after the early return, the unused rerange_temp() and the
  unbuilt legacy driver.cpp are deleted
- Image fetch runs on a dedicated thread instead of self-rescheduling
  one-shot ROS timers; MultiThreadedExecutor services events/services
- Watchdog/Trigger/parse helpers in utils ported to rclcpp types;
  ROS_* logging call sites kept via rclcpp shims
- gige_scan/decode_error/genicam_ctl were already ROS-free
- Launch files consolidated: flir_a6750/flir_a645 ROS2 XML launches
  named after the config.yaml camera model (the genicam_* duplicates
  are dropped)
- The 1800-line ProsilicaNodelet becomes a plain rclcpp node
  (prosilica_node): nodelets don't exist in ROS2 and the nodelet was
  always run standalone under its own manager anyway
- dynamic_reconfigure replaced by node parameters applied at startup;
  the GainMode/GainValue launch params (previously declared but never
  read - auto-gain always won) now actually map onto gain settings
- Dropped ROS1-only plumbing with no ROS2 equivalent or no consumers:
  polled_camera request_image, diagnostic_updater state reporting,
  self_test, the disabled view_server_nodelet, and generic/streaming
  launch variants
- Event/image fusion now uses the shared cam_utils EventCache with
  explicit event_num (header.seq is gone in ROS2); frame drop
  accounting is keyed on event_num
- Published image now gets its frame_id set before publish (the
  nodelet set it on the buffer copy after publishing - stale id)
- libprosilica Watchdog/CvtPvTimestamp ported to rclcpp types; unused
  OneShotManager removed; ROS_* call sites kept via rclcpp shims
- prosilica_gige_sdk vendored SDK exported as an ament imported target
  instead of the catkin copy hack
- kw_detector_fusion_adapter_node ported roscpp -> rclcpp: node
  parameters replace private-nodehandle params, background spin
  thread replaces the AsyncSpinner, detections publish on
  ~/detections_out remapped as before
- Dropped sources that were never built into the node: the
  ros_dynamic_config / ros_detector_scaling sprokit plugins (ROS1
  dynamic_reconfigure based) and netbeans project files
- publish_sync_msgs / rebroadcast / save_* debug scripts left as-is;
  they are not launched by nayak/taiga supervisors
- Launch converted to ROS2 XML
- backend becomes a plain ament metapackage depending on the ported
  ROS2 packages (GUI/phase_one entries removed - not part of the
  nayak/taiga supervisor set)
- Dropped the unfinished diagnostics.py stub (copy of ins driver
  boilerplate that was never completed or launched)
- The postproc supervisor group (flight_summary, homography,
  detections) runs pure bash + kamera.postflight python with no ROS
  dependency, so those entry points are unchanged
- compose: ROS_MASTER_URI env replaced with ROS_DOMAIN_ID everywhere;
  core image ROS_DISTRO noetic -> humble
- tmux env.sh (nayak/taiga): drop ROS master/hostname exports; export
  a shared ROS_DOMAIN_ID for DDS discovery instead
- The roscore supervisor program/compose service becomes core_init:
  with no master in ROS2 its only remaining job is seeding Redis with
  the static system config (ros2 run kamcore seed_redis_config)
- Entry scripts: roslaunch --wait -> ros2 launch *.launch.xml with
  norespawn mapped to the launch respawn arg; catkin build debug
  rebuilds -> colcon build --packages-select; dropped the
  'REQUIRED node has died' log-scrape hack (roslaunch-specific,
  ros2 launch propagates exit properly)
- viame.sh: detection csv/image-list dirs passed via environment
  (pipelines read env; the old launch env-vars are gone)
- rosnode_list.sh diagnostic -> ros2 node list; dev aliases updated

Not touched (out of nayak/taiga supervisor scope): gui.sh,
cam_phaseone.sh, postproc.sh (cas), uas tmux configs, wxpython_gui.
- ROS_DISTRO humble -> jazzy in tmux env.sh and compose camera services
- cv_bridge includes moved to cv_bridge.hpp (the .h shim is deprecated
  since Iron)
- docker: core-ros base moves to nvidia/cuda ubuntu24.04, ROS2 apt
  source (noble) with keyring-based signing, ros-jazzy-* packages, and
  colcon in place of catkin tools; pip installs use
  --break-system-packages for PEP-668 (24.04) pythons
- core-deps: jazzy equivalents of the perception deps; dropped ROS1-only
  nodelet/self_test/polled_camera/message-generation debs and the
  gtk2-era glademm libs that no longer exist on noble
- core/detector images build with colcon --packages-up-to so unported
  ROS1 packages (phase_one, wxpython_gui, ...) are not touched;
  detector-viame-deps notes the VIAME base image must be rebuilt on
  24.04 to host jazzy debs
- activate_ros.bash sources the colcon install/setup.bash overlay and
  RCUTILS log format instead of devel/setup.bash + ROSCONSOLE_FORMAT

gui.dockerfile intentionally left on catkin: wxpython_gui is still ROS1
and outside the nayak/taiga supervisor scope.
The current gpu-algorithms tag is built on Ubuntu 24.04, so it can host
the Jazzy debs directly - drops the stale focal-based
gpu-algorithms-seal tag and the rebuild caveat.
- phase_one_standalone becomes a plain rclcpp node; services are
  declared with the ~/ prefix so they resolve to the same
  /<host>/rgb/<driver>/... names the GUI and cam_param_monitor call.
  The old ROS_NAMESPACE env plumbing is replaced by a launch namespace
  arg (cam_phaseone.sh updated to ros2 launch)
- srvs (Get/SetPhaseOneParameter, Get[Compressed]ImageView) generated
  via rosidl; cam_param_monitor's optional phase_one import now works
  on ROS2 systems that install this package
- Event/image matching uses the shared cam_utils EventCache with
  explicit event_num (header.seq removed in ROS2); the debayer-queue
  seq map and save_every_x modulo now key on event_num
- Dropped the nodelet variants (phase_one_nodelet, view_server_nodelet,
  phase_one_node loader) and the local phase_one_utils copy replaced
  by cam_utils; boost::filesystem -> std::filesystem
- backend metapackage depends on phase_one again
- New wxpython_gui.rosnode module owns a single rclpy node serviced by
  a background MultiThreadedExecutor thread, exposing the imperative
  surface the wx GUI needs: Subscriber, a synchronous ServiceProxy
  (call_async + wait, raising ServiceException on unavailable/timeout,
  matching the rospy semantics every call site already handles), Rate,
  logging, and stamp_to_sec
- gui.py / UpdateImageThread.py call sites moved from rospy to rosnode;
  GSOF_INS -> GsofIns; header.stamp.to_sec() -> stamp_to_sec();
  Subscriber.unregister -> destroy_subscription; clean rclpy shutdown
  wired into the window-close handler
- The single rospy.set_param('/sys/arch/is_archiving') becomes a Redis
  kv.put - there is no global param server in ROS2 and every consumer
  already reads that key from Redis
- cfg.py/utils.py drop now-unused rospy imports (ros_immediate was dead)
- Package converted to ament_python with a system_control_panel console
  script (node script moved into the package); launch converted to ROS2
  XML; stale .pyc files dropped from the repo
- gui.sh launches via ros2 launch and checks ROS_DOMAIN_ID instead of
  ROS_MASTER_URI; start_gui.sh no longer spawns a local roscore;
  gui.dockerfile builds with colcon --packages-up-to wxpython_gui
  ins_driver (base image must be Jazzy, tracked with the gui-deps image)
- sysinfo: syscall service node -> rclpy console script (ament_python);
  syscall.sh uses ros2 launch
- kamerahealth: missed_frame_node and exit_code_node -> rclpy
  (ament_python); health_node.py deleted - it was a byte-for-byte copy
  of sysinfo's syscall node, never a health check
- testbed: test_roskv exerciser -> rclcpp/ament
- sensor_simulator: camera/INS simulators -> rclpy console scripts;
  GSOF_INS -> GsofIns, genpy stamps -> builtin_interfaces
- sprokit_adapters debug scripts (publish_sync_msgs, rebroadcast,
  save_images/chips) and kw_genicam display_latency -> rclpy; scripts
  now installed by the package
- bag_file_explode: rosbag_to_png rewritten on the 'rosbags' pip
  library, which reads the legacy ROS1 .bag archives this tool exists
  for (plus ROS2 bags) with no ROS1 install; drops the py2-era
  multiprocessing time-slicing for a straightforward sequential read
- Deleted kwiver_ros_param_interface and rqt_sprokit_adapter: both were
  already CATKIN_IGNOREd (disabled even under ROS1) and are built on
  ROS1-only mechanisms (global param server, dynamic_reconfigure rqt)
  with no ROS2 equivalent or consumer
- Entry/dev scripts: spoofins/publish_sync_msgs/healthcheck/health_cam
  -> ros2 equivalents; wat.sh rewritten around ros2 doctor (no master
  to hunt for); aliases.sh dev shortcuts -> ros2/colcon; deleted
  debay.sh and ros2jaeger.sh (referenced packages that do not exist in
  the repo) and EXPORT_ROS_MASTER.sh
check_system/setup_kamera_env/basic-aliases/aliases setmaster helper,
prosilica test_homography, genicam run_simple_driver (now points at
the installed a6750 node via ros2 pkg prefix), and the uas tmux env
all move to ROS_DOMAIN_ID / ros2 CLI equivalents. Remaining grep hits
for rospy/catkin in the tree are comments and docstrings only.
- libgl1-mesa-glx no longer exists on noble; use libgl1 + libglx-mesa0
- pip installs use --break-system-packages (PEP 668) and drop the pip
  self-upgrade; Pillow folded into the single install layer
- Drop the legacy 'PyGeodesy<19.12' pin (py2-era, untested on py3):
  the GUI uses pygeodesy.geoids.GeoidPGM, which the unpinned core-deps
  install provides and shapefile_monitor already runs against
- Drop the pip install of roskv: its setup.py was removed in the ROS2
  port, and gui.dockerfile's colcon --packages-up-to wxpython_gui
  builds roskv into the workspace overlay instead
The two lineinfile tasks matched on exact line text, so any change to
mount options or disk UUID appended a new fstab variant instead of
replacing the old one; deployed systems accumulated stale entries
(cas3 carries three generations of /mnt/data lines, including a
literal by-uuid/TODO from hosts.yml's placeholder ssd_id).

Key each entry on its mount point with a regexp so changes replace in
place. Verified against a container seeded with cas3's exact fstab:
first pass rewrites stale variants in situ, repeat passes are no-ops.
lineinfile only replaces the last match, so pre-existing duplicates on
deployed boxes still need a one-time manual cleanup.
Ubuntu 24.04 stays; CUDA moves to the newest major release. 13.0 is
the ceiling for native driver support across the machines we can
verify (dev box runs the r580 branch; 13.1+ would lean on CUDA
minor-version compatibility there). The Phase One ImageSDKCuda ships
no hard-linked CUDA sonames (runtime probe via IsCudaSupported), so
the major bump is safe for it.

Deployment prerequisite: every node needs NVIDIA driver >= 580 before
this image lands; aircraft systems should be checked before rollout.
The VIAME detector image is unaffected (separate CUDA 12.6 base from
kitware/viame).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants