geopic-tag-reader 1.3.2__tar.gz → 1.4.0__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 (59) hide show
  1. {geopic_tag_reader-1.3.2 → geopic_tag_reader-1.4.0}/CHANGELOG.md +17 -1
  2. {geopic_tag_reader-1.3.2 → geopic_tag_reader-1.4.0}/PKG-INFO +2 -2
  3. {geopic_tag_reader-1.3.2 → geopic_tag_reader-1.4.0}/docs/develop.md +14 -0
  4. geopic_tag_reader-1.4.0/docs/images/quality_score.png +0 -0
  5. geopic_tag_reader-1.4.0/docs/images/quality_score_viewer.png +0 -0
  6. {geopic_tag_reader-1.3.2 → geopic_tag_reader-1.4.0}/docs/index.md +1 -1
  7. geopic_tag_reader-1.4.0/docs/quality_score.md +84 -0
  8. {geopic_tag_reader-1.3.2 → geopic_tag_reader-1.4.0}/geopic_tag_reader/__init__.py +1 -1
  9. geopic_tag_reader-1.4.0/geopic_tag_reader/camera.py +148 -0
  10. geopic_tag_reader-1.4.0/geopic_tag_reader/cameras.csv +3775 -0
  11. {geopic_tag_reader-1.3.2 → geopic_tag_reader-1.4.0}/geopic_tag_reader/main.py +3 -0
  12. {geopic_tag_reader-1.3.2 → geopic_tag_reader-1.4.0}/geopic_tag_reader/reader.py +89 -8
  13. geopic_tag_reader-1.4.0/geopic_tag_reader/translations/da/LC_MESSAGES/geopic_tag_reader.mo +0 -0
  14. geopic_tag_reader-1.4.0/geopic_tag_reader/translations/da/LC_MESSAGES/geopic_tag_reader.po +209 -0
  15. geopic_tag_reader-1.4.0/geopic_tag_reader/translations/de/LC_MESSAGES/geopic_tag_reader.mo +0 -0
  16. {geopic_tag_reader-1.3.2 → geopic_tag_reader-1.4.0}/geopic_tag_reader/translations/de/LC_MESSAGES/geopic_tag_reader.po +44 -1
  17. geopic_tag_reader-1.4.0/geopic_tag_reader/translations/en/LC_MESSAGES/geopic_tag_reader.mo +0 -0
  18. {geopic_tag_reader-1.3.2 → geopic_tag_reader-1.4.0}/geopic_tag_reader/translations/en/LC_MESSAGES/geopic_tag_reader.po +77 -34
  19. geopic_tag_reader-1.4.0/geopic_tag_reader/translations/eo/LC_MESSAGES/geopic_tag_reader.mo +0 -0
  20. geopic_tag_reader-1.4.0/geopic_tag_reader/translations/eo/LC_MESSAGES/geopic_tag_reader.po +207 -0
  21. geopic_tag_reader-1.4.0/geopic_tag_reader/translations/fr/LC_MESSAGES/geopic_tag_reader.mo +0 -0
  22. {geopic_tag_reader-1.3.2 → geopic_tag_reader-1.4.0}/geopic_tag_reader/translations/fr/LC_MESSAGES/geopic_tag_reader.po +152 -119
  23. {geopic_tag_reader-1.3.2 → geopic_tag_reader-1.4.0}/geopic_tag_reader/translations/geopic_tag_reader.pot +68 -30
  24. {geopic_tag_reader-1.3.2 → geopic_tag_reader-1.4.0}/geopic_tag_reader/translations/hu/LC_MESSAGES/geopic_tag_reader.mo +0 -0
  25. {geopic_tag_reader-1.3.2 → geopic_tag_reader-1.4.0}/geopic_tag_reader/translations/hu/LC_MESSAGES/geopic_tag_reader.po +4 -4
  26. geopic_tag_reader-1.4.0/geopic_tag_reader/translations/it/LC_MESSAGES/geopic_tag_reader.mo +0 -0
  27. geopic_tag_reader-1.4.0/geopic_tag_reader/translations/it/LC_MESSAGES/geopic_tag_reader.po +212 -0
  28. geopic_tag_reader-1.4.0/geopic_tag_reader/translations/nl/LC_MESSAGES/geopic_tag_reader.mo +0 -0
  29. geopic_tag_reader-1.4.0/geopic_tag_reader/translations/nl/LC_MESSAGES/geopic_tag_reader.po +213 -0
  30. geopic_tag_reader-1.4.0/geopic_tag_reader/translations/pl/LC_MESSAGES/geopic_tag_reader.mo +0 -0
  31. geopic_tag_reader-1.4.0/geopic_tag_reader/translations/pl/LC_MESSAGES/geopic_tag_reader.po +203 -0
  32. {geopic_tag_reader-1.3.2 → geopic_tag_reader-1.4.0}/mkdocs.yml +1 -0
  33. {geopic_tag_reader-1.3.2 → geopic_tag_reader-1.4.0}/pyproject.toml +1 -1
  34. geopic_tag_reader-1.3.2/geopic_tag_reader/camera.py +0 -60
  35. geopic_tag_reader-1.3.2/geopic_tag_reader/translations/de/LC_MESSAGES/geopic_tag_reader.mo +0 -0
  36. geopic_tag_reader-1.3.2/geopic_tag_reader/translations/en/LC_MESSAGES/geopic_tag_reader.mo +0 -0
  37. geopic_tag_reader-1.3.2/geopic_tag_reader/translations/fr/LC_MESSAGES/geopic_tag_reader.mo +0 -0
  38. {geopic_tag_reader-1.3.2 → geopic_tag_reader-1.4.0}/.gitignore +0 -0
  39. {geopic_tag_reader-1.3.2 → geopic_tag_reader-1.4.0}/.gitlab-ci.yml +0 -0
  40. {geopic_tag_reader-1.3.2 → geopic_tag_reader-1.4.0}/.pre-commit-config.yaml +0 -0
  41. {geopic_tag_reader-1.3.2 → geopic_tag_reader-1.4.0}/CODE_OF_CONDUCT.md +0 -0
  42. {geopic_tag_reader-1.3.2 → geopic_tag_reader-1.4.0}/LICENSE +0 -0
  43. {geopic_tag_reader-1.3.2 → geopic_tag_reader-1.4.0}/Makefile +0 -0
  44. {geopic_tag_reader-1.3.2 → geopic_tag_reader-1.4.0}/README.md +0 -0
  45. {geopic_tag_reader-1.3.2 → geopic_tag_reader-1.4.0}/docs/install.md +0 -0
  46. {geopic_tag_reader-1.3.2 → geopic_tag_reader-1.4.0}/docs/tech/api_reference.md +0 -0
  47. {geopic_tag_reader-1.3.2 → geopic_tag_reader-1.4.0}/docs/tech/cli.md +0 -0
  48. {geopic_tag_reader-1.3.2 → geopic_tag_reader-1.4.0}/geopic_tag_reader/i18n.py +0 -0
  49. {geopic_tag_reader-1.3.2 → geopic_tag_reader-1.4.0}/geopic_tag_reader/model.py +0 -0
  50. {geopic_tag_reader-1.3.2 → geopic_tag_reader-1.4.0}/geopic_tag_reader/py.typed +0 -0
  51. {geopic_tag_reader-1.3.2 → geopic_tag_reader-1.4.0}/geopic_tag_reader/sequence.py +0 -0
  52. {geopic_tag_reader-1.3.2 → geopic_tag_reader-1.4.0}/geopic_tag_reader/translations/es/LC_MESSAGES/geopic_tag_reader.mo +0 -0
  53. {geopic_tag_reader-1.3.2 → geopic_tag_reader-1.4.0}/geopic_tag_reader/translations/es/LC_MESSAGES/geopic_tag_reader.po +0 -0
  54. {geopic_tag_reader-1.3.2 → geopic_tag_reader-1.4.0}/geopic_tag_reader/translations/fi/LC_MESSAGES/geopic_tag_reader.mo +0 -0
  55. {geopic_tag_reader-1.3.2 → geopic_tag_reader-1.4.0}/geopic_tag_reader/translations/fi/LC_MESSAGES/geopic_tag_reader.po +0 -0
  56. {geopic_tag_reader-1.3.2 → geopic_tag_reader-1.4.0}/geopic_tag_reader/translations/ko/LC_MESSAGES/geopic_tag_reader.mo +0 -0
  57. {geopic_tag_reader-1.3.2 → geopic_tag_reader-1.4.0}/geopic_tag_reader/translations/ko/LC_MESSAGES/geopic_tag_reader.po +0 -0
  58. {geopic_tag_reader-1.3.2 → geopic_tag_reader-1.4.0}/geopic_tag_reader/writer.py +0 -0
  59. {geopic_tag_reader-1.3.2 → geopic_tag_reader-1.4.0}/pytest.ini +0 -0
