viewtif 0.1.5__tar.gz → 0.1.7__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.
@@ -0,0 +1,8 @@
1
+ # virtual environments
2
+ .venv
3
+ .venv-test
4
+
5
+ # build artifacts
6
+ dist/
7
+ build/
8
+ *.egg-info/
viewtif-0.1.7/PKG-INFO ADDED
@@ -0,0 +1,137 @@
1
+ Metadata-Version: 2.4
2
+ Name: viewtif
3
+ Version: 0.1.7
4
+ Summary: Lightweight GeoTIFF, HDF/HDF5, and Esri File Geodatabase (.gdb) viewer with shapefile overlay and large-raster safeguard.
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
+ [![Downloads](https://static.pepy.tech/badge/viewtif)](https://pepy.tech/project/viewtif)
23
+ [![PyPI version](https://img.shields.io/pypi/v/viewtif)](https://pypi.org/project/viewtif/)
24
+ [![Python versions](https://img.shields.io/pypi/pyversions/viewtif)](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: HDF/HDF5 support
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
+ ### Update in v1.0.7: File Geodatabase (.gdb) support
89
+ `viewtif` can now open raster datasets stored inside Esri File Geodatabases (`.gdb`), using the GDAL `OpenFileGDB` driver.
90
+
91
+ ```bash
92
+ # Example
93
+ viewtif "OpenFileGDB:/path/to/geodatabase.gdb:RasterName"
94
+ ```
95
+ > **Note:** Requires GDAL 3.7 or later with the OpenFileGDB driver enabled. The .gdb path and raster name must be separated by a colon (:).
96
+
97
+ ### Update in v1.0.7: Large raster safeguard
98
+ As of v1.0.7, `viewtif` automatically checks the raster size before loading.
99
+ If the dataset is very large (e.g., >20 million pixels), it will pause and warn that loading may freeze your system.
100
+ You can proceed manually or rerun with the `--scale` option for a smaller, faster preview.
101
+
102
+ ## Controls
103
+ | Key | Action |
104
+ | -------------------- | --------------------------------------- |
105
+ | `+` / `-` or mouse / trackpad | Zoom in / out |
106
+ | Arrow keys or `WASD` | Pan |
107
+ | `C` / `V` | Increase / decrease contrast |
108
+ | `G` / `H` | Increase / decrease gamma |
109
+ | `M` | Toggle colormap (`viridis` ↔ `magma`) |
110
+ | `[` / `]` | Previous / next band (single-band only) |
111
+ | `R` | Reset view |
112
+
113
+ ## Features
114
+ - Command-line driven GeoTIFF viewer.
115
+ - Supports single-band, RGB composite, and HDF/HDF5 subdatasets.
116
+ - Optional shapefile overlay for geographic context.
117
+ - Adjustable contrast, gamma, and colormap.
118
+ - Fast preview using rasterio and PySide6.
119
+
120
+ ## Example Data
121
+ - ECOSTRESS_LST.tif
122
+ - Zip_Codes.shp and associated files
123
+ - HLS_B04.tif, HLS_B03.tif, HLS_B02.tif (RGB sample)
124
+ - AG100.v003.33.-107.0001.h5 (HDF5 file)
125
+
126
+ ## Credit & License
127
+ `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.
128
+
129
+ ## Citation
130
+ 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.
131
+
132
+ ## License
133
+ This project is released under the MIT License.
134
+
135
+ ## Contributors
136
+ - [@HarshShinde0](https://github.com/HarshShinde0) — added mouse-wheel and trackpad zoom support
137
+ - [@p-vdp](https://github.com/p-vdp) — added File Geodatabase (.gdb) raster support
@@ -0,0 +1,117 @@
1
+ # viewtif
2
+ [![Downloads](https://static.pepy.tech/badge/viewtif)](https://pepy.tech/project/viewtif)
3
+ [![PyPI version](https://img.shields.io/pypi/v/viewtif)](https://pypi.org/project/viewtif/)
4
+ [![Python versions](https://img.shields.io/pypi/pyversions/viewtif)](https://pypi.org/project/viewtif/)
5
+
6
+ A lightweight GeoTIFF viewer for quick visualization directly from the command line.
7
+
8
+ You can visualize single-band GeoTIFFs, RGB composites, and shapefile overlays in a simple Qt-based window.
9
+
10
+ ---
11
+
12
+ ## Installation
13
+
14
+ ```bash
15
+ pip install viewtif
16
+ ```
17
+ > **Note:** On Linux, you may need python3-tk, libqt5gui5, or PySide6 dependencies.
18
+ >
19
+ >`viewtif` requires a graphical display environment.
20
+ > It may not run properly on headless systems (e.g., HPC compute nodes or remote servers without X11 forwarding).
21
+
22
+ ### Optional features
23
+ #### Shapefile overlay support
24
+ ```bash
25
+ pip install "viewtif[geo]"
26
+ ```
27
+ > **Note:** For macOS(zsh) users:
28
+ > Make sure to include the quotes, or zsh will interpret it as a pattern.
29
+
30
+ #### HDF/HDF5 support
31
+ ```bash
32
+ brew install gdal # macOS
33
+ sudo apt install gdal-bin python3-gdal # Linux
34
+ pip install GDAL
35
+ ```
36
+ > **Note:** GDAL is required to open `.hdf`, .`h5`, and `.hdf5` files. If it’s missing, viewtif will display: `RuntimeError: HDF support requires GDAL.`
37
+
38
+ ## Quick Start
39
+ ```bash
40
+ # View a GeoTIFF
41
+ viewtif ECOSTRESS_LST.tif
42
+
43
+ # View an RGB composite
44
+ viewtif --rgbfiles \
45
+ HLS_B04.tif \
46
+ HLS_B03.tif \
47
+ HLS_B02.tif
48
+
49
+ # View with shapefile overlay
50
+ viewtif ECOSTRESS_LST.tif \
51
+ --shapefile Zip_Codes.shp
52
+ ```
53
+ ### Update in v1.0.6: HDF/HDF5 support
54
+ `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 ']'.
55
+ ```bash
56
+ # List subdatasets
57
+ viewtif AG100.v003.33.-107.0001.h5
58
+
59
+ # View a specific subdataset
60
+ viewtif AG100.v003.33.-107.0001.h5 --subset 1
61
+
62
+ # View a specific subdataset and band
63
+ viewtif AG100.v003.33.-107.0001.h5 --subset 1 --band 3
64
+ ```
65
+ > **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:
66
+ `[WARN] raster lacks CRS/transform; cannot place overlays.`
67
+
68
+ ### Update in v1.0.7: File Geodatabase (.gdb) support
69
+ `viewtif` can now open raster datasets stored inside Esri File Geodatabases (`.gdb`), using the GDAL `OpenFileGDB` driver.
70
+
71
+ ```bash
72
+ # Example
73
+ viewtif "OpenFileGDB:/path/to/geodatabase.gdb:RasterName"
74
+ ```
75
+ > **Note:** Requires GDAL 3.7 or later with the OpenFileGDB driver enabled. The .gdb path and raster name must be separated by a colon (:).
76
+
77
+ ### Update in v1.0.7: Large raster safeguard
78
+ As of v1.0.7, `viewtif` automatically checks the raster size before loading.
79
+ If the dataset is very large (e.g., >20 million pixels), it will pause and warn that loading may freeze your system.
80
+ You can proceed manually or rerun with the `--scale` option for a smaller, faster preview.
81
+
82
+ ## Controls
83
+ | Key | Action |
84
+ | -------------------- | --------------------------------------- |
85
+ | `+` / `-` or mouse / trackpad | Zoom in / out |
86
+ | Arrow keys or `WASD` | Pan |
87
+ | `C` / `V` | Increase / decrease contrast |
88
+ | `G` / `H` | Increase / decrease gamma |
89
+ | `M` | Toggle colormap (`viridis` ↔ `magma`) |
90
+ | `[` / `]` | Previous / next band (single-band only) |
91
+ | `R` | Reset view |
92
+
93
+ ## Features
94
+ - Command-line driven GeoTIFF viewer.
95
+ - Supports single-band, RGB composite, and HDF/HDF5 subdatasets.
96
+ - Optional shapefile overlay for geographic context.
97
+ - Adjustable contrast, gamma, and colormap.
98
+ - Fast preview using rasterio and PySide6.
99
+
100
+ ## Example Data
101
+ - ECOSTRESS_LST.tif
102
+ - Zip_Codes.shp and associated files
103
+ - HLS_B04.tif, HLS_B03.tif, HLS_B02.tif (RGB sample)
104
+ - AG100.v003.33.-107.0001.h5 (HDF5 file)
105
+
106
+ ## Credit & License
107
+ `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.
108
+
109
+ ## Citation
110
+ 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.
111
+
112
+ ## License
113
+ This project is released under the MIT License.
114
+
115
+ ## Contributors
116
+ - [@HarshShinde0](https://github.com/HarshShinde0) — added mouse-wheel and trackpad zoom support
117
+ - [@p-vdp](https://github.com/p-vdp) — added File Geodatabase (.gdb) raster support
@@ -4,8 +4,8 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "viewtif"
7
- version = "0.1.5"
8
- description = "Simple GeoTIFF viewer with optional shapefile overlay."
7
+ version = "0.1.7"
8
+ description = "Lightweight GeoTIFF, HDF/HDF5, and Esri File Geodatabase (.gdb) viewer with shapefile overlay and large-raster safeguard."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
11
11
  license = { text = "MIT" }
@@ -52,10 +52,38 @@ try:
52
52
  except Exception:
53
53
  HAVE_GEO = False
54
54
 
55
+ def warn_if_large(tif_path, scale=1):
56
+ """Warn and confirm before loading very large rasters (GeoTIFF, GDB, or HDF)."""
57
+ from osgeo import gdal
58
+ import os
59
+
60
+ try:
61
+ gdal.UseExceptions()
62
+ info = gdal.Info(tif_path, format="json")
63
+ width, height = info.get("size", [0, 0])
64
+ total_pixels = (width * height) / (scale ** 2) # account for downsampling
65
+ size_mb = None
66
+ if os.path.exists(tif_path):
67
+ size_mb = os.path.getsize(tif_path) / (1024 ** 2)
68
+
69
+ # Only warn if the *effective* pixels remain large
70
+ if total_pixels > 20_000_000 and scale <= 5:
71
+ print(
72
+ f"[WARN] Large raster detected ({width}×{height}, ~{total_pixels/1e6:.1f}M effective pixels"
73
+ + (f", ~{size_mb:.1f} MB" if size_mb else "")
74
+ + "). Loading may freeze. Consider rerunning with --scale (e.g. --scale 10)."
75
+ )
76
+ ans = input("Proceed anyway? [y/N]: ").strip().lower()
77
+ if ans not in ("y", "yes"):
78
+ print("Cancelled.")
79
+ sys.exit(0)
80
+ except Exception as e:
81
+ print(f"[WARN] Could not pre-check raster size: {e}")
55
82
 
56
83
  # -------------------------- QGraphicsView tweaks -------------------------- #
57
84
  class RasterView(QGraphicsView):
58
85
  def __init__(self, *args, **kwargs):
86
+ import numpy as np
59
87
  super().__init__(*args, **kwargs)
60
88
  self.setRenderHint(QPainter.RenderHint.SmoothPixmapTransform, False)
61
89
  self.setDragMode(QGraphicsView.DragMode.ScrollHandDrag)
@@ -101,6 +129,7 @@ class TiffViewer(QMainWindow):
101
129
  shapefiles: list[str] | None = None,
102
130
  shp_color: str = "white",
103
131
  shp_width: float = 2,
132
+ subset: int | None = None,
104
133
  ):
105
134
  super().__init__()
106
135
 
@@ -141,38 +170,123 @@ class TiffViewer(QMainWindow):
141
170
  self.tif_path = self.tif_path or (os.path.commonprefix([red, green, blue]) or red)
142
171
 
143
172
  elif tif_path:
144
- with rasterio.open(tif_path) as src:
145
- self._transform = src.transform
146
- self._crs = src.crs
147
- if rgb is not None:
148
- bands = [src.read(b, out_shape=(src.height // self._scale_arg, src.width // self._scale_arg))
149
- for b in rgb]
150
- arr = np.stack(bands, axis=-1).astype(np.float32)
151
- nd = src.nodata
152
- if nd is not None:
153
- arr[arr == nd] = np.nan
173
+ # --- Universal size check before loading ---
174
+ warn_if_large(tif_path, scale=self._scale_arg)
175
+ # --------------------- Detect HDF/HDF5 --------------------- #
176
+ if tif_path.lower().endswith((".hdf", ".h5", ".hdf5")):
177
+ try:
178
+ from osgeo import gdal
179
+ gdal.UseExceptions()
180
+
181
+ ds = gdal.Open(tif_path)
182
+ subs = ds.GetSubDatasets()
183
+
184
+ if not subs:
185
+ raise ValueError("No subdatasets found in HDF/HDF5 file.")
186
+
187
+ print(f"Found {len(subs)} subdatasets in {os.path.basename(tif_path)}:")
188
+ for i, (_, desc) in enumerate(subs):
189
+ print(f"[{i}] {desc}")
190
+
191
+ # Only list subsets if --subset not given
192
+ if subset is None:
193
+ print("\nUse --subset N to open a specific subdataset.")
194
+ sys.exit(0)
195
+
196
+ # Validate subset index
197
+ if subset < 0 or subset >= len(subs):
198
+ raise ValueError(f"Invalid subset index {subset}. Valid range: 0–{len(subs)-1}")
199
+
200
+ sub_name, desc = subs[subset]
201
+ print(f"\nOpening subdataset [{subset}]: {desc}")
202
+ sub_ds = gdal.Open(sub_name)
203
+
204
+ # --- Read once ---
205
+ arr = sub_ds.ReadAsArray().astype(np.float32)
206
+ #print(f"Raw array shape from GDAL: {arr.shape} (ndim={arr.ndim})")
207
+
208
+ # --- Normalize shape ---
209
+ arr = np.squeeze(arr)
210
+ if arr.ndim == 3:
211
+ # Convert from (bands, rows, cols) → (rows, cols, bands)
212
+ arr = np.transpose(arr, (1, 2, 0))
213
+ #print(f"Transposed to {arr.shape} (rows, cols, bands)")
214
+ elif arr.ndim == 2:
215
+ print("Single-band dataset.")
216
+ else:
217
+ raise ValueError(f"Unexpected array shape {arr.shape}")
218
+
219
+ # --- Downsample large arrays for responsiveness ---
220
+ h, w = arr.shape[:2]
221
+ if h * w > 4_000_000:
222
+ step = max(2, int((h * w / 4_000_000) ** 0.5))
223
+ arr = arr[::step, ::step] if arr.ndim == 2 else arr[::step, ::step, :]
224
+ print(f"⚠️ Large dataset preview: downsampled by {step}x")
225
+
226
+ # --- Final assignments ---
154
227
  self.data = arr
155
- self.band_count = 3
156
- else:
157
- arr = src.read(
158
- self.band,
159
- out_shape=(src.height // self._scale_arg, src.width // self._scale_arg)
160
- ).astype(np.float32)
161
- nd = src.nodata
162
- if nd is not None:
163
- arr[arr == nd] = np.nan
164
- self.data = arr
165
- self.band_count = src.count
166
-
167
- # single-band display range (fast stats or fallback)
168
- try:
169
- stats = src.stats(self.band)
170
- if stats and stats.min is not None and stats.max is not None:
171
- self.vmin, self.vmax = stats.min, stats.max
172
- else:
173
- raise ValueError("No stats in file")
174
- except Exception:
175
- self.vmin, self.vmax = np.nanmin(arr), np.nanmax(arr)
228
+ self._transform = None
229
+ self._crs = None
230
+ self.band_count = arr.shape[2] if arr.ndim == 3 else 1
231
+ self.band_index = 0
232
+ self.vmin, self.vmax = np.nanmin(arr), np.nanmax(arr)
233
+
234
+ if self.band_count > 1:
235
+ print(f"This subdataset has {self.band_count} bands — switch with [ and ] keys.")
236
+ else:
237
+ print("This subdataset has 1 band.")
238
+
239
+ # --- If user specified --band, start there ---
240
+ if self.band and self.band <= self.band_count:
241
+ self.band_index = self.band - 1
242
+ print(f"Opening band {self.band}/{self.band_count}")
243
+ else:
244
+ self.band_index = 0
245
+
246
+ except ImportError:
247
+ raise RuntimeError(
248
+ "HDF support requires GDAL.\n"
249
+ "Install it first (e.g., brew install gdal && pip install GDAL)"
250
+ )
251
+
252
+ # --------------------- Regular GeoTIFF --------------------- #
253
+ else:
254
+ if os.path.dirname(tif_path).endswith(".gdb"):
255
+ tif_path = f"OpenFileGDB:{os.path.dirname(tif_path)}:{os.path.basename(tif_path)}"
256
+
257
+ with rasterio.open(tif_path) as src:
258
+ self._transform = src.transform
259
+ self._crs = src.crs
260
+ if rgb is not None:
261
+ bands = [src.read(b, out_shape=(src.height // self._scale_arg, src.width // self._scale_arg))
262
+ for b in rgb]
263
+ arr = np.stack(bands, axis=-1).astype(np.float32)
264
+ nd = src.nodata
265
+ if nd is not None:
266
+ arr[arr == nd] = np.nan
267
+ self.data = arr
268
+ self.band_count = 3
269
+ else:
270
+ arr = src.read(
271
+ self.band,
272
+ out_shape=(src.height // self._scale_arg, src.width // self._scale_arg)
273
+ ).astype(np.float32)
274
+ nd = src.nodata
275
+ if nd is not None:
276
+ arr[arr == nd] = np.nan
277
+ self.data = arr
278
+ self.band_count = src.count
279
+
280
+ # single-band display range (fast stats or fallback)
281
+ try:
282
+ stats = src.stats(self.band)
283
+ if stats and stats.min is not None and stats.max is not None:
284
+ self.vmin, self.vmax = stats.min, stats.max
285
+ else:
286
+ raise ValueError("No stats in file")
287
+ except Exception:
288
+ self.vmin, self.vmax = np.nanmin(arr), np.nanmax(arr)
289
+
176
290
  else:
177
291
  raise ValueError("Provide a TIFF path or --rgbfiles.")
178
292
 
@@ -339,6 +453,10 @@ class TiffViewer(QMainWindow):
339
453
  self.setWindowTitle(f"RGB ({', '.join(names)})")
340
454
  elif self.rgb_mode and self.rgb:
341
455
  self.setWindowTitle(f"RGB {self.rgb} — {os.path.basename(self.tif_path)}")
456
+ elif hasattr(self, "band_index"):
457
+ self.setWindowTitle(
458
+ f"Band {self.band_index + 1}/{self.band_count} — {os.path.basename(self.tif_path)}"
459
+ )
342
460
  else:
343
461
  self.setWindowTitle(f"Band {self.band}/{self.band_count} — {os.path.basename(self.tif_path)}")
344
462
 
@@ -371,10 +489,44 @@ class TiffViewer(QMainWindow):
371
489
  return rgb
372
490
 
373
491
  def update_pixmap(self):
374
- rgb = self._render_rgb()
492
+ # --- Select display data ---
493
+ if hasattr(self, "band_index"):
494
+ # HDF or scientific multi-band
495
+ if self.data.ndim == 3:
496
+ a = self.data[:, :, self.band_index]
497
+ else:
498
+ a = self.data
499
+ rgb = None
500
+ else:
501
+ # Regular GeoTIFF (could be RGB or single-band)
502
+ if self.rgb_mode: # user explicitly passed --rgb or --rgbtiles
503
+ rgb = self.data
504
+ a = None
505
+ else:
506
+ a = self.data
507
+ rgb = None
508
+ # ----------------------------
509
+
510
+ # --- Render image ---
511
+ if rgb is None:
512
+ # Grayscale rendering for single-band (scientific) data
513
+ finite = np.isfinite(a)
514
+ rng = max(np.nanmax(a) - np.nanmin(a), 1e-12)
515
+ norm = np.zeros_like(a, dtype=np.float32)
516
+ if np.any(finite):
517
+ norm[finite] = (a[finite] - np.nanmin(a)) / rng
518
+ norm = np.clip(norm, 0, 1)
519
+ norm = np.power(norm * self.contrast, self.gamma)
520
+ cmap = getattr(cm, self.cmap_name, cm.viridis)
521
+ rgb = (cmap(norm)[..., :3] * 255).astype(np.uint8)
522
+ else:
523
+ # True RGB mode (unchanged)
524
+ rgb = self._render_rgb()
525
+ # ----------------------
526
+
375
527
  h, w, _ = rgb.shape
376
528
  self._last_rgb = rgb
377
- qimg = QImage(self._last_rgb.data, w, h, 3 * w, QImage.Format.Format_RGB888)
529
+ qimg = QImage(rgb.data, w, h, 3 * w, QImage.Format.Format_RGB888)
378
530
  pix = QPixmap.fromImage(qimg)
379
531
  if self.pixmap_item is None:
380
532
  self.pixmap_item = QGraphicsPixmapItem(pix)
@@ -387,7 +539,13 @@ class TiffViewer(QMainWindow):
387
539
  def load_band(self, band_num: int):
388
540
  if self.rgb_mode:
389
541
  return
390
- with rasterio.open(self.tif_path) as src:
542
+
543
+ tif_path = self.tif_path
544
+
545
+ if os.path.dirname(self.tif_path).endswith(".gdb"):
546
+ tif_path = f"OpenFileGDB:{os.path.dirname(self.tif_path)}:{os.path.basename(self.tif_path)}"
547
+
548
+ with rasterio.open(tif_path) as src:
391
549
  self.band = band_num
392
550
  arr = src.read(self.band).astype(np.float32)
393
551
  nd = src.nodata
@@ -433,13 +591,24 @@ class TiffViewer(QMainWindow):
433
591
  self.cmap_name, self.alt_cmap_name = self.alt_cmap_name, self.cmap_name
434
592
  self.update_pixmap()
435
593
 
436
- # Band switch (single-band)
437
- elif not self.rgb_mode and k == Qt.Key.Key_BracketRight:
438
- new_band = self.band + 1 if self.band < self.band_count else 1
439
- self.load_band(new_band)
440
- elif not self.rgb_mode and k == Qt.Key.Key_BracketLeft:
441
- new_band = self.band - 1 if self.band > 1 else self.band_count
442
- self.load_band(new_band)
594
+ # Band switch
595
+ elif k == Qt.Key.Key_BracketRight:
596
+ if hasattr(self, "band_index"): # HDF/NetCDF mode
597
+ self.band_index = (self.band_index + 1) % self.band_count
598
+ self.update_pixmap()
599
+ self.update_title()
600
+ elif not self.rgb_mode: # GeoTIFF single-band mode
601
+ new_band = self.band + 1 if self.band < self.band_count else 1
602
+ self.load_band(new_band)
603
+
604
+ elif k == Qt.Key.Key_BracketLeft:
605
+ if hasattr(self, "band_index"): # HDF/NetCDF mode
606
+ self.band_index = (self.band_index - 1) % self.band_count
607
+ self.update_pixmap()
608
+ self.update_title()
609
+ elif not self.rgb_mode: # GeoTIFF single-band mode
610
+ new_band = self.band - 1 if self.band > 1 else self.band_count
611
+ self.load_band(new_band)
443
612
 
444
613
  elif k == Qt.Key.Key_R:
445
614
  self.contrast = 1.0
@@ -488,6 +657,7 @@ def run_viewer(
488
657
  shapefile=None,
489
658
  shp_color=None,
490
659
  shp_width=None,
660
+ subset=None,
491
661
  ):
492
662
  """Launch the TiffViewer app"""
493
663
  app = QApplication(sys.argv)
@@ -500,6 +670,7 @@ def run_viewer(
500
670
  shapefiles=shapefile,
501
671
  shp_color=shp_color,
502
672
  shp_width=shp_width,
673
+ subset=subset,
503
674
  )
504
675
  win.show()
505
676
  sys.exit(app.exec())
@@ -507,6 +678,7 @@ def run_viewer(
507
678
  import click
508
679
 
509
680
  @click.command()
681
+ @click.version_option("1.0.6", prog_name="viewtif")
510
682
  @click.argument("tif_path", required=False)
511
683
  @click.option("--band", default=1, show_default=True, type=int, help="Band number to display")
512
684
  @click.option("--scale", default=1.0, show_default=True, type=float, help="Scale factor for display")
@@ -515,9 +687,9 @@ import click
515
687
  @click.option("--shapefile", multiple=True, type=str, help="One or more shapefiles to overlay")
516
688
  @click.option("--shp-color", default="white", show_default=True, help="Overlay color (name or #RRGGBB).")
517
689
  @click.option("--shp-width", default=1.0, show_default=True, type=float, help="Overlay line width (screen pixels).")
518
- def main(tif_path, band, scale, rgb, rgbfiles, shapefile, shp_color, shp_width):
690
+ @click.option("--subset", default=None, type=int, help="Open specific subdataset index in .hdf/.h5 file")
691
+ def main(tif_path, band, scale, rgb, rgbfiles, shapefile, shp_color, shp_width, subset):
519
692
  """Lightweight GeoTIFF viewer."""
520
-
521
693
  # --- Warn early if shapefile requested but geopandas missing ---
522
694
  if shapefile and not HAVE_GEO:
523
695
  print(
@@ -535,9 +707,9 @@ def main(tif_path, band, scale, rgb, rgbfiles, shapefile, shp_color, shp_width):
535
707
  shapefile=shapefile,
536
708
  shp_color=shp_color,
537
709
  shp_width=shp_width,
710
+ subset=subset,
538
711
  )
539
712
 
540
-
541
713
  if __name__ == "__main__":
542
714
  main()
543
715
 
viewtif-0.1.5/.gitignore DELETED
@@ -1,4 +0,0 @@
1
- .venv/
2
- dist/
3
- __pycache__/
4
- *.pyc
viewtif-0.1.5/PKG-INFO DELETED
@@ -1,93 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: viewtif
3
- Version: 0.1.5
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_B04.tif \
54
- examples/sample_data/HLS_B03.tif \
55
- examples/sample_data/HLS_B02.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_B04.tif, HLS_B03.tif, HLS_B02.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,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.
87
-
88
- # License
89
- This project is released under the MIT License.
90
-
91
- ## Contributors
92
- - [@HarshShinde0](https://github.com/HarshShinde0) — added mouse-wheel and trackpad zoom support
93
-
viewtif-0.1.5/README.md DELETED
@@ -1,73 +0,0 @@
1
- # viewtif
2
-
3
- A lightweight GeoTIFF viewer for quick visualization directly from the command line.
4
-
5
- You can visualize single-band GeoTIFFs, RGB composites, and shapefile overlays in a simple Qt-based window.
6
-
7
- ---
8
-
9
- ## Installation
10
-
11
- ```bash
12
- pip install viewtif
13
- ```
14
-
15
- If you want to enable shapefile overlays, install with optional dependencies:
16
- ```bash
17
- pip install "viewtif[geo]"
18
- ```
19
- Note for macOS(zsh) users:
20
- Make sure to include the quotes, or zsh will interpret it as a pattern.
21
-
22
- ## Quick Start
23
- ```bash
24
- # View a GeoTIFF
25
- viewtif examples/sample_data/ECOSTRESS_LST.tif
26
-
27
- # View with shapefile overlay
28
- viewtif examples/sample_data/ECOSTRESS_LST.tif \
29
- --shapefile examples/sample_data/Zip_Codes.shp
30
-
31
- # View an RGB composite
32
- viewtif --rgbfiles \
33
- examples/sample_data/HLS_B04.tif \
34
- examples/sample_data/HLS_B03.tif \
35
- examples/sample_data/HLS_B02.tif
36
-
37
- ```
38
-
39
- ## Controls
40
- | Key | Action |
41
- | -------------------- | --------------------------------------- |
42
- | `+` / `-` | Zoom in / out |
43
- | Arrow keys or `WASD` | Pan |
44
- | `C` / `V` | Increase / decrease contrast |
45
- | `G` / `H` | Increase / decrease gamma |
46
- | `M` | Toggle colormap (`viridis` ↔ `magma`) |
47
- | `[` / `]` | Previous / next band (single-band only) |
48
- | `R` | Reset view |
49
-
50
- ## Features
51
- - Command-line driven GeoTIFF viewer
52
- - Supports single-band or RGB composite display.
53
- - Optional shapefile overlay for geographic context.
54
- - Adjustable contrast, gamma, and colormap.
55
- - Fast preview using rasterio and PySide6.
56
-
57
- ## Example Data
58
- - ECOSTRESS_LST.tif
59
- - Zip_Codes.shp and associated files
60
- - HLS_B04.tif, HLS_B03.tif, HLS_B02.tif (RGB sample)
61
-
62
- ## Credit & License
63
- `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.
64
-
65
- # Citation
66
- 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.
67
-
68
- # License
69
- This project is released under the MIT License.
70
-
71
- ## Contributors
72
- - [@HarshShinde0](https://github.com/HarshShinde0) — added mouse-wheel and trackpad zoom support
73
-
File without changes