Skip to content

Thagdorn/picture saver - #2

Open
TomHagdorn wants to merge 4 commits into
mainfrom
thagdorn/picture_saver
Open

TomHagdorn wants to merge 4 commits into
mainfrom
thagdorn/picture_saver

Conversation

@TomHagdorn

Copy link
Copy Markdown
Contributor

add srv for taking pictures with the picam

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a ROS 2 service to persist camera frames from picam_client to disk, configurable via a default save directory parameter.

Changes:

  • Introduces a new SavePicture service definition and wires it into the node.
  • Adds save_directory parameter (and config) plus runtime directory creation and PNG writing.
  • Stores a “latest image” by subscribing to the node’s own camera/image topic.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
picam_client/srv/SavePicture.srv Defines request/response for saving one or more images with an interval.
picam_client/src/picam_client_node.cpp Implements /picam_client/save_picture, adds save-directory param, stores latest image via self-subscription, changes image encoding on publish.
picam_client/include/picam_client/picam_client_node.hpp Declares new service, subscription, and latest_image_ storage.
picam_client/config/params.yaml Adds default save_directory parameter value.
picam_client/CMakeLists.txt Registers the new service interface for code generation.
Comments suppressed due to low confidence (1)

picam_client/CMakeLists.txt:23

  • This PR introduces std::filesystem usage, but the package does not set a C++ standard in CMake. On some toolchains/distros this may still default to C++14 and fail to compile/link. Set CMAKE_CXX_STANDARD 17 (or use target_compile_features(... cxx_std_17)) for this package/target.
find_package(cv_bridge REQUIRED)
find_package(OpenCV REQUIRED)
find_package(rosidl_default_generators REQUIRED)

rosidl_generate_interfaces(${PROJECT_NAME}
  "srv/SavePicture.srv"
  "srv/SetConfidence.srv"
  "srv/SetIOU.srv"
  "srv/StreamControl.srv"
  DEPENDENCIES std_msgs

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.


auto msg =
cv_bridge::CvImage(std_msgs::msg::Header(), "bgr8", img).toImageMsg();
cv_bridge::CvImage(std_msgs::msg::Header(), "rgb8", img).toImageMsg();

Copilot AI Mar 11, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cv::imdecode(..., cv::IMREAD_COLOR) returns a BGR image, but the message is published with encoding "rgb8". This makes the sensor_msgs/Image encoding inconsistent with the underlying pixel data and will swap channels for downstream consumers (including your own toCvCopy(..., BGR8) subscriber). Publish as bgr8 or convert the Mat to RGB before publishing with rgb8.

Suggested change
cv_bridge::CvImage(std_msgs::msg::Header(), "rgb8", img).toImageMsg();
cv_bridge::CvImage(std_msgs::msg::Header(), "bgr8", img).toImageMsg();

Copilot uses AI. Check for mistakes.
Comment on lines +438 to +453
for (int i = 0; i < count; ++i) {
// Wait for the interval before taking subsequent pictures
if (i > 0) {
std::this_thread::sleep_for(
std::chrono::milliseconds(static_cast<int>(interval * 1000)));
}

// Grab the latest image
cv::Mat image;
if (latest_image_.empty()) {
response->success = false;
response->message = "No image available from camera";
response->saved_files = saved_files;
return;
}
image = latest_image_.clone();

Copilot AI Mar 11, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

handle_save_picture runs in the same (single-threaded) executor as the image_sub_ callback (see main() using rclcpp::spin). While this service callback is sleeping/writing files, the subscription callback cannot run, so latest_image_ won’t update and the loop will likely save the same frame repeatedly (and starve other callbacks). Consider updating latest_image_ directly in handle_image_message (read thread) with proper synchronization, or moving save work to a separate thread / callback group with a multithreaded executor.

Copilot uses AI. Check for mistakes.
Comment on lines +418 to +426
// Determine save directory
std::string save_dir = request->save_directory.empty()
? save_directory_
: request->save_directory;

// Create directory if it doesn't exist
try {
std::filesystem::create_directories(save_dir);
} catch (const std::filesystem::filesystem_error &e) {

Copilot AI Mar 11, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The service allows callers to provide an arbitrary save_directory and the node will create directories and write files there. In ROS deployments where services are reachable over the network, this is a filesystem write primitive. Consider restricting saves to a configured base directory (e.g., ignore/validate request paths, require relative paths, reject ../absolute paths) or adding an explicit allowlist.

Copilot uses AI. Check for mistakes.
Comment on lines +433 to +438
int count = request->count <= 0 ? 1 : request->count;
float interval = request->interval <= 0.0f ? 1.0f : request->interval;

std::vector<std::string> saved_files;

for (int i = 0; i < count; ++i) {

Copilot AI Mar 11, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

count is effectively unbounded (any positive int32). A large request can block the node for a long time and potentially fill disk. Consider enforcing a reasonable maximum (constant/parameter) and returning an error when exceeded.

Copilot uses AI. Check for mistakes.
Comment on lines +85 to +86
RCLCPP_INFO(get_logger(), "Service '/save_picture' created at: %s",
save_picture_srv_->get_service_name());

Copilot AI Mar 11, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The log line says Service '/save_picture' created... but the actual service name registered is /picam_client/save_picture. This is misleading when debugging service discovery; update the log label to match the real service path.

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +7
string save_directory # Directory to save pictures (empty = use default from params)
int32 count # Number of pictures to take (default: 1)
float32 interval # Interval between pictures in seconds (default: 1.0)
---
bool success
string message
string[] saved_files # List of saved file paths

Copilot AI Mar 11, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The save_directory field is used directly by handle_save_picture as a filesystem path, allowing any ROS client that can call this service to choose an arbitrary directory for file creation. An attacker can supply values like /tmp/attacker or ../../var/app/data and the node will pass this straight into std::filesystem::create_directories and cv::imwrite, enabling path traversal and unauthorized creation of directories/files outside the intended picture directory. To mitigate this, constrain save_directory to a configured base path (e.g. normalize and enforce that it stays under save_directory_, or expose only predefined subdirectories/relative names instead of accepting an arbitrary absolute path).

Suggested change
string save_directory # Directory to save pictures (empty = use default from params)
int32 count # Number of pictures to take (default: 1)
float32 interval # Interval between pictures in seconds (default: 1.0)
---
bool success
string message
string[] saved_files # List of saved file paths
string save_directory # Relative subdirectory under the configured base picture directory
# (empty = use default from params; must NOT be an absolute path or contain "..")
int32 count # Number of pictures to take (default: 1)
float32 interval # Interval between pictures in seconds (default: 1.0)
---
bool success
string message
string[] saved_files # List of saved file paths (relative to the configured base picture directory)

Copilot uses AI. Check for mistakes.
@TomHagdorn

Copy link
Copy Markdown
Contributor Author

@copilot open a new pull request to apply changes based on the comments in this thread

Copilot AI commented Mar 11, 2026

Copy link
Copy Markdown

@TomHagdorn I've opened a new pull request, #3, to work on those changes. Once the pull request is ready, I'll request review from you.

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.

3 participants