bosdyn-client 3.3.2__py3-none-any.whl → 4.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- bosdyn/client/__init__.py +5 -6
- bosdyn/client/area_callback_region_handler_base.py +19 -4
- bosdyn/client/area_callback_service_servicer.py +29 -1
- bosdyn/client/area_callback_service_utils.py +45 -51
- bosdyn/client/auth.py +13 -28
- bosdyn/client/autowalk.py +3 -0
- bosdyn/client/channel.py +23 -26
- bosdyn/client/command_line.py +64 -13
- bosdyn/client/common.py +4 -4
- bosdyn/client/data_acquisition.py +47 -6
- bosdyn/client/data_acquisition_plugin.py +12 -2
- bosdyn/client/data_acquisition_plugin_service.py +33 -2
- bosdyn/client/data_acquisition_store.py +38 -0
- bosdyn/client/data_buffer.py +22 -8
- bosdyn/client/data_chunk.py +1 -0
- bosdyn/client/directory_registration.py +1 -14
- bosdyn/client/exceptions.py +0 -4
- bosdyn/client/frame_helpers.py +3 -1
- bosdyn/client/gps/NMEAParser.py +189 -0
- bosdyn/client/gps/__init__.py +6 -0
- bosdyn/client/gps/aggregator_client.py +56 -0
- bosdyn/client/gps/gps_listener.py +153 -0
- bosdyn/client/gps/registration_client.py +48 -0
- bosdyn/client/graph_nav.py +50 -20
- bosdyn/client/image.py +20 -7
- bosdyn/client/image_service_helpers.py +14 -14
- bosdyn/client/lease.py +27 -22
- bosdyn/client/lease_validator.py +5 -5
- bosdyn/client/manipulation_api_client.py +1 -1
- bosdyn/client/map_processing.py +10 -5
- bosdyn/client/math_helpers.py +21 -11
- bosdyn/client/metrics_logging.py +147 -0
- bosdyn/client/network_compute_bridge_client.py +6 -0
- bosdyn/client/power.py +40 -0
- bosdyn/client/recording.py +3 -3
- bosdyn/client/robot.py +15 -16
- bosdyn/client/robot_command.py +341 -203
- bosdyn/client/robot_id.py +6 -5
- bosdyn/client/robot_state.py +6 -0
- bosdyn/client/sdk.py +5 -11
- bosdyn/client/server_util.py +11 -11
- bosdyn/client/service_customization_helpers.py +776 -64
- bosdyn/client/signals_helpers.py +105 -0
- bosdyn/client/spot_cam/compositor.py +6 -2
- bosdyn/client/spot_cam/ptz.py +24 -14
- bosdyn/client/spot_check.py +160 -0
- bosdyn/client/time_sync.py +5 -5
- bosdyn/client/units_helpers.py +39 -0
- bosdyn/client/util.py +100 -64
- bosdyn/client/world_object.py +5 -5
- {bosdyn_client-3.3.2.dist-info → bosdyn_client-4.0.1.dist-info}/METADATA +4 -3
- bosdyn_client-4.0.1.dist-info/RECORD +97 -0
- {bosdyn_client-3.3.2.dist-info → bosdyn_client-4.0.1.dist-info}/WHEEL +1 -1
- bosdyn/client/log_annotation.py +0 -359
- bosdyn_client-3.3.2.dist-info/RECORD +0 -90
- {bosdyn_client-3.3.2.dist-info → bosdyn_client-4.0.1.dist-info}/top_level.txt +0 -0
bosdyn/client/image.py
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
"""For clients to use the image service."""
|
|
8
8
|
import collections
|
|
9
9
|
import os
|
|
10
|
+
import warnings
|
|
10
11
|
|
|
11
12
|
import numpy as np
|
|
12
13
|
|
|
@@ -336,7 +337,8 @@ def pixel_to_camera_space(image_proto, pixel_x, pixel_y, depth=1.0):
|
|
|
336
337
|
the (u,v) pixel coordinates.
|
|
337
338
|
|
|
338
339
|
Args:
|
|
339
|
-
image_proto (image_pb2.
|
|
340
|
+
image_proto (image_pb2.ImageSource): The image source proto which the pixel coordinates are from.
|
|
341
|
+
Use of image_pb2.ImageCaptureAndSource or image_pb2.ImageResponse types here have been deprecated.
|
|
340
342
|
pixel_x (int): x-coordinate.
|
|
341
343
|
pixel_y (int): y-coordinate.
|
|
342
344
|
depth (double): The depth from the camera to the point of interest.
|
|
@@ -345,14 +347,25 @@ def pixel_to_camera_space(image_proto, pixel_x, pixel_y, depth=1.0):
|
|
|
345
347
|
An (x,y,z) tuple representing the pixel point of interest now described as a point
|
|
346
348
|
in the camera frame.
|
|
347
349
|
"""
|
|
348
|
-
if
|
|
350
|
+
if 'source' in image_proto.ListFields() or isinstance(
|
|
351
|
+
image_proto, image_pb2.ImageResponse) or isinstance(image_proto,
|
|
352
|
+
image_pb2.ImageCaptureAndSource):
|
|
353
|
+
warnings.warn(
|
|
354
|
+
"Use of image_pb2.ImageCaptureAndSource or image_pb2.ImageResponse types for image_proto"
|
|
355
|
+
" argument have been deprecated, use image_pb2.ImageSource instead. version=4.0.0",
|
|
356
|
+
DeprecationWarning, stacklevel=2)
|
|
357
|
+
image_source = image_proto.source
|
|
358
|
+
else:
|
|
359
|
+
image_source = image_proto
|
|
360
|
+
|
|
361
|
+
if not image_source.HasField('pinhole'):
|
|
349
362
|
raise ValueError('Requires a pinhole camera_model.')
|
|
350
363
|
|
|
351
|
-
focal_x =
|
|
352
|
-
principal_x =
|
|
364
|
+
focal_x = image_source.pinhole.intrinsics.focal_length.x
|
|
365
|
+
principal_x = image_source.pinhole.intrinsics.principal_point.x
|
|
353
366
|
|
|
354
|
-
focal_y =
|
|
355
|
-
principal_y =
|
|
367
|
+
focal_y = image_source.pinhole.intrinsics.focal_length.y
|
|
368
|
+
principal_y = image_source.pinhole.intrinsics.principal_point.y
|
|
356
369
|
|
|
357
370
|
x_rt_camera = depth * (pixel_x - principal_x) / focal_x
|
|
358
371
|
y_rt_camera = depth * (pixel_y - principal_y) / focal_y
|
|
@@ -403,7 +416,7 @@ def depth_image_to_pointcloud(image_response, min_dist=0, max_dist=1000):
|
|
|
403
416
|
"""Converts a depth image into a point cloud using the camera intrinsics. The point
|
|
404
417
|
cloud is represented as a numpy array of (x,y,z) values. Requests can optionally filter
|
|
405
418
|
the results based on the points distance to the image plane. A depth image is represented
|
|
406
|
-
with an unsigned 16
|
|
419
|
+
with an unsigned 16-bit integer and a scale factor to convert that distance to meters. In
|
|
407
420
|
addition, values of zero and 2^16 (uint 16 maximum) are used to represent invalid indices.
|
|
408
421
|
A (min_dist * depth_scale) value that casts to an integer value <=0 will be assigned a
|
|
409
422
|
value of 1 (the minimum representational distance). Similarly, a (max_dist * depth_scale)
|
|
@@ -65,9 +65,9 @@ class CameraInterface(ABC):
|
|
|
65
65
|
threads/requests may try to call blocking_capture at the same time. A Lock (self.capture_lock) is provided
|
|
66
66
|
as a convenience to help with this.
|
|
67
67
|
|
|
68
|
-
|
|
68
|
+
Args:
|
|
69
69
|
custom_params (service_customization_pb2.DictParam): Custom parameters defined by the image source
|
|
70
|
-
affecting the resulting capture
|
|
70
|
+
affecting the resulting capture
|
|
71
71
|
**kwargs: Other keyword arguments including future additions to the request that affect the capture.
|
|
72
72
|
If an implementer is looking to use kwargs, it is expected that they update their
|
|
73
73
|
blocking_capture function signature to accept named keyword arguments such as custom_params
|
|
@@ -181,7 +181,7 @@ class VisualImageSource():
|
|
|
181
181
|
|
|
182
182
|
def create_capture_thread(self, custom_params=None):
|
|
183
183
|
"""Initialize a background thread to continuously capture images.
|
|
184
|
-
|
|
184
|
+
|
|
185
185
|
"""
|
|
186
186
|
self.capture_thread = ImageCaptureThread(self.image_source_name, self.capture_function,
|
|
187
187
|
custom_params=custom_params)
|
|
@@ -277,7 +277,7 @@ class VisualImageSource():
|
|
|
277
277
|
Args:
|
|
278
278
|
capture_func (CameraInterface.blocking_capture): The function capturing the image
|
|
279
279
|
data and timestamp.
|
|
280
|
-
custom_params (service_customization_pb2.DictParam): Custom parameters passed to the blocking
|
|
280
|
+
custom_params (service_customization_pb2.DictParam): Custom parameters passed to the blocking
|
|
281
281
|
capture affecting the resulting capture
|
|
282
282
|
**capture_func_kwargs: Other keyword arguments for the capture_func
|
|
283
283
|
"""
|
|
@@ -309,7 +309,7 @@ class VisualImageSource():
|
|
|
309
309
|
"""Retrieve the latest captured image and timestamp.
|
|
310
310
|
|
|
311
311
|
Args:
|
|
312
|
-
custom_params (service_customization_pb2.DictParam): Custom parameters passed to the object's
|
|
312
|
+
custom_params (service_customization_pb2.DictParam): Custom parameters passed to the object's
|
|
313
313
|
capture_function affecting the resulting capture
|
|
314
314
|
**capture_func_kwargs: Other keyword arguments for the capture_function
|
|
315
315
|
|
|
@@ -343,7 +343,7 @@ class VisualImageSource():
|
|
|
343
343
|
image_req (image_pb2.ImageRequest): The image request associated with the image_data.
|
|
344
344
|
|
|
345
345
|
Returns:
|
|
346
|
-
image_pb2.ImageResponse.Status indicating if the
|
|
346
|
+
image_pb2.ImageResponse.Status indicating if the decoding succeeds, or image format conversion or
|
|
347
347
|
pixel conversion failed. Throws a decode data fault if the image or
|
|
348
348
|
pixels cannot be decoded to the desired format. Mutates the image_proto Image proto
|
|
349
349
|
with the decoded data if successful.
|
|
@@ -402,7 +402,7 @@ class VisualImageSource():
|
|
|
402
402
|
image_type (image_pb2.ImageType): The type of image (e.g. visual, depth).
|
|
403
403
|
image_formats (image_pb2.Image.Format): The image formats supported (jpeg, raw)
|
|
404
404
|
pixel_formats (image_pb2.Image.PixelFormat): The pixel formats supported
|
|
405
|
-
param_spec (service_customization_pb2.DictParam.Spec): A set of custom parameters
|
|
405
|
+
param_spec (service_customization_pb2.DictParam.Spec): A set of custom parameters
|
|
406
406
|
passed into this image source
|
|
407
407
|
Returns:
|
|
408
408
|
An ImageSource with the cols, rows, and image type populated.
|
|
@@ -431,7 +431,7 @@ class VisualImageSource():
|
|
|
431
431
|
which returns the gain as a float.
|
|
432
432
|
exposure (float | function): The exposure time for an image in seconds. This can be a fixed
|
|
433
433
|
value or a function which returns the exposure time as a float.
|
|
434
|
-
request_custom_params (service_customization_pb2.DictParam): Custom Params associated with the image
|
|
434
|
+
request_custom_params (service_customization_pb2.DictParam): Custom Params associated with the image
|
|
435
435
|
request. Should not be 'None', but left as an option to accomodate old callers
|
|
436
436
|
Returns:
|
|
437
437
|
An instance of the protobuf CaptureParameters message.
|
|
@@ -457,11 +457,11 @@ class VisualImageSource():
|
|
|
457
457
|
class ThreadCaptureOutput:
|
|
458
458
|
"""Small struct to represent the output of an ImageCaptureThread's get_latest_captured_image
|
|
459
459
|
in a future-compatible way
|
|
460
|
-
|
|
460
|
+
|
|
461
461
|
Args:
|
|
462
462
|
is_valid (Boolean): Whether the latest capture uses the custom parameters and other arguments
|
|
463
463
|
supplied in the latest request, and is therefore returned
|
|
464
|
-
image (Any | None): If the latest capture is valid, the image data in any format
|
|
464
|
+
image (Any | None): If the latest capture is valid, the image data in any format
|
|
465
465
|
(e.g. numpy, bytes, array)
|
|
466
466
|
timestamp (float): The timestamp that the latest valid capture was taken
|
|
467
467
|
"""
|
|
@@ -547,7 +547,7 @@ class ImageCaptureThread():
|
|
|
547
547
|
self.has_updated_capture = True
|
|
548
548
|
|
|
549
549
|
def get_latest_captured_image(self, custom_params=None, **capture_func_kwargs):
|
|
550
|
-
"""Returns the last found image and timestamp in a ThreadCaptureOutput object if that
|
|
550
|
+
"""Returns the last found image and timestamp in a ThreadCaptureOutput object if that
|
|
551
551
|
image uses the latest params. Otherwise returns a ThreadCaptureOutput object with
|
|
552
552
|
is_valid = False and capture/timestamp as None.
|
|
553
553
|
"""
|
|
@@ -567,8 +567,8 @@ class ImageCaptureThread():
|
|
|
567
567
|
# Still, check for the custom_params argument to ensure we accomodate pre-3.3 direct implementations of ImageCaptureThreads
|
|
568
568
|
if "custom_params" in inspect.signature(capture_func).parameters.keys():
|
|
569
569
|
#If using a blocking capture function that takes in custom params or kwargs, one should supply those
|
|
570
|
-
output_capture_function
|
|
571
|
-
|
|
570
|
+
def output_capture_function():
|
|
571
|
+
return capture_func(custom_params=custom_params, **capture_func_kwargs)
|
|
572
572
|
else:
|
|
573
573
|
output_capture_function = capture_func
|
|
574
574
|
return output_capture_function
|
|
@@ -664,7 +664,7 @@ class CameraBaseImageServicer(image_service_pb2_grpc.ImageServiceServicer):
|
|
|
664
664
|
return response
|
|
665
665
|
|
|
666
666
|
def _set_format_and_decode(self, image_data, img_proto, img_req):
|
|
667
|
-
"""Calls the image_decode_with_error_checking function, which returns a (Boolean, Boolean) if the
|
|
667
|
+
"""Calls the image_decode_with_error_checking function, which returns a (Boolean, Boolean) if the decoding succeeds."""
|
|
668
668
|
# This function should set the image data, pixel format, image format, and transform snapshot fields.
|
|
669
669
|
return self.image_sources_mapped[
|
|
670
670
|
img_req.image_source_name].image_decode_with_error_checking(
|
bosdyn/client/lease.py
CHANGED
|
@@ -52,7 +52,7 @@ class ResourceAlreadyClaimedError(LeaseResponseError):
|
|
|
52
52
|
|
|
53
53
|
|
|
54
54
|
class RevokedLeaseError(LeaseResponseError):
|
|
55
|
-
"""Lease is stale because the lease
|
|
55
|
+
"""Lease is stale because the lease-holder did not check in regularly enough."""
|
|
56
56
|
|
|
57
57
|
|
|
58
58
|
class UnmanagedResourceError(LeaseResponseError):
|
|
@@ -135,14 +135,11 @@ class Lease(object):
|
|
|
135
135
|
|
|
136
136
|
Args:
|
|
137
137
|
lease_proto: bosdyn.api.Lease protobuf object.
|
|
138
|
+
Raises:
|
|
139
|
+
ValueError if lease_proto is not present or valid.
|
|
138
140
|
"""
|
|
139
141
|
|
|
140
142
|
def __init__(self, lease_proto, ignore_is_valid_check=False):
|
|
141
|
-
"""Initializes a Lease object.
|
|
142
|
-
|
|
143
|
-
Raises:
|
|
144
|
-
ValueError if lease_proto is not present or valid.
|
|
145
|
-
"""
|
|
146
143
|
if not ignore_is_valid_check and not self.is_valid_proto(lease_proto):
|
|
147
144
|
raise ValueError('invalid lease_proto: {}'.format(lease_proto))
|
|
148
145
|
self.lease_proto = lease_proto
|
|
@@ -289,6 +286,17 @@ class Lease(object):
|
|
|
289
286
|
|
|
290
287
|
|
|
291
288
|
class LeaseState(object):
|
|
289
|
+
"""State of lease ownership in the wallet.
|
|
290
|
+
|
|
291
|
+
Args:
|
|
292
|
+
lease_status(LeaseState.Status): The ownership status of the lease.
|
|
293
|
+
lease_owner(lease_pb2.LeaseOwner): The name of the owner of the lease.
|
|
294
|
+
lease (Lease): The original lease used to initialize the LeaseState, before
|
|
295
|
+
applying any subleasing/incrementing.
|
|
296
|
+
lease_current(Lease): The newest version of the lease (subleased, and
|
|
297
|
+
incremented from the original lease).
|
|
298
|
+
client_name(string): The name of the client using this lease.
|
|
299
|
+
"""
|
|
292
300
|
|
|
293
301
|
class Status(enum.Enum):
|
|
294
302
|
UNOWNED = 0
|
|
@@ -306,16 +314,7 @@ class LeaseState(object):
|
|
|
306
314
|
|
|
307
315
|
def __init__(self, lease_status, lease_owner=None, lease=None, lease_current=None,
|
|
308
316
|
client_name=None):
|
|
309
|
-
|
|
310
|
-
Args:
|
|
311
|
-
lease_status(LeaseState.Status): The ownership status of the lease.
|
|
312
|
-
lease_owner(lease_pb2.LeaseOwner): The name of the owner of the lease.
|
|
313
|
-
lease (Lease): The original lease used to initialize the LeaseState, before
|
|
314
|
-
applying any subleasing/incrementing.
|
|
315
|
-
lease_current(Lease): The newest version of the lease (subleased, and
|
|
316
|
-
incremented from the original lease).
|
|
317
|
-
client_name(string): The name of the client using this lease.
|
|
318
|
-
"""
|
|
317
|
+
|
|
319
318
|
self.lease_status = lease_status
|
|
320
319
|
self.lease_owner = lease_owner
|
|
321
320
|
self.lease_original = lease
|
|
@@ -349,10 +348,16 @@ class LeaseState(object):
|
|
|
349
348
|
"""
|
|
350
349
|
if lease_use_result.status == lease_use_result.STATUS_OLDER:
|
|
351
350
|
if self.lease_current:
|
|
352
|
-
|
|
353
|
-
if
|
|
354
|
-
|
|
355
|
-
|
|
351
|
+
latest_known_lease = Lease(lease_use_result.latest_known_lease)
|
|
352
|
+
if latest_known_lease.is_valid_lease():
|
|
353
|
+
cmp = self.lease_current.compare(latest_known_lease)
|
|
354
|
+
if cmp is Lease.CompareResult.NEWER or cmp is Lease.CompareResult.SAME:
|
|
355
|
+
# The attempted lease was older, but the lease in the wallet has been updated
|
|
356
|
+
# in the meantime to something that is newer than what the robot has seen, so
|
|
357
|
+
# this OLDER result is no longer relevant.
|
|
358
|
+
return self
|
|
359
|
+
# The lease from the lease wallet was an older lease.
|
|
360
|
+
return LeaseState(LeaseState.Status.OTHER_OWNER, lease_owner=lease_use_result.owner)
|
|
356
361
|
elif lease_use_result.status == lease_use_result.STATUS_WRONG_EPOCH:
|
|
357
362
|
if self.lease_current:
|
|
358
363
|
attempted_lease = Lease(lease_use_result.attempted_lease)
|
|
@@ -837,10 +842,10 @@ def add_lease_wallet_processors(client, lease_wallet, resource_list=None):
|
|
|
837
842
|
class LeaseKeepAlive(object):
|
|
838
843
|
"""LeaseKeepAlive issues lease liveness checks on a background thread.
|
|
839
844
|
|
|
840
|
-
The robot's lease system expects lease
|
|
845
|
+
The robot's lease system expects lease-holders to check in at a regular
|
|
841
846
|
cadence. If the check-ins do not happen, the robot will treat it as a
|
|
842
847
|
communications loss. Typically this will result in the robot stopping,
|
|
843
|
-
powering off, and the lease
|
|
848
|
+
powering off, and the lease-holder getting their lease revoked.
|
|
844
849
|
|
|
845
850
|
Using a LeaseKeepAlive object hides most of the details of issuing the
|
|
846
851
|
lease liveness check. Developers can also manage liveness checks directly
|
bosdyn/client/lease_validator.py
CHANGED
|
@@ -268,13 +268,13 @@ class LeaseValidator:
|
|
|
268
268
|
|
|
269
269
|
class LeaseValidatorResponseProcessor: # pylint: disable=too-few-public-methods
|
|
270
270
|
"""LeaseValidatorResponseProcessor updates the lease validator using the
|
|
271
|
-
latest_known_lease from the response's LeaseUseResult.
|
|
271
|
+
latest_known_lease from the response's LeaseUseResult.
|
|
272
|
+
|
|
273
|
+
Args:
|
|
274
|
+
lease_validator (LeaseValidator): validator for a specific robot to be updated.
|
|
275
|
+
"""
|
|
272
276
|
|
|
273
277
|
def __init__(self, lease_validator):
|
|
274
|
-
"""
|
|
275
|
-
Args:
|
|
276
|
-
lease_validator (LeaseValidator): validator for a specific robot to be updated.
|
|
277
|
-
"""
|
|
278
278
|
self.lease_validator = lease_validator
|
|
279
279
|
|
|
280
280
|
def mutate(self, response):
|
bosdyn/client/map_processing.py
CHANGED
|
@@ -34,7 +34,7 @@ class InvalidGraphError(MapProcessingServiceResponseError):
|
|
|
34
34
|
|
|
35
35
|
|
|
36
36
|
class InvalidParamsError(MapProcessingServiceResponseError):
|
|
37
|
-
"""The parameters passed to the optimizer do not make sense (e.g negative weights)."""
|
|
37
|
+
"""The parameters passed to the optimizer do not make sense (e.g. negative weights)."""
|
|
38
38
|
|
|
39
39
|
|
|
40
40
|
class MaxIterationsError(MapProcessingServiceResponseError):
|
|
@@ -167,11 +167,13 @@ class MapProcessingServiceClient(BaseClient):
|
|
|
167
167
|
|
|
168
168
|
@staticmethod
|
|
169
169
|
def _build_process_anchoring_request(params, modify_anchoring_on_server,
|
|
170
|
-
stream_intermediate_results, initial_hint
|
|
170
|
+
stream_intermediate_results, initial_hint,
|
|
171
|
+
apply_gps_results):
|
|
171
172
|
return map_processing_pb2.ProcessAnchoringRequest(
|
|
172
173
|
params=params, initial_hint=initial_hint,
|
|
173
174
|
modify_anchoring_on_server=modify_anchoring_on_server,
|
|
174
|
-
stream_intermediate_results=stream_intermediate_results
|
|
175
|
+
stream_intermediate_results=stream_intermediate_results,
|
|
176
|
+
apply_gps_result_to_waypoints_on_server=apply_gps_results)
|
|
175
177
|
|
|
176
178
|
def process_topology(self, params, modify_map_on_server, **kwargs):
|
|
177
179
|
"""Process the topology of the map on the server, closing loops and producing a
|
|
@@ -196,7 +198,7 @@ class MapProcessingServiceClient(BaseClient):
|
|
|
196
198
|
**kwargs)
|
|
197
199
|
|
|
198
200
|
def process_anchoring(self, params, modify_anchoring_on_server, stream_intermediate_results,
|
|
199
|
-
initial_hint=None, **kwargs):
|
|
201
|
+
initial_hint=None, apply_gps_results=False, **kwargs):
|
|
200
202
|
"""Process the anchoring of the map on the server, producing a metrically consistent anchoring.
|
|
201
203
|
|
|
202
204
|
Args:
|
|
@@ -208,13 +210,16 @@ class MapProcessingServiceClient(BaseClient):
|
|
|
208
210
|
iterations may be included in the response. If false, only the last iteration will be returned.
|
|
209
211
|
initial_hint: Initial guess at some number of waypoints and world objects and their anchorings.
|
|
210
212
|
This field is an AnchoringHint object (see map_processing.proto)
|
|
213
|
+
apply_gps_results: if true, the annotations of waypoints in the graph will be modified to include
|
|
214
|
+
the pose of each waypoint in a GPS centered frame, if the map has GPS (see map_processing.proto)
|
|
211
215
|
Returns:
|
|
212
216
|
The ProcessAnchoringResponse containing a new optimized anchoring.
|
|
213
217
|
Raises:
|
|
214
218
|
RpcError: Problem communicating with the robot
|
|
215
219
|
"""
|
|
216
220
|
request = self._build_process_anchoring_request(params, modify_anchoring_on_server,
|
|
217
|
-
stream_intermediate_results, initial_hint
|
|
221
|
+
stream_intermediate_results, initial_hint,
|
|
222
|
+
apply_gps_results)
|
|
218
223
|
|
|
219
224
|
return self.call(self._stub.ProcessAnchoring, request,
|
|
220
225
|
value_from_response=_get_streamed_anchoring_response,
|
bosdyn/client/math_helpers.py
CHANGED
|
@@ -36,7 +36,7 @@ def angle_diff_degrees(a1, a2):
|
|
|
36
36
|
|
|
37
37
|
|
|
38
38
|
class Vec2(object):
|
|
39
|
-
"""Class representing a two
|
|
39
|
+
"""Class representing a two-dimensional vector."""
|
|
40
40
|
|
|
41
41
|
def __init__(self, x, y):
|
|
42
42
|
self.x = x
|
|
@@ -115,7 +115,7 @@ class Vec2(object):
|
|
|
115
115
|
|
|
116
116
|
|
|
117
117
|
class Vec3(object):
|
|
118
|
-
"""Class representing a three
|
|
118
|
+
"""Class representing a three-dimensional vector."""
|
|
119
119
|
|
|
120
120
|
def __init__(self, x, y, z):
|
|
121
121
|
self.x = x
|
|
@@ -183,6 +183,10 @@ class Vec3(object):
|
|
|
183
183
|
"""Converts the math_helpers.Vec3 into an output of the protobuf geometry_pb2.Vec3."""
|
|
184
184
|
return geometry_pb2.Vec3(x=self.x, y=self.y, z=self.z)
|
|
185
185
|
|
|
186
|
+
def to_numpy(self):
|
|
187
|
+
"""Converts the math_helpers.Vec3 into an output of numpy format."""
|
|
188
|
+
return numpy.array([self.x, self.y, self.z])
|
|
189
|
+
|
|
186
190
|
def dot(self, other):
|
|
187
191
|
if not isinstance(other, Vec3):
|
|
188
192
|
raise TypeError("Can't dot types %s and %s." % (type(self), type(other)))
|
|
@@ -199,6 +203,11 @@ class Vec3(object):
|
|
|
199
203
|
"""Create a math_helpers.Vec3 from a geometry_pb2.Vec3 proto."""
|
|
200
204
|
return Vec3(proto.x, proto.y, proto.z)
|
|
201
205
|
|
|
206
|
+
@staticmethod
|
|
207
|
+
def from_numpy(array):
|
|
208
|
+
"""Create a math_helpers.Vec3 from a numpy array."""
|
|
209
|
+
return Vec3(array[0], array[1], array[2])
|
|
210
|
+
|
|
202
211
|
|
|
203
212
|
class SE2Pose(object):
|
|
204
213
|
"""Class representing an SE2Pose with position and angle."""
|
|
@@ -382,7 +391,7 @@ class SE2Velocity(object):
|
|
|
382
391
|
@staticmethod
|
|
383
392
|
def from_vector(se2_vel_vector):
|
|
384
393
|
"""Converts a 3x1 velocity vector (of either a numpy array or a list) into a math_helpers.SE2Velocity object."""
|
|
385
|
-
if
|
|
394
|
+
if isinstance(se2_vel_vector, list):
|
|
386
395
|
if len(se2_vel_vector) != 3:
|
|
387
396
|
# Must have 3 elements to be a complete SE2Velocity
|
|
388
397
|
print("Velocity list must have 3 elements. The input has the wrong dimension of: " +
|
|
@@ -391,7 +400,7 @@ class SE2Velocity(object):
|
|
|
391
400
|
else:
|
|
392
401
|
return SE2Velocity(x=se2_vel_vector[0], y=se2_vel_vector[1],
|
|
393
402
|
angular=se2_vel_vector[2])
|
|
394
|
-
if
|
|
403
|
+
if isinstance(se2_vel_vector, numpy.ndarray):
|
|
395
404
|
if se2_vel_vector.shape[0] != 3:
|
|
396
405
|
# Must have 3 elements to be a complete SE2Velocity
|
|
397
406
|
print(
|
|
@@ -494,7 +503,7 @@ class SE3Velocity(object):
|
|
|
494
503
|
@staticmethod
|
|
495
504
|
def from_vector(se3_vel_vector):
|
|
496
505
|
"""Converts a 6x1 velocity vector (of either a numpy array or a list) into a math_helpers.SE3Velocity object."""
|
|
497
|
-
if
|
|
506
|
+
if isinstance(se3_vel_vector, list):
|
|
498
507
|
if len(se3_vel_vector) != 6:
|
|
499
508
|
# Must have 6 elements to be a complete SE3Velocity
|
|
500
509
|
print("Velocity list must have 6 elements. The input has the wrong dimension of: " +
|
|
@@ -504,7 +513,7 @@ class SE3Velocity(object):
|
|
|
504
513
|
return SE3Velocity(lin_x=se3_vel_vector[0], lin_y=se3_vel_vector[1],
|
|
505
514
|
lin_z=se3_vel_vector[2], ang_x=se3_vel_vector[3],
|
|
506
515
|
ang_y=se3_vel_vector[4], ang_z=se3_vel_vector[5])
|
|
507
|
-
if
|
|
516
|
+
if isinstance(se3_vel_vector, numpy.ndarray):
|
|
508
517
|
if se3_vel_vector.shape[0] != 6:
|
|
509
518
|
# Must have 6 elements to be a complete SE3Velocity
|
|
510
519
|
print(
|
|
@@ -526,7 +535,7 @@ class SE3Pose(object):
|
|
|
526
535
|
self.z = z
|
|
527
536
|
# Expect the declaration of math_helpers.SE3Pose to pass a math_helpers.Quat, however we will convert
|
|
528
537
|
# a protobuf Quaternion into the math_helpers object as well.
|
|
529
|
-
if
|
|
538
|
+
if isinstance(rot, geometry_pb2.Quaternion):
|
|
530
539
|
rot = Quat.from_proto(rot)
|
|
531
540
|
self.rot = rot
|
|
532
541
|
|
|
@@ -676,6 +685,7 @@ class SE3Pose(object):
|
|
|
676
685
|
for the math_helper.SE3Pose."""
|
|
677
686
|
return geometry_pb2.Vec3(x=self.x, y=self.y, z=self.z)
|
|
678
687
|
|
|
688
|
+
|
|
679
689
|
@property
|
|
680
690
|
def rotation(self):
|
|
681
691
|
"""Property to allow attribute access of the protobuf message field 'rotation' similar to the geometry_pb2.SE3Pose
|
|
@@ -1102,9 +1112,9 @@ def transform_se2velocity(a_adjoint_b_matrix, se2_velocity_in_b):
|
|
|
1102
1112
|
Returns:
|
|
1103
1113
|
math_helpers.SE2Velocity described in frame a. None if the input velocity is an unknown type.
|
|
1104
1114
|
"""
|
|
1105
|
-
if
|
|
1115
|
+
if isinstance(se2_velocity_in_b, geometry_pb2.SE2Velocity):
|
|
1106
1116
|
se2_velocity_in_b_vector = (SE2Velocity.from_proto(se2_velocity_in_b)).to_vector()
|
|
1107
|
-
elif
|
|
1117
|
+
elif isinstance(se2_velocity_in_b, SE2Velocity):
|
|
1108
1118
|
se2_velocity_in_b_vector = se2_velocity_in_b.to_vector()
|
|
1109
1119
|
else:
|
|
1110
1120
|
return None
|
|
@@ -1126,9 +1136,9 @@ def transform_se3velocity(a_adjoint_b_matrix, se3_velocity_in_b):
|
|
|
1126
1136
|
Returns:
|
|
1127
1137
|
math_helpers.SE3Velocity described in frame a. None if the input velocity is an unknown type.
|
|
1128
1138
|
"""
|
|
1129
|
-
if
|
|
1139
|
+
if isinstance(se3_velocity_in_b, geometry_pb2.SE3Velocity):
|
|
1130
1140
|
se3_velocity_in_b_vec = (SE3Velocity.from_proto(se3_velocity_in_b)).to_vector()
|
|
1131
|
-
elif
|
|
1141
|
+
elif isinstance(se3_velocity_in_b, SE3Velocity):
|
|
1132
1142
|
se3_velocity_in_b_vec = se3_velocity_in_b.to_vector()
|
|
1133
1143
|
else:
|
|
1134
1144
|
return None
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# Copyright (c) 2023 Boston Dynamics, Inc. All rights reserved.
|
|
2
|
+
#
|
|
3
|
+
# Downloading, reproducing, distributing or otherwise using the SDK Software
|
|
4
|
+
# is subject to the terms and conditions of the Boston Dynamics Software
|
|
5
|
+
# Development Kit License (20191101-BDSDK-SL).
|
|
6
|
+
|
|
7
|
+
"""Clients for the metrics logging service."""
|
|
8
|
+
import collections
|
|
9
|
+
import threading
|
|
10
|
+
|
|
11
|
+
from google.protobuf.duration_pb2 import Duration
|
|
12
|
+
from google.protobuf.timestamp_pb2 import Timestamp
|
|
13
|
+
|
|
14
|
+
from bosdyn.api.metrics_logging import (absolute_metrics_pb2, metrics_logging_robot_pb2,
|
|
15
|
+
metrics_logging_robot_service_pb2_grpc)
|
|
16
|
+
from bosdyn.client.common import (BaseClient, common_header_errors, error_factory,
|
|
17
|
+
handle_common_header_errors, handle_unset_status_error)
|
|
18
|
+
from bosdyn.client.data_buffer import make_parameter
|
|
19
|
+
from bosdyn.client.exceptions import ResponseError
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class MissingKeysError(ResponseError):
|
|
23
|
+
"""Metrics requested from the metrics service did not exist."""
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class UnableToOptOutError(ResponseError):
|
|
27
|
+
"""Unable to opt-out of metrics logging due to invalid license permissions."""
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class MetricsLoggingClient(BaseClient):
|
|
31
|
+
"""A client for the metrics logging service on the robot.
|
|
32
|
+
"""
|
|
33
|
+
default_service_name = 'metrics-logging'
|
|
34
|
+
service_type = 'bosdyn.api.metrics_logging.MetricsLoggingRobotService'
|
|
35
|
+
|
|
36
|
+
def __init__(self):
|
|
37
|
+
super(MetricsLoggingClient,
|
|
38
|
+
self).__init__(metrics_logging_robot_service_pb2_grpc.MetricsLoggingRobotServiceStub)
|
|
39
|
+
|
|
40
|
+
def get_metrics(self, keys=None, include_events=False, **kwargs):
|
|
41
|
+
"""Get metrics from the robot.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
keys(List[strings]): A list of strings representing the keys for metrics that should be returned.
|
|
45
|
+
include_events(bool): Whether events should be included in the response.
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
The GetMetricsResponse response.
|
|
49
|
+
|
|
50
|
+
Raises:
|
|
51
|
+
RpcError: Problem communicating with the robot.
|
|
52
|
+
"""
|
|
53
|
+
req = self._get_metrics_request(keys, include_events)
|
|
54
|
+
return self.call(self._stub.GetMetrics, req, None, _get_metrics_error_from_response,
|
|
55
|
+
copy_request=False, **kwargs)
|
|
56
|
+
|
|
57
|
+
def get_metrics_async(self, keys=None, include_events=False, **kwargs):
|
|
58
|
+
"""Async version of get_metrics()."""
|
|
59
|
+
req = self._get_metrics_request(keys, include_events)
|
|
60
|
+
return self.call_async(self._stub.GetMetrics, req, None, _get_metrics_error_from_response,
|
|
61
|
+
copy_request=False, **kwargs)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def get_store_sequence_range(self, **kwargs):
|
|
65
|
+
"""Determine the range of sequence numbers currently being used by the metrics system's store.
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
A list where the first number represents the starting index (inclusive), and the second number represents
|
|
69
|
+
the final index (inclusive) for the entries in the metrics store.
|
|
70
|
+
|
|
71
|
+
Raises:
|
|
72
|
+
RpcError: Problem communicating with the robot.
|
|
73
|
+
"""
|
|
74
|
+
req = metrics_logging_robot_pb2.GetStoreSequenceRangeRequest()
|
|
75
|
+
return self.call(self._stub.GetStoreSequenceRange, req,
|
|
76
|
+
self._store_sequence_range_from_response, common_header_errors,
|
|
77
|
+
copy_request=False, **kwargs)
|
|
78
|
+
|
|
79
|
+
def get_store_sequence_range_async(self, **kwargs):
|
|
80
|
+
"""Async version of get_store_sequence_range()."""
|
|
81
|
+
req = metrics_logging_robot_pb2.GetStoreSequenceRangeRequest()
|
|
82
|
+
return self.call_async(self._stub.GetStoreSequenceRange, req,
|
|
83
|
+
self._store_sequence_range_from_response, common_header_errors,
|
|
84
|
+
copy_request=False, **kwargs)
|
|
85
|
+
|
|
86
|
+
def get_absolute_metric_snapshot(self, sequence_numbers, **kwargs):
|
|
87
|
+
"""Get absolute metric snapshots for specific sequence numbers' entries.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
sequence_numbers(List(int)): The list of sequence numbers whose entries should be returned as
|
|
91
|
+
absolute metric snapshots.
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
A list of signed_proto_pb2.SignedProto(). Each signed proto will contain the serialized
|
|
95
|
+
absolute_metric_pb2.AbsoluteMetricsSnapshot() for the requested sequence number's timestamp.
|
|
96
|
+
The robot will exclude snapshots if the sequence number does not exist, or the response will exceed
|
|
97
|
+
the grpc limit.
|
|
98
|
+
|
|
99
|
+
Raises:
|
|
100
|
+
RpcError: Problem communicating with the robot.
|
|
101
|
+
"""
|
|
102
|
+
req = metrics_logging_robot_pb2.GetAbsoluteMetricSnapshotRequest(
|
|
103
|
+
sequence_numbers=sequence_numbers)
|
|
104
|
+
return self.call(self._stub.GetAbsoluteMetricSnapshot, req,
|
|
105
|
+
self._get_absolute_metric_snapshots_from_response, common_header_errors,
|
|
106
|
+
copy_request=False, **kwargs)
|
|
107
|
+
|
|
108
|
+
def get_absolute_metric_snapshot_async(self, sequence_numbers, **kwargs):
|
|
109
|
+
"""Async version of get_absolute_metric_snapshot()."""
|
|
110
|
+
req = metrics_logging_robot_pb2.GetAbsoluteMetricSnapshotRequest(
|
|
111
|
+
sequence_numbers=sequence_numbers)
|
|
112
|
+
return self.call_async(self._stub.GetAbsoluteMetricSnapshot, req,
|
|
113
|
+
self._get_absolute_metric_snapshots_from_response,
|
|
114
|
+
common_header_errors, copy_request=False, **kwargs)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
@staticmethod
|
|
118
|
+
def _get_metrics_request(keys, include_events):
|
|
119
|
+
return metrics_logging_robot_pb2.GetMetricsRequest(keys=keys, include_events=include_events)
|
|
120
|
+
|
|
121
|
+
@staticmethod
|
|
122
|
+
def _store_sequence_range_from_response(response):
|
|
123
|
+
return [response.first_sequence_number, response.last_sequence_number]
|
|
124
|
+
|
|
125
|
+
@staticmethod
|
|
126
|
+
def _get_absolute_metric_snapshots_from_response(response):
|
|
127
|
+
return response.snapshots
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def make_parameter_update(label, value, is_incremental, units="", notes=""):
|
|
131
|
+
"""Create a parameter update proto from the label, param value, and if it is incremental."""
|
|
132
|
+
param_update = absolute_metrics_pb2.ParameterUpdate(incremental=is_incremental)
|
|
133
|
+
param = make_parameter(label, value, units, notes)
|
|
134
|
+
if param is not None:
|
|
135
|
+
param_update.parameter.CopyFrom(param)
|
|
136
|
+
return param_update
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
@handle_common_header_errors
|
|
140
|
+
def _get_metrics_error_from_response(response):
|
|
141
|
+
"""Return an exception based on response from GetMetrics RPC, None if no error."""
|
|
142
|
+
# This could return a MissingKeysError for invalid keys, but at the time of writing
|
|
143
|
+
# this would cause it to throw errors on unreported metrics (not just invalid).
|
|
144
|
+
# Eg, this consistently would throw errors on a new robot with no metrics reported yet.
|
|
145
|
+
return None
|
|
146
|
+
|
|
147
|
+
|
|
@@ -28,6 +28,10 @@ class NetworkComputeRotationError(ResponseError):
|
|
|
28
28
|
"""The robot failed to rotate the image as requested."""
|
|
29
29
|
|
|
30
30
|
|
|
31
|
+
class NetworkComputeAnalysisFailedError(ResponseError):
|
|
32
|
+
"""The model failed to analyze the set of input images, but a retry might work."""
|
|
33
|
+
|
|
34
|
+
|
|
31
35
|
class NetworkComputeBridgeClient(BaseClient):
|
|
32
36
|
"""Client to either the NetworkComputeBridgeService or the NetworkComputeBridgeWorkerService."""
|
|
33
37
|
|
|
@@ -143,6 +147,8 @@ _NETWORK_COMPUTE_STATUS_TO_ERROR.update({
|
|
|
143
147
|
error_pair(ExternalServerError),
|
|
144
148
|
network_compute_bridge_pb2.NETWORK_COMPUTE_STATUS_ROTATION_ERROR:
|
|
145
149
|
error_pair(NetworkComputeRotationError),
|
|
150
|
+
network_compute_bridge_pb2.NETWORK_COMPUTE_STATUS_ANALYSIS_FAILED:
|
|
151
|
+
error_pair(NetworkComputeAnalysisFailedError),
|
|
146
152
|
})
|
|
147
153
|
|
|
148
154
|
|