Skip to content

Commit 3913578

Browse files
HipsterBrownclaude
andcommitted
fix(sdk): address Vision service review feedback
- Respect server-returned mime_type in get_object_point_clouds: server now populates response.mime_type from the result's cloud.mime_type (not echoing the request), and client reads response.mime_type() rather than the request mime. Matches the proto contract and the Go RDK client behavior. Adds a regression test where request mime differs from server-returned mime. - Initialize Vision::detection::confidence, classification::confidence, and all three properties bools via NSDMIs so default-constructed values are well-defined. - Introduce Vision::image as a Vision-specific input type for get_detections / get_classifications. Camera::raw_image (alias) is kept only for capture_all_result.image where the wire type is the full Camera Image proto. Avoids silently dropping source_name on inputs whose wire format doesn't carry one — mirrors how Go's RDK takes image.Image (decoupled from the camera service). - Rename point_cloud_object::point_cloud field to ::cloud so it no longer shadows the Vision::point_cloud type alias. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 1ad943d commit 3913578

9 files changed

Lines changed: 114 additions & 77 deletions

File tree

src/viam/sdk/services/private/vision.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -83,16 +83,16 @@ Vision::classification from_proto(const vpb::Classification& p) {
8383
}
8484

8585
void to_proto(const Vision::point_cloud_object& o, ::viam::common::v1::PointCloudObject* out) {
86-
out->set_point_cloud(std::string(reinterpret_cast<const char*>(o.point_cloud.pc.data()),
87-
o.point_cloud.pc.size()));
86+
out->set_point_cloud(
87+
std::string(reinterpret_cast<const char*>(o.cloud.pc.data()), o.cloud.pc.size()));
8888
*(out->mutable_geometries()->mutable_geometries()) = impl::to_repeated_field(o.geometries);
8989
}
9090

9191
Vision::point_cloud_object from_proto(const ::viam::common::v1::PointCloudObject& p) {
9292
Vision::point_cloud_object out;
9393
const auto& bytes = p.point_cloud();
94-
out.point_cloud.pc.assign(reinterpret_cast<const unsigned char*>(bytes.data()),
95-
reinterpret_cast<const unsigned char*>(bytes.data()) + bytes.size());
94+
out.cloud.pc.assign(reinterpret_cast<const unsigned char*>(bytes.data()),
95+
reinterpret_cast<const unsigned char*>(bytes.data()) + bytes.size());
9696
out.geometries = impl::from_repeated_field(p.geometries().geometries());
9797
return out;
9898
}

src/viam/sdk/services/private/vision_client.cpp

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,13 @@ std::vector<Vision::detection> VisionClient::get_detections_from_camera(
4545
});
4646
}
4747