@@ -7,6 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.4.0] - 2025-01-06
11
+
12
+ ### Added
13
+
14
+ - 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.
15
+ - New fields are offered in GeoPicTags: `sensor_width, gps_accuracy, field_of_view`
16
+
17
+ ## [1.3.3] - 2024-11-24
18
+
19
+ ### Added
20
+
21
+ - Warnings are shown if any useful metadata is missing for computing a quality score (make, model, GPS accuracy...).
22
+ - New languages : 🇵🇱, 🇳🇱, 🇭🇺, 🇪🇸, 🇩🇪 (thanks to all translators !)
23
+
10
24
  ## [1.3.2] - 2024-10-22
11
25
 
12
26
  ### Changed
@@ -237,7 +251,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
237
251
 
238
252
  - EXIF tag reading methods extracted from [Panoramax/GeoVisio API](https://gitlab.com/panoramax/server/api)
239
253
 
240
- [Unreleased]: https://gitlab.com/panoramax/server/geo-picture-tag-reader/-/compare/1.3.2...main
254
+ [Unreleased]: https://gitlab.com/panoramax/server/geo-picture-tag-reader/-/compare/1.4.0...main
255
+ [1.4.0]: https://gitlab.com/panoramax/server/geo-picture-tag-reader/-/compare/1.3.3...1.4.0
256
+ [1.3.3]: https://gitlab.com/panoramax/server/geo-picture-tag-reader/-/compare/1.3.2...1.3.3
241
257
  [1.3.2]: https://gitlab.com/panoramax/server/geo-picture-tag-reader/-/compare/1.3.1...1.3.2
242
258
  [1.3.1]: https://gitlab.com/panoramax/server/geo-picture-tag-reader/-/compare/1.3.0...1.3.1
243
259
  [1.3.0]: https://gitlab.com/panoramax/server/geo-picture-tag-reader/-/compare/1.2.0...1.3.0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.3
2
2
  Name: geopic-tag-reader
3
- Version: 1.3.2
3
+ Version: 1.4.0
4
4
  Summary: GeoPicTagReader
5
5
  Author-email: Adrien PAVIE <panieravide@riseup.net>
6
6
  Requires-Python: >=3.8
@@ -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
@@ -54,7 +54,7 @@ Image orientation is read from
54
54
 
55
55
  #### :material-timer: Milliseconds in date
56
56
 
57
- Milliseconds in date is reade from `SubSecTimeOriginal`.
57
+ Milliseconds in date is read from `SubSecTimeOriginal`.
58
58
 
59
59
  #### :material-panorama-sphere-outline: 360° or flat
60
60
 
@@ -0,0 +1,84 @@
1
+ # :white_check_mark: Quality score
2
+
3
+ Panoramax offers a _Quality Score_ for each picture in its web viewer. It allows easy map filtering and comprehensive display of high-quality pictures availability.
4
+
5
+ The grade is shown to users as a A/B/C/D/E score (A is the best, E the worst), and shown graphically through this scale (inspired by [Nutri-Score](https://en.wikipedia.org/wiki/Nutri-Score)):
6
+
7
+ ![Quality score graphics](./images/quality_score.png)
8
+
9
+ ## :material-calculator: Score computation
10
+
11
+ Quality score is based on two indicators:
12
+
13
+ - :material-crosshairs-gps: __GPS position accuracy__: how precisely the GPS coordinates are set, in meters. This is 1/5 of the score.
14
+ - :material-camera-iris: __Horizontal pixel density__: how many pixels we have on horizon per _field of view_ degree. This is 4/5 of the score.
15
+
16
+ They are displayed through web viewer in picture metadata popup:
17
+
18
+ ![Metadata popup and score display](./images/quality_score_viewer.png)
19
+
20
+ !!! info
21
+
22
+ We know that this is a pretty simple indicator. Many other information could be taken into account (blurriness, light...) and may be used in the future. We started with something simple to make the _quality score_ available on as many pictures as possible. You can [come and help us](https://docs.panoramax.fr/how-to-contribute/#contribute-to-the-development) to make this happen sooner :grinning:
23
+
24
+ ### :material-crosshairs-gps: GPS position accuracy
25
+
26
+ This indicator allows to know if we can rely or not on GPS position, based on its precision. It is expressed as a decimal value in meters. The grade is a 5-star rating, and is applied based on these values:
27
+
28
+ | GPS Accuracy | Grade |
29
+ | ----------------- | ----- |
30
+ | <= 1 m | 5/5 |
31
+ | <= 2 m | 4/5 |
32
+ | <= 5 m | 3/5 |
33
+ | <= 10 m | 2/5 |
34
+ | > 10 m or unknown | 1/5 |
35
+
36
+ The value is read from picture EXIF metadata. We rely on the following tags:
37
+
38
+ - __Horizontal positioning error__ (`Exif.GPSInfo.GPSHPositioningError` or `Xmp.exif.GPSHPositioningError`) : expected positioning error in meters.
39
+ - __Dilution of precision__ (`Exif.GPSInfo.GPSDOP` or `Xmp.exif.GPSDOP`) : indicator of expected precision on coordinates (<= 1 means good precision for the sensor, >= 5 is not good, [more info on Wikipedia](https://en.wikipedia.org/wiki/Dilution_of_precision_(navigation))).
40
+ - __Is the GPS differential__ (`Exif.GPSInfo.GPSDifferential` or `Xmp.exif.GPSDifferential`) : indicates if GPS sensor is using [Differential GPS](https://en.wikipedia.org/wiki/Differential_GPS) or not. If yes, we expect a better precision.
41
+
42
+ These are used to define a precision value in meters. If none of these tags are set, we also rely on a default value for several camera vendors (based on `Make` and `Model` EXIF tags).
43
+
44
+ So, to get a good grade on GPS precision, you can (in order):
45
+
46
+ - Set a precise _Horizontal positioning error_
47
+ - Set a value for _Differential GPS_ and/or _Dilution of precision_
48
+ - Set values for _Make_ and _Model_ if you use a common camera
49
+
50
+
51
+ !!! info
52
+
53
+ If you find the shown value not reflecting the real precision of your GPS sensor, feel free to contact us with an example link. We tried to make precision estimates on a large set of cameras, but we may have missed some :material-map-search:
54
+
55
+ ### :material-camera-iris: Horizontal pixel density
56
+
57
+ This indicator allows to have an idea of how high the picture resolution is, independently of its field of view (360° or classic picture). The formula is: `picture width in pixels / field of view in degrees`. We have in result a number of pixels per FOV degree. The grade is a 5-star rating, and is applied based on these values:
58
+
59
+ | Grade | Density for 360° | Density for non 360° |
60
+ | ----- | ---------------- | --------------------------- |
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
+
67
+ !!! info
68
+
69
+ A different rating is applied for 360°, as they offer a nicer user experience on web viewer. Note that if you're a technical reuser, the API offers raw values for each indicator, allowing you to do your own custom rating.
70
+
71
+ The value is computed from picture width in pixels, and some EXIF metadata. We rely on the following tags:
72
+
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
+ - __Make and model__ (`Exif.Image.Make` and `Exif.Image.Make`): camera model, which is used to find camera sensor width.
75
+ - __Projection type__ (for 360° pictures, `Xmp.GPano.ProjectionType`): to mark a picture as 360°
76
+
77
+ Based on these information, we are able to compute the _field of view_ (how wide the camera can look horizontally).
78
+
79
+ As these information are hard to deduce, you may carefully set all of them in EXIF metadata.
80
+
81
+ !!! info
82
+
83
+ If you have pictures with an unknown value for this grade, it's probably because we miss information on your camera model. Make sure your pictures have required information, and contact us with the camera _sensor width_ so we can add it in our listing.
84
+
@@ -2,4 +2,4 @@
2
2
  GeoPicTagReader
3
3
  """
4
4
 
5
- __version__ = "1.3.2"
5
+ __version__ = "1.4.0"
@@ -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