geopic-tag-reader 1.1.0__tar.gz → 1.1.1__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.
Files changed (66) hide show
  1. {geopic_tag_reader-1.1.0 → geopic_tag_reader-1.1.1}/CHANGELOG.md +8 -1
  2. {geopic_tag_reader-1.1.0 → geopic_tag_reader-1.1.1}/PKG-INFO +1 -1
  3. {geopic_tag_reader-1.1.0 → geopic_tag_reader-1.1.1}/docs/reader.md +24 -18
  4. {geopic_tag_reader-1.1.0 → geopic_tag_reader-1.1.1}/geopic_tag_reader/__init__.py +1 -1
  5. {geopic_tag_reader-1.1.0 → geopic_tag_reader-1.1.1}/geopic_tag_reader/main.py +2 -0
  6. {geopic_tag_reader-1.1.0 → geopic_tag_reader-1.1.1}/geopic_tag_reader/reader.py +41 -2
  7. {geopic_tag_reader-1.1.0 → geopic_tag_reader-1.1.1}/tests/test_reader.py +10 -0
  8. {geopic_tag_reader-1.1.0 → geopic_tag_reader-1.1.1}/.gitignore +0 -0
  9. {geopic_tag_reader-1.1.0 → geopic_tag_reader-1.1.1}/.gitlab-ci.yml +0 -0
  10. {geopic_tag_reader-1.1.0 → geopic_tag_reader-1.1.1}/.pre-commit-config.yaml +0 -0
  11. {geopic_tag_reader-1.1.0 → geopic_tag_reader-1.1.1}/CODE_OF_CONDUCT.md +0 -0
  12. {geopic_tag_reader-1.1.0 → geopic_tag_reader-1.1.1}/LICENSE +0 -0
  13. {geopic_tag_reader-1.1.0 → geopic_tag_reader-1.1.1}/Makefile +0 -0
  14. {geopic_tag_reader-1.1.0 → geopic_tag_reader-1.1.1}/README.md +0 -0
  15. {geopic_tag_reader-1.1.0 → geopic_tag_reader-1.1.1}/docs/.pages +0 -0
  16. {geopic_tag_reader-1.1.0 → geopic_tag_reader-1.1.1}/docs/API_USAGE.md +0 -0
  17. {geopic_tag_reader-1.1.0 → geopic_tag_reader-1.1.1}/docs/CLI_USAGE.md +0 -0
  18. {geopic_tag_reader-1.1.0 → geopic_tag_reader-1.1.1}/docs/Develop.md +0 -0
  19. {geopic_tag_reader-1.1.0 → geopic_tag_reader-1.1.1}/docs/Install.md +0 -0
  20. {geopic_tag_reader-1.1.0 → geopic_tag_reader-1.1.1}/docs/camera.md +0 -0
  21. {geopic_tag_reader-1.1.0 → geopic_tag_reader-1.1.1}/docs/model.md +0 -0
  22. {geopic_tag_reader-1.1.0 → geopic_tag_reader-1.1.1}/docs/writer.md +0 -0
  23. {geopic_tag_reader-1.1.0 → geopic_tag_reader-1.1.1}/geopic_tag_reader/camera.py +0 -0
  24. {geopic_tag_reader-1.1.0 → geopic_tag_reader-1.1.1}/geopic_tag_reader/model.py +0 -0
  25. {geopic_tag_reader-1.1.0 → geopic_tag_reader-1.1.1}/geopic_tag_reader/py.typed +0 -0
  26. {geopic_tag_reader-1.1.0 → geopic_tag_reader-1.1.1}/geopic_tag_reader/writer.py +0 -0
  27. {geopic_tag_reader-1.1.0 → geopic_tag_reader-1.1.1}/pyproject.toml +0 -0
  28. {geopic_tag_reader-1.1.0 → geopic_tag_reader-1.1.1}/pytest.ini +0 -0
  29. {geopic_tag_reader-1.1.0 → geopic_tag_reader-1.1.1}/tests/__init__.py +0 -0
  30. {geopic_tag_reader-1.1.0 → geopic_tag_reader-1.1.1}/tests/conftest.py +0 -0
  31. {geopic_tag_reader-1.1.0 → geopic_tag_reader-1.1.1}/tests/fixtures/1.jpg +0 -0
  32. {geopic_tag_reader-1.1.0 → geopic_tag_reader-1.1.1}/tests/fixtures/IMG_20210720_144918.jpg +0 -0
  33. {geopic_tag_reader-1.1.0 → geopic_tag_reader-1.1.1}/tests/fixtures/IMG_20210720_161352.jpg +0 -0
  34. {geopic_tag_reader-1.1.0 → geopic_tag_reader-1.1.1}/tests/fixtures/a1.jpg +0 -0
  35. {geopic_tag_reader-1.1.0 → geopic_tag_reader-1.1.1}/tests/fixtures/b1.jpg +0 -0
  36. {geopic_tag_reader-1.1.0 → geopic_tag_reader-1.1.1}/tests/fixtures/broken_makernotes.jpg +0 -0
  37. {geopic_tag_reader-1.1.0 → geopic_tag_reader-1.1.1}/tests/fixtures/c1.jpg +0 -0
  38. {geopic_tag_reader-1.1.0 → geopic_tag_reader-1.1.1}/tests/fixtures/charset.jpg +0 -0
  39. {geopic_tag_reader-1.1.0 → geopic_tag_reader-1.1.1}/tests/fixtures/cropped.jpg +0 -0
  40. {geopic_tag_reader-1.1.0 → geopic_tag_reader-1.1.1}/tests/fixtures/d1.jpg +0 -0
  41. {geopic_tag_reader-1.1.0 → geopic_tag_reader-1.1.1}/tests/fixtures/datetime_ms_float.jpg +0 -0
  42. {geopic_tag_reader-1.1.0 → geopic_tag_reader-1.1.1}/tests/fixtures/datetime_offset.jpg +0 -0
  43. {geopic_tag_reader-1.1.0 → geopic_tag_reader-1.1.1}/tests/fixtures/e1.jpg +0 -0
  44. {geopic_tag_reader-1.1.0 → geopic_tag_reader-1.1.1}/tests/fixtures/flat.jpg +0 -0
  45. {geopic_tag_reader-1.1.0 → geopic_tag_reader-1.1.1}/tests/fixtures/gopromax_flat.jpg +0 -0
  46. {geopic_tag_reader-1.1.0 → geopic_tag_reader-1.1.1}/tests/fixtures/gps_date_slash.jpg +0 -0
  47. {geopic_tag_reader-1.1.0 → geopic_tag_reader-1.1.1}/tests/fixtures/gps_date_time_stamp.jpg +0 -0
  48. {geopic_tag_reader-1.1.0 → geopic_tag_reader-1.1.1}/tests/fixtures/img_Ricoh_Theta.jpg +0 -0
  49. {geopic_tag_reader-1.1.0 → geopic_tag_reader-1.1.1}/tests/fixtures/img_V4MPack.jpg +0 -0
  50. {geopic_tag_reader-1.1.0 → geopic_tag_reader-1.1.1}/tests/fixtures/img_categorisee.jpg +0 -0
  51. {geopic_tag_reader-1.1.0 → geopic_tag_reader-1.1.1}/tests/fixtures/img_datetimeoriginal.jpg +0 -0
  52. {geopic_tag_reader-1.1.0 → geopic_tag_reader-1.1.1}/tests/fixtures/img_gps_date_string.jpg +0 -0
  53. {geopic_tag_reader-1.1.0 → geopic_tag_reader-1.1.1}/tests/fixtures/img_gps_datestamp.jpg +0 -0
  54. {geopic_tag_reader-1.1.0 → geopic_tag_reader-1.1.1}/tests/fixtures/img_gps_sotm.jpg +0 -0
  55. {geopic_tag_reader-1.1.0 → geopic_tag_reader-1.1.1}/tests/fixtures/img_invalid_gps_date.jpg +0 -0
  56. {geopic_tag_reader-1.1.0 → geopic_tag_reader-1.1.1}/tests/fixtures/img_without_coord.jpg +0 -0
  57. {geopic_tag_reader-1.1.0 → geopic_tag_reader-1.1.1}/tests/fixtures/img_without_dt.jpg +0 -0
  58. {geopic_tag_reader-1.1.0 → geopic_tag_reader-1.1.1}/tests/fixtures/img_without_exif_tags.jpg +0 -0
  59. {geopic_tag_reader-1.1.0 → geopic_tag_reader-1.1.1}/tests/fixtures/insta360_date.jpg +0 -0
  60. {geopic_tag_reader-1.1.0 → geopic_tag_reader-1.1.1}/tests/fixtures/int_long_tag.jpg +0 -0
  61. {geopic_tag_reader-1.1.0 → geopic_tag_reader-1.1.1}/tests/fixtures/out_of_bounds_lat.jpg +0 -0
  62. {geopic_tag_reader-1.1.0 → geopic_tag_reader-1.1.1}/tests/fixtures/out_of_bounds_lon.jpg +0 -0
  63. {geopic_tag_reader-1.1.0 → geopic_tag_reader-1.1.1}/tests/fixtures/pic_with_float_lat.jpg +0 -0
  64. {geopic_tag_reader-1.1.0 → geopic_tag_reader-1.1.1}/tests/fixtures/ricoh_theta_no_projection.jpg +0 -0
  65. {geopic_tag_reader-1.1.0 → geopic_tag_reader-1.1.1}/tests/test_main.py +0 -0
  66. {geopic_tag_reader-1.1.0 → geopic_tag_reader-1.1.1}/tests/test_writer.py +0 -0
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.1.1] - 2024-04-26
11
+
12
+ ### Added
13
+
14
+ - Reader now handles pitch & roll values from various EXIF/XMP tags.
15
+
10
16
  ## [1.1.0] - 2024-04-17