48-
std::vector<Vision::detection> VisionClient::get_detections(const Vision::raw_image& image,
48+
std::vector<Vision::detection> VisionClient::get_detections(const Vision::image& img,
4949
const ProtoStruct& extra) {
5050
return make_client_helper(this, *stub_, &service_type::StubInterface::GetDetections)
5151
.with(extra,
5252
[&](auto& req) {
53-
req.set_image(std::string(image.bytes.begin(), image.bytes.end()));
54-
req.set_mime_type(image.mime_type);
53+
req.set_image(std::string(img.bytes.begin(), img.bytes.end()));
54+
req.set_mime_type(img.mime_type);
5555
})
5656
.invoke([](auto& response) {
5757
std::vector<Vision::detection> out;
@@ -82,13 +82,14 @@ std::vector<Vision::classification> VisionClient::get_classifications_from_camer
8282
});
8383
}
8484

85-
std::vector<Vision::classification> VisionClient::get_classifications(
86-
const Vision::raw_image& image, int count, const ProtoStruct& extra) {
85+
std::vector<Vision::classification> VisionClient::get_classifications(const Vision::image& img,
86+
int count,
87+
const ProtoStruct& extra) {
8788
return make_client_helper(this, *stub_, &service_type::StubInterface::GetClassifications)
8889
.with(extra,
8990
[&](auto& req) {
90-
req.set_image(std::string(image.bytes.begin(), image.bytes.end()));
91-
req.set_mime_type(image.mime_type);
91+
req.set_image(std::string(img.bytes.begin(), img.bytes.end()));
92+
req.set_mime_type(img.mime_type);
9293
req.set_n(count);
9394
})
9495
.invoke([](auto& response) {
@@ -109,12 +110,15 @@ std::vector<Vision::point_cloud_object> VisionClient::get_object_point_clouds(
109110
req.set_camera_name(camera_name);
110111
req.set_mime_type(mime_type);
111112
})
112-
.invoke([&mime_type](auto& response) {
113+
.invoke([](auto& response) {
113114
std::vector<Vision::point_cloud_object> out;
114115
out.reserve(response.objects_size());
116+
// The wire format carries one mime_type for all objects; copy it
117+
// onto each returned object. May differ from the requested mime —
118+
// see GetObjectPointCloudsResponse.mime_type in vision.proto.
115119
for (const auto& proto_obj : response.objects()) {
116120
auto pco = impl::vision::from_proto(proto_obj);
117-
pco.point_cloud.mime_type = mime_type; // populate from request
121+
pco.cloud.mime_type = response.mime_type();
118122
out.push_back(std::move(pco));
119123
}
120124
return out;

src/viam/sdk/services/private/vision_client.hpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,11 @@ class VisionClient : public Vision {
3636

3737
std::vector<Vision::detection> get_detections_from_camera(const std::string& camera_name,
3838
const ProtoStruct& extra) override;
39-
std::vector<Vision::detection> get_detections(const Vision::raw_image& image,
39+
std::vector<Vision::detection> get_detections(const Vision::image& img,
4040
const ProtoStruct& extra) override;
4141
std::vector<Vision::classification> get_classifications_from_camera(
4242
const std::string& camera_name, int count, const ProtoStruct& extra) override;
43-
std::vector<Vision::classification> get_classifications(const Vision::raw_image& image,
43+
std::vector<Vision::classification> get_classifications(const Vision::image& img,
4444
int count,
4545
const ProtoStruct& extra) override;
4646
std::vector<Vision::point_cloud_object> get_object_point_clouds(

src/viam/sdk/services/private/vision_server.cpp

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,10 @@ ::grpc::Status VisionServer::GetDetections(
4646
::viam::service::vision::v1::GetDetectionsResponse* response) noexcept {
4747
return make_service_helper<Vision>(
4848
"VisionServer::GetDetections", this, context, request)([&](auto& helper, auto& vs) {
49-
Vision::raw_image image;
50-
image.bytes.assign(request->image().begin(), request->image().end());
51-
image.mime_type = request->mime_type();
52-
const auto results = vs->get_detections(image, helper.getExtra());
49+
Vision::image img;
50+
img.bytes.assign(request->image().begin(), request->image().end());
51+
img.mime_type = request->mime_type();
52+
const auto results = vs->get_detections(img, helper.getExtra());
5353
for (const auto& d : results) {
5454
*response->add_detections() = impl::vision::to_proto(d);
5555
}
@@ -77,10 +77,10 @@ ::grpc::Status VisionServer::GetClassifications(
7777
::viam::service::vision::v1::GetClassificationsResponse* response) noexcept {
7878
return make_service_helper<Vision>(
7979
"VisionServer::GetClassifications", this, context, request)([&](auto& helper, auto& vs) {
80-
Vision::raw_image image;
81-
image.bytes.assign(request->image().begin(), request->image().end());
82-
image.mime_type = request->mime_type();
83-
const auto results = vs->get_classifications(image, request->n(), helper.getExtra());
80+
Vision::image img;
81+
img.bytes.assign(request->image().begin(), request->image().end());
82+
img.mime_type = request->mime_type();
83+
const auto results = vs->get_classifications(img, request->n(), helper.getExtra());
8484
for (const auto& c : results) {
8585
*response->add_classifications() = impl::vision::to_proto(c);
8686
}
@@ -98,7 +98,12 @@ ::grpc::Status VisionServer::GetObjectPointClouds(
9898
for (const auto& obj : results) {
9999
impl::vision::to_proto(obj, response->add_objects());
100100
}
101-
response->set_mime_type(request->mime_type());
101+
// The wire format carries a single mime_type for all objects; take it
102+
// from the first result (the server may legitimately convert formats
103+
// and return something different from request->mime_type()).
104+
if (!results.empty()) {
105+
response->set_mime_type(results.front().cloud.mime_type);
106+
}
102107
});
103108
}
104109

src/viam/sdk/services/vision.cpp

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -29,25 +29,27 @@ API API::traits<Vision>::api() {
2929

3030
Vision::Vision(std::string name) : Service(std::move(name)) {}
3131

32-
bool operator==(const Vision::detection& a, const Vision::detection& b) {
33-
return a.x_min == b.x_min && a.y_min == b.y_min && a.x_max == b.x_max && a.y_max == b.y_max &&
34-
a.class_name == b.class_name && a.confidence == b.confidence &&
35-
a.x_min_normalized == b.x_min_normalized && a.y_min_normalized == b.y_min_normalized &&
36-
a.x_max_normalized == b.x_max_normalized && a.y_max_normalized == b.y_max_normalized;
32+
bool operator==(const Vision::detection& lhs, const Vision::detection& rhs) {
33+
return lhs.x_min == rhs.x_min && lhs.y_min == rhs.y_min && lhs.x_max == rhs.x_max &&
34+
lhs.y_max == rhs.y_max && lhs.class_name == rhs.class_name &&
35+
lhs.confidence == rhs.confidence && lhs.x_min_normalized == rhs.x_min_normalized &&
36+
lhs.y_min_normalized == rhs.y_min_normalized &&
37+
lhs.x_max_normalized == rhs.x_max_normalized &&
38+
lhs.y_max_normalized == rhs.y_max_normalized;
3739
}
3840

39-
bool operator==(const Vision::classification& a, const Vision::classification& b) {
40-
return a.class_name == b.class_name && a.confidence == b.confidence;
41+
bool operator==(const Vision::classification& lhs, const Vision::classification& rhs) {
42+
return lhs.class_name == rhs.class_name && lhs.confidence == rhs.confidence;
4143
}
4244

43-
bool operator==(const Vision::point_cloud_object& a, const Vision::point_cloud_object& b) {
44-
return a.point_cloud == b.point_cloud && a.geometries == b.geometries;
45+
bool operator==(const Vision::point_cloud_object& lhs, const Vision::point_cloud_object& rhs) {
46+
return lhs.cloud == rhs.cloud && lhs.geometries == rhs.geometries;
4547
}
4648

47-
bool operator==(const Vision::properties& a, const Vision::properties& b) {
48-
return a.classifications_supported == b.classifications_supported &&
49-
a.detections_supported == b.detections_supported &&
50-
a.object_point_clouds_supported == b.object_point_clouds_supported;
49+
bool operator==(const Vision::properties& lhs, const Vision::properties& rhs) {
50+
return lhs.classifications_supported == rhs.classifications_supported &&
51+
lhs.detections_supported == rhs.detections_supported &&
52+
lhs.object_point_clouds_supported == rhs.object_point_clouds_supported;
5153
}
5254

5355
} // namespace sdk

src/viam/sdk/services/vision.hpp

Lines changed: 31 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,6 @@ class Vision : public Service {
2828
/// @struct detection
2929
/// @brief Result of a single object detection.
3030
/// Pixel bbox fields are optional int64; normalized bbox fields are optional double.
31-
/// @note The field name `point_cloud` in `point_cloud_object` shadows the type alias
32-
/// `point_cloud` — this is intentional and matches the proto field layout.
3331
struct detection {
3432
boost::optional<std::int64_t> x_min;
3533
boost::optional<std::int64_t> y_min;
@@ -40,34 +38,47 @@ class Vision : public Service {
4038
boost::optional<double> x_max_normalized;
4139
boost::optional<double> y_max_normalized;
4240
std::string class_name;
43-
double confidence;
41+
double confidence = 0.0;
4442
};
4543

4644
/// @struct classification
4745
/// @brief Result of a single image classification.
4846
struct classification {
4947
std::string class_name;
50-
double confidence;
48+
double confidence = 0.0;
5149
};
5250

5351
using point_cloud = Camera::point_cloud;
5452
using raw_image = Camera::raw_image;
5553

54+
/// @struct image
55+
/// @brief Image input to `get_detections` / `get_classifications`.
56+
///
57+
/// Distinct from `raw_image` (a.k.a. `Camera::raw_image`) because the Vision request
58+
/// messages on the wire only carry bytes + mime_type — they do not carry a `source_name`.
59+
/// See the proto definitions for `GetDetectionsRequest` and `GetClassificationsRequest`.
60+
/// The `raw_image` alias remains for `capture_all_result.image`, whose wire type is the
61+
/// full Camera Image proto and does carry a source name.
62+
struct image {
63+
std::string mime_type;
64+
std::vector<unsigned char> bytes;
65+
};
66+
5667
/// @struct point_cloud_object
5768
/// @brief A point cloud and its associated geometry.
58-
/// @note The field name `point_cloud` shadows the type alias `point_cloud` — intentional,
59-
/// matching the proto field name.
69+
/// @note The proto field is named `point_cloud`; we use `cloud` here to avoid shadowing
70+
/// the `point_cloud` type alias (GCC rejects the shadowing under -fpermissive).
6071
struct point_cloud_object {
61-
point_cloud point_cloud;
72+
point_cloud cloud;
6273
std::vector<GeometryConfig> geometries;
6374
};
6475

6576
/// @struct properties
6677
/// @brief Describes the vision service's supported capabilities.
6778
struct properties {
68-
bool classifications_supported;
69-
bool detections_supported;
70-
bool object_point_clouds_supported;
79+
bool classifications_supported = false;
80+
bool detections_supported = false;
81+
bool object_point_clouds_supported = false;
7182
};
7283

7384
/// @struct capture_options
@@ -99,15 +110,14 @@ class Vision : public Service {
99110
virtual std::vector<detection> get_detections_from_camera(const std::string& camera_name,
100111
const ProtoStruct& extra) = 0;
101112

102-
/// @brief Get detections from a raw image.
103-
inline std::vector<detection> get_detections(const raw_image& image) {
104-
return get_detections(image, {});
113+
/// @brief Get detections from an image.
114+
inline std::vector<detection> get_detections(const image& img) {
115+
return get_detections(img, {});
105116
}
106117

107-
/// @brief Get detections from a raw image.
118+
/// @brief Get detections from an image.
108119
/// @param extra Any additional arguments to the method.
109-
virtual std::vector<detection> get_detections(const raw_image& image,
110-
const ProtoStruct& extra) = 0;
120+
virtual std::vector<detection> get_detections(const image& img, const ProtoStruct& extra) = 0;
111121

112122
/// @brief Get classifications from a named camera's next image.
113123
/// @param count The number of classifications to return.
@@ -122,16 +132,16 @@ class Vision : public Service {
122132
virtual std::vector<classification> get_classifications_from_camera(
123133
const std::string& camera_name, int count, const ProtoStruct& extra) = 0;
124134

125-
/// @brief Get classifications from a raw image.
135+
/// @brief Get classifications from an image.
126136
/// @param count The number of classifications to return.
127-
inline std::vector<classification> get_classifications(const raw_image& image, int count) {
128-
return get_classifications(image, count, {});
137+
inline std::vector<classification> get_classifications(const image& img, int count) {
138+
return get_classifications(img, count, {});
129139
}
130140

131-
/// @brief Get classifications from a raw image.
141+
/// @brief Get classifications from an image.
132142
/// @param count The number of classifications to return.
133143
/// @param extra Any additional arguments to the method.
134-
virtual std::vector<classification> get_classifications(const raw_image& image,
144+
virtual std::vector<classification> get_classifications(const image& img,
135145
int count,
136146
const ProtoStruct& extra) = 0;
137147

src/viam/sdk/tests/mocks/mock_vision.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,13 @@ std::vector<Vision::detection> MockVision::get_detections_from_camera(
2121
return canned_detections;
2222
}
2323

24-
std::vector<Vision::detection> MockVision::get_detections(const raw_image& image,
24+
std::vector<Vision::detection> MockVision::get_detections(const Vision::image& img,
2525
const ProtoStruct& extra) {
2626
if (throw_on_next_call) {
2727
throw_on_next_call = false;
2828
throw sdk::Exception("mock failure");
2929
}
30-
last_image = image;
30+
last_image = img;
3131
last_extra = extra;
3232
return canned_detections;
3333
}
@@ -44,14 +44,14 @@ std::vector<Vision::classification> MockVision::get_classifications_from_camera(
4444
return canned_classifications;
4545
}
4646

47-
std::vector<Vision::classification> MockVision::get_classifications(const raw_image& image,
47+
std::vector<Vision::classification> MockVision::get_classifications(const Vision::image& img,
4848
int count,
4949
const ProtoStruct& extra) {
5050
if (throw_on_next_call) {
5151
throw_on_next_call = false;
5252
throw sdk::Exception("mock failure");
5353
}
54-
last_image = image;
54+
last_image = img;
5555
last_count = count;
5656
last_extra = extra;
5757
return canned_classifications;

src/viam/sdk/tests/mocks/mock_vision.hpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ class MockVision : public sdk::Vision {
2525
std::string last_camera_name;
2626
int last_count = 0;
2727
std::string last_mime_type;
28-
sdk::Vision::raw_image last_image;
28+
sdk::Vision::image last_image;
2929
sdk::Vision::capture_options last_capture_options;
3030
sdk::ProtoStruct last_extra;
3131
sdk::ProtoStruct last_command;
@@ -35,12 +35,12 @@ class MockVision : public sdk::Vision {
3535

3636
std::vector<sdk::Vision::detection> get_detections_from_camera(
3737
const std::string& camera_name, const sdk::ProtoStruct& extra) override;
38-
std::vector<sdk::Vision::detection> get_detections(const sdk::Vision::raw_image& image,
38+
std::vector<sdk::Vision::detection> get_detections(const sdk::Vision::image& img,
3939
const sdk::ProtoStruct& extra) override;
4040
std::vector<sdk::Vision::classification> get_classifications_from_camera(
4141
const std::string& camera_name, int count, const sdk::ProtoStruct& extra) override;
4242
std::vector<sdk::Vision::classification> get_classifications(
43-
const sdk::Vision::raw_image& image, int count, const sdk::ProtoStruct& extra) override;
43+
const sdk::Vision::image& img, int count, const sdk::ProtoStruct& extra) override;
4444
std::vector<sdk::Vision::point_cloud_object> get_object_point_clouds(
4545
const std::string& camera_name,
4646
const std::string& mime_type,

0 commit comments

Comments
 (0)