cesiumjs-anywidget 0.7.0__py3-none-any.whl → 0.8.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.
- cesiumjs_anywidget/__init__.py +0 -4
- cesiumjs_anywidget/geoid.py +0 -1
- {cesiumjs_anywidget-0.7.0.dist-info → cesiumjs_anywidget-0.8.0.dist-info}/METADATA +3 -18
- cesiumjs_anywidget-0.8.0.dist-info/RECORD +10 -0
- cesiumjs_anywidget/exif_utils.py +0 -281
- cesiumjs_anywidget-0.7.0.dist-info/RECORD +0 -11
- {cesiumjs_anywidget-0.7.0.dist-info → cesiumjs_anywidget-0.8.0.dist-info}/WHEEL +0 -0
- {cesiumjs_anywidget-0.7.0.dist-info → cesiumjs_anywidget-0.8.0.dist-info}/licenses/LICENSE +0 -0
cesiumjs_anywidget/__init__.py
CHANGED
|
@@ -9,7 +9,6 @@ from .geoid import (
|
|
|
9
9
|
set_geoid_data_url,
|
|
10
10
|
)
|
|
11
11
|
from .logger import get_logger, set_log_level
|
|
12
|
-
from .exif_utils import extract_all_metadata, extract_gps_data, extract_datetime
|
|
13
12
|
|
|
14
13
|
__version__ = "0.6.0"
|
|
15
14
|
__all__ = [
|
|
@@ -21,7 +20,4 @@ __all__ = [
|
|
|
21
20
|
"set_geoid_data_url",
|
|
22
21
|
"get_logger",
|
|
23
22
|
"set_log_level",
|
|
24
|
-
"extract_all_metadata",
|
|
25
|
-
"extract_gps_data",
|
|
26
|
-
"extract_datetime",
|
|
27
23
|
]
|
cesiumjs_anywidget/geoid.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cesiumjs-anywidget
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.0
|
|
4
4
|
Summary: A Jupyter widget for CesiumJS 3D globe visualization using anywidget
|
|
5
5
|
Project-URL: Homepage, https://github.com/Alex-PLACET/cesiumjs_anywidget
|
|
6
6
|
Project-URL: Repository, https://github.com/Alex-PLACET/cesiumjs_anywidget
|
|
@@ -216,25 +216,10 @@ Classifier: Programming Language :: Python :: 3
|
|
|
216
216
|
Classifier: Programming Language :: Python :: 3.12
|
|
217
217
|
Requires-Python: >=3.12
|
|
218
218
|
Requires-Dist: anywidget>=0.9.0
|
|
219
|
-
Requires-Dist:
|
|
220
|
-
Requires-Dist: ipycanvas>=0.14.3
|
|
221
|
-
Requires-Dist: jupyterlab>=4.3.8
|
|
222
|
-
Requires-Dist: matplotlib>=3.7.5
|
|
223
|
-
Requires-Dist: numpy>=1.24.4
|
|
224
|
-
Requires-Dist: opencv-python>=4.11.0.86
|
|
225
|
-
Requires-Dist: pillow>=10.4.0
|
|
219
|
+
Requires-Dist: numpy>=2.4.0
|
|
226
220
|
Requires-Dist: pygeodesy>=25.11.5
|
|
227
|
-
Requires-Dist: scipy>=1.
|
|
221
|
+
Requires-Dist: scipy>=1.17.0
|
|
228
222
|
Requires-Dist: traitlets>=5.0.0
|
|
229
|
-
Provides-Extra: dev
|
|
230
|
-
Requires-Dist: huggingface-hub>=1.3.4; extra == 'dev'
|
|
231
|
-
Requires-Dist: jupyterlab>=4.0.0; extra == 'dev'
|
|
232
|
-
Requires-Dist: notebook>=7.0.0; extra == 'dev'
|
|
233
|
-
Requires-Dist: piexif>=1.1.3; extra == 'dev'
|
|
234
|
-
Requires-Dist: pyarrow>=23.0.0; extra == 'dev'
|
|
235
|
-
Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
|
|
236
|
-
Requires-Dist: pytest>=7.0.0; extra == 'dev'
|
|
237
|
-
Requires-Dist: sidecar>=0.8.0; extra == 'dev'
|
|
238
223
|
Description-Content-Type: text/markdown
|
|
239
224
|
|
|
240
225
|
# CesiumJS Anywidget
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
cesiumjs_anywidget/__init__.py,sha256=2HDc9vodXRcFenpEYK9lZrDpWrrf3JddbqSKQYJ60jE,508
|
|
2
|
+
cesiumjs_anywidget/geoid.py,sha256=UJQM2p5OPtLcmbE9FCEYSiOg_nBSaSQ3JyvYNkIYHAw,9475
|
|
3
|
+
cesiumjs_anywidget/logger.py,sha256=W8yxB8ni1modVH7VdSiT0IbIZFGTr1Y-FPQDfpHA5ao,2037
|
|
4
|
+
cesiumjs_anywidget/styles.css,sha256=1N2nya5nsd5Lhq9FuEoMJ1_CVXj9XghdMmX-KC5xwQI,1278
|
|
5
|
+
cesiumjs_anywidget/widget.py,sha256=8L6CFM0eynUcpI8l8lUvS9d0xM3YEHImziMzdkhqIZE,59471
|
|
6
|
+
cesiumjs_anywidget/index.js,sha256=dGG7AtlBXbx8SLkrjpSjRfolRx2Fo-_N4rgDHBR-F-g,113957
|
|
7
|
+
cesiumjs_anywidget-0.8.0.dist-info/METADATA,sha256=IzTaPprNida4dLL38TZyifsybrDFj_KJoUYzanca7as,25606
|
|
8
|
+
cesiumjs_anywidget-0.8.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
9
|
+
cesiumjs_anywidget-0.8.0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
10
|
+
cesiumjs_anywidget-0.8.0.dist-info/RECORD,,
|
cesiumjs_anywidget/exif_utils.py
DELETED
|
@@ -1,281 +0,0 @@
|
|
|
1
|
-
"""EXIF data extraction utilities for photo geolocation."""
|
|
2
|
-
|
|
3
|
-
import exifread
|
|
4
|
-
from datetime import datetime
|
|
5
|
-
from typing import Optional, Dict, Any, Tuple
|
|
6
|
-
from pathlib import Path
|
|
7
|
-
from .logger import get_logger
|
|
8
|
-
|
|
9
|
-
logger = get_logger(__name__)
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
def _convert_to_degrees(value) -> float:
|
|
13
|
-
"""Convert GPS coordinates to degrees.
|
|
14
|
-
|
|
15
|
-
Parameters
|
|
16
|
-
----------
|
|
17
|
-
value : exifread.utils.Ratio list
|
|
18
|
-
GPS coordinate in degrees, minutes, seconds format
|
|
19
|
-
|
|
20
|
-
Returns
|
|
21
|
-
-------
|
|
22
|
-
float
|
|
23
|
-
Coordinate in decimal degrees
|
|
24
|
-
"""
|
|
25
|
-
d = float(value.values[0].num) / float(value.values[0].den)
|
|
26
|
-
m = float(value.values[1].num) / float(value.values[1].den)
|
|
27
|
-
s = float(value.values[2].num) / float(value.values[2].den)
|
|
28
|
-
|
|
29
|
-
# Normalize malformed DMS values from some writers
|
|
30
|
-
# Some encoders store seconds as total arc-seconds * 60 (e.g., 1425 instead of 23.75)
|
|
31
|
-
if s >= 60 and m < 60:
|
|
32
|
-
s = s / 60.0
|
|
33
|
-
|
|
34
|
-
if s >= 60:
|
|
35
|
-
carry = int(s // 60)
|
|
36
|
-
s = s - (carry * 60)
|
|
37
|
-
m += carry
|
|
38
|
-
|
|
39
|
-
if m >= 60:
|
|
40
|
-
carry = int(m // 60)
|
|
41
|
-
m = m - (carry * 60)
|
|
42
|
-
d += carry
|
|
43
|
-
|
|
44
|
-
return d + (m / 60.0) + (s / 3600.0)
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
def extract_gps_data(image_path: str) -> Optional[Dict[str, float]]:
|
|
48
|
-
"""Extract GPS coordinates from image EXIF data.
|
|
49
|
-
|
|
50
|
-
Parameters
|
|
51
|
-
----------
|
|
52
|
-
image_path : str
|
|
53
|
-
Path to the image file
|
|
54
|
-
|
|
55
|
-
Returns
|
|
56
|
-
-------
|
|
57
|
-
dict or None
|
|
58
|
-
Dictionary containing 'latitude', 'longitude', and optionally 'altitude'
|
|
59
|
-
Returns None if GPS data is not available
|
|
60
|
-
|
|
61
|
-
Examples
|
|
62
|
-
--------
|
|
63
|
-
>>> gps_data = extract_gps_data('photo.jpg')
|
|
64
|
-
>>> if gps_data:
|
|
65
|
-
... print(f"Location: {gps_data['latitude']}, {gps_data['longitude']}")
|
|
66
|
-
"""
|
|
67
|
-
try:
|
|
68
|
-
with open(image_path, 'rb') as f:
|
|
69
|
-
tags = exifread.process_file(f, details=False)
|
|
70
|
-
|
|
71
|
-
# Check if GPS data exists
|
|
72
|
-
if 'GPS GPSLatitude' not in tags or 'GPS GPSLongitude' not in tags:
|
|
73
|
-
logger.warning(f"No GPS data found in {image_path}")
|
|
74
|
-
return None
|
|
75
|
-
|
|
76
|
-
# Extract latitude
|
|
77
|
-
lat = _convert_to_degrees(tags['GPS GPSLatitude'])
|
|
78
|
-
if tags.get('GPS GPSLatitudeRef', 'N').values[0] == 'S':
|
|
79
|
-
lat = -lat
|
|
80
|
-
|
|
81
|
-
# Extract longitude
|
|
82
|
-
lon = _convert_to_degrees(tags['GPS GPSLongitude'])
|
|
83
|
-
if tags.get('GPS GPSLongitudeRef', 'E').values[0] == 'W':
|
|
84
|
-
lon = -lon
|
|
85
|
-
|
|
86
|
-
result = {
|
|
87
|
-
'latitude': lat,
|
|
88
|
-
'longitude': lon,
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
# Extract altitude if available
|
|
92
|
-
if 'GPS GPSAltitude' in tags:
|
|
93
|
-
alt_tag = tags['GPS GPSAltitude']
|
|
94
|
-
altitude = float(alt_tag.values[0].num) / float(alt_tag.values[0].den)
|
|
95
|
-
|
|
96
|
-
# Check altitude reference (0 = above sea level, 1 = below sea level)
|
|
97
|
-
if 'GPS GPSAltitudeRef' in tags:
|
|
98
|
-
alt_ref = tags['GPS GPSAltitudeRef'].values[0]
|
|
99
|
-
if alt_ref == 1:
|
|
100
|
-
altitude = -altitude
|
|
101
|
-
|
|
102
|
-
result['altitude'] = altitude
|
|
103
|
-
|
|
104
|
-
logger.info(f"Extracted GPS data from {image_path}: {result}")
|
|
105
|
-
return result
|
|
106
|
-
|
|
107
|
-
except Exception as e:
|
|
108
|
-
logger.error(f"Error extracting GPS data from {image_path}: {e}")
|
|
109
|
-
return None
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
def extract_datetime(image_path: str) -> Optional[datetime]:
|
|
113
|
-
"""Extract capture datetime from image EXIF data.
|
|
114
|
-
|
|
115
|
-
Parameters
|
|
116
|
-
----------
|
|
117
|
-
image_path : str
|
|
118
|
-
Path to the image file
|
|
119
|
-
|
|
120
|
-
Returns
|
|
121
|
-
-------
|
|
122
|
-
datetime or None
|
|
123
|
-
Image capture datetime, or None if not available
|
|
124
|
-
|
|
125
|
-
Examples
|
|
126
|
-
--------
|
|
127
|
-
>>> dt = extract_datetime('photo.jpg')
|
|
128
|
-
>>> if dt:
|
|
129
|
-
... print(f"Captured on: {dt.strftime('%Y-%m-%d %H:%M:%S')}")
|
|
130
|
-
"""
|
|
131
|
-
try:
|
|
132
|
-
with open(image_path, 'rb') as f:
|
|
133
|
-
tags = exifread.process_file(f, details=False)
|
|
134
|
-
|
|
135
|
-
# Try different datetime tags
|
|
136
|
-
for tag_name in ['EXIF DateTimeOriginal', 'EXIF DateTime', 'Image DateTime']:
|
|
137
|
-
if tag_name in tags:
|
|
138
|
-
dt_str = str(tags[tag_name])
|
|
139
|
-
# Parse datetime (format: "YYYY:MM:DD HH:MM:SS")
|
|
140
|
-
dt = datetime.strptime(dt_str, '%Y:%m:%d %H:%M:%S')
|
|
141
|
-
logger.info(f"Extracted datetime from {image_path}: {dt}")
|
|
142
|
-
return dt
|
|
143
|
-
|
|
144
|
-
logger.warning(f"No datetime found in {image_path}")
|
|
145
|
-
return None
|
|
146
|
-
|
|
147
|
-
except Exception as e:
|
|
148
|
-
logger.error(f"Error extracting datetime from {image_path}: {e}")
|
|
149
|
-
return None
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
def extract_camera_info(image_path: str) -> Dict[str, Any]:
|
|
153
|
-
"""Extract camera information from image EXIF data.
|
|
154
|
-
|
|
155
|
-
Parameters
|
|
156
|
-
----------
|
|
157
|
-
image_path : str
|
|
158
|
-
Path to the image file
|
|
159
|
-
|
|
160
|
-
Returns
|
|
161
|
-
-------
|
|
162
|
-
dict
|
|
163
|
-
Dictionary containing camera make, model, focal length, sensor size, etc.
|
|
164
|
-
|
|
165
|
-
Examples
|
|
166
|
-
--------
|
|
167
|
-
>>> info = extract_camera_info('photo.jpg')
|
|
168
|
-
>>> print(f"Camera: {info.get('make')} {info.get('model')}")
|
|
169
|
-
"""
|
|
170
|
-
result = {}
|
|
171
|
-
|
|
172
|
-
try:
|
|
173
|
-
with open(image_path, 'rb') as f:
|
|
174
|
-
tags = exifread.process_file(f, details=False)
|
|
175
|
-
|
|
176
|
-
# Camera make and model
|
|
177
|
-
if 'Image Make' in tags:
|
|
178
|
-
result['make'] = str(tags['Image Make'])
|
|
179
|
-
if 'Image Model' in tags:
|
|
180
|
-
result['model'] = str(tags['Image Model'])
|
|
181
|
-
|
|
182
|
-
# Focal length
|
|
183
|
-
if 'EXIF FocalLength' in tags:
|
|
184
|
-
focal_tag = tags['EXIF FocalLength']
|
|
185
|
-
focal_length = float(focal_tag.values[0].num) / float(focal_tag.values[0].den)
|
|
186
|
-
result['focal_length_mm'] = focal_length
|
|
187
|
-
|
|
188
|
-
# Focal length in 35mm equivalent
|
|
189
|
-
if 'EXIF FocalLengthIn35mmFilm' in tags:
|
|
190
|
-
result['focal_length_35mm'] = int(tags['EXIF FocalLengthIn35mmFilm'].values[0])
|
|
191
|
-
|
|
192
|
-
# Image dimensions
|
|
193
|
-
if 'EXIF ExifImageWidth' in tags:
|
|
194
|
-
result['image_width'] = int(tags['EXIF ExifImageWidth'].values[0])
|
|
195
|
-
if 'EXIF ExifImageLength' in tags:
|
|
196
|
-
result['image_height'] = int(tags['EXIF ExifImageLength'].values[0])
|
|
197
|
-
|
|
198
|
-
# Orientation
|
|
199
|
-
if 'Image Orientation' in tags:
|
|
200
|
-
result['orientation'] = str(tags['Image Orientation'])
|
|
201
|
-
|
|
202
|
-
logger.info(f"Extracted camera info from {image_path}: {result}")
|
|
203
|
-
return result
|
|
204
|
-
|
|
205
|
-
except Exception as e:
|
|
206
|
-
logger.error(f"Error extracting camera info from {image_path}: {e}")
|
|
207
|
-
return result
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
def get_image_dimensions(image_path: str) -> Optional[Tuple[int, int]]:
|
|
211
|
-
"""Get image dimensions (width, height) from file.
|
|
212
|
-
|
|
213
|
-
Parameters
|
|
214
|
-
----------
|
|
215
|
-
image_path : str
|
|
216
|
-
Path to the image file
|
|
217
|
-
|
|
218
|
-
Returns
|
|
219
|
-
-------
|
|
220
|
-
tuple or None
|
|
221
|
-
(width, height) in pixels, or None if unable to read
|
|
222
|
-
"""
|
|
223
|
-
try:
|
|
224
|
-
from PIL import Image
|
|
225
|
-
with Image.open(image_path) as img:
|
|
226
|
-
return img.size
|
|
227
|
-
except Exception as e:
|
|
228
|
-
logger.error(f"Error reading image dimensions from {image_path}: {e}")
|
|
229
|
-
return None
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
def extract_all_metadata(image_path: str) -> Dict[str, Any]:
|
|
233
|
-
"""Extract all relevant metadata from an image.
|
|
234
|
-
|
|
235
|
-
Combines GPS, datetime, camera info, and image dimensions into one dictionary.
|
|
236
|
-
|
|
237
|
-
Parameters
|
|
238
|
-
----------
|
|
239
|
-
image_path : str
|
|
240
|
-
Path to the image file
|
|
241
|
-
|
|
242
|
-
Returns
|
|
243
|
-
-------
|
|
244
|
-
dict
|
|
245
|
-
Dictionary containing all extracted metadata
|
|
246
|
-
|
|
247
|
-
Examples
|
|
248
|
-
--------
|
|
249
|
-
>>> metadata = extract_all_metadata('photo.jpg')
|
|
250
|
-
>>> print(metadata)
|
|
251
|
-
{'latitude': 48.8566, 'longitude': 2.3522, 'altitude': 100,
|
|
252
|
-
'datetime': datetime(2024, 1, 15, 14, 30, 0),
|
|
253
|
-
'make': 'Canon', 'model': 'EOS R5', ...}
|
|
254
|
-
"""
|
|
255
|
-
metadata = {
|
|
256
|
-
'file_path': str(Path(image_path).absolute()),
|
|
257
|
-
'file_name': Path(image_path).name,
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
# GPS data
|
|
261
|
-
gps_data = extract_gps_data(image_path)
|
|
262
|
-
if gps_data:
|
|
263
|
-
metadata.update(gps_data)
|
|
264
|
-
|
|
265
|
-
# Datetime
|
|
266
|
-
dt = extract_datetime(image_path)
|
|
267
|
-
if dt:
|
|
268
|
-
metadata['datetime'] = dt
|
|
269
|
-
metadata['datetime_str'] = dt.isoformat()
|
|
270
|
-
|
|
271
|
-
# Camera info
|
|
272
|
-
camera_info = extract_camera_info(image_path)
|
|
273
|
-
metadata.update(camera_info)
|
|
274
|
-
|
|
275
|
-
# Image dimensions
|
|
276
|
-
dims = get_image_dimensions(image_path)
|
|
277
|
-
if dims:
|
|
278
|
-
metadata['width'] = dims[0]
|
|
279
|
-
metadata['height'] = dims[1]
|
|
280
|
-
|
|
281
|
-
return metadata
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
cesiumjs_anywidget/__init__.py,sha256=mdoSqnikLbSjLK8SYDpey40DHKzgsNUAQoIeLR2PMBQ,665
|
|
2
|
-
cesiumjs_anywidget/exif_utils.py,sha256=Avk_JZAPk8EjGGluLK9tc-V8GE52rR0aDYgM113p2Z4,8439
|
|
3
|
-
cesiumjs_anywidget/geoid.py,sha256=aS-GtzQ0f8XOm-FHbCpDuIQ5PN4QpMYYS4Wf8q1gb-I,9485
|
|
4
|
-
cesiumjs_anywidget/logger.py,sha256=W8yxB8ni1modVH7VdSiT0IbIZFGTr1Y-FPQDfpHA5ao,2037
|
|
5
|
-
cesiumjs_anywidget/styles.css,sha256=1N2nya5nsd5Lhq9FuEoMJ1_CVXj9XghdMmX-KC5xwQI,1278
|
|
6
|
-
cesiumjs_anywidget/widget.py,sha256=8L6CFM0eynUcpI8l8lUvS9d0xM3YEHImziMzdkhqIZE,59471
|
|
7
|
-
cesiumjs_anywidget/index.js,sha256=dGG7AtlBXbx8SLkrjpSjRfolRx2Fo-_N4rgDHBR-F-g,113957
|
|
8
|
-
cesiumjs_anywidget-0.7.0.dist-info/METADATA,sha256=iN4D86KZ5Q6S_rbKmiAdgymyNItlzdULdAu2IXYPFSs,26209
|
|
9
|
-
cesiumjs_anywidget-0.7.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
10
|
-
cesiumjs_anywidget-0.7.0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
11
|
-
cesiumjs_anywidget-0.7.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|