geopic-tag-reader 1.3.2__py3-none-any.whl → 1.4.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.
Files changed (30) hide show
  1. geopic_tag_reader/__init__.py +1 -1
  2. geopic_tag_reader/camera.py +118 -30
  3. geopic_tag_reader/cameras.csv +3775 -0
  4. geopic_tag_reader/main.py +3 -0
  5. geopic_tag_reader/reader.py +89 -8
  6. geopic_tag_reader/translations/da/LC_MESSAGES/geopic_tag_reader.mo +0 -0
  7. geopic_tag_reader/translations/da/LC_MESSAGES/geopic_tag_reader.po +209 -0
  8. geopic_tag_reader/translations/de/LC_MESSAGES/geopic_tag_reader.mo +0 -0
  9. geopic_tag_reader/translations/de/LC_MESSAGES/geopic_tag_reader.po +44 -1
  10. geopic_tag_reader/translations/en/LC_MESSAGES/geopic_tag_reader.mo +0 -0
  11. geopic_tag_reader/translations/en/LC_MESSAGES/geopic_tag_reader.po +77 -34
  12. geopic_tag_reader/translations/eo/LC_MESSAGES/geopic_tag_reader.mo +0 -0
  13. geopic_tag_reader/translations/eo/LC_MESSAGES/geopic_tag_reader.po +207 -0
  14. geopic_tag_reader/translations/fr/LC_MESSAGES/geopic_tag_reader.mo +0 -0
  15. geopic_tag_reader/translations/fr/LC_MESSAGES/geopic_tag_reader.po +152 -119
  16. geopic_tag_reader/translations/geopic_tag_reader.pot +68 -30
  17. geopic_tag_reader/translations/hu/LC_MESSAGES/geopic_tag_reader.mo +0 -0
  18. geopic_tag_reader/translations/hu/LC_MESSAGES/geopic_tag_reader.po +4 -4
  19. geopic_tag_reader/translations/it/LC_MESSAGES/geopic_tag_reader.mo +0 -0
  20. geopic_tag_reader/translations/it/LC_MESSAGES/geopic_tag_reader.po +212 -0
  21. geopic_tag_reader/translations/nl/LC_MESSAGES/geopic_tag_reader.mo +0 -0
  22. geopic_tag_reader/translations/nl/LC_MESSAGES/geopic_tag_reader.po +213 -0
  23. geopic_tag_reader/translations/pl/LC_MESSAGES/geopic_tag_reader.mo +0 -0
  24. geopic_tag_reader/translations/pl/LC_MESSAGES/geopic_tag_reader.po +203 -0
  25. {geopic_tag_reader-1.3.2.dist-info → geopic_tag_reader-1.4.0.dist-info}/METADATA +2 -2
  26. geopic_tag_reader-1.4.0.dist-info/RECORD +40 -0
  27. {geopic_tag_reader-1.3.2.dist-info → geopic_tag_reader-1.4.0.dist-info}/WHEEL +1 -1
  28. geopic_tag_reader-1.3.2.dist-info/RECORD +0 -29
  29. {geopic_tag_reader-1.3.2.dist-info → geopic_tag_reader-1.4.0.dist-info}/LICENSE +0 -0
  30. {geopic_tag_reader-1.3.2.dist-info → geopic_tag_reader-1.4.0.dist-info}/entry_points.txt +0 -0
@@ -2,4 +2,4 @@
2
2
  GeoPicTagReader
3
3
  """
4
4
 
5
- __version__ = "1.3.2"
5
+ __version__ = "1.4.0"
@@ -1,30 +1,123 @@
1
1
  from typing import Optional, Dict, List
2
+ import importlib.resources
3
+ import csv
4
+ from dataclasses import dataclass
2
5
 
3
- # List of equirectangular cameras (make, model)
4
- # https://en.wikipedia.org/wiki/List_of_omnidirectional_(360-degree)_cameras
5
- EQUIRECTANGULAR_MODELS: Dict[str, List[str]] = {
6
- "Panono": ["Panono"],
7
- "Vuze": ["Vuze"],
8
- "Vuze+": ["Vuze+"],
9
- "BUBL": ["Bublcam"],
10
- "Ricoh": ["Theta", "Theta m15", "Theta S", "Theta SC", "Theta SC2", "Theta V", "Theta Z1"],
11
- "Insta360": ["4K", "Nano", "Air", "One", "Pro", "One X", "One R", "X3", "Titan"],
12
- "Samsung": ["Gear360"],
13
- "LG": ["360 CAM"],
14
- "MadV": ["Madventure 360"],
15
- "Nikon": ["Keymission 360"],
16
- "Xiaomi": ["米家全景相机"],
17
- "小蚁(YI)": ["小蚁VR全景相机"],
18
- "Giroptic iO": ["Giroptic iO"],
19
- "Garmin": ["Virb 360"],
20
- "Nokia": ["OZO"],
21
- "Z Cam": ["S1 Pro", "V1 Pro"],
22
- "Rylo": ["Rylo"],
23
- "GoPro": ["Fusion", "Max"],
24
- "FXG": ["SEIZE", "FM360 Duo"],
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,
25
46
  }
26
47
 
27
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
+
28
121
  def is_360(make: Optional[str] = None, model: Optional[str] = None, width: Optional[str] = None, height: Optional[str] = None) -> bool:
29
122
  """
30
123
  Checks if given camera is equirectangular (360°) based on its make, model and dimensions (width, height).
@@ -44,17 +137,12 @@ def is_360(make: Optional[str] = None, model: Optional[str] = None, width: Optio
44
137
  """
45
138
 
46
139
  # Check make and model are defined
47
- if not make or not model:
140
+ camera = find_camera(make, model)
141
+ if not camera:
48
142
  return False
49
143
 
50
144
  # Check width and height are equirectangular
51
145
  if not ((width is None or height is None) or int(width) == 2 * int(height)):
52
146
  return False
53
147
 
54
- # Find make
55
- matchMake = next((m for m in EQUIRECTANGULAR_MODELS.keys() if make.lower() == m.lower()), None)
56
- if matchMake is None:
57
- return False
58
-
59
- # Find model
60
- return any(model.lower().startswith(m.lower()) for m in EQUIRECTANGULAR_MODELS[matchMake])
148
+ return camera.is_360