geopic-tag-reader 1.3.3__tar.gz → 1.4.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.
- {geopic_tag_reader-1.3.3 → geopic_tag_reader-1.4.1}/CHANGELOG.md +20 -1
- {geopic_tag_reader-1.3.3 → geopic_tag_reader-1.4.1}/PKG-INFO +1 -1
- {geopic_tag_reader-1.3.3 → geopic_tag_reader-1.4.1}/docs/develop.md +14 -0
- {geopic_tag_reader-1.3.3 → geopic_tag_reader-1.4.1}/docs/index.md +3 -1
- {geopic_tag_reader-1.3.3 → geopic_tag_reader-1.4.1}/docs/quality_score.md +6 -5
- {geopic_tag_reader-1.3.3 → geopic_tag_reader-1.4.1}/geopic_tag_reader/__init__.py +1 -1
- geopic_tag_reader-1.4.1/geopic_tag_reader/camera.py +148 -0
- geopic_tag_reader-1.4.1/geopic_tag_reader/cameras.csv +3777 -0
- {geopic_tag_reader-1.3.3 → geopic_tag_reader-1.4.1}/geopic_tag_reader/main.py +3 -0
- {geopic_tag_reader-1.3.3 → geopic_tag_reader-1.4.1}/geopic_tag_reader/reader.py +119 -21
- geopic_tag_reader-1.4.1/geopic_tag_reader/translations/da/LC_MESSAGES/geopic_tag_reader.mo +0 -0
- geopic_tag_reader-1.4.1/geopic_tag_reader/translations/da/LC_MESSAGES/geopic_tag_reader.po +221 -0
- geopic_tag_reader-1.4.1/geopic_tag_reader/translations/de/LC_MESSAGES/geopic_tag_reader.mo +0 -0
- {geopic_tag_reader-1.3.3 → geopic_tag_reader-1.4.1}/geopic_tag_reader/translations/de/LC_MESSAGES/geopic_tag_reader.po +14 -2
- geopic_tag_reader-1.4.1/geopic_tag_reader/translations/en/LC_MESSAGES/geopic_tag_reader.mo +0 -0
- {geopic_tag_reader-1.3.3 → geopic_tag_reader-1.4.1}/geopic_tag_reader/translations/en/LC_MESSAGES/geopic_tag_reader.po +52 -39
- geopic_tag_reader-1.4.1/geopic_tag_reader/translations/eo/LC_MESSAGES/geopic_tag_reader.mo +0 -0
- geopic_tag_reader-1.4.1/geopic_tag_reader/translations/eo/LC_MESSAGES/geopic_tag_reader.po +219 -0
- geopic_tag_reader-1.4.1/geopic_tag_reader/translations/fr/LC_MESSAGES/geopic_tag_reader.mo +0 -0
- {geopic_tag_reader-1.3.3 → geopic_tag_reader-1.4.1}/geopic_tag_reader/translations/fr/LC_MESSAGES/geopic_tag_reader.po +17 -5
- {geopic_tag_reader-1.3.3 → geopic_tag_reader-1.4.1}/geopic_tag_reader/translations/geopic_tag_reader.pot +48 -35
- geopic_tag_reader-1.4.1/geopic_tag_reader/translations/it/LC_MESSAGES/geopic_tag_reader.mo +0 -0
- geopic_tag_reader-1.4.1/geopic_tag_reader/translations/it/LC_MESSAGES/geopic_tag_reader.po +224 -0
- {geopic_tag_reader-1.3.3/geopic_tag_reader/translations/it → geopic_tag_reader-1.4.1/geopic_tag_reader/translations/ja}/LC_MESSAGES/geopic_tag_reader.mo +0 -0
- {geopic_tag_reader-1.3.3/geopic_tag_reader/translations/it → geopic_tag_reader-1.4.1/geopic_tag_reader/translations/ja}/LC_MESSAGES/geopic_tag_reader.po +49 -36
- geopic_tag_reader-1.4.1/geopic_tag_reader/translations/zh_Hant/LC_MESSAGES/geopic_tag_reader.mo +0 -0
- geopic_tag_reader-1.4.1/geopic_tag_reader/translations/zh_Hant/LC_MESSAGES/geopic_tag_reader.po +196 -0
- {geopic_tag_reader-1.3.3 → geopic_tag_reader-1.4.1}/pyproject.toml +1 -1
- geopic_tag_reader-1.3.3/geopic_tag_reader/camera.py +0 -60
- geopic_tag_reader-1.3.3/geopic_tag_reader/translations/de/LC_MESSAGES/geopic_tag_reader.mo +0 -0
- geopic_tag_reader-1.3.3/geopic_tag_reader/translations/en/LC_MESSAGES/geopic_tag_reader.mo +0 -0
- geopic_tag_reader-1.3.3/geopic_tag_reader/translations/fr/LC_MESSAGES/geopic_tag_reader.mo +0 -0
- {geopic_tag_reader-1.3.3 → geopic_tag_reader-1.4.1}/.gitignore +0 -0
- {geopic_tag_reader-1.3.3 → geopic_tag_reader-1.4.1}/.gitlab-ci.yml +0 -0
- {geopic_tag_reader-1.3.3 → geopic_tag_reader-1.4.1}/.pre-commit-config.yaml +0 -0
- {geopic_tag_reader-1.3.3 → geopic_tag_reader-1.4.1}/CODE_OF_CONDUCT.md +0 -0
- {geopic_tag_reader-1.3.3 → geopic_tag_reader-1.4.1}/LICENSE +0 -0
- {geopic_tag_reader-1.3.3 → geopic_tag_reader-1.4.1}/Makefile +0 -0
- {geopic_tag_reader-1.3.3 → geopic_tag_reader-1.4.1}/README.md +0 -0
- {geopic_tag_reader-1.3.3 → geopic_tag_reader-1.4.1}/docs/images/quality_score.png +0 -0
- {geopic_tag_reader-1.3.3 → geopic_tag_reader-1.4.1}/docs/images/quality_score_viewer.png +0 -0
- {geopic_tag_reader-1.3.3 → geopic_tag_reader-1.4.1}/docs/install.md +0 -0
- {geopic_tag_reader-1.3.3 → geopic_tag_reader-1.4.1}/docs/tech/api_reference.md +0 -0
- {geopic_tag_reader-1.3.3 → geopic_tag_reader-1.4.1}/docs/tech/cli.md +0 -0
- {geopic_tag_reader-1.3.3 → geopic_tag_reader-1.4.1}/geopic_tag_reader/i18n.py +0 -0
- {geopic_tag_reader-1.3.3 → geopic_tag_reader-1.4.1}/geopic_tag_reader/model.py +0 -0
- {geopic_tag_reader-1.3.3 → geopic_tag_reader-1.4.1}/geopic_tag_reader/py.typed +0 -0
- {geopic_tag_reader-1.3.3 → geopic_tag_reader-1.4.1}/geopic_tag_reader/sequence.py +0 -0
- {geopic_tag_reader-1.3.3 → geopic_tag_reader-1.4.1}/geopic_tag_reader/translations/es/LC_MESSAGES/geopic_tag_reader.mo +0 -0
- {geopic_tag_reader-1.3.3 → geopic_tag_reader-1.4.1}/geopic_tag_reader/translations/es/LC_MESSAGES/geopic_tag_reader.po +0 -0
- {geopic_tag_reader-1.3.3 → geopic_tag_reader-1.4.1}/geopic_tag_reader/translations/fi/LC_MESSAGES/geopic_tag_reader.mo +0 -0
- {geopic_tag_reader-1.3.3 → geopic_tag_reader-1.4.1}/geopic_tag_reader/translations/fi/LC_MESSAGES/geopic_tag_reader.po +0 -0
- {geopic_tag_reader-1.3.3 → geopic_tag_reader-1.4.1}/geopic_tag_reader/translations/hu/LC_MESSAGES/geopic_tag_reader.mo +0 -0
- {geopic_tag_reader-1.3.3 → geopic_tag_reader-1.4.1}/geopic_tag_reader/translations/hu/LC_MESSAGES/geopic_tag_reader.po +0 -0
- {geopic_tag_reader-1.3.3 → geopic_tag_reader-1.4.1}/geopic_tag_reader/translations/ko/LC_MESSAGES/geopic_tag_reader.mo +0 -0
- {geopic_tag_reader-1.3.3 → geopic_tag_reader-1.4.1}/geopic_tag_reader/translations/ko/LC_MESSAGES/geopic_tag_reader.po +0 -0
- {geopic_tag_reader-1.3.3 → geopic_tag_reader-1.4.1}/geopic_tag_reader/translations/nl/LC_MESSAGES/geopic_tag_reader.mo +0 -0
- {geopic_tag_reader-1.3.3 → geopic_tag_reader-1.4.1}/geopic_tag_reader/translations/nl/LC_MESSAGES/geopic_tag_reader.po +0 -0
- {geopic_tag_reader-1.3.3 → geopic_tag_reader-1.4.1}/geopic_tag_reader/translations/pl/LC_MESSAGES/geopic_tag_reader.mo +0 -0
- {geopic_tag_reader-1.3.3 → geopic_tag_reader-1.4.1}/geopic_tag_reader/translations/pl/LC_MESSAGES/geopic_tag_reader.po +0 -0
- {geopic_tag_reader-1.3.3 → geopic_tag_reader-1.4.1}/geopic_tag_reader/writer.py +0 -0
- {geopic_tag_reader-1.3.3 → geopic_tag_reader-1.4.1}/mkdocs.yml +0 -0
- {geopic_tag_reader-1.3.3 → geopic_tag_reader-1.4.1}/pytest.ini +0 -0
|
@@ -7,6 +7,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.4.1] - 2025-02-03
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- Field of view is also computed based on focal length and its equivalent in 35mm format (based on ExifTool formula).
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- GPS horizontal precision and dilution of precision are also read from fraction values.
|
|
19
|
+
|
|
20
|
+
## [1.4.0] - 2025-01-06
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
|
|
24
|
+
- Camera general metadata (sensor width, 360° devices, GPS average accuracy) is made available through a single CSV file named `cameras.csv`. This make info centralized and will lighten camera info in Panoramax API.
|
|
25
|
+
- New fields are offered in GeoPicTags: `sensor_width, gps_accuracy, field_of_view`
|
|
26
|
+
|
|
10
27
|
## [1.3.3] - 2024-11-24
|
|
11
28
|
|
|
12
29
|
### Added
|
|
@@ -244,7 +261,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
244
261
|
|
|
245
262
|
- EXIF tag reading methods extracted from [Panoramax/GeoVisio API](https://gitlab.com/panoramax/server/api)
|
|
246
263
|
|
|
247
|
-
[Unreleased]: https://gitlab.com/panoramax/server/geo-picture-tag-reader/-/compare/1.
|
|
264
|
+
[Unreleased]: https://gitlab.com/panoramax/server/geo-picture-tag-reader/-/compare/1.4.1...main
|
|
265
|
+
[1.4.1]: https://gitlab.com/panoramax/server/geo-picture-tag-reader/-/compare/1.4.0...1.4.1
|
|
266
|
+
[1.4.0]: https://gitlab.com/panoramax/server/geo-picture-tag-reader/-/compare/1.3.3...1.4.0
|
|
248
267
|
[1.3.3]: https://gitlab.com/panoramax/server/geo-picture-tag-reader/-/compare/1.3.2...1.3.3
|
|
249
268
|
[1.3.2]: https://gitlab.com/panoramax/server/geo-picture-tag-reader/-/compare/1.3.1...1.3.2
|
|
250
269
|
[1.3.1]: https://gitlab.com/panoramax/server/geo-picture-tag-reader/-/compare/1.3.0...1.3.1
|
|
@@ -69,6 +69,20 @@ pip install -e .[dev]
|
|
|
69
69
|
pre-commit install
|
|
70
70
|
```
|
|
71
71
|
|
|
72
|
+
### Add camera information
|
|
73
|
+
|
|
74
|
+
Many fallback information are made available through the `geopic_tag_reader/cameras.csv` file, which is structured this way:
|
|
75
|
+
|
|
76
|
+
- `make`: name of camera builder (as noted in `Make` EXIF field in picture)
|
|
77
|
+
- `model`: name of camera model (as noted in `Model` EXIF field in picture)
|
|
78
|
+
- `sensor_width`: the width of camera sensor (in millimeters)
|
|
79
|
+
- `is_360`: set to `1` if camera is a 360° device, left empty or set to `0` otherwise
|
|
80
|
+
- `gps_accuracy`: average GPS accuracy (in meters) if the camera has an embed GPS and pictures don't contain GPS horizontal positioning error in their metadata
|
|
81
|
+
|
|
82
|
+
If a camera is missing in this list, feel free to offer a pull request or send us these information through an issue or an email so we can expand the list.
|
|
83
|
+
|
|
84
|
+
Also note that more generic information about GPS accuracy from various camera brands is present in `geopic_tag_reader/camera.py` file (`GPS_ACCURACY_MAKE` constant). This allows us to say _"all smartphones from this brand have this GPS accuracy"_, feel free to contact us if we missed some brands.
|
|
85
|
+
|
|
72
86
|
## Make a release
|
|
73
87
|
|
|
74
88
|
```bash
|
|
@@ -74,10 +74,12 @@ Camera model is read from:
|
|
|
74
74
|
|
|
75
75
|
#### Focal length
|
|
76
76
|
|
|
77
|
-
Camera focal length (to get precise field of view) is read from:
|
|
77
|
+
Camera focal length (basic and 35mm-equivalent, to get precise field of view) is read from:
|
|
78
78
|
|
|
79
79
|
- `Exif.Image.FocalLength`
|
|
80
80
|
- `Exif.Photo.FocalLength`
|
|
81
|
+
- `Exif.Image.FocalLengthIn35mmFilm`
|
|
82
|
+
- `Exif.Photo.FocalLengthIn35mmFilm`
|
|
81
83
|
|
|
82
84
|
#### :octicons-horizontal-rule-16: Yaw, Pitch and Roll
|
|
83
85
|
|
|
@@ -58,11 +58,11 @@ This indicator allows to have an idea of how high the picture resolution is, ind
|
|
|
58
58
|
|
|
59
59
|
| Grade | Density for 360° | Density for non 360° |
|
|
60
60
|
| ----- | ---------------- | --------------------------- |
|
|
61
|
-
| 5/5 | >=
|
|
62
|
-
| 4/5 | >=
|
|
63
|
-
| 3/5 |
|
|
64
|
-
| 2/5 |
|
|
65
|
-
| 1/5 | Unknown value | <
|
|
61
|
+
| 5/5 | >= 30 px/deg | Not possible |
|
|
62
|
+
| 4/5 | >= 15 px/deg | >= 30 px/deg |
|
|
63
|
+
| 3/5 | < 15px/deg | >= 15px/deg |
|
|
64
|
+
| 2/5 | Not possible | >= 10 px/deg |
|
|
65
|
+
| 1/5 | Unknown value | < 10 px/deg or unknown value |
|
|
66
66
|
|
|
67
67
|
!!! info
|
|
68
68
|
|
|
@@ -71,6 +71,7 @@ This indicator allows to have an idea of how high the picture resolution is, ind
|
|
|
71
71
|
The value is computed from picture width in pixels, and some EXIF metadata. We rely on the following tags:
|
|
72
72
|
|
|
73
73
|
- __Focal length__ (`Exif.Image.FocalLength` or `Exif.Photo.FocalLength`): distance in millimeters between the nodal point of the lens and the camera sensor.
|
|
74
|
+
- __Focal length (35mm equivalent)__ (`Exif.Image.FocalLengthIn35mmFilm` or `Exif.Photo.FocalLengthIn35mmFilm`): same as focal length, but in 35mm film equivalent.
|
|
74
75
|
- __Make and model__ (`Exif.Image.Make` and `Exif.Image.Make`): camera model, which is used to find camera sensor width.
|
|
75
76
|
- __Projection type__ (for 360° pictures, `Xmp.GPano.ProjectionType`): to mark a picture as 360°
|
|
76
77
|
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
from typing import Optional, Dict, List
|
|
2
|
+
import importlib.resources
|
|
3
|
+
import csv
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
# Per-make GPS estimated accuracy (in meters)
|
|
8
|
+
GPS_ACCURACY_MAKE = {
|
|
9
|
+
# Diff GPS
|
|
10
|
+
"stfmani": 2,
|
|
11
|
+
"trimble": 2,
|
|
12
|
+
"imajing": 2,
|
|
13
|
+
# Good GPS
|
|
14
|
+
"gopro": 4,
|
|
15
|
+
"insta360": 4,
|
|
16
|
+
"garmin": 4,
|
|
17
|
+
"viofo": 4,
|
|
18
|
+
"xiaoyi": 4,
|
|
19
|
+
"blackvue": 4,
|
|
20
|
+
"tectectec": 4,
|
|
21
|
+
"arashi vision": 4,
|
|
22
|
+
# Smartphone GPS
|
|
23
|
+
"samsung": 5,
|
|
24
|
+
"xiaomi": 5,
|
|
25
|
+
"huawei": 5,
|
|
26
|
+
"ricoh": 5,
|
|
27
|
+
"lenovo": 5,
|
|
28
|
+
"motorola": 5,
|
|
29
|
+
"oneplus": 5,
|
|
30
|
+
"apple": 5,
|
|
31
|
+
"google": 5,
|
|
32
|
+
"sony": 5,
|
|
33
|
+
"wiko": 5,
|
|
34
|
+
"asus": 5,
|
|
35
|
+
"cubot": 5,
|
|
36
|
+
"lge": 5,
|
|
37
|
+
"fairphone": 5,
|
|
38
|
+
"realme": 5,
|
|
39
|
+
"symphony": 5,
|
|
40
|
+
"crosscall": 5,
|
|
41
|
+
"htc": 5,
|
|
42
|
+
"homtom": 5,
|
|
43
|
+
"hmd global": 5,
|
|
44
|
+
"oppo": 5,
|
|
45
|
+
"ulefone": 5,
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@dataclass
|
|
50
|
+
class CameraMetadata:
|
|
51
|
+
is_360: bool = False
|
|
52
|
+
sensor_width: Optional[float] = None
|
|
53
|
+
gps_accuracy: Optional[float] = None
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
CAMERAS: Dict[str, Dict[str, CameraMetadata]] = {} # Make -> Model -> Metadata
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def get_cameras() -> Dict[str, Dict[str, CameraMetadata]]:
|
|
60
|
+
"""
|
|
61
|
+
Retrieve general metadata about cameras
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
if len(CAMERAS) > 0:
|
|
65
|
+
return CAMERAS
|
|
66
|
+
|
|
67
|
+
# Cameras.csv file is a composite of various sources:
|
|
68
|
+
# - Wikipedia's list of 360° cameras ( https://en.wikipedia.org/wiki/List_of_omnidirectional_(360-degree)_cameras )
|
|
69
|
+
# - OpenSfM's sensor widths ( https://github.com/mapillary/OpenSfM/blob/main/opensfm/data/sensor_data.json )
|
|
70
|
+
|
|
71
|
+
with importlib.resources.open_text("geopic_tag_reader", "cameras.csv") as camerasCsv:
|
|
72
|
+
camerasReader = csv.DictReader(camerasCsv, delimiter=";")
|
|
73
|
+
for camera in camerasReader:
|
|
74
|
+
make = camera["make"].lower()
|
|
75
|
+
model = camera["model"].lower()
|
|
76
|
+
sensorWidth = float(camera["sensor_width"]) if camera["sensor_width"] != "" else None
|
|
77
|
+
is360 = camera["is_360"] == "1"
|
|
78
|
+
gpsAccuracy = float(camera["gps_accuracy"]) if camera["gps_accuracy"] != "" else None
|
|
79
|
+
|
|
80
|
+
# Override GPS Accuracy with Make one if necessary
|
|
81
|
+
if gpsAccuracy is None and make in GPS_ACCURACY_MAKE:
|
|
82
|
+
gpsAccuracy = GPS_ACCURACY_MAKE[make]
|
|
83
|
+
|
|
84
|
+
# Append to general list
|
|
85
|
+
if not make in CAMERAS:
|
|
86
|
+
CAMERAS[make] = {}
|
|
87
|
+
|
|
88
|
+
CAMERAS[make][model] = CameraMetadata(is360, sensorWidth, gpsAccuracy)
|
|
89
|
+
|
|
90
|
+
return CAMERAS
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def find_camera(make: Optional[str] = None, model: Optional[str] = None) -> Optional[CameraMetadata]:
|
|
94
|
+
"""
|
|
95
|
+
Finds camera metadata based on make and model.
|
|
96
|
+
|
|
97
|
+
>>> find_camera()
|
|
98
|
+
|
|
99
|
+
>>> find_camera("GoPro")
|
|
100
|
+
|
|
101
|
+
>>> find_camera("GoPro", "Max")
|
|
102
|
+
CameraMetadata(is_360=True, sensor_width=6.17, gps_accuracy=4)
|
|
103
|
+
>>> find_camera("GoPro", "Max 360")
|
|
104
|
+
CameraMetadata(is_360=True, sensor_width=6.17, gps_accuracy=4)
|
|
105
|
+
"""
|
|
106
|
+
|
|
107
|
+
# Check make and model are defined
|
|
108
|
+
if not make or not model:
|
|
109
|
+
return None
|
|
110
|
+
|
|
111
|
+
# Find make
|
|
112
|
+
cameras = get_cameras()
|
|
113
|
+
matchMake = next((m for m in cameras.keys() if m in make.lower()), None)
|
|
114
|
+
if matchMake is None:
|
|
115
|
+
return None
|
|
116
|
+
|
|
117
|
+
# Find model
|
|
118
|
+
return next((cameras[matchMake][matchModel] for matchModel in cameras[matchMake].keys() if model.lower().startswith(matchModel)), None)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def is_360(make: Optional[str] = None, model: Optional[str] = None, width: Optional[str] = None, height: Optional[str] = None) -> bool:
|
|
122
|
+
"""
|
|
123
|
+
Checks if given camera is equirectangular (360°) based on its make, model and dimensions (width, height).
|
|
124
|
+
|
|
125
|
+
>>> is_360()
|
|
126
|
+
False
|
|
127
|
+
>>> is_360("GoPro")
|
|
128
|
+
False
|
|
129
|
+
>>> is_360("GoPro", "Max 360")
|
|
130
|
+
True
|
|
131
|
+
>>> is_360("GoPro", "Max 360", "2048", "1024")
|
|
132
|
+
True
|
|
133
|
+
>>> is_360("GoPro", "Max 360", "1024", "768")
|
|
134
|
+
False
|
|
135
|
+
>>> is_360("RICOH", "THETA S", "5376", "2688")
|
|
136
|
+
True
|
|
137
|
+
"""
|
|
138
|
+
|
|
139
|
+
# Check make and model are defined
|
|
140
|
+
camera = find_camera(make, model)
|
|
141
|
+
if not camera:
|
|
142
|
+
return False
|
|
143
|
+
|
|
144
|
+
# Check width and height are equirectangular
|
|
145
|
+
if not ((width is None or height is None) or int(width) == 2 * int(height)):
|
|
146
|
+
return False
|
|
147
|
+
|
|
148
|
+
return camera.is_360
|