mapillary-tools 0.13.1__tar.gz → 0.13.2__tar.gz
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.
- {mapillary_tools-0.13.1/mapillary_tools.egg-info → mapillary_tools-0.13.2}/PKG-INFO +1 -1
- mapillary_tools-0.13.2/mapillary_tools/__init__.py +1 -0
- {mapillary_tools-0.13.1 → mapillary_tools-0.13.2}/mapillary_tools/camm/camm_builder.py +62 -14
- mapillary_tools-0.13.2/mapillary_tools/camm/camm_parser.py +342 -0
- {mapillary_tools-0.13.1 → mapillary_tools-0.13.2}/mapillary_tools/telemetry.py +8 -20
- {mapillary_tools-0.13.1 → mapillary_tools-0.13.2}/mapillary_tools/upload.py +3 -2
- {mapillary_tools-0.13.1 → mapillary_tools-0.13.2/mapillary_tools.egg-info}/PKG-INFO +1 -1
- mapillary_tools-0.13.1/mapillary_tools/__init__.py +0 -1
- mapillary_tools-0.13.1/mapillary_tools/camm/camm_parser.py +0 -561
- {mapillary_tools-0.13.1 → mapillary_tools-0.13.2}/LICENSE +0 -0
- {mapillary_tools-0.13.1 → mapillary_tools-0.13.2}/MANIFEST.in +0 -0
- {mapillary_tools-0.13.1 → mapillary_tools-0.13.2}/README.md +0 -0
- {mapillary_tools-0.13.1 → mapillary_tools-0.13.2}/mapillary_tools/api_v4.py +0 -0
- {mapillary_tools-0.13.1 → mapillary_tools-0.13.2}/mapillary_tools/authenticate.py +0 -0
- {mapillary_tools-0.13.1 → mapillary_tools-0.13.2}/mapillary_tools/commands/__init__.py +0 -0
- {mapillary_tools-0.13.1 → mapillary_tools-0.13.2}/mapillary_tools/commands/__main__.py +0 -0
- {mapillary_tools-0.13.1 → mapillary_tools-0.13.2}/mapillary_tools/commands/authenticate.py +0 -0
- {mapillary_tools-0.13.1 → mapillary_tools-0.13.2}/mapillary_tools/commands/process.py +0 -0
- {mapillary_tools-0.13.1 → mapillary_tools-0.13.2}/mapillary_tools/commands/process_and_upload.py +0 -0
- {mapillary_tools-0.13.1 → mapillary_tools-0.13.2}/mapillary_tools/commands/sample_video.py +0 -0
- {mapillary_tools-0.13.1 → mapillary_tools-0.13.2}/mapillary_tools/commands/upload.py +0 -0
- {mapillary_tools-0.13.1 → mapillary_tools-0.13.2}/mapillary_tools/commands/video_process.py +0 -0
- {mapillary_tools-0.13.1 → mapillary_tools-0.13.2}/mapillary_tools/commands/video_process_and_upload.py +0 -0
- {mapillary_tools-0.13.1 → mapillary_tools-0.13.2}/mapillary_tools/commands/zip.py +0 -0
- {mapillary_tools-0.13.1 → mapillary_tools-0.13.2}/mapillary_tools/config.py +0 -0
- {mapillary_tools-0.13.1 → mapillary_tools-0.13.2}/mapillary_tools/constants.py +0 -0
- {mapillary_tools-0.13.1 → mapillary_tools-0.13.2}/mapillary_tools/exceptions.py +0 -0
- {mapillary_tools-0.13.1 → mapillary_tools-0.13.2}/mapillary_tools/exif_read.py +0 -0
- {mapillary_tools-0.13.1 → mapillary_tools-0.13.2}/mapillary_tools/exif_write.py +0 -0
- {mapillary_tools-0.13.1 → mapillary_tools-0.13.2}/mapillary_tools/exiftool_read.py +0 -0
- {mapillary_tools-0.13.1 → mapillary_tools-0.13.2}/mapillary_tools/exiftool_read_video.py +0 -0
- {mapillary_tools-0.13.1 → mapillary_tools-0.13.2}/mapillary_tools/ffmpeg.py +0 -0
- {mapillary_tools-0.13.1 → mapillary_tools-0.13.2}/mapillary_tools/geo.py +0 -0
- {mapillary_tools-0.13.1 → mapillary_tools-0.13.2}/mapillary_tools/geotag/__init__.py +0 -0
- {mapillary_tools-0.13.1 → mapillary_tools-0.13.2}/mapillary_tools/geotag/blackvue_parser.py +0 -0
- {mapillary_tools-0.13.1 → mapillary_tools-0.13.2}/mapillary_tools/geotag/geotag_from_generic.py +0 -0
- {mapillary_tools-0.13.1 → mapillary_tools-0.13.2}/mapillary_tools/geotag/geotag_images_from_exif.py +0 -0
- {mapillary_tools-0.13.1 → mapillary_tools-0.13.2}/mapillary_tools/geotag/geotag_images_from_exiftool.py +0 -0
- {mapillary_tools-0.13.1 → mapillary_tools-0.13.2}/mapillary_tools/geotag/geotag_images_from_exiftool_both_image_and_video.py +0 -0
- {mapillary_tools-0.13.1 → mapillary_tools-0.13.2}/mapillary_tools/geotag/geotag_images_from_gpx.py +0 -0
- {mapillary_tools-0.13.1 → mapillary_tools-0.13.2}/mapillary_tools/geotag/geotag_images_from_gpx_file.py +0 -0
- {mapillary_tools-0.13.1 → mapillary_tools-0.13.2}/mapillary_tools/geotag/geotag_images_from_nmea_file.py +0 -0
- {mapillary_tools-0.13.1 → mapillary_tools-0.13.2}/mapillary_tools/geotag/geotag_images_from_video.py +0 -0
- {mapillary_tools-0.13.1 → mapillary_tools-0.13.2}/mapillary_tools/geotag/geotag_videos_from_exiftool_video.py +0 -0
- {mapillary_tools-0.13.1 → mapillary_tools-0.13.2}/mapillary_tools/geotag/geotag_videos_from_video.py +0 -0
- {mapillary_tools-0.13.1 → mapillary_tools-0.13.2}/mapillary_tools/geotag/gpmf_gps_filter.py +0 -0
- {mapillary_tools-0.13.1 → mapillary_tools-0.13.2}/mapillary_tools/geotag/gpmf_parser.py +0 -0
- {mapillary_tools-0.13.1 → mapillary_tools-0.13.2}/mapillary_tools/geotag/gps_filter.py +0 -0
- {mapillary_tools-0.13.1 → mapillary_tools-0.13.2}/mapillary_tools/geotag/utils.py +0 -0
- {mapillary_tools-0.13.1 → mapillary_tools-0.13.2}/mapillary_tools/history.py +0 -0
- {mapillary_tools-0.13.1 → mapillary_tools-0.13.2}/mapillary_tools/ipc.py +0 -0
- {mapillary_tools-0.13.1 → mapillary_tools-0.13.2}/mapillary_tools/mp4/__init__.py +0 -0
- {mapillary_tools-0.13.1 → mapillary_tools-0.13.2}/mapillary_tools/mp4/construct_mp4_parser.py +0 -0
- {mapillary_tools-0.13.1 → mapillary_tools-0.13.2}/mapillary_tools/mp4/io_utils.py +0 -0
- {mapillary_tools-0.13.1 → mapillary_tools-0.13.2}/mapillary_tools/mp4/mp4_sample_parser.py +0 -0
- {mapillary_tools-0.13.1 → mapillary_tools-0.13.2}/mapillary_tools/mp4/simple_mp4_builder.py +0 -0
- {mapillary_tools-0.13.1 → mapillary_tools-0.13.2}/mapillary_tools/mp4/simple_mp4_parser.py +0 -0
- {mapillary_tools-0.13.1 → mapillary_tools-0.13.2}/mapillary_tools/process_geotag_properties.py +0 -0
- {mapillary_tools-0.13.1 → mapillary_tools-0.13.2}/mapillary_tools/process_sequence_properties.py +0 -0
- {mapillary_tools-0.13.1 → mapillary_tools-0.13.2}/mapillary_tools/sample_video.py +0 -0
- {mapillary_tools-0.13.1 → mapillary_tools-0.13.2}/mapillary_tools/types.py +0 -0
- {mapillary_tools-0.13.1 → mapillary_tools-0.13.2}/mapillary_tools/upload_api_v4.py +0 -0
- {mapillary_tools-0.13.1 → mapillary_tools-0.13.2}/mapillary_tools/uploader.py +0 -0
- {mapillary_tools-0.13.1 → mapillary_tools-0.13.2}/mapillary_tools/utils.py +0 -0
- {mapillary_tools-0.13.1 → mapillary_tools-0.13.2}/mapillary_tools/video_data_extraction/cli_options.py +0 -0
- {mapillary_tools-0.13.1 → mapillary_tools-0.13.2}/mapillary_tools/video_data_extraction/extract_video_data.py +0 -0
- {mapillary_tools-0.13.1 → mapillary_tools-0.13.2}/mapillary_tools/video_data_extraction/extractors/base_parser.py +0 -0
- {mapillary_tools-0.13.1 → mapillary_tools-0.13.2}/mapillary_tools/video_data_extraction/extractors/blackvue_parser.py +0 -0
- {mapillary_tools-0.13.1 → mapillary_tools-0.13.2}/mapillary_tools/video_data_extraction/extractors/camm_parser.py +0 -0
- {mapillary_tools-0.13.1 → mapillary_tools-0.13.2}/mapillary_tools/video_data_extraction/extractors/exiftool_runtime_parser.py +0 -0
- {mapillary_tools-0.13.1 → mapillary_tools-0.13.2}/mapillary_tools/video_data_extraction/extractors/exiftool_xml_parser.py +0 -0
- {mapillary_tools-0.13.1 → mapillary_tools-0.13.2}/mapillary_tools/video_data_extraction/extractors/generic_video_parser.py +0 -0
- {mapillary_tools-0.13.1 → mapillary_tools-0.13.2}/mapillary_tools/video_data_extraction/extractors/gopro_parser.py +0 -0
- {mapillary_tools-0.13.1 → mapillary_tools-0.13.2}/mapillary_tools/video_data_extraction/extractors/gpx_parser.py +0 -0
- {mapillary_tools-0.13.1 → mapillary_tools-0.13.2}/mapillary_tools/video_data_extraction/extractors/nmea_parser.py +0 -0
- {mapillary_tools-0.13.1 → mapillary_tools-0.13.2}/mapillary_tools/video_data_extraction/video_data_parser_factory.py +0 -0
- {mapillary_tools-0.13.1 → mapillary_tools-0.13.2}/mapillary_tools.egg-info/SOURCES.txt +0 -0
- {mapillary_tools-0.13.1 → mapillary_tools-0.13.2}/mapillary_tools.egg-info/dependency_links.txt +0 -0
- {mapillary_tools-0.13.1 → mapillary_tools-0.13.2}/mapillary_tools.egg-info/entry_points.txt +0 -0
- {mapillary_tools-0.13.1 → mapillary_tools-0.13.2}/mapillary_tools.egg-info/requires.txt +0 -0
- {mapillary_tools-0.13.1 → mapillary_tools-0.13.2}/mapillary_tools.egg-info/top_level.txt +0 -0
- {mapillary_tools-0.13.1 → mapillary_tools-0.13.2}/requirements.txt +0 -0
- {mapillary_tools-0.13.1 → mapillary_tools-0.13.2}/schema/image_description_schema.json +0 -0
- {mapillary_tools-0.13.1 → mapillary_tools-0.13.2}/setup.cfg +0 -0
- {mapillary_tools-0.13.1 → mapillary_tools-0.13.2}/setup.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
VERSION = "0.13.2"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import io
|
|
2
2
|
import typing as T
|
|
3
3
|
|
|
4
|
-
from .. import geo, types
|
|
4
|
+
from .. import geo, telemetry, types
|
|
5
5
|
from ..mp4 import (
|
|
6
6
|
construct_mp4_parser as cparser,
|
|
7
7
|
mp4_sample_parser as sample_parser,
|
|
@@ -11,11 +11,62 @@ from ..mp4 import (
|
|
|
11
11
|
from . import camm_parser
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
TelemetryMeasurement = T.Union[
|
|
15
|
+
geo.Point,
|
|
16
|
+
telemetry.TelemetryMeasurement,
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _build_camm_sample(measurement: TelemetryMeasurement) -> bytes:
|
|
21
|
+
if isinstance(measurement, geo.Point):
|
|
22
|
+
return camm_parser.CAMMSampleData.build(
|
|
23
|
+
{
|
|
24
|
+
"type": camm_parser.CAMMType.MIN_GPS.value,
|
|
25
|
+
"data": [
|
|
26
|
+
measurement.lat,
|
|
27
|
+
measurement.lon,
|
|
28
|
+
-1.0 if measurement.alt is None else measurement.alt,
|
|
29
|
+
],
|
|
30
|
+
}
|
|
31
|
+
)
|
|
32
|
+
elif isinstance(measurement, telemetry.AccelerationData):
|
|
33
|
+
# Accelerometer reading in meters/second^2 along XYZ axes of the camera.
|
|
34
|
+
return camm_parser.CAMMSampleData.build(
|
|
35
|
+
{
|
|
36
|
+
"type": camm_parser.CAMMType.ACCELERATION.value,
|
|
37
|
+
"data": [
|
|
38
|
+
measurement.x,
|
|
39
|
+
measurement.y,
|
|
40
|
+
measurement.z,
|
|
41
|
+
],
|
|
42
|
+
}
|
|
43
|
+
)
|
|
44
|
+
elif isinstance(measurement, telemetry.GyroscopeData):
|
|
45
|
+
# Gyroscope signal in radians/seconds around XYZ axes of the camera. Rotation is positive in the counterclockwise direction.
|
|
46
|
+
return camm_parser.CAMMSampleData.build(
|
|
47
|
+
{
|
|
48
|
+
"type": camm_parser.CAMMType.GYRO.value,
|
|
49
|
+
"data": [
|
|
50
|
+
measurement.x,
|
|
51
|
+
measurement.y,
|
|
52
|
+
measurement.z,
|
|
53
|
+
],
|
|
54
|
+
}
|
|
55
|
+
)
|
|
56
|
+
elif isinstance(measurement, telemetry.MagnetometerData):
|
|
57
|
+
# Ambient magnetic field.
|
|
58
|
+
return camm_parser.CAMMSampleData.build(
|
|
59
|
+
{
|
|
60
|
+
"type": camm_parser.CAMMType.MAGNETIC_FIELD.value,
|
|
61
|
+
"data": [
|
|
62
|
+
measurement.x,
|
|
63
|
+
measurement.y,
|
|
64
|
+
measurement.z,
|
|
65
|
+
],
|
|
66
|
+
}
|
|
67
|
+
)
|
|
68
|
+
else:
|
|
69
|
+
raise ValueError(f"unexpected measurement type {type(measurement)}")
|
|
19
70
|
|
|
20
71
|
|
|
21
72
|
def _create_edit_list_from_points(
|
|
@@ -70,19 +121,16 @@ def _create_edit_list_from_points(
|
|
|
70
121
|
|
|
71
122
|
def _multiplex(
|
|
72
123
|
points: T.Sequence[geo.Point],
|
|
73
|
-
measurements: T.Optional[T.List[
|
|
74
|
-
) -> T.List[
|
|
75
|
-
mutiplexed: T.List[
|
|
76
|
-
*points,
|
|
77
|
-
*(measurements or []),
|
|
78
|
-
]
|
|
124
|
+
measurements: T.Optional[T.List[telemetry.TelemetryMeasurement]] = None,
|
|
125
|
+
) -> T.List[TelemetryMeasurement]:
|
|
126
|
+
mutiplexed: T.List[TelemetryMeasurement] = [*points, *(measurements or [])]
|
|
79
127
|
mutiplexed.sort(key=lambda m: m.time)
|
|
80
128
|
|
|
81
129
|
return mutiplexed
|
|
82
130
|
|
|
83
131
|
|
|
84
132
|
def convert_telemetry_to_raw_samples(
|
|
85
|
-
measurements: T.Sequence[
|
|
133
|
+
measurements: T.Sequence[TelemetryMeasurement],
|
|
86
134
|
timescale: int,
|
|
87
135
|
) -> T.Generator[sample_parser.RawSample, None, None]:
|
|
88
136
|
for idx, measurement in enumerate(measurements):
|
|
@@ -235,7 +283,7 @@ def create_camm_trak(
|
|
|
235
283
|
|
|
236
284
|
def camm_sample_generator2(
|
|
237
285
|
video_metadata: types.VideoMetadata,
|
|
238
|
-
telemetry_measurements: T.Optional[T.List[
|
|
286
|
+
telemetry_measurements: T.Optional[T.List[telemetry.TelemetryMeasurement]] = None,
|
|
239
287
|
):
|
|
240
288
|
def _f(
|
|
241
289
|
fp: T.BinaryIO,
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
# pyre-ignore-all-errors[5, 11, 16, 21, 24, 58]
|
|
2
|
+
|
|
3
|
+
import dataclasses
|
|
4
|
+
import io
|
|
5
|
+
import logging
|
|
6
|
+
import pathlib
|
|
7
|
+
import typing as T
|
|
8
|
+
from enum import Enum
|
|
9
|
+
|
|
10
|
+
import construct as C
|
|
11
|
+
|
|
12
|
+
from .. import geo, telemetry
|
|
13
|
+
from ..mp4 import simple_mp4_parser as sparser
|
|
14
|
+
from ..mp4.mp4_sample_parser import MovieBoxParser, Sample, TrackBoxParser
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
LOG = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
TelemetryMeasurement = T.Union[
|
|
21
|
+
geo.Point,
|
|
22
|
+
telemetry.AccelerationData,
|
|
23
|
+
telemetry.GyroscopeData,
|
|
24
|
+
telemetry.MagnetometerData,
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# Camera Motion Metadata Spec https://developers.google.com/streetview/publish/camm-spec
|
|
29
|
+
class CAMMType(Enum):
|
|
30
|
+
ANGLE_AXIS = 0
|
|
31
|
+
EXPOSURE_TIME = 1
|
|
32
|
+
GYRO = 2
|
|
33
|
+
ACCELERATION = 3
|
|
34
|
+
POSITION = 4
|
|
35
|
+
MIN_GPS = 5
|
|
36
|
+
GPS = 6
|
|
37
|
+
MAGNETIC_FIELD = 7
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
# All fields are little-endian
|
|
41
|
+
Float = C.Float32l
|
|
42
|
+
Double = C.Float64l
|
|
43
|
+
|
|
44
|
+
_SWITCH: T.Dict[int, C.Struct] = {
|
|
45
|
+
# angle_axis
|
|
46
|
+
CAMMType.ANGLE_AXIS.value: Float[3],
|
|
47
|
+
CAMMType.EXPOSURE_TIME.value: C.Struct(
|
|
48
|
+
"pixel_exposure_time" / C.Int32sl,
|
|
49
|
+
"rolling_shutter_skew_time" / C.Int32sl,
|
|
50
|
+
),
|
|
51
|
+
# gyro
|
|
52
|
+
CAMMType.GYRO.value: Float[3],
|
|
53
|
+
# acceleration
|
|
54
|
+
CAMMType.ACCELERATION.value: Float[3],
|
|
55
|
+
# position
|
|
56
|
+
CAMMType.POSITION.value: Float[3],
|
|
57
|
+
# lat, lon, alt
|
|
58
|
+
CAMMType.MIN_GPS.value: Double[3],
|
|
59
|
+
CAMMType.GPS.value: C.Struct(
|
|
60
|
+
"time_gps_epoch" / Double,
|
|
61
|
+
"gps_fix_type" / C.Int32sl,
|
|
62
|
+
"latitude" / Double,
|
|
63
|
+
"longitude" / Double,
|
|
64
|
+
"altitude" / Float,
|
|
65
|
+
"horizontal_accuracy" / Float,
|
|
66
|
+
"vertical_accuracy" / Float,
|
|
67
|
+
"velocity_east" / Float,
|
|
68
|
+
"velocity_north" / Float,
|
|
69
|
+
"velocity_up" / Float,
|
|
70
|
+
"speed_accuracy" / Float,
|
|
71
|
+
),
|
|
72
|
+
# magnetic_field
|
|
73
|
+
CAMMType.MAGNETIC_FIELD.value: Float[3],
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
CAMMSampleData = C.Struct(
|
|
77
|
+
C.Padding(2),
|
|
78
|
+
"type" / C.Int16ul,
|
|
79
|
+
"data"
|
|
80
|
+
/ C.Switch(
|
|
81
|
+
C.this.type,
|
|
82
|
+
_SWITCH,
|
|
83
|
+
),
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _parse_telemetry_from_sample(
|
|
88
|
+
fp: T.BinaryIO, sample: Sample
|
|
89
|
+
) -> T.Optional[TelemetryMeasurement]:
|
|
90
|
+
fp.seek(sample.raw_sample.offset, io.SEEK_SET)
|
|
91
|
+
data = fp.read(sample.raw_sample.size)
|
|
92
|
+
box = CAMMSampleData.parse(data)
|
|
93
|
+
if box.type == CAMMType.MIN_GPS.value:
|
|
94
|
+
return geo.Point(
|
|
95
|
+
time=sample.exact_time,
|
|
96
|
+
lat=box.data[0],
|
|
97
|
+
lon=box.data[1],
|
|
98
|
+
alt=box.data[2],
|
|
99
|
+
angle=None,
|
|
100
|
+
)
|
|
101
|
+
elif box.type == CAMMType.GPS.value:
|
|
102
|
+
# Not using box.data.time_gps_epoch as the point timestamp
|
|
103
|
+
# because it is from another clock
|
|
104
|
+
return geo.Point(
|
|
105
|
+
time=sample.exact_time,
|
|
106
|
+
lat=box.data.latitude,
|
|
107
|
+
lon=box.data.longitude,
|
|
108
|
+
alt=box.data.altitude,
|
|
109
|
+
angle=None,
|
|
110
|
+
)
|
|
111
|
+
elif box.type == CAMMType.ACCELERATION.value:
|
|
112
|
+
return telemetry.AccelerationData(
|
|
113
|
+
time=sample.exact_time,
|
|
114
|
+
x=box.data[0],
|
|
115
|
+
y=box.data[1],
|
|
116
|
+
z=box.data[2],
|
|
117
|
+
)
|
|
118
|
+
elif box.type == CAMMType.GYRO.value:
|
|
119
|
+
return telemetry.GyroscopeData(
|
|
120
|
+
time=sample.exact_time,
|
|
121
|
+
x=box.data[0],
|
|
122
|
+
y=box.data[1],
|
|
123
|
+
z=box.data[2],
|
|
124
|
+
)
|
|
125
|
+
elif box.type == CAMMType.MAGNETIC_FIELD.value:
|
|
126
|
+
return telemetry.MagnetometerData(
|
|
127
|
+
time=sample.exact_time,
|
|
128
|
+
x=box.data[0],
|
|
129
|
+
y=box.data[1],
|
|
130
|
+
z=box.data[2],
|
|
131
|
+
)
|
|
132
|
+
return None
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _filter_telemetry_by_elst_segments(
|
|
136
|
+
measurements: T.Iterable[TelemetryMeasurement],
|
|
137
|
+
elst: T.Sequence[T.Tuple[float, float]],
|
|
138
|
+
) -> T.Generator[TelemetryMeasurement, None, None]:
|
|
139
|
+
empty_elst = [entry for entry in elst if entry[0] == -1]
|
|
140
|
+
if empty_elst:
|
|
141
|
+
offset = empty_elst[-1][1]
|
|
142
|
+
else:
|
|
143
|
+
offset = 0
|
|
144
|
+
|
|
145
|
+
elst = [entry for entry in elst if entry[0] != -1]
|
|
146
|
+
|
|
147
|
+
if not elst:
|
|
148
|
+
for m in measurements:
|
|
149
|
+
yield dataclasses.replace(m, time=m.time + offset)
|
|
150
|
+
return
|
|
151
|
+
|
|
152
|
+
elst.sort(key=lambda entry: entry[0])
|
|
153
|
+
elst_idx = 0
|
|
154
|
+
for m in measurements:
|
|
155
|
+
if len(elst) <= elst_idx:
|
|
156
|
+
break
|
|
157
|
+
media_time, duration = elst[elst_idx]
|
|
158
|
+
if m.time < media_time:
|
|
159
|
+
pass
|
|
160
|
+
elif m.time <= media_time + duration:
|
|
161
|
+
yield dataclasses.replace(m, time=m.time + offset)
|
|
162
|
+
else:
|
|
163
|
+
elst_idx += 1
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def elst_entry_to_seconds(
|
|
167
|
+
entry: T.Dict, movie_timescale: int, media_timescale: int
|
|
168
|
+
) -> T.Tuple[float, float]:
|
|
169
|
+
assert movie_timescale > 0, "expected positive movie_timescale"
|
|
170
|
+
assert media_timescale > 0, "expected positive media_timescale"
|
|
171
|
+
media_time, duration = entry["media_time"], entry["segment_duration"]
|
|
172
|
+
if media_time != -1:
|
|
173
|
+
media_time = media_time / media_timescale
|
|
174
|
+
duration = duration / movie_timescale
|
|
175
|
+
return (media_time, duration)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def _is_camm_description(description: T.Dict) -> bool:
|
|
179
|
+
return description["format"] == b"camm"
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def _contains_camm_description(track: TrackBoxParser) -> bool:
|
|
183
|
+
descriptions = track.extract_sample_descriptions()
|
|
184
|
+
return any(_is_camm_description(d) for d in descriptions)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def _filter_telemetry_by_track_elst(
|
|
188
|
+
moov: MovieBoxParser,
|
|
189
|
+
track: TrackBoxParser,
|
|
190
|
+
measurements: T.Iterable[TelemetryMeasurement],
|
|
191
|
+
) -> T.List[TelemetryMeasurement]:
|
|
192
|
+
elst_boxdata = track.extract_elst_boxdata()
|
|
193
|
+
|
|
194
|
+
if elst_boxdata is not None:
|
|
195
|
+
elst_entries = elst_boxdata["entries"]
|
|
196
|
+
if elst_entries:
|
|
197
|
+
# media_timescale
|
|
198
|
+
mdhd_boxdata = track.extract_mdhd_boxdata()
|
|
199
|
+
media_timescale = mdhd_boxdata["timescale"]
|
|
200
|
+
|
|
201
|
+
# movie_timescale
|
|
202
|
+
mvhd_boxdata = moov.extract_mvhd_boxdata()
|
|
203
|
+
movie_timescale = mvhd_boxdata["timescale"]
|
|
204
|
+
|
|
205
|
+
segments = [
|
|
206
|
+
elst_entry_to_seconds(
|
|
207
|
+
entry,
|
|
208
|
+
movie_timescale=movie_timescale,
|
|
209
|
+
media_timescale=media_timescale,
|
|
210
|
+
)
|
|
211
|
+
for entry in elst_entries
|
|
212
|
+
]
|
|
213
|
+
|
|
214
|
+
return list(_filter_telemetry_by_elst_segments(measurements, segments))
|
|
215
|
+
|
|
216
|
+
return list(measurements)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def extract_points(fp: T.BinaryIO) -> T.Optional[T.List[geo.Point]]:
|
|
220
|
+
"""
|
|
221
|
+
Return a list of points (could be empty) if it is a valid CAMM video,
|
|
222
|
+
otherwise None
|
|
223
|
+
"""
|
|
224
|
+
|
|
225
|
+
moov = MovieBoxParser.parse_stream(fp)
|
|
226
|
+
|
|
227
|
+
for track in moov.extract_tracks():
|
|
228
|
+
if _contains_camm_description(track):
|
|
229
|
+
maybe_measurements = (
|
|
230
|
+
_parse_telemetry_from_sample(fp, sample)
|
|
231
|
+
for sample in track.extract_samples()
|
|
232
|
+
if _is_camm_description(sample.description)
|
|
233
|
+
)
|
|
234
|
+
points = [m for m in maybe_measurements if isinstance(m, geo.Point)]
|
|
235
|
+
|
|
236
|
+
return T.cast(
|
|
237
|
+
T.List[geo.Point], _filter_telemetry_by_track_elst(moov, track, points)
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
return None
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def extract_telemetry_data(fp: T.BinaryIO) -> T.Optional[T.List[TelemetryMeasurement]]:
|
|
244
|
+
moov = MovieBoxParser.parse_stream(fp)
|
|
245
|
+
|
|
246
|
+
for track in moov.extract_tracks():
|
|
247
|
+
if _contains_camm_description(track):
|
|
248
|
+
maybe_measurements = (
|
|
249
|
+
_parse_telemetry_from_sample(fp, sample)
|
|
250
|
+
for sample in track.extract_samples()
|
|
251
|
+
if _is_camm_description(sample.description)
|
|
252
|
+
)
|
|
253
|
+
measurements = [m for m in maybe_measurements if m is not None]
|
|
254
|
+
|
|
255
|
+
measurements = _filter_telemetry_by_track_elst(moov, track, measurements)
|
|
256
|
+
|
|
257
|
+
return measurements
|
|
258
|
+
|
|
259
|
+
return None
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def parse_gpx(path: pathlib.Path) -> T.List[geo.Point]:
|
|
263
|
+
with path.open("rb") as fp:
|
|
264
|
+
points = extract_points(fp)
|
|
265
|
+
if points is None:
|
|
266
|
+
return []
|
|
267
|
+
return points
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
MakeOrModel = C.Struct(
|
|
271
|
+
"size" / C.Int16ub,
|
|
272
|
+
C.Padding(2),
|
|
273
|
+
"data" / C.FixedSized(C.this.size, C.GreedyBytes),
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def _decode_quietly(data: bytes, h: sparser.Header) -> str:
|
|
278
|
+
try:
|
|
279
|
+
return data.decode("utf-8")
|
|
280
|
+
except UnicodeDecodeError:
|
|
281
|
+
LOG.warning("Failed to decode %s: %s", h, data[:512])
|
|
282
|
+
return ""
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def _parse_quietly(data: bytes, h: sparser.Header) -> bytes:
|
|
286
|
+
try:
|
|
287
|
+
parsed = MakeOrModel.parse(data)
|
|
288
|
+
except C.ConstructError:
|
|
289
|
+
LOG.warning("Failed to parse %s: %s", h, data[:512])
|
|
290
|
+
return b""
|
|
291
|
+
return parsed["data"]
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def extract_camera_make_and_model(fp: T.BinaryIO) -> T.Tuple[str, str]:
|
|
295
|
+
header_and_stream = sparser.parse_path(
|
|
296
|
+
fp,
|
|
297
|
+
[
|
|
298
|
+
b"moov",
|
|
299
|
+
b"udta",
|
|
300
|
+
[
|
|
301
|
+
# Insta360 Titan
|
|
302
|
+
b"\xa9mak",
|
|
303
|
+
b"\xa9mod",
|
|
304
|
+
# RICHO THETA V
|
|
305
|
+
b"@mod",
|
|
306
|
+
b"@mak",
|
|
307
|
+
# RICHO THETA V
|
|
308
|
+
b"manu",
|
|
309
|
+
b"modl",
|
|
310
|
+
],
|
|
311
|
+
],
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
make: T.Optional[str] = None
|
|
315
|
+
model: T.Optional[str] = None
|
|
316
|
+
|
|
317
|
+
try:
|
|
318
|
+
for h, s in header_and_stream:
|
|
319
|
+
data = s.read(h.maxsize)
|
|
320
|
+
if h.type == b"\xa9mak":
|
|
321
|
+
make_data = _parse_quietly(data, h)
|
|
322
|
+
make_data = make_data.rstrip(b"\x00")
|
|
323
|
+
make = _decode_quietly(make_data, h)
|
|
324
|
+
elif h.type == b"\xa9mod":
|
|
325
|
+
model_data = _parse_quietly(data, h)
|
|
326
|
+
model_data = model_data.rstrip(b"\x00")
|
|
327
|
+
model = _decode_quietly(model_data, h)
|
|
328
|
+
elif h.type in [b"@mak", b"manu"]:
|
|
329
|
+
make = _decode_quietly(data, h)
|
|
330
|
+
elif h.type in [b"@mod", b"modl"]:
|
|
331
|
+
model = _decode_quietly(data, h)
|
|
332
|
+
# quit when both found
|
|
333
|
+
if make and model:
|
|
334
|
+
break
|
|
335
|
+
except sparser.ParsingError:
|
|
336
|
+
pass
|
|
337
|
+
|
|
338
|
+
if make:
|
|
339
|
+
make = make.strip()
|
|
340
|
+
if model:
|
|
341
|
+
model = model.strip()
|
|
342
|
+
return make or "", model or ""
|
|
@@ -12,6 +12,14 @@ class GPSFix(Enum):
|
|
|
12
12
|
FIX_3D = 3
|
|
13
13
|
|
|
14
14
|
|
|
15
|
+
@dataclasses.dataclass
|
|
16
|
+
class GPSPoint(Point):
|
|
17
|
+
epoch_time: T.Optional[float]
|
|
18
|
+
fix: T.Optional[GPSFix]
|
|
19
|
+
precision: T.Optional[float]
|
|
20
|
+
ground_speed: T.Optional[float]
|
|
21
|
+
|
|
22
|
+
|
|
15
23
|
@dataclasses.dataclass(order=True)
|
|
16
24
|
class TelemetryMeasurement:
|
|
17
25
|
"""Base class for all telemetry measurements.
|
|
@@ -24,26 +32,6 @@ class TelemetryMeasurement:
|
|
|
24
32
|
time: float
|
|
25
33
|
|
|
26
34
|
|
|
27
|
-
@dataclasses.dataclass
|
|
28
|
-
class GPSPoint(TelemetryMeasurement, Point):
|
|
29
|
-
epoch_time: T.Optional[float]
|
|
30
|
-
fix: T.Optional[GPSFix]
|
|
31
|
-
precision: T.Optional[float]
|
|
32
|
-
ground_speed: T.Optional[float]
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
@dataclasses.dataclass
|
|
36
|
-
class CAMMGPSPoint(TelemetryMeasurement, Point):
|
|
37
|
-
time_gps_epoch: float
|
|
38
|
-
gps_fix_type: int
|
|
39
|
-
horizontal_accuracy: float
|
|
40
|
-
vertical_accuracy: float
|
|
41
|
-
velocity_east: float
|
|
42
|
-
velocity_north: float
|
|
43
|
-
velocity_up: float
|
|
44
|
-
speed_accuracy: float
|
|
45
|
-
|
|
46
|
-
|
|
47
35
|
@dataclasses.dataclass(order=True)
|
|
48
36
|
class GyroscopeData(TelemetryMeasurement):
|
|
49
37
|
"""Gyroscope signal in radians/seconds around XYZ axes of the camera."""
|
|
@@ -18,13 +18,14 @@ from . import (
|
|
|
18
18
|
exceptions,
|
|
19
19
|
history,
|
|
20
20
|
ipc,
|
|
21
|
+
telemetry,
|
|
21
22
|
types,
|
|
22
23
|
upload_api_v4,
|
|
23
24
|
uploader,
|
|
24
25
|
utils,
|
|
25
26
|
VERSION,
|
|
26
27
|
)
|
|
27
|
-
from .camm import camm_builder
|
|
28
|
+
from .camm import camm_builder
|
|
28
29
|
from .geotag import gpmf_parser
|
|
29
30
|
from .mp4 import simple_mp4_builder
|
|
30
31
|
from .types import FileType
|
|
@@ -615,7 +616,7 @@ def upload(
|
|
|
615
616
|
assert isinstance(video_metadata.md5sum, str), "md5sum should be updated"
|
|
616
617
|
|
|
617
618
|
# extract telemetry measurements from GoPro videos
|
|
618
|
-
telemetry_measurements: T.List[
|
|
619
|
+
telemetry_measurements: T.List[telemetry.TelemetryMeasurement] = []
|
|
619
620
|
if MAPILLARY__EXPERIMENTAL_ENABLE_IMU == "YES":
|
|
620
621
|
if video_metadata.filetype is FileType.GOPRO:
|
|
621
622
|
with video_metadata.filename.open("rb") as fp:
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
VERSION = "0.13.1"
|