isar 1.11.0__py3-none-any.whl → 1.12.0__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.
Potentially problematic release.
This version of isar might be problematic. Click here for more details.
- isar/apis/api.py +54 -1
- isar/apis/models/start_mission_definition.py +40 -31
- isar/apis/security/authentication.py +2 -2
- isar/config/keyvault/keyvault_service.py +36 -21
- isar/config/log.py +42 -13
- isar/config/predefined_mission_definition/__init__.py +0 -0
- isar/config/predefined_mission_definition/default_mission.json +98 -0
- isar/config/predefined_mission_definition/default_turtlebot.json +136 -0
- isar/config/predefined_missions/default.json +84 -84
- isar/config/settings.env +5 -0
- isar/config/settings.py +15 -3
- isar/mission_planner/echo_planner.py +1 -1
- isar/models/mission/status.py +5 -5
- isar/models/mission_metadata/mission_metadata.py +2 -0
- isar/modules.py +3 -2
- isar/services/service_connections/request_handler.py +1 -1
- isar/state_machine/state_machine.py +9 -1
- isar/storage/slimm_storage.py +108 -44
- isar/storage/utilities.py +1 -23
- {isar-1.11.0.dist-info → isar-1.12.0.dist-info}/LICENSE +0 -0
- {isar-1.11.0.dist-info → isar-1.12.0.dist-info}/METADATA +4 -1
- {isar-1.11.0.dist-info → isar-1.12.0.dist-info}/RECORD +26 -23
- {isar-1.11.0.dist-info → isar-1.12.0.dist-info}/WHEEL +0 -0
- {isar-1.11.0.dist-info → isar-1.12.0.dist-info}/top_level.txt +0 -0
- robot_interface/models/inspection/inspection.py +3 -22
- robot_interface/models/mission/step.py +4 -30
isar/apis/api.py
CHANGED
|
@@ -1,21 +1,34 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
from http import HTTPStatus
|
|
3
3
|
from logging import Logger
|
|
4
|
+
import os
|
|
4
5
|
from typing import List, Union
|
|
5
6
|
|
|
6
7
|
import click
|
|
7
8
|
import uvicorn
|
|
8
|
-
from fastapi import FastAPI, Security
|
|
9
|
+
from fastapi import FastAPI, Security, Request
|
|
9
10
|
from fastapi.middleware.cors import CORSMiddleware
|
|
10
11
|
from fastapi.routing import APIRouter
|
|
11
12
|
from injector import inject
|
|
12
13
|
from pydantic import AnyHttpUrl
|
|
13
14
|
|
|
15
|
+
from opencensus.ext.azure.trace_exporter import AzureExporter
|
|
16
|
+
from opencensus.trace.samplers import ProbabilitySampler
|
|
17
|
+
from opencensus.trace.tracer import Tracer
|
|
18
|
+
from opencensus.trace.span import SpanKind
|
|
19
|
+
from opencensus.trace.attributes_helper import COMMON_ATTRIBUTES
|
|
20
|
+
|
|
14
21
|
from isar.apis.models.models import ControlMissionResponse, StartMissionResponse
|
|
15
22
|
from isar.apis.schedule.scheduling_controller import SchedulingController
|
|
16
23
|
from isar.apis.security.authentication import Authenticator
|
|
24
|
+
from isar.config.configuration_error import ConfigurationError
|
|
25
|
+
from isar.config.keyvault.keyvault_error import KeyvaultError
|
|
26
|
+
from isar.config.keyvault.keyvault_service import Keyvault
|
|
17
27
|
from isar.config.settings import settings
|
|
18
28
|
|
|
29
|
+
HTTP_URL = COMMON_ATTRIBUTES["HTTP_URL"]
|
|
30
|
+
HTTP_STATUS_CODE = COMMON_ATTRIBUTES["HTTP_STATUS_CODE"]
|
|
31
|
+
|
|
19
32
|
|
|
20
33
|
class API:
|
|
21
34
|
@inject
|
|
@@ -23,12 +36,16 @@ class API:
|
|
|
23
36
|
self,
|
|
24
37
|
authenticator: Authenticator,
|
|
25
38
|
scheduling_controller: SchedulingController,
|
|
39
|
+
keyvault_client: Keyvault,
|
|
26
40
|
port: int = settings.API_PORT,
|
|
41
|
+
azure_ai_logging_enabled: bool = settings.LOG_HANDLER_APPLICATION_INSIGHTS_ENABLED,
|
|
27
42
|
) -> None:
|
|
28
43
|
self.authenticator: Authenticator = authenticator
|
|
29
44
|
self.scheduling_controller: SchedulingController = scheduling_controller
|
|
45
|
+
self.keyvault_client: Keyvault = keyvault_client
|
|
30
46
|
self.host: str = "0.0.0.0" # Locking uvicorn to use 0.0.0.0
|
|
31
47
|
self.port: int = port
|
|
48
|
+
self.azure_ai_logging_enabled: bool = azure_ai_logging_enabled
|
|
32
49
|
|
|
33
50
|
self.logger: Logger = logging.getLogger("api")
|
|
34
51
|
|
|
@@ -76,6 +93,9 @@ class API:
|
|
|
76
93
|
allow_headers=["*"],
|
|
77
94
|
)
|
|
78
95
|
|
|
96
|
+
if self.azure_ai_logging_enabled:
|
|
97
|
+
self._add_request_logging_middleware(app)
|
|
98
|
+
|
|
79
99
|
app.include_router(router=self._create_scheduler_router())
|
|
80
100
|
|
|
81
101
|
app.include_router(router=self._create_info_router())
|
|
@@ -241,3 +261,36 @@ class API:
|
|
|
241
261
|
self.port,
|
|
242
262
|
extra={"color_message": color_message},
|
|
243
263
|
)
|
|
264
|
+
|
|
265
|
+
def _add_request_logging_middleware(self, app: FastAPI) -> None:
|
|
266
|
+
connection_string: str
|
|
267
|
+
try:
|
|
268
|
+
connection_string = self.keyvault_client.get_secret(
|
|
269
|
+
"application-insights-connection-string"
|
|
270
|
+
).value
|
|
271
|
+
except KeyvaultError:
|
|
272
|
+
message: str = (
|
|
273
|
+
"Missing connection string for Application Insights in key vault. "
|
|
274
|
+
)
|
|
275
|
+
self.logger.critical(message)
|
|
276
|
+
raise ConfigurationError(message)
|
|
277
|
+
|
|
278
|
+
@app.middleware("http")
|
|
279
|
+
async def middlewareOpencensus(request: Request, call_next):
|
|
280
|
+
tracer = Tracer(
|
|
281
|
+
exporter=AzureExporter(connection_string=connection_string),
|
|
282
|
+
sampler=ProbabilitySampler(1.0),
|
|
283
|
+
)
|
|
284
|
+
with tracer.span("main") as span:
|
|
285
|
+
span.span_kind = SpanKind.SERVER
|
|
286
|
+
|
|
287
|
+
response = await call_next(request)
|
|
288
|
+
|
|
289
|
+
tracer.add_attribute_to_current_span(
|
|
290
|
+
attribute_key=HTTP_STATUS_CODE, attribute_value=response.status_code
|
|
291
|
+
)
|
|
292
|
+
tracer.add_attribute_to_current_span(
|
|
293
|
+
attribute_key=HTTP_URL, attribute_value=str(request.url)
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
return response
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
from
|
|
1
|
+
from enum import Enum
|
|
2
|
+
from typing import List, Optional
|
|
2
3
|
|
|
3
4
|
from alitra import Position
|
|
4
5
|
from pydantic import BaseModel, Field
|
|
@@ -9,29 +10,32 @@ from isar.models.mission.mission import Mission, Task
|
|
|
9
10
|
from robot_interface.models.mission.step import (
|
|
10
11
|
STEPS,
|
|
11
12
|
DriveToPose,
|
|
12
|
-
InspectionStep,
|
|
13
13
|
TakeImage,
|
|
14
14
|
TakeThermalImage,
|
|
15
15
|
TakeThermalVideo,
|
|
16
16
|
TakeVideo,
|
|
17
17
|
)
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
19
|
+
|
|
20
|
+
class InspectionTypes(str, Enum):
|
|
21
|
+
image = "Image"
|
|
22
|
+
thermal_image = "ThermalImage"
|
|
23
|
+
video = "Video"
|
|
24
|
+
thermal_video = "ThermalVideo"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class StartMissionInspectionDefinition(BaseModel):
|
|
28
|
+
type: InspectionTypes = Field(default=InspectionTypes.image)
|
|
29
|
+
inspection_target: InputPosition
|
|
30
|
+
analysis_types: Optional[List]
|
|
31
|
+
duration: Optional[float]
|
|
32
|
+
metadata: Optional[dict]
|
|
25
33
|
|
|
26
34
|
|
|
27
35
|
class StartMissionTaskDefinition(BaseModel):
|
|
28
36
|
pose: InputPose
|
|
29
37
|
tag: Optional[str]
|
|
30
|
-
|
|
31
|
-
inspection_types: List[str] = Field(
|
|
32
|
-
default=[step.get_inspection_type().__name__ for step in inspection_step_types]
|
|
33
|
-
)
|
|
34
|
-
video_duration: Optional[float] = Field(default=10)
|
|
38
|
+
inspections: List[StartMissionInspectionDefinition]
|
|
35
39
|
|
|
36
40
|
|
|
37
41
|
class StartMissionDefinition(BaseModel):
|
|
@@ -47,12 +51,14 @@ def to_isar_mission(mission_definition: StartMissionDefinition) -> Mission:
|
|
|
47
51
|
drive_step: DriveToPose = DriveToPose(pose=task.pose.to_alitra_pose())
|
|
48
52
|
inspection_steps: List[STEPS] = [
|
|
49
53
|
create_inspection_step(
|
|
50
|
-
inspection_type=
|
|
51
|
-
duration=
|
|
52
|
-
target=
|
|
54
|
+
inspection_type=inspection.type,
|
|
55
|
+
duration=inspection.duration,
|
|
56
|
+
target=inspection.inspection_target.to_alitra_position(),
|
|
53
57
|
tag_id=tag_id,
|
|
58
|
+
analysis=inspection.analysis_types,
|
|
59
|
+
metadata=inspection.metadata,
|
|
54
60
|
)
|
|
55
|
-
for
|
|
61
|
+
for inspection in task.inspections
|
|
56
62
|
]
|
|
57
63
|
except ValueError as e:
|
|
58
64
|
raise MissionPlannerError(f"Failed to create task: {str(e)}")
|
|
@@ -68,27 +74,30 @@ def to_isar_mission(mission_definition: StartMissionDefinition) -> Mission:
|
|
|
68
74
|
|
|
69
75
|
|
|
70
76
|
def create_inspection_step(
|
|
71
|
-
inspection_type:
|
|
77
|
+
inspection_type: InspectionTypes,
|
|
78
|
+
duration: float,
|
|
79
|
+
target: Position,
|
|
80
|
+
analysis: Optional[List],
|
|
81
|
+
tag_id: Optional[str],
|
|
82
|
+
metadata: Optional[dict],
|
|
72
83
|
) -> STEPS:
|
|
73
84
|
inspection_step: STEPS
|
|
74
|
-
|
|
75
|
-
if inspection_type == TakeImage.get_inspection_type().__name__:
|
|
85
|
+
if inspection_type == InspectionTypes.image.value:
|
|
76
86
|
inspection_step = TakeImage(target=target)
|
|
77
|
-
|
|
78
|
-
inspection_step.tag_id = tag_id
|
|
79
|
-
elif inspection_type == TakeVideo.get_inspection_type().__name__:
|
|
87
|
+
elif inspection_type == InspectionTypes.video.value:
|
|
80
88
|
inspection_step = TakeVideo(target=target, duration=duration)
|
|
81
|
-
|
|
82
|
-
inspection_step.tag_id = tag_id
|
|
83
|
-
elif inspection_type == TakeThermalImage.get_inspection_type().__name__:
|
|
89
|
+
elif inspection_type == InspectionTypes.thermal_image.value:
|
|
84
90
|
inspection_step = TakeThermalImage(target=target)
|
|
85
|
-
|
|
86
|
-
inspection_step.tag_id = tag_id
|
|
87
|
-
elif inspection_type == TakeThermalVideo.get_inspection_type().__name__:
|
|
91
|
+
elif inspection_type == InspectionTypes.thermal_video.value:
|
|
88
92
|
inspection_step = TakeThermalVideo(target=target, duration=duration)
|
|
89
|
-
if tag_id:
|
|
90
|
-
inspection_step.tag_id = tag_id
|
|
91
93
|
else:
|
|
92
94
|
raise ValueError(f"Inspection type '{inspection_type}' not supported")
|
|
93
95
|
|
|
96
|
+
if tag_id:
|
|
97
|
+
inspection_step.tag_id = tag_id
|
|
98
|
+
if analysis:
|
|
99
|
+
inspection_step.analysis = analysis
|
|
100
|
+
if metadata:
|
|
101
|
+
inspection_step.metadata = metadata
|
|
102
|
+
|
|
94
103
|
return inspection_step
|
|
@@ -31,8 +31,8 @@ azure_scheme = SingleTenantAzureAuthorizationCodeBearer(
|
|
|
31
31
|
|
|
32
32
|
async def validate_has_role(user: User = Depends(azure_scheme)) -> None:
|
|
33
33
|
"""
|
|
34
|
-
Validate
|
|
35
|
-
Raises a 401
|
|
34
|
+
Validate if the user has the required role in order to access the API.
|
|
35
|
+
Raises a 401 authorization error if not.
|
|
36
36
|
"""
|
|
37
37
|
if settings.REQUIRED_ROLE not in user.roles:
|
|
38
38
|
raise InvalidAuth(
|
|
@@ -21,25 +21,33 @@ class Keyvault:
|
|
|
21
21
|
client_secret: str = None,
|
|
22
22
|
tenant_id: str = None,
|
|
23
23
|
):
|
|
24
|
+
self.name = keyvault_name
|
|
24
25
|
self.url = "https://" + keyvault_name + ".vault.azure.net"
|
|
25
26
|
self.client_id = client_id
|
|
26
27
|
self.client_secret = client_secret
|
|
27
28
|
self.tenant_id = tenant_id
|
|
28
29
|
self.logger = logging.getLogger("API")
|
|
30
|
+
self.client: SecretClient = None
|
|
29
31
|
|
|
30
32
|
def get_secret(self, secret_name: str) -> KeyVaultSecret:
|
|
31
33
|
secret_client: SecretClient = self.get_secret_client()
|
|
32
34
|
try:
|
|
33
35
|
secret: KeyVaultSecret = secret_client.get_secret(name=secret_name)
|
|
34
36
|
except ResourceNotFoundError:
|
|
35
|
-
self.logger.error(
|
|
36
|
-
|
|
37
|
+
self.logger.error(
|
|
38
|
+
"Secret '%s' was not found in keyvault '%s'.",
|
|
39
|
+
secret_name,
|
|
40
|
+
self.name,
|
|
41
|
+
exc_info=True,
|
|
42
|
+
)
|
|
37
43
|
raise KeyvaultError # type: ignore
|
|
38
44
|
except HttpResponseError:
|
|
39
45
|
self.logger.error(
|
|
40
|
-
"An error occurred while retrieving
|
|
46
|
+
"An error occurred while retrieving the secret '%s' from keyvault '%s'.",
|
|
47
|
+
secret_name,
|
|
48
|
+
self.name,
|
|
49
|
+
exc_info=True,
|
|
41
50
|
)
|
|
42
|
-
traceback.print_exc()
|
|
43
51
|
raise KeyvaultError # type: ignore
|
|
44
52
|
|
|
45
53
|
return secret
|
|
@@ -49,25 +57,32 @@ class Keyvault:
|
|
|
49
57
|
try:
|
|
50
58
|
secret_client.set_secret(name=secret_name, value=secret_value)
|
|
51
59
|
except HttpResponseError:
|
|
52
|
-
self.logger.error(
|
|
53
|
-
|
|
60
|
+
self.logger.error(
|
|
61
|
+
"An error occurred while setting secret '%s' in keyvault '%s'.",
|
|
62
|
+
secret_name,
|
|
63
|
+
self.name,
|
|
64
|
+
exc_info=True,
|
|
65
|
+
)
|
|
54
66
|
raise KeyvaultError # type: ignore
|
|
55
67
|
|
|
56
68
|
def get_secret_client(self) -> SecretClient:
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
69
|
+
if self.client == None:
|
|
70
|
+
try:
|
|
71
|
+
credential: Union[ClientSecretCredential, DefaultAzureCredential]
|
|
72
|
+
if self.client_id and self.client_secret and self.tenant_id:
|
|
73
|
+
credential = ClientSecretCredential(
|
|
74
|
+
tenant_id=self.tenant_id,
|
|
75
|
+
client_id=self.client_id,
|
|
76
|
+
client_secret=self.client_secret,
|
|
77
|
+
)
|
|
78
|
+
else:
|
|
79
|
+
credential = DefaultAzureCredential()
|
|
80
|
+
except ClientAuthenticationError:
|
|
81
|
+
self.logger.error(
|
|
82
|
+
"Failed to authenticate to Azure while connecting to KeyVault",
|
|
83
|
+
exc_info=True,
|
|
64
84
|
)
|
|
65
|
-
|
|
66
|
-
credential = DefaultAzureCredential()
|
|
67
|
-
except ClientAuthenticationError:
|
|
68
|
-
self.logger.error("Failed to authenticate to Azure.")
|
|
69
|
-
traceback.print_exc()
|
|
70
|
-
raise KeyvaultError
|
|
85
|
+
raise KeyvaultError
|
|
71
86
|
|
|
72
|
-
|
|
73
|
-
return
|
|
87
|
+
self.client = SecretClient(vault_url=self.url, credential=credential)
|
|
88
|
+
return self.client
|
isar/config/log.py
CHANGED
|
@@ -1,32 +1,61 @@
|
|
|
1
1
|
import importlib.resources as pkg_resources
|
|
2
2
|
import logging
|
|
3
3
|
import logging.config
|
|
4
|
-
|
|
5
4
|
import yaml
|
|
6
5
|
from uvicorn.logging import ColourizedFormatter
|
|
7
|
-
|
|
6
|
+
from opencensus.ext.azure.log_exporter import AzureLogHandler
|
|
7
|
+
from isar.config.configuration_error import ConfigurationError
|
|
8
|
+
from isar.config.keyvault.keyvault_error import KeyvaultError
|
|
9
|
+
from isar.config.keyvault.keyvault_service import Keyvault
|
|
8
10
|
from isar.config.settings import settings
|
|
9
11
|
|
|
10
12
|
|
|
11
|
-
def
|
|
13
|
+
def setup_loggers(keyvault: Keyvault) -> None:
|
|
12
14
|
log_levels: dict = settings.LOG_LEVELS
|
|
13
15
|
with pkg_resources.path("isar.config", "logging.conf") as path:
|
|
14
16
|
log_config = yaml.safe_load(open(path))
|
|
15
17
|
|
|
16
|
-
|
|
18
|
+
logging.config.dictConfig(log_config)
|
|
19
|
+
|
|
20
|
+
handlers = []
|
|
21
|
+
if settings.LOG_HANDLER_LOCAL_ENABLED:
|
|
22
|
+
handlers.append(configure_console_handler(log_config=log_config))
|
|
23
|
+
if settings.LOG_HANDLER_APPLICATION_INSIGHTS_ENABLED:
|
|
24
|
+
handlers.append(
|
|
25
|
+
configure_azure_handler(log_config=log_config, keyvault=keyvault)
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
for log_handler in handlers:
|
|
29
|
+
for loggers in log_config["loggers"].keys():
|
|
30
|
+
logging.getLogger(loggers).addHandler(log_handler)
|
|
31
|
+
logging.getLogger(loggers).setLevel(log_levels[loggers])
|
|
32
|
+
logging.getLogger().addHandler(log_handler)
|
|
33
|
+
|
|
17
34
|
|
|
18
|
-
|
|
19
|
-
|
|
35
|
+
def configure_console_handler(log_config: dict) -> logging.Handler:
|
|
36
|
+
handler = logging.StreamHandler()
|
|
37
|
+
handler.setLevel(log_config["root"]["level"])
|
|
38
|
+
handler.setFormatter(
|
|
20
39
|
ColourizedFormatter(
|
|
21
40
|
log_config["formatters"]["colourized"]["format"],
|
|
22
41
|
style="{",
|
|
23
42
|
use_colors=True,
|
|
24
43
|
)
|
|
25
44
|
)
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
45
|
+
return handler
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def configure_azure_handler(log_config: dict, keyvault: Keyvault) -> logging.Handler:
|
|
49
|
+
connection_string: str
|
|
50
|
+
try:
|
|
51
|
+
connection_string = keyvault.get_secret(
|
|
52
|
+
"application-insights-connection-string"
|
|
53
|
+
).value
|
|
54
|
+
except KeyvaultError:
|
|
55
|
+
message: str = f"CRITICAL ERROR: Missing connection string for Application Insights in key vault '{keyvault.name}'."
|
|
56
|
+
print(f"\n{message} \n")
|
|
57
|
+
raise ConfigurationError(message)
|
|
58
|
+
|
|
59
|
+
handler = AzureLogHandler(connection_string=connection_string)
|
|
60
|
+
handler.setLevel(log_config["root"]["level"])
|
|
61
|
+
return handler
|
|
File without changes
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
{
|
|
2
|
+
"mission_definition": {
|
|
3
|
+
"tasks": [
|
|
4
|
+
{
|
|
5
|
+
"pose": {
|
|
6
|
+
"position": {
|
|
7
|
+
"x": 0,
|
|
8
|
+
"y": 0,
|
|
9
|
+
"z": 0,
|
|
10
|
+
"frame_name": "robot"
|
|
11
|
+
},
|
|
12
|
+
"orientation": {
|
|
13
|
+
"x": 0,
|
|
14
|
+
"y": 0,
|
|
15
|
+
"z": 0,
|
|
16
|
+
"w": 1,
|
|
17
|
+
"frame_name": "robot"
|
|
18
|
+
},
|
|
19
|
+
"frame_name": "robot"
|
|
20
|
+
},
|
|
21
|
+
"tag": "1-A",
|
|
22
|
+
"inspections": [
|
|
23
|
+
{
|
|
24
|
+
"type": "Image",
|
|
25
|
+
"inspection_target": {
|
|
26
|
+
"x": 0,
|
|
27
|
+
"y": 0,
|
|
28
|
+
"z": 0,
|
|
29
|
+
"frame_name": "robot"
|
|
30
|
+
},
|
|
31
|
+
"analysis_types": [
|
|
32
|
+
"CarSeal",
|
|
33
|
+
"Rust"
|
|
34
|
+
],
|
|
35
|
+
"metadata": {
|
|
36
|
+
"zoom": "2x"
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"type": "ThermalVideo",
|
|
41
|
+
"inspection_target": {
|
|
42
|
+
"x": 0,
|
|
43
|
+
"y": 0,
|
|
44
|
+
"z": 0,
|
|
45
|
+
"frame_name": "robot"
|
|
46
|
+
},
|
|
47
|
+
"analysis_types": [
|
|
48
|
+
"GasDetection"
|
|
49
|
+
],
|
|
50
|
+
"duration": 10
|
|
51
|
+
}
|
|
52
|
+
]
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"pose": {
|
|
56
|
+
"position": {
|
|
57
|
+
"x": 1,
|
|
58
|
+
"y": 1,
|
|
59
|
+
"z": 1,
|
|
60
|
+
"frame_name": "robot"
|
|
61
|
+
},
|
|
62
|
+
"orientation": {
|
|
63
|
+
"x": 0,
|
|
64
|
+
"y": 0,
|
|
65
|
+
"z": 0,
|
|
66
|
+
"w": 1,
|
|
67
|
+
"frame_name": "robot"
|
|
68
|
+
},
|
|
69
|
+
"frame_name": "robot"
|
|
70
|
+
},
|
|
71
|
+
"inspections": [
|
|
72
|
+
{
|
|
73
|
+
"type": "ThermalImage",
|
|
74
|
+
"inspection_target": {
|
|
75
|
+
"x": 0,
|
|
76
|
+
"y": 0,
|
|
77
|
+
"z": 0,
|
|
78
|
+
"frame_name": "robot"
|
|
79
|
+
},
|
|
80
|
+
"analysis_types": [
|
|
81
|
+
"ColdSpot",
|
|
82
|
+
"HotSpot"
|
|
83
|
+
]
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
"type": "Video",
|
|
87
|
+
"inspection_target": {
|
|
88
|
+
"x": 0,
|
|
89
|
+
"y": 0,
|
|
90
|
+
"z": 0,
|
|
91
|
+
"frame_name": "robot"
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
]
|
|
95
|
+
}
|
|
96
|
+
]
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
{
|
|
2
|
+
"mission_definition": {
|
|
3
|
+
"tasks": [
|
|
4
|
+
{
|
|
5
|
+
"pose": {
|
|
6
|
+
"position": {
|
|
7
|
+
"x": -3.6,
|
|
8
|
+
"y": 4,
|
|
9
|
+
"z": 0,
|
|
10
|
+
"frame": "asset"
|
|
11
|
+
},
|
|
12
|
+
"orientation": {
|
|
13
|
+
"x": 0,
|
|
14
|
+
"y": 0,
|
|
15
|
+
"z": -0.7286672256879113,
|
|
16
|
+
"w": -0.6848660759820616,
|
|
17
|
+
"frame": "asset"
|
|
18
|
+
},
|
|
19
|
+
"frame_name": "asset"
|
|
20
|
+
},
|
|
21
|
+
"tag": "1-A",
|
|
22
|
+
"inspections": [
|
|
23
|
+
{
|
|
24
|
+
"type": "Image",
|
|
25
|
+
"inspection_target": {
|
|
26
|
+
"x": -4.7,
|
|
27
|
+
"y": 4.9,
|
|
28
|
+
"z": 0,
|
|
29
|
+
"frame": "robot"
|
|
30
|
+
},
|
|
31
|
+
"analysis_types": [
|
|
32
|
+
"CarSeal",
|
|
33
|
+
"Rust"
|
|
34
|
+
],
|
|
35
|
+
"metadata": {
|
|
36
|
+
"zoom": "2x"
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"type": "ThermalImage",
|
|
41
|
+
"inspection_target": {
|
|
42
|
+
"x": -4.7,
|
|
43
|
+
"y": 4.9,
|
|
44
|
+
"z": 0,
|
|
45
|
+
"frame": "robot"
|
|
46
|
+
},
|
|
47
|
+
"analysis_types": [
|
|
48
|
+
"GasDetection"
|
|
49
|
+
]
|
|
50
|
+
}
|
|
51
|
+
]
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"pose": {
|
|
55
|
+
"position": {
|
|
56
|
+
"x": 4.7,
|
|
57
|
+
"y": 3,
|
|
58
|
+
"z": 0,
|
|
59
|
+
"frame": "asset"
|
|
60
|
+
},
|
|
61
|
+
"orientation": {
|
|
62
|
+
"x": 0,
|
|
63
|
+
"y": 0,
|
|
64
|
+
"z": 0.5769585,
|
|
65
|
+
"w": 0.8167734,
|
|
66
|
+
"frame": "asset"
|
|
67
|
+
},
|
|
68
|
+
"frame_name": "asset"
|
|
69
|
+
},
|
|
70
|
+
"tag": "2-B",
|
|
71
|
+
"inspections": [
|
|
72
|
+
{
|
|
73
|
+
"type": "Image",
|
|
74
|
+
"inspection_target": {
|
|
75
|
+
"x": 5.6,
|
|
76
|
+
"y": 5.2,
|
|
77
|
+
"z": 0,
|
|
78
|
+
"frame": "robot"
|
|
79
|
+
},
|
|
80
|
+
"analysis_types": [
|
|
81
|
+
"ColdSpot",
|
|
82
|
+
"HotSpot"
|
|
83
|
+
]
|
|
84
|
+
}
|
|
85
|
+
]
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
"pose": {
|
|
89
|
+
"position": {
|
|
90
|
+
"x": 0.95,
|
|
91
|
+
"y": 2.6,
|
|
92
|
+
"z": 0,
|
|
93
|
+
"frame": "asset"
|
|
94
|
+
},
|
|
95
|
+
"orientation": {
|
|
96
|
+
"x": 0,
|
|
97
|
+
"y": 0,
|
|
98
|
+
"z": -0.6992469,
|
|
99
|
+
"w": 0.7148802,
|
|
100
|
+
"frame": "asset"
|
|
101
|
+
},
|
|
102
|
+
"frame_name": "asset"
|
|
103
|
+
},
|
|
104
|
+
"tag": "3-C",
|
|
105
|
+
"inspections": [
|
|
106
|
+
{
|
|
107
|
+
"type": "Image",
|
|
108
|
+
"inspection_target": {
|
|
109
|
+
"x": 5.6,
|
|
110
|
+
"y": 5.2,
|
|
111
|
+
"z": 0,
|
|
112
|
+
"frame": "robot"
|
|
113
|
+
},
|
|
114
|
+
"analysis_types": [
|
|
115
|
+
"ColdSpot",
|
|
116
|
+
"HotSpot"
|
|
117
|
+
]
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
"type": "ThermalImage",
|
|
121
|
+
"inspection_target": {
|
|
122
|
+
"x": 1.9,
|
|
123
|
+
"y": 1.9,
|
|
124
|
+
"z": 0,
|
|
125
|
+
"frame": "robot"
|
|
126
|
+
},
|
|
127
|
+
"analysis_types": [
|
|
128
|
+
"ColdSpot",
|
|
129
|
+
"HotSpot"
|
|
130
|
+
]
|
|
131
|
+
}
|
|
132
|
+
]
|
|
133
|
+
}
|
|
134
|
+
]
|
|
135
|
+
}
|
|
136
|
+
}
|