viewtif 0.1.4__py3-none-any.whl → 0.1.6__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.
- viewtif/tif_viewer.py +202 -43
- viewtif-0.1.6.dist-info/METADATA +123 -0
- viewtif-0.1.6.dist-info/RECORD +5 -0
- viewtif-0.1.4.dist-info/METADATA +0 -89
- viewtif-0.1.4.dist-info/RECORD +0 -5
- {viewtif-0.1.4.dist-info → viewtif-0.1.6.dist-info}/WHEEL +0 -0
- {viewtif-0.1.4.dist-info → viewtif-0.1.6.dist-info}/entry_points.txt +0 -0
viewtif/tif_viewer.py
CHANGED
|
@@ -56,12 +56,38 @@ except Exception:
|
|
|
56
56
|
# -------------------------- QGraphicsView tweaks -------------------------- #
|
|
57
57
|
class RasterView(QGraphicsView):
|
|
58
58
|
def __init__(self, *args, **kwargs):
|
|
59
|
+
import numpy as np
|
|
59
60
|
super().__init__(*args, **kwargs)
|
|
60
61
|
self.setRenderHint(QPainter.RenderHint.SmoothPixmapTransform, False)
|
|
61
62
|
self.setDragMode(QGraphicsView.DragMode.ScrollHandDrag)
|
|
62
63
|
self.setTransformationAnchor(QGraphicsView.ViewportAnchor.AnchorUnderMouse)
|
|
63
64
|
self.setResizeAnchor(QGraphicsView.ViewportAnchor.AnchorViewCenter)
|
|
64
65
|
self.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
|
|
66
|
+
self._wheel_zoom_step = 1.2
|
|
67
|
+
|
|
68
|
+
def wheelEvent(self, event):
|
|
69
|
+
"""Zoom in/out centered at the cursor position.
|
|
70
|
+
|
|
71
|
+
Uses a multiplicative scale per 15° wheel step.
|
|
72
|
+
"""
|
|
73
|
+
delta = event.angleDelta().y()
|
|
74
|
+
if delta == 0:
|
|
75
|
+
# Trackpads may report pixelDelta; fall back to it if angleDelta is 0
|
|
76
|
+
pixel_delta = event.pixelDelta().y()
|
|
77
|
+
delta = pixel_delta
|
|
78
|
+
|
|
79
|
+
if delta == 0:
|
|
80
|
+
event.ignore()
|
|
81
|
+
return
|
|
82
|
+
|
|
83
|
+
steps = delta / 120.0 # 120 units per 15° step
|
|
84
|
+
if steps > 0:
|
|
85
|
+
factor = self._wheel_zoom_step ** steps
|
|
86
|
+
else:
|
|
87
|
+
factor = (1.0 / self._wheel_zoom_step) ** (-steps)
|
|
88
|
+
|
|
89
|
+
self.scale(factor, factor)
|
|
90
|
+
event.accept()
|
|
65
91
|
|
|
66
92
|
|
|
67
93
|
# ------------------------------- Main Window ------------------------------ #
|
|
@@ -76,6 +102,7 @@ class TiffViewer(QMainWindow):
|
|
|
76
102
|
shapefiles: list[str] | None = None,
|
|
77
103
|
shp_color: str = "white",
|
|
78
104
|
shp_width: float = 2,
|
|
105
|
+
subset: int | None = None,
|
|
79
106
|
):
|
|
80
107
|
super().__init__()
|
|
81
108
|
|
|
@@ -116,38 +143,118 @@ class TiffViewer(QMainWindow):
|
|
|
116
143
|
self.tif_path = self.tif_path or (os.path.commonprefix([red, green, blue]) or red)
|
|
117
144
|
|
|
118
145
|
elif tif_path:
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
146
|
+
# --------------------- Detect HDF/HDF5 --------------------- #
|
|
147
|
+
if tif_path.lower().endswith((".hdf", ".h5", ".hdf5")):
|
|
148
|
+
try:
|
|
149
|
+
from osgeo import gdal
|
|
150
|
+
gdal.UseExceptions()
|
|
151
|
+
|
|
152
|
+
ds = gdal.Open(tif_path)
|
|
153
|
+
subs = ds.GetSubDatasets()
|
|
154
|
+
|
|
155
|
+
if not subs:
|
|
156
|
+
raise ValueError("No subdatasets found in HDF/HDF5 file.")
|
|
157
|
+
|
|
158
|
+
print(f"Found {len(subs)} subdatasets in {os.path.basename(tif_path)}:")
|
|
159
|
+
for i, (_, desc) in enumerate(subs):
|
|
160
|
+
print(f"[{i}] {desc}")
|
|
161
|
+
|
|
162
|
+
# Only list subsets if --subset not given
|
|
163
|
+
if subset is None:
|
|
164
|
+
print("\nUse --subset N to open a specific subdataset.")
|
|
165
|
+
sys.exit(0)
|
|
166
|
+
|
|
167
|
+
# Validate subset index
|
|
168
|
+
if subset < 0 or subset >= len(subs):
|
|
169
|
+
raise ValueError(f"Invalid subset index {subset}. Valid range: 0–{len(subs)-1}")
|
|
170
|
+
|
|
171
|
+
sub_name, desc = subs[subset]
|
|
172
|
+
print(f"\nOpening subdataset [{subset}]: {desc}")
|
|
173
|
+
sub_ds = gdal.Open(sub_name)
|
|
174
|
+
|
|
175
|
+
# --- Read once ---
|
|
176
|
+
arr = sub_ds.ReadAsArray().astype(np.float32)
|
|
177
|
+
#print(f"Raw array shape from GDAL: {arr.shape} (ndim={arr.ndim})")
|
|
178
|
+
|
|
179
|
+
# --- Normalize shape ---
|
|
180
|
+
arr = np.squeeze(arr)
|
|
181
|
+
if arr.ndim == 3:
|
|
182
|
+
# Convert from (bands, rows, cols) → (rows, cols, bands)
|
|
183
|
+
arr = np.transpose(arr, (1, 2, 0))
|
|
184
|
+
#print(f"Transposed to {arr.shape} (rows, cols, bands)")
|
|
185
|
+
elif arr.ndim == 2:
|
|
186
|
+
print("Single-band dataset.")
|
|
187
|
+
else:
|
|
188
|
+
raise ValueError(f"Unexpected array shape {arr.shape}")
|
|
189
|
+
|
|
190
|
+
# --- Downsample large arrays for responsiveness ---
|
|
191
|
+
h, w = arr.shape[:2]
|
|
192
|
+
if h * w > 4_000_000:
|
|
193
|
+
step = max(2, int((h * w / 4_000_000) ** 0.5))
|
|
194
|
+
arr = arr[::step, ::step] if arr.ndim == 2 else arr[::step, ::step, :]
|
|
195
|
+
print(f"⚠️ Large dataset preview: downsampled by {step}x")
|
|
196
|
+
|
|
197
|
+
# --- Final assignments ---
|
|
129
198
|
self.data = arr
|
|
130
|
-
self.
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
199
|
+
self._transform = None
|
|
200
|
+
self._crs = None
|
|
201
|
+
self.band_count = arr.shape[2] if arr.ndim == 3 else 1
|
|
202
|
+
self.band_index = 0
|
|
203
|
+
self.vmin, self.vmax = np.nanmin(arr), np.nanmax(arr)
|
|
204
|
+
|
|
205
|
+
if self.band_count > 1:
|
|
206
|
+
print(f"This subdataset has {self.band_count} bands — switch with [ and ] keys.")
|
|
207
|
+
else:
|
|
208
|
+
print("This subdataset has 1 band.")
|
|
209
|
+
|
|
210
|
+
# --- If user specified --band, start there ---
|
|
211
|
+
if self.band and self.band <= self.band_count:
|
|
212
|
+
self.band_index = self.band - 1
|
|
213
|
+
print(f"Opening band {self.band}/{self.band_count}")
|
|
214
|
+
else:
|
|
215
|
+
self.band_index = 0
|
|
216
|
+
|
|
217
|
+
except ImportError:
|
|
218
|
+
raise RuntimeError(
|
|
219
|
+
"HDF support requires GDAL.\n"
|
|
220
|
+
"Install it first (e.g., brew install gdal && pip install GDAL)"
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
# --------------------- Regular GeoTIFF --------------------- #
|
|
224
|
+
else:
|
|
225
|
+
with rasterio.open(tif_path) as src:
|
|
226
|
+
self._transform = src.transform
|
|
227
|
+
self._crs = src.crs
|
|
228
|
+
if rgb is not None:
|
|
229
|
+
bands = [src.read(b, out_shape=(src.height // self._scale_arg, src.width // self._scale_arg))
|
|
230
|
+
for b in rgb]
|
|
231
|
+
arr = np.stack(bands, axis=-1).astype(np.float32)
|
|
232
|
+
nd = src.nodata
|
|
233
|
+
if nd is not None:
|
|
234
|
+
arr[arr == nd] = np.nan
|
|
235
|
+
self.data = arr
|
|
236
|
+
self.band_count = 3
|
|
237
|
+
else:
|
|
238
|
+
arr = src.read(
|
|
239
|
+
self.band,
|
|
240
|
+
out_shape=(src.height // self._scale_arg, src.width // self._scale_arg)
|
|
241
|
+
).astype(np.float32)
|
|
242
|
+
nd = src.nodata
|
|
243
|
+
if nd is not None:
|
|
244
|
+
arr[arr == nd] = np.nan
|
|
245
|
+
self.data = arr
|
|
246
|
+
self.band_count = src.count
|
|
247
|
+
|
|
248
|
+
# single-band display range (fast stats or fallback)
|
|
249
|
+
try:
|
|
250
|
+
stats = src.stats(self.band)
|
|
251
|
+
if stats and stats.min is not None and stats.max is not None:
|
|
252
|
+
self.vmin, self.vmax = stats.min, stats.max
|
|
253
|
+
else:
|
|
254
|
+
raise ValueError("No stats in file")
|
|
255
|
+
except Exception:
|
|
256
|
+
self.vmin, self.vmax = np.nanmin(arr), np.nanmax(arr)
|
|
257
|
+
|
|
151
258
|
else:
|
|
152
259
|
raise ValueError("Provide a TIFF path or --rgbfiles.")
|
|
153
260
|
|
|
@@ -314,6 +421,10 @@ class TiffViewer(QMainWindow):
|
|
|
314
421
|
self.setWindowTitle(f"RGB ({', '.join(names)})")
|
|
315
422
|
elif self.rgb_mode and self.rgb:
|
|
316
423
|
self.setWindowTitle(f"RGB {self.rgb} — {os.path.basename(self.tif_path)}")
|
|
424
|
+
elif hasattr(self, "band_index"):
|
|
425
|
+
self.setWindowTitle(
|
|
426
|
+
f"Band {self.band_index + 1}/{self.band_count} — {os.path.basename(self.tif_path)}"
|
|
427
|
+
)
|
|
317
428
|
else:
|
|
318
429
|
self.setWindowTitle(f"Band {self.band}/{self.band_count} — {os.path.basename(self.tif_path)}")
|
|
319
430
|
|
|
@@ -346,10 +457,44 @@ class TiffViewer(QMainWindow):
|
|
|
346
457
|
return rgb
|
|
347
458
|
|
|
348
459
|
def update_pixmap(self):
|
|
349
|
-
|
|
460
|
+
# --- Select display data ---
|
|
461
|
+
if hasattr(self, "band_index"):
|
|
462
|
+
# HDF or scientific multi-band
|
|
463
|
+
if self.data.ndim == 3:
|
|
464
|
+
a = self.data[:, :, self.band_index]
|
|
465
|
+
else:
|
|
466
|
+
a = self.data
|
|
467
|
+
rgb = None
|
|
468
|
+
else:
|
|
469
|
+
# Regular GeoTIFF (could be RGB or single-band)
|
|
470
|
+
if self.rgb_mode: # user explicitly passed --rgb or --rgbtiles
|
|
471
|
+
rgb = self.data
|
|
472
|
+
a = None
|
|
473
|
+
else:
|
|
474
|
+
a = self.data
|
|
475
|
+
rgb = None
|
|
476
|
+
# ----------------------------
|
|
477
|
+
|
|
478
|
+
# --- Render image ---
|
|
479
|
+
if rgb is None:
|
|
480
|
+
# Grayscale rendering for single-band (scientific) data
|
|
481
|
+
finite = np.isfinite(a)
|
|
482
|
+
rng = max(np.nanmax(a) - np.nanmin(a), 1e-12)
|
|
483
|
+
norm = np.zeros_like(a, dtype=np.float32)
|
|
484
|
+
if np.any(finite):
|
|
485
|
+
norm[finite] = (a[finite] - np.nanmin(a)) / rng
|
|
486
|
+
norm = np.clip(norm, 0, 1)
|
|
487
|
+
norm = np.power(norm * self.contrast, self.gamma)
|
|
488
|
+
cmap = getattr(cm, self.cmap_name, cm.viridis)
|
|
489
|
+
rgb = (cmap(norm)[..., :3] * 255).astype(np.uint8)
|
|
490
|
+
else:
|
|
491
|
+
# True RGB mode (unchanged)
|
|
492
|
+
rgb = self._render_rgb()
|
|
493
|
+
# ----------------------
|
|
494
|
+
|
|
350
495
|
h, w, _ = rgb.shape
|
|
351
496
|
self._last_rgb = rgb
|
|
352
|
-
qimg = QImage(
|
|
497
|
+
qimg = QImage(rgb.data, w, h, 3 * w, QImage.Format.Format_RGB888)
|
|
353
498
|
pix = QPixmap.fromImage(qimg)
|
|
354
499
|
if self.pixmap_item is None:
|
|
355
500
|
self.pixmap_item = QGraphicsPixmapItem(pix)
|
|
@@ -408,13 +553,24 @@ class TiffViewer(QMainWindow):
|
|
|
408
553
|
self.cmap_name, self.alt_cmap_name = self.alt_cmap_name, self.cmap_name
|
|
409
554
|
self.update_pixmap()
|
|
410
555
|
|
|
411
|
-
# Band switch
|
|
412
|
-
elif
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
self.
|
|
556
|
+
# Band switch
|
|
557
|
+
elif k == Qt.Key.Key_BracketRight:
|
|
558
|
+
if hasattr(self, "band_index"): # HDF/NetCDF mode
|
|
559
|
+
self.band_index = (self.band_index + 1) % self.band_count
|
|
560
|
+
self.update_pixmap()
|
|
561
|
+
self.update_title()
|
|
562
|
+
elif not self.rgb_mode: # GeoTIFF single-band mode
|
|
563
|
+
new_band = self.band + 1 if self.band < self.band_count else 1
|
|
564
|
+
self.load_band(new_band)
|
|
565
|
+
|
|
566
|
+
elif k == Qt.Key.Key_BracketLeft:
|
|
567
|
+
if hasattr(self, "band_index"): # HDF/NetCDF mode
|
|
568
|
+
self.band_index = (self.band_index - 1) % self.band_count
|
|
569
|
+
self.update_pixmap()
|
|
570
|
+
self.update_title()
|
|
571
|
+
elif not self.rgb_mode: # GeoTIFF single-band mode
|
|
572
|
+
new_band = self.band - 1 if self.band > 1 else self.band_count
|
|
573
|
+
self.load_band(new_band)
|
|
418
574
|
|
|
419
575
|
elif k == Qt.Key.Key_R:
|
|
420
576
|
self.contrast = 1.0
|
|
@@ -463,6 +619,7 @@ def run_viewer(
|
|
|
463
619
|
shapefile=None,
|
|
464
620
|
shp_color=None,
|
|
465
621
|
shp_width=None,
|
|
622
|
+
subset=None,
|
|
466
623
|
):
|
|
467
624
|
"""Launch the TiffViewer app"""
|
|
468
625
|
app = QApplication(sys.argv)
|
|
@@ -475,6 +632,7 @@ def run_viewer(
|
|
|
475
632
|
shapefiles=shapefile,
|
|
476
633
|
shp_color=shp_color,
|
|
477
634
|
shp_width=shp_width,
|
|
635
|
+
subset=subset,
|
|
478
636
|
)
|
|
479
637
|
win.show()
|
|
480
638
|
sys.exit(app.exec())
|
|
@@ -482,6 +640,7 @@ def run_viewer(
|
|
|
482
640
|
import click
|
|
483
641
|
|
|
484
642
|
@click.command()
|
|
643
|
+
@click.version_option("1.0.6", prog_name="viewtif")
|
|
485
644
|
@click.argument("tif_path", required=False)
|
|
486
645
|
@click.option("--band", default=1, show_default=True, type=int, help="Band number to display")
|
|
487
646
|
@click.option("--scale", default=1.0, show_default=True, type=float, help="Scale factor for display")
|
|
@@ -490,9 +649,9 @@ import click
|
|
|
490
649
|
@click.option("--shapefile", multiple=True, type=str, help="One or more shapefiles to overlay")
|
|
491
650
|
@click.option("--shp-color", default="white", show_default=True, help="Overlay color (name or #RRGGBB).")
|
|
492
651
|
@click.option("--shp-width", default=1.0, show_default=True, type=float, help="Overlay line width (screen pixels).")
|
|
493
|
-
|
|
652
|
+
@click.option("--subset", default=None, type=int, help="Open specific subdataset index in .hdf/.h5 file")
|
|
653
|
+
def main(tif_path, band, scale, rgb, rgbfiles, shapefile, shp_color, shp_width, subset):
|
|
494
654
|
"""Lightweight GeoTIFF viewer."""
|
|
495
|
-
|
|
496
655
|
# --- Warn early if shapefile requested but geopandas missing ---
|
|
497
656
|
if shapefile and not HAVE_GEO:
|
|
498
657
|
print(
|
|
@@ -510,9 +669,9 @@ def main(tif_path, band, scale, rgb, rgbfiles, shapefile, shp_color, shp_width):
|
|
|
510
669
|
shapefile=shapefile,
|
|
511
670
|
shp_color=shp_color,
|
|
512
671
|
shp_width=shp_width,
|
|
672
|
+
subset=subset,
|
|
513
673
|
)
|
|
514
674
|
|
|
515
|
-
|
|
516
675
|
if __name__ == "__main__":
|
|
517
676
|
main()
|
|
518
677
|
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: viewtif
|
|
3
|
+
Version: 0.1.6
|
|
4
|
+
Summary: Simple GeoTIFF viewer with optional shapefile overlay.
|
|
5
|
+
Project-URL: Homepage, https://github.com/nkeikon/tifviewer
|
|
6
|
+
Project-URL: Source, https://github.com/nkeikon/tifviewer
|
|
7
|
+
Project-URL: Issues, https://github.com/nkeikon/tifviewer/issues
|
|
8
|
+
Author: Keiko Nomura
|
|
9
|
+
License: MIT
|
|
10
|
+
Requires-Python: >=3.9
|
|
11
|
+
Requires-Dist: click>=8.1
|
|
12
|
+
Requires-Dist: matplotlib>=3.7
|
|
13
|
+
Requires-Dist: numpy>=1.23
|
|
14
|
+
Requires-Dist: pyside6>=6.5
|
|
15
|
+
Requires-Dist: rasterio>=1.3
|
|
16
|
+
Provides-Extra: geo
|
|
17
|
+
Requires-Dist: geopandas>=0.13; extra == 'geo'
|
|
18
|
+
Requires-Dist: shapely>=2.0; extra == 'geo'
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
21
|
+
# viewtif
|
|
22
|
+
[](https://pepy.tech/project/viewtif)
|
|
23
|
+
[](https://pypi.org/project/viewtif/)
|
|
24
|
+
[](https://pypi.org/project/viewtif/)
|
|
25
|
+
|
|
26
|
+
A lightweight GeoTIFF viewer for quick visualization directly from the command line.
|
|
27
|
+
|
|
28
|
+
You can visualize single-band GeoTIFFs, RGB composites, and shapefile overlays in a simple Qt-based window.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Installation
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
pip install viewtif
|
|
36
|
+
```
|
|
37
|
+
> **Note:** On Linux, you may need python3-tk, libqt5gui5, or PySide6 dependencies.
|
|
38
|
+
>
|
|
39
|
+
>`viewtif` requires a graphical display environment.
|
|
40
|
+
> It may not run properly on headless systems (e.g., HPC compute nodes or remote servers without X11 forwarding).
|
|
41
|
+
|
|
42
|
+
### Optional features
|
|
43
|
+
#### Shapefile overlay support
|
|
44
|
+
```bash
|
|
45
|
+
pip install "viewtif[geo]"
|
|
46
|
+
```
|
|
47
|
+
> **Note:** For macOS(zsh) users:
|
|
48
|
+
> Make sure to include the quotes, or zsh will interpret it as a pattern.
|
|
49
|
+
|
|
50
|
+
#### HDF/HDF5 support
|
|
51
|
+
```bash
|
|
52
|
+
brew install gdal # macOS
|
|
53
|
+
sudo apt install gdal-bin python3-gdal # Linux
|
|
54
|
+
pip install GDAL
|
|
55
|
+
```
|
|
56
|
+
> **Note:** GDAL is required to open `.hdf`, .`h5`, and `.hdf5` files. If it’s missing, viewtif will display: `RuntimeError: HDF support requires GDAL.`
|
|
57
|
+
|
|
58
|
+
## Quick Start
|
|
59
|
+
```bash
|
|
60
|
+
# View a GeoTIFF
|
|
61
|
+
viewtif ECOSTRESS_LST.tif
|
|
62
|
+
|
|
63
|
+
# View an RGB composite
|
|
64
|
+
viewtif --rgbfiles \
|
|
65
|
+
HLS_B04.tif \
|
|
66
|
+
HLS_B03.tif \
|
|
67
|
+
HLS_B02.tif
|
|
68
|
+
|
|
69
|
+
# View with shapefile overlay
|
|
70
|
+
viewtif ECOSTRESS_LST.tif \
|
|
71
|
+
--shapefile Zip_Codes.shp
|
|
72
|
+
```
|
|
73
|
+
### Update in v1.0.6
|
|
74
|
+
`viewtif` can open `.hdf`, `.h5`, and `.hdf5` files that contain multiple subdatasets. When opened, it lists available subdatasets and lets you view one by index. You can also specify a band to display (default is the first band) or change bands interactively with '[' and ']'.
|
|
75
|
+
```bash
|
|
76
|
+
# List subdatasets
|
|
77
|
+
viewtif AG100.v003.33.-107.0001.h5
|
|
78
|
+
|
|
79
|
+
# View a specific subdataset
|
|
80
|
+
viewtif AG100.v003.33.-107.0001.h5 --subset 1
|
|
81
|
+
|
|
82
|
+
# View a specific subdataset and band
|
|
83
|
+
viewtif AG100.v003.33.-107.0001.h5 --subset 1 --band 3
|
|
84
|
+
```
|
|
85
|
+
> **Note:** Some datasets (perhaps the majority of .hdf files) lack CRS information encoded, so shapefile overlays may not work. In that case, viewtif will display:
|
|
86
|
+
`[WARN] raster lacks CRS/transform; cannot place overlays.`
|
|
87
|
+
|
|
88
|
+
## Controls
|
|
89
|
+
| Key | Action |
|
|
90
|
+
| -------------------- | --------------------------------------- |
|
|
91
|
+
| `+` / `-` or mouse / trackpad | Zoom in / out |
|
|
92
|
+
| Arrow keys or `WASD` | Pan |
|
|
93
|
+
| `C` / `V` | Increase / decrease contrast |
|
|
94
|
+
| `G` / `H` | Increase / decrease gamma |
|
|
95
|
+
| `M` | Toggle colormap (`viridis` ↔ `magma`) |
|
|
96
|
+
| `[` / `]` | Previous / next band (single-band only) |
|
|
97
|
+
| `R` | Reset view |
|
|
98
|
+
|
|
99
|
+
## Features
|
|
100
|
+
- Command-line driven GeoTIFF viewer.
|
|
101
|
+
- Supports single-band, RGB composite, and HDF/HDF5 subdatasets.
|
|
102
|
+
- Optional shapefile overlay for geographic context.
|
|
103
|
+
- Adjustable contrast, gamma, and colormap.
|
|
104
|
+
- Fast preview using rasterio and PySide6.
|
|
105
|
+
|
|
106
|
+
## Example Data
|
|
107
|
+
- ECOSTRESS_LST.tif
|
|
108
|
+
- Zip_Codes.shp and associated files
|
|
109
|
+
- HLS_B04.tif, HLS_B03.tif, HLS_B02.tif (RGB sample)
|
|
110
|
+
- AG100.v003.33.-107.0001.h5 (HDF5 file)
|
|
111
|
+
|
|
112
|
+
## Credit & License
|
|
113
|
+
`viewtif` was inspired by the NASA JPL Thermal Viewer — Semi-Automated Georeferencer (GeoViewer v1.12) developed by Jake Longenecker (University of Miami Rosenstiel School of Marine, Atmospheric & Earth Science) while at the NASA Jet Propulsion Laboratory, California Institute of Technology, with inspiration from JPL’s ECOSTRESS geolocation batch workflow by Andrew Alamillo. The original GeoViewer was released under the MIT License (2025) and may be freely adapted with citation.
|
|
114
|
+
|
|
115
|
+
## Citation
|
|
116
|
+
Longenecker, Jake; Lee, Christine; Hulley, Glynn; Cawse-Nicholson, Kerry; Purkis, Sam; Gleason, Art; Otis, Dan; Galdamez, Ileana; Meiseles, Jacquelyn. GeoViewer v1.12: NASA JPL Thermal Viewer—Semi-Automated Georeferencer User Guide & Reference Manual. Jet Propulsion Laboratory, California Institute of Technology, 2025. PDF.
|
|
117
|
+
|
|
118
|
+
## License
|
|
119
|
+
This project is released under the MIT License.
|
|
120
|
+
|
|
121
|
+
## Contributors
|
|
122
|
+
- [@HarshShinde0](https://github.com/HarshShinde0) — added mouse-wheel and trackpad zoom support
|
|
123
|
+
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
viewtif/tif_viewer.py,sha256=30Z4fHz5F630lcM1C5a7AM_qBJNhyqP7tCjx5nBwN9g,26716
|
|
2
|
+
viewtif-0.1.6.dist-info/METADATA,sha256=SJwnxmRREta33rWBsLAGfgY6m0DeokLZb7PhTNxiQa8,5037
|
|
3
|
+
viewtif-0.1.6.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
4
|
+
viewtif-0.1.6.dist-info/entry_points.txt,sha256=NVEjlRyJ7R7hFPOVsZJio3Hl0VqlX7_oVfA7819XvHM,52
|
|
5
|
+
viewtif-0.1.6.dist-info/RECORD,,
|
viewtif-0.1.4.dist-info/METADATA
DELETED
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: viewtif
|
|
3
|
-
Version: 0.1.4
|
|
4
|
-
Summary: Simple GeoTIFF viewer with optional shapefile overlay.
|
|
5
|
-
Project-URL: Homepage, https://github.com/nkeikon/tifviewer
|
|
6
|
-
Project-URL: Source, https://github.com/nkeikon/tifviewer
|
|
7
|
-
Project-URL: Issues, https://github.com/nkeikon/tifviewer/issues
|
|
8
|
-
Author: Keiko Nomura
|
|
9
|
-
License: MIT
|
|
10
|
-
Requires-Python: >=3.9
|
|
11
|
-
Requires-Dist: click>=8.1
|
|
12
|
-
Requires-Dist: matplotlib>=3.7
|
|
13
|
-
Requires-Dist: numpy>=1.23
|
|
14
|
-
Requires-Dist: pyside6>=6.5
|
|
15
|
-
Requires-Dist: rasterio>=1.3
|
|
16
|
-
Provides-Extra: geo
|
|
17
|
-
Requires-Dist: geopandas>=0.13; extra == 'geo'
|
|
18
|
-
Requires-Dist: shapely>=2.0; extra == 'geo'
|
|
19
|
-
Description-Content-Type: text/markdown
|
|
20
|
-
|
|
21
|
-
# viewtif
|
|
22
|
-
|
|
23
|
-
A lightweight GeoTIFF viewer for quick visualization directly from the command line.
|
|
24
|
-
|
|
25
|
-
You can visualize single-band GeoTIFFs, RGB composites, and shapefile overlays in a simple Qt-based window.
|
|
26
|
-
|
|
27
|
-
---
|
|
28
|
-
|
|
29
|
-
## Installation
|
|
30
|
-
|
|
31
|
-
```bash
|
|
32
|
-
pip install viewtif
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
If you want to enable shapefile overlays, install with optional dependencies:
|
|
36
|
-
```bash
|
|
37
|
-
pip install "viewtif[geo]"
|
|
38
|
-
```
|
|
39
|
-
Note for macOS(zsh) users:
|
|
40
|
-
Make sure to include the quotes, or zsh will interpret it as a pattern.
|
|
41
|
-
|
|
42
|
-
## Quick Start
|
|
43
|
-
```bash
|
|
44
|
-
# View a GeoTIFF
|
|
45
|
-
viewtif examples/sample_data/ECOSTRESS_LST.tif
|
|
46
|
-
|
|
47
|
-
# View with shapefile overlay
|
|
48
|
-
viewtif examples/sample_data/ECOSTRESS_LST.tif \
|
|
49
|
-
--shapefile examples/sample_data/Zip_Codes.shp
|
|
50
|
-
|
|
51
|
-
# View an RGB composite
|
|
52
|
-
viewtif --rgbfiles \
|
|
53
|
-
examples/sample_data/HLS_B4.tif \
|
|
54
|
-
examples/sample_data/HLS_B3.tif \
|
|
55
|
-
examples/sample_data/HLS_B2.tif
|
|
56
|
-
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
## Controls
|
|
60
|
-
| Key | Action |
|
|
61
|
-
| -------------------- | --------------------------------------- |
|
|
62
|
-
| `+` / `-` | Zoom in / out |
|
|
63
|
-
| Arrow keys or `WASD` | Pan |
|
|
64
|
-
| `C` / `V` | Increase / decrease contrast |
|
|
65
|
-
| `G` / `H` | Increase / decrease gamma |
|
|
66
|
-
| `M` | Toggle colormap (`viridis` ↔ `magma`) |
|
|
67
|
-
| `[` / `]` | Previous / next band (single-band only) |
|
|
68
|
-
| `R` | Reset view |
|
|
69
|
-
|
|
70
|
-
## Features
|
|
71
|
-
- Command-line driven GeoTIFF viewer
|
|
72
|
-
- Supports single-band or RGB composite display.
|
|
73
|
-
- Optional shapefile overlay for geographic context.
|
|
74
|
-
- Adjustable contrast, gamma, and colormap.
|
|
75
|
-
- Fast preview using rasterio and PySide6.
|
|
76
|
-
|
|
77
|
-
## Example Data
|
|
78
|
-
- ECOSTRESS_LST.tif
|
|
79
|
-
- Zip_Codes.shp and associated files
|
|
80
|
-
- HLS_B4.tif, HLS_B3.tif, HLS_B2.tif (RGB sample)
|
|
81
|
-
|
|
82
|
-
## Credit & License
|
|
83
|
-
`viewtif` was inspired by the NASA JPL Thermal Viewer — Semi-Automated Georeferencer (GeoViewer v1.12) developed by Jake Longenecker (University of Miami Rosenstiel School of Marine, Atmospheric & Earth Science) while at the NASA Jet Propulsion Laboratory, California Institute of Technology, with inspiration from JPL’s ECOSTRESS geolocation batch workflow by Andrew Alamillo. The original GeoViewer was released under the MIT License (2025) and may be freely adapted with citation.
|
|
84
|
-
|
|
85
|
-
# Citation
|
|
86
|
-
Longenecker, Jake; Lee, Christine; Hulley, Glynn; Cawse-Nicholson, Kerry; Purkis, Sam; Gleason, Art; Otis, Dan; Galdamez, Illeana; Meiseles, Jacquelyn. GeoViewer v1.12: NASA JPL Thermal Viewer—Semi-Automated Georeferencer User Guide & Reference Manual. Jet Propulsion Laboratory, California Institute of Technology, 2025. PDF.
|
|
87
|
-
|
|
88
|
-
# License
|
|
89
|
-
This project is released under the MIT License.
|
viewtif-0.1.4.dist-info/RECORD
DELETED
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
viewtif/tif_viewer.py,sha256=MGOSrYYe5rJpJRALTNQIHii8JNb5I8vMlo3Edlb09vA,19891
|
|
2
|
-
viewtif-0.1.4.dist-info/METADATA,sha256=Odi7eo1Zx0QlWJhS4hi8bByAPSkciwh80X18ATJaMLY,3391
|
|
3
|
-
viewtif-0.1.4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
4
|
-
viewtif-0.1.4.dist-info/entry_points.txt,sha256=NVEjlRyJ7R7hFPOVsZJio3Hl0VqlX7_oVfA7819XvHM,52
|
|
5
|
-
viewtif-0.1.4.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|