11
17
 
12
18
  ### Changed
@@ -160,7 +166,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
160
166
 
161
167
  - EXIF tag reading methods extracted from [GeoVisio API](https://gitlab.com/geovisio/api)
162
168
 
163
- [Unreleased]: https://gitlab.com/geovisio/geo-picture-tag-reader/-/compare/1.1.0...main
169
+ [Unreleased]: https://gitlab.com/geovisio/geo-picture-tag-reader/-/compare/1.1.1...main
170
+ [1.1.1]: https://gitlab.com/geovisio/geo-picture-tag-reader/-/compare/1.1.0...1.1.1
164
171
  [1.1.0]: https://gitlab.com/geovisio/geo-picture-tag-reader/-/compare/1.0.6...1.1.0
165
172
  [1.0.6]: https://gitlab.com/geovisio/geo-picture-tag-reader/-/compare/1.0.5...1.0.6
166
173
  [1.0.5]: https://gitlab.com/geovisio/geo-picture-tag-reader/-/compare/1.0.4...1.0.5
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: geopic-tag-reader
3
- Version: 1.1.0
3
+ Version: 1.1.1
4
4
  Summary: GeoPicTagReader
5
5
  Author-email: Adrien PAVIE <panieravide@riseup.net>
6
6
  Requires-Python: >=3.8
@@ -10,7 +10,7 @@
10
10
 
11
11
  ---
12
12
 
13
- <a href="../geopic_tag_reader/reader.py#L118"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
13
+ <a href="../geopic_tag_reader/reader.py#L124"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
14
14
 
15
15
  ## <kbd>function</kbd> `readPictureMetadata`
16
16
 
@@ -35,7 +35,7 @@ Extracts metadata from picture file
35
35
 
36
36
  ---
37
37
 
38
- <a href="../geopic_tag_reader/reader.py#L355"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
38
+ <a href="../geopic_tag_reader/reader.py#L394"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
39
39
 
40
40
  ## <kbd>function</kbd> `decodeMakeModel`
41
41
 
@@ -48,7 +48,7 @@ Python 2/3 compatible decoding of make/model field.
48
48
 
49
49
  ---
50
50
 
51
- <a href="../geopic_tag_reader/reader.py#L366"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
51
+ <a href="../geopic_tag_reader/reader.py#L405"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
52
52
 
53
53
  ## <kbd>function</kbd> `isValidManyFractions`
54
54
 
@@ -63,7 +63,7 @@ isValidManyFractions(value: str) → bool
63
63
 
64
64
  ---
65
65
 
66
- <a href="../geopic_tag_reader/reader.py#L373"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
66
+ <a href="../geopic_tag_reader/reader.py#L412"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
67
67
 
68
68
  ## <kbd>function</kbd> `decodeManyFractions`
69
69
 
@@ -76,7 +76,7 @@ Try to decode a list of fractions, separated by spaces
76
76
 
77
77
  ---
78
78
 
79
- <a href="../geopic_tag_reader/reader.py#L386"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
79
+ <a href="../geopic_tag_reader/reader.py#L425"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
80
80
 
81
81
  ## <kbd>function</kbd> `decodeLatLon`
82
82
 
@@ -92,7 +92,7 @@ Reads GPS info from given group to get latitude/longitude as float coordinates
92
92
 
93
93
  ---
94
94
 
95
- <a href="../geopic_tag_reader/reader.py#L441"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
95
+ <a href="../geopic_tag_reader/reader.py#L480"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
96
96
 
97
97
  ## <kbd>function</kbd> `decodeDateTimeOriginal`
98
98
 
@@ -112,7 +112,7 @@ decodeDateTimeOriginal(
112
112
 
113
113
  ---
114
114
 
115
- <a href="../geopic_tag_reader/reader.py#L495"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
115
+ <a href="../geopic_tag_reader/reader.py#L534"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
116
116
 
117
117
  ## <kbd>function</kbd> `decodeTimeOffset`
118
118
 
@@ -127,7 +127,7 @@ decodeTimeOffset(data: dict, offsetTimeField: str) → Optional[tzinfo]
127
127
 
128
128
  ---
129
129
 
130
- <a href="../geopic_tag_reader/reader.py#L501"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
130
+ <a href="../geopic_tag_reader/reader.py#L540"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
131
131
 
132
132
  ## <kbd>function</kbd> `decodeGPSDateTime`
133
133
 
@@ -147,7 +147,7 @@ decodeGPSDateTime(
147
147
 
148
148
  ---
149
149
 
150
- <a href="../geopic_tag_reader/reader.py#L552"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
150
+ <a href="../geopic_tag_reader/reader.py#L591"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
151
151
 
152
152
  ## <kbd>function</kbd> `decodeSecondsAndMicroSeconds`
153
153
 
@@ -165,7 +165,7 @@ decodeSecondsAndMicroSeconds(
165
165
 
166
166
  ---
167
167
 
168
- <a href="../geopic_tag_reader/reader.py#L578"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
168
+ <a href="../geopic_tag_reader/reader.py#L617"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
169
169
 
170
170
  ## <kbd>function</kbd> `isExifTagUsable`
171
171
 
@@ -245,7 +245,7 @@ Tags associated to a geolocated picture
245
245
  - <b>`lat`</b> (float): GPS Latitude (in WGS84)
246
246
  - <b>`lon`</b> (float): GPS Longitude (in WGS84)
247
247
  - <b>`ts`</b> (datetime): The capture date (date & time with timezone)
248
- - <b>`heading`</b> (int): Picture heading (in degrees, North = 0°, East = 90°, South = 180°, West = 270°)
248
+ - <b>`heading`</b> (int): Picture heading/yaw (in degrees, North = 0°, East = 90°, South = 180°, West = 270°)
249
249
  - <b>`type`</b> (str): The kind of picture (flat, equirectangular)
250
250
  - <b>`make`</b> (str): The camera manufacturer name
251
251
  - <b>`model`</b> (str): The camera model name
@@ -254,6 +254,8 @@ Tags associated to a geolocated picture
254
254
  - <b>`exif`</b> (dict[str, str]): Raw EXIF tags from picture (following Exiv2 naming scheme, see https://exiv2.org/metadata.html)
255
255
  - <b>`tagreader_warnings`</b> (list[str]): List of thrown warnings during metadata reading
256
256
  - <b>`altitude`</b> (float): altitude (in m) (optional)
257
+ - <b>`pitch`</b> (float): Picture pitch angle, compared to horizon (in degrees, bottom = -90°, horizon = 0°, top = 90°)
258
+ - <b>`roll`</b> (float): Picture roll angle, on a right/left axis (in degrees, left-arm down = -90°, flat = 0°, right-arm down = 90°)
257
259
 
258
260
 
259
261
 
@@ -276,7 +278,9 @@ __init__(
276
278
  crop: Optional[CropValues],
277
279
  exif: Dict[str, str] = <factory>,
278
280
  tagreader_warnings: List[str] = <factory>,
279
- altitude: Optional[float] = None
281
+ altitude: Optional[float] = None,
282
+ pitch: Optional[float] = None,
283
+ roll: Optional[float] = None
280
284
  ) → None
281
285
  ```
282
286
 
@@ -290,12 +294,12 @@ __init__(
290
294
 
291
295
  ---
292
296
 
293
- <a href="../geopic_tag_reader/reader.py#L77"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
297
+ <a href="../geopic_tag_reader/reader.py#L81"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
294
298
 
295
299
  ## <kbd>class</kbd> `InvalidExifException`
296
300
  Exception for invalid EXIF information from image
297
301
 
298
- <a href="../geopic_tag_reader/reader.py#L80"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
302
+ <a href="../geopic_tag_reader/reader.py#L84"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
299
303
 
300
304
  ### <kbd>method</kbd> `__init__`
301
305
 
@@ -313,7 +317,7 @@ __init__(msg)
313
317
 
314
318
  ---
315
319
 
316
- <a href="../geopic_tag_reader/reader.py#L84"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
320
+ <a href="../geopic_tag_reader/reader.py#L88"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
317
321
 
318
322
  ## <kbd>class</kbd> `PartialGeoPicTags`
319
323
  Tags associated to a geolocated picture when not all tags have been found
@@ -337,7 +341,9 @@ __init__(
337
341
  crop: Optional[CropValues] = None,
338
342
  exif: Dict[str, str] = <factory>,
339
343
  tagreader_warnings: List[str] = <factory>,
340
- altitude: Optional[float] = None
344
+ altitude: Optional[float] = None,
345
+ pitch: Optional[float] = None,
346
+ roll: Optional[float] = None
341
347
  ) → None
342
348
  ```
343
349
 
@@ -351,14 +357,14 @@ __init__(
351
357
 
352
358
  ---
353
359
 
354
- <a href="../geopic_tag_reader/reader.py#L105"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
360
+ <a href="../geopic_tag_reader/reader.py#L111"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
355
361
 
356
362
  ## <kbd>class</kbd> `PartialExifException`
357
363
  Exception for partial / missing EXIF information from image
358
364
 
359
365
  Contains a PartialGeoPicTags with all tags that have been read and the list of missing tags
360
366
 
361
- <a href="../geopic_tag_reader/reader.py#L112"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
367
+ <a href="../geopic_tag_reader/reader.py#L118"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
362
368
 
363
369
  ### <kbd>method</kbd> `__init__`
364
370
 
@@ -2,4 +2,4 @@
2
2
  GeoPicTagReader
3
3
  """
4
4
 
5
- __version__ = "1.1.0"
5
+ __version__ = "1.1.1"
@@ -29,6 +29,8 @@ def read(
29
29
  print("Model:", metadata.model)
30
30
  print("Focal length:", metadata.focal_length)
31
31
  print("Crop parameters:", metadata.crop)
32
+ print("Pitch:", metadata.pitch)
33
+ print("Roll:", metadata.roll)
32
34
 
33
35
  if len(metadata.tagreader_warnings) > 0:
34
36
  print("Warnings raised by reader:")
@@ -46,7 +46,7 @@ class GeoPicTags:
46
46
  lat (float): GPS Latitude (in WGS84)
47
47
  lon (float): GPS Longitude (in WGS84)
48
48
  ts (datetime): The capture date (date & time with timezone)
49
- heading (int): Picture heading (in degrees, North = 0°, East = 90°, South = 180°, West = 270°)
49
+ heading (int): Picture heading/yaw (in degrees, North = 0°, East = 90°, South = 180°, West = 270°)
50
50
  type (str): The kind of picture (flat, equirectangular)
51
51
  make (str): The camera manufacturer name
52
52
  model (str): The camera model name
@@ -55,6 +55,8 @@ class GeoPicTags:
55
55
  exif (dict[str, str]): Raw EXIF tags from picture (following Exiv2 naming scheme, see https://exiv2.org/metadata.html)
56
56
  tagreader_warnings (list[str]): List of thrown warnings during metadata reading
57
57
  altitude (float): altitude (in m) (optional)
58
+ pitch (float): Picture pitch angle, compared to horizon (in degrees, bottom = -90°, horizon = 0°, top = 90°)
59
+ roll (float): Picture roll angle, on a right/left axis (in degrees, left-arm down = -90°, flat = 0°, right-arm down = 90°)
58
60
 
59
61
 
60
62
  Implementation note: this needs to be sync with the PartialGeoPicTags structure
@@ -72,6 +74,8 @@ class GeoPicTags:
72
74
  exif: Dict[str, str] = field(default_factory=lambda: {})
73
75
  tagreader_warnings: List[str] = field(default_factory=lambda: [])
74
76
  altitude: Optional[float] = None
77
+ pitch: Optional[float] = None
78
+ roll: Optional[float] = None
75
79
 
76
80
 
77
81
  class InvalidExifException(Exception):
@@ -100,6 +104,8 @@ class PartialGeoPicTags:
100
104
  exif: Dict[str, str] = field(default_factory=lambda: {})
101
105
  tagreader_warnings: List[str] = field(default_factory=lambda: [])
102
106
  altitude: Optional[float] = None
107
+ pitch: Optional[float] = None
108
+ roll: Optional[float] = None
103
109
 
104
110
 
105
111
  class PartialExifException(Exception):
@@ -213,7 +219,7 @@ def readPictureMetadata(picture: bytes) -> GeoPicTags:
213
219
  except Exception as e:
214
220
  warnings.append("Skipping Mapillary date/time as it was not recognized:\n\t" + str(e))
215
221
 
216
- # Heading
222
+ # Heading/Yaw
217
223
  heading = None
218
224
  if isExifTagUsable(data, "Xmp.GPano.PoseHeadingDegrees", float) and isExifTagUsable(data, "Exif.GPSInfo.GPSImgDirection", Fraction):
219
225
  gpsDir = int(round(float(Fraction(data["Exif.GPSInfo.GPSImgDirection"]))))
@@ -236,6 +242,35 @@ def readPictureMetadata(picture: bytes) -> GeoPicTags:
236
242
  elif "MAPCompassHeading" in data and isExifTagUsable(data["MAPCompassHeading"], "TrueHeading", float):
237
243
  heading = int(round(float(data["MAPCompassHeading"]["TrueHeading"])))
238
244
 
245
+ # Pitch & roll
246
+ pitch = None
247
+ roll = None
248
+ exifPRFields = ["Xmp.Camera.$$", "Exif.GPSInfo.GPS$$", "Xmp.GPano.Pose$$Degrees", "Xmp.GPano.InitialView$$Degrees"]
249
+ # For each potential EXIF field
250
+ for exifField in exifPRFields:
251
+ # Try out both Pitch & Roll variants
252
+ for checkField in ["Pitch", "Roll"]:
253
+ exifCheckField = exifField.replace("$$", checkField)
254
+ foundValue = None
255
+ # Look for float or fraction
256
+ if isExifTagUsable(data, exifCheckField, float):
257
+ foundValue = float(data[exifCheckField])
258
+ elif isExifTagUsable(data, exifCheckField, Fraction):
259
+ foundValue = float(Fraction(data[exifCheckField]))
260
+
261
+ # Save to correct variable (if not already set)
262
+ if foundValue is not None:
263
+ if checkField == "Pitch":
264
+ if pitch is None:
265
+ pitch = foundValue
266
+ else:
267
+ continue
268
+ elif checkField == "Roll":
269
+ if roll is None:
270
+ roll = foundValue
271
+ else:
272
+ continue
273
+
239
274
  # Make and model
240
275
  make = data.get("Exif.Image.Make") or data.get("MAPDeviceMake")
241
276
  model = data.get("Exif.Image.Model") or data.get("MAPDeviceModel")
@@ -332,6 +367,8 @@ def readPictureMetadata(picture: bytes) -> GeoPicTags:
332
367
  exif=data,
333
368
  tagreader_warnings=warnings,
334
369
  altitude=altitude,
370
+ pitch=pitch,
371
+ roll=roll,
335
372
  ),
336
373
  )
337
374
 
@@ -349,6 +386,8 @@ def readPictureMetadata(picture: bytes) -> GeoPicTags:
349
386
  exif=data,
350
387
  tagreader_warnings=warnings,
351
388
  altitude=altitude,
389
+ pitch=pitch,
390
+ roll=roll,
352
391
  )
353
392
 
354
393
 
@@ -13,6 +13,8 @@ def assertGeoPicTagsEquals(gpt, expectedDict):
13
13
  assert gpt.model == expectedDict.get("model")
14
14
  assert gpt.focal_length == expectedDict.get("focal_length")
15
15
  assert gpt.altitude == expectedDict.get("altitude")
16
+ assert gpt.pitch == expectedDict.get("pitch")
17
+ assert gpt.roll == expectedDict.get("roll")
16
18
  assert gpt.tagreader_warnings == expectedDict.get("tagreader_warnings", [])
17
19
  assert len(gpt.exif) > 0
18
20
 
@@ -49,6 +51,8 @@ def test_readPictureMetadata(datafiles):
49
51
  "model": "Max",
50
52
  "focal_length": 3,
51
53
  "altitude": 93,
54
+ "roll": 0,
55
+ "pitch": 0,
52
56
  },
53
57
  )
54
58
 
@@ -68,6 +72,8 @@ def test_readPictureMetadata_negCoords(datafiles):
68
72
  "model": "Max",
69
73
  "focal_length": 3,
70
74
  "altitude": 79,
75
+ "roll": 0,
76
+ "pitch": 0,
71
77
  },
72
78
  )
73
79
 
@@ -161,6 +167,8 @@ def test_readPictureMetadata_ricoh_theta(datafiles):
161
167
  "model": "THETA m15",
162
168
  "ts": "2016-03-25T14:12:13+01:00",
163
169
  "type": "equirectangular",
170
+ "roll": 3,
171
+ "pitch": 1,
164
172
  },
165
173
  )
166
174
 
@@ -602,6 +610,8 @@ def test_readPictureMetadata_datetime_offset(datafiles):
602
610
  "model": "Max",
603
611
  "focal_length": 3,
604
612
  "altitude": 79,
613
+ "roll": 0,
614
+ "pitch": 0,
605
615
  },
606
616
  )
607
617