viewtif 0.1.9__tar.gz → 0.2.1__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.
- {viewtif-0.1.9 → viewtif-0.2.1}/PKG-INFO +17 -13
- {viewtif-0.1.9 → viewtif-0.2.1}/README.md +15 -11
- {viewtif-0.1.9 → viewtif-0.2.1}/pyproject.toml +2 -2
- {viewtif-0.1.9 → viewtif-0.2.1}/src/viewtif/tif_viewer.py +136 -100
- {viewtif-0.1.9 → viewtif-0.2.1}/.gitignore +0 -0
- {viewtif-0.1.9 → viewtif-0.2.1}/examples/README.md +0 -0
- {viewtif-0.1.9 → viewtif-0.2.1}/examples/sample_data/AG100.v003.13.-017.0001.h5 +0 -0
- {viewtif-0.1.9 → viewtif-0.2.1}/examples/sample_data/ECOSTRESS_LST.tif +0 -0
- {viewtif-0.1.9 → viewtif-0.2.1}/examples/sample_data/HLS_B02.tif +0 -0
- {viewtif-0.1.9 → viewtif-0.2.1}/examples/sample_data/HLS_B03.tif +0 -0
- {viewtif-0.1.9 → viewtif-0.2.1}/examples/sample_data/HLS_B04.tif +0 -0
- {viewtif-0.1.9 → viewtif-0.2.1}/examples/sample_data/Zip_Codes.cpg +0 -0
- {viewtif-0.1.9 → viewtif-0.2.1}/examples/sample_data/Zip_Codes.dbf +0 -0
- {viewtif-0.1.9 → viewtif-0.2.1}/examples/sample_data/Zip_Codes.prj +0 -0
- {viewtif-0.1.9 → viewtif-0.2.1}/examples/sample_data/Zip_Codes.shp +0 -0
- {viewtif-0.1.9 → viewtif-0.2.1}/examples/sample_data/Zip_Codes.shx +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: viewtif
|
|
3
|
-
Version: 0.1
|
|
4
|
-
Summary: Lightweight GeoTIFF, HDF/HDF5, and Esri File Geodatabase (.gdb) viewer with shapefile overlay and
|
|
3
|
+
Version: 0.2.1
|
|
4
|
+
Summary: Lightweight GeoTIFF, NetCDF, HDF/HDF5, and Esri File Geodatabase (.gdb) viewer with optional shapefile overlay. NetCDF and cartopy support available via pip install viewtif[netcdf].
|
|
5
5
|
Project-URL: Homepage, https://github.com/nkeikon/tifviewer
|
|
6
6
|
Project-URL: Source, https://github.com/nkeikon/tifviewer
|
|
7
7
|
Project-URL: Issues, https://github.com/nkeikon/tifviewer/issues
|
|
@@ -25,9 +25,7 @@ Description-Content-Type: text/markdown
|
|
|
25
25
|
|
|
26
26
|
A lightweight GeoTIFF viewer for quick visualization directly from the command line.
|
|
27
27
|
|
|
28
|
-
You can visualize single-band GeoTIFFs, RGB composites, and shapefile overlays in a simple Qt-based window.
|
|
29
|
-
|
|
30
|
-
---
|
|
28
|
+
You can visualize single-band GeoTIFFs, RGB composites, HDF, NetCDF files and shapefile overlays in a simple Qt-based window.
|
|
31
29
|
|
|
32
30
|
## Installation
|
|
33
31
|
|
|
@@ -47,7 +45,7 @@ pip install "viewtif[geo]"
|
|
|
47
45
|
> **Note:** For macOS(zsh) users:
|
|
48
46
|
> Make sure to include the quotes, or zsh will interpret it as a pattern.
|
|
49
47
|
|
|
50
|
-
#### HDF/HDF5 support
|
|
48
|
+
#### HDF/HDF5 support
|
|
51
49
|
```bash
|
|
52
50
|
brew install gdal # macOS
|
|
53
51
|
sudo apt install gdal-bin python3-gdal # Linux
|
|
@@ -86,8 +84,14 @@ viewtif AG100.v003.33.-107.0001.h5 --subset 1 --band 3
|
|
|
86
84
|
`[WARN] raster lacks CRS/transform; cannot place overlays.`
|
|
87
85
|
|
|
88
86
|
### Update in v1.0.7: File Geodatabase (.gdb) support
|
|
89
|
-
`viewtif` can now open raster datasets stored inside Esri File Geodatabases (`.gdb`)
|
|
90
|
-
When you open a .gdb directly, `viewtif
|
|
87
|
+
`viewtif` can now open raster datasets stored inside Esri File Geodatabases (`.gdb`).
|
|
88
|
+
When you open a .gdb directly, `viewtif` will list available raster datasets first, then you can choose one to view.
|
|
89
|
+
|
|
90
|
+
Most Rasterio installations already include the OpenFileGDB driver, so .gdb datasets often open without installing GDAL manually.
|
|
91
|
+
|
|
92
|
+
If you encounter:
|
|
93
|
+
RuntimeError: GDB support requires GDAL,
|
|
94
|
+
install GDAL as shown above to enable the driver.
|
|
91
95
|
|
|
92
96
|
```bash
|
|
93
97
|
# List available raster datasets
|
|
@@ -96,7 +100,7 @@ viewtif /path/to/geodatabase.gdb
|
|
|
96
100
|
# Open a specific raster
|
|
97
101
|
viewtif "OpenFileGDB:/path/to/geodatabase.gdb:RasterName"
|
|
98
102
|
```
|
|
99
|
-
> **Note:**
|
|
103
|
+
> **Note:** If multiple raster datasets are present, viewtif lists them all and shows how to open each. The .gdb path and raster name must be separated by a colon (:).
|
|
100
104
|
|
|
101
105
|
### Update in v1.0.7: Large raster safeguard
|
|
102
106
|
As of v1.0.7, `viewtif` automatically checks the raster size before loading.
|
|
@@ -111,12 +115,12 @@ You can proceed manually or rerun with the `--scale` option for a smaller, faste
|
|
|
111
115
|
| `C` / `V` | Increase / decrease contrast |
|
|
112
116
|
| `G` / `H` | Increase / decrease gamma |
|
|
113
117
|
| `M` | Toggle colormap (`viridis` ↔ `magma`) |
|
|
114
|
-
| `[` / `]` | Previous / next band (
|
|
118
|
+
| `[` / `]` | Previous / next band (or time step) |
|
|
115
119
|
| `R` | Reset view |
|
|
116
120
|
|
|
117
121
|
## Features
|
|
118
122
|
- Command-line driven GeoTIFF viewer.
|
|
119
|
-
- Supports single-band, RGB composite,
|
|
123
|
+
- Supports single-band, RGB composite, HDF/HDF5 subdatasets, and NetCDF.
|
|
120
124
|
- Optional shapefile overlay for geographic context.
|
|
121
125
|
- Adjustable contrast, gamma, and colormap.
|
|
122
126
|
- Fast preview using rasterio and PySide6.
|
|
@@ -137,5 +141,5 @@ Longenecker, Jake; Lee, Christine; Hulley, Glynn; Cawse-Nicholson, Kerry; Purkis
|
|
|
137
141
|
This project is released under the MIT License.
|
|
138
142
|
|
|
139
143
|
## Contributors
|
|
140
|
-
- [@HarshShinde0](https://github.com/HarshShinde0) — added mouse-wheel and trackpad zoom support
|
|
141
|
-
- [@p-vdp](https://github.com/p-vdp) — added File Geodatabase (.gdb) raster support
|
|
144
|
+
- [@HarshShinde0](https://github.com/HarshShinde0) — added mouse-wheel and trackpad zoom support; added NetCDF support with [@nkeikon](https://github.com/nkeikon)
|
|
145
|
+
- [@p-vdp](https://github.com/p-vdp) — added File Geodatabase (.gdb) raster support
|
|
@@ -5,9 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
A lightweight GeoTIFF viewer for quick visualization directly from the command line.
|
|
7
7
|
|
|
8
|
-
You can visualize single-band GeoTIFFs, RGB composites, and shapefile overlays in a simple Qt-based window.
|
|
9
|
-
|
|
10
|
-
---
|
|
8
|
+
You can visualize single-band GeoTIFFs, RGB composites, HDF, NetCDF files and shapefile overlays in a simple Qt-based window.
|
|
11
9
|
|
|
12
10
|
## Installation
|
|
13
11
|
|
|
@@ -27,7 +25,7 @@ pip install "viewtif[geo]"
|
|
|
27
25
|
> **Note:** For macOS(zsh) users:
|
|
28
26
|
> Make sure to include the quotes, or zsh will interpret it as a pattern.
|
|
29
27
|
|
|
30
|
-
#### HDF/HDF5 support
|
|
28
|
+
#### HDF/HDF5 support
|
|
31
29
|
```bash
|
|
32
30
|
brew install gdal # macOS
|
|
33
31
|
sudo apt install gdal-bin python3-gdal # Linux
|
|
@@ -66,8 +64,14 @@ viewtif AG100.v003.33.-107.0001.h5 --subset 1 --band 3
|
|
|
66
64
|
`[WARN] raster lacks CRS/transform; cannot place overlays.`
|
|
67
65
|
|
|
68
66
|
### Update in v1.0.7: File Geodatabase (.gdb) support
|
|
69
|
-
`viewtif` can now open raster datasets stored inside Esri File Geodatabases (`.gdb`)
|
|
70
|
-
When you open a .gdb directly, `viewtif
|
|
67
|
+
`viewtif` can now open raster datasets stored inside Esri File Geodatabases (`.gdb`).
|
|
68
|
+
When you open a .gdb directly, `viewtif` will list available raster datasets first, then you can choose one to view.
|
|
69
|
+
|
|
70
|
+
Most Rasterio installations already include the OpenFileGDB driver, so .gdb datasets often open without installing GDAL manually.
|
|
71
|
+
|
|
72
|
+
If you encounter:
|
|
73
|
+
RuntimeError: GDB support requires GDAL,
|
|
74
|
+
install GDAL as shown above to enable the driver.
|
|
71
75
|
|
|
72
76
|
```bash
|
|
73
77
|
# List available raster datasets
|
|
@@ -76,7 +80,7 @@ viewtif /path/to/geodatabase.gdb
|
|
|
76
80
|
# Open a specific raster
|
|
77
81
|
viewtif "OpenFileGDB:/path/to/geodatabase.gdb:RasterName"
|
|
78
82
|
```
|
|
79
|
-
> **Note:**
|
|
83
|
+
> **Note:** If multiple raster datasets are present, viewtif lists them all and shows how to open each. The .gdb path and raster name must be separated by a colon (:).
|
|
80
84
|
|
|
81
85
|
### Update in v1.0.7: Large raster safeguard
|
|
82
86
|
As of v1.0.7, `viewtif` automatically checks the raster size before loading.
|
|
@@ -91,12 +95,12 @@ You can proceed manually or rerun with the `--scale` option for a smaller, faste
|
|
|
91
95
|
| `C` / `V` | Increase / decrease contrast |
|
|
92
96
|
| `G` / `H` | Increase / decrease gamma |
|
|
93
97
|
| `M` | Toggle colormap (`viridis` ↔ `magma`) |
|
|
94
|
-
| `[` / `]` | Previous / next band (
|
|
98
|
+
| `[` / `]` | Previous / next band (or time step) |
|
|
95
99
|
| `R` | Reset view |
|
|
96
100
|
|
|
97
101
|
## Features
|
|
98
102
|
- Command-line driven GeoTIFF viewer.
|
|
99
|
-
- Supports single-band, RGB composite,
|
|
103
|
+
- Supports single-band, RGB composite, HDF/HDF5 subdatasets, and NetCDF.
|
|
100
104
|
- Optional shapefile overlay for geographic context.
|
|
101
105
|
- Adjustable contrast, gamma, and colormap.
|
|
102
106
|
- Fast preview using rasterio and PySide6.
|
|
@@ -117,5 +121,5 @@ Longenecker, Jake; Lee, Christine; Hulley, Glynn; Cawse-Nicholson, Kerry; Purkis
|
|
|
117
121
|
This project is released under the MIT License.
|
|
118
122
|
|
|
119
123
|
## Contributors
|
|
120
|
-
- [@HarshShinde0](https://github.com/HarshShinde0) — added mouse-wheel and trackpad zoom support
|
|
121
|
-
- [@p-vdp](https://github.com/p-vdp) — added File Geodatabase (.gdb) raster support
|
|
124
|
+
- [@HarshShinde0](https://github.com/HarshShinde0) — added mouse-wheel and trackpad zoom support; added NetCDF support with [@nkeikon](https://github.com/nkeikon)
|
|
125
|
+
- [@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
|
|
8
|
-
description = "Lightweight GeoTIFF, HDF/HDF5, and Esri File Geodatabase (.gdb) viewer with shapefile overlay and
|
|
7
|
+
version = "0.2.1"
|
|
8
|
+
description = "Lightweight GeoTIFF, NetCDF, HDF/HDF5, and Esri File Geodatabase (.gdb) viewer with optional shapefile overlay. NetCDF and cartopy support available via pip install viewtif[netcdf]."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.9"
|
|
11
11
|
license = { text = "MIT" }
|
|
@@ -40,6 +40,10 @@ from PySide6.QtGui import QImage, QPixmap, QPainter, QPen, QColor, QPainterPath
|
|
|
40
40
|
from PySide6.QtCore import Qt
|
|
41
41
|
|
|
42
42
|
import matplotlib.cm as cm
|
|
43
|
+
import warnings
|
|
44
|
+
warnings.filterwarnings("ignore", category=RuntimeWarning, module="shapely")
|
|
45
|
+
|
|
46
|
+
__version__ = "0.2.1"
|
|
43
47
|
|
|
44
48
|
# Optional overlay deps
|
|
45
49
|
try:
|
|
@@ -53,37 +57,52 @@ except Exception:
|
|
|
53
57
|
HAVE_GEO = False
|
|
54
58
|
|
|
55
59
|
def warn_if_large(tif_path, scale=1):
|
|
56
|
-
"""Warn and confirm before loading very large rasters (GeoTIFF, GDB, or HDF).
|
|
57
|
-
|
|
60
|
+
"""Warn and confirm before loading very large rasters (GeoTIFF, GDB, or HDF).
|
|
61
|
+
Works even if GDAL is not installed.
|
|
62
|
+
"""
|
|
58
63
|
import os
|
|
64
|
+
width = height = None
|
|
65
|
+
size_mb = None
|
|
59
66
|
|
|
67
|
+
# Try GDAL if available
|
|
60
68
|
try:
|
|
69
|
+
from osgeo import gdal
|
|
61
70
|
gdal.UseExceptions()
|
|
62
71
|
info = gdal.Info(tif_path, format="json")
|
|
63
72
|
width, height = info.get("size", [0, 0])
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
print(
|
|
72
|
-
|
|
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)
|
|
73
|
+
except ImportError:
|
|
74
|
+
# Fallback if GDAL not installed
|
|
75
|
+
try:
|
|
76
|
+
import rasterio
|
|
77
|
+
with rasterio.open(tif_path) as src:
|
|
78
|
+
width, height = src.width, src.height
|
|
79
|
+
except Exception:
|
|
80
|
+
print("[WARN] Could not estimate raster size (no GDAL/rasterio). Skipping size check.")
|
|
81
|
+
return
|
|
80
82
|
except Exception as e:
|
|
81
|
-
print(f"[WARN] Could not pre-check raster size: {e}")
|
|
83
|
+
print(f"[WARN] Could not pre-check raster size with GDAL: {e}")
|
|
84
|
+
return
|
|
85
|
+
|
|
86
|
+
# File size
|
|
87
|
+
if os.path.exists(tif_path):
|
|
88
|
+
size_mb = os.path.getsize(tif_path) / (1024 ** 2)
|
|
89
|
+
|
|
90
|
+
total_pixels = (width * height) / (scale ** 2)
|
|
91
|
+
if total_pixels > 20_000_000 and scale <= 5:
|
|
92
|
+
msg = (
|
|
93
|
+
f"[WARN] Large raster detected ({width}×{height}, ~{total_pixels/1e6:.1f}M pixels"
|
|
94
|
+
+ (f", ~{size_mb:.1f} MB" if size_mb else "")
|
|
95
|
+
+ "). Loading may freeze. Consider --scale (e.g. --scale 10)."
|
|
96
|
+
)
|
|
97
|
+
print(msg)
|
|
98
|
+
ans = input("Proceed anyway? [y/N]: ").strip().lower()
|
|
99
|
+
if ans not in ("y", "yes"):
|
|
100
|
+
print("Cancelled.")
|
|
101
|
+
sys.exit(0)
|
|
82
102
|
|
|
83
103
|
# -------------------------- QGraphicsView tweaks -------------------------- #
|
|
84
104
|
class RasterView(QGraphicsView):
|
|
85
105
|
def __init__(self, *args, **kwargs):
|
|
86
|
-
import numpy as np
|
|
87
106
|
super().__init__(*args, **kwargs)
|
|
88
107
|
self.setRenderHint(QPainter.RenderHint.SmoothPixmapTransform, False)
|
|
89
108
|
self.setDragMode(QGraphicsView.DragMode.ScrollHandDrag)
|
|
@@ -190,84 +209,96 @@ class TiffViewer(QMainWindow):
|
|
|
190
209
|
except subprocess.CalledProcessError as e:
|
|
191
210
|
print(f"[WARN] Could not inspect FileGDB: {e}")
|
|
192
211
|
sys.exit(0)
|
|
193
|
-
|
|
212
|
+
|
|
213
|
+
# --- Universal size check before loading ---
|
|
194
214
|
warn_if_large(tif_path, scale=self._scale_arg)
|
|
215
|
+
|
|
195
216
|
# --------------------- Detect HDF/HDF5 --------------------- #
|
|
196
217
|
if tif_path.lower().endswith((".hdf", ".h5", ".hdf5")):
|
|
197
218
|
try:
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
219
|
+
# Try reading directly with Rasterio first (works for simple HDF layouts)
|
|
220
|
+
with rasterio.open(tif_path) as src:
|
|
221
|
+
print(f"Opened HDF with rasterio: {os.path.basename(tif_path)}")
|
|
222
|
+
arr = src.read().astype(np.float32)
|
|
223
|
+
arr = np.squeeze(arr)
|
|
224
|
+
if arr.ndim == 3:
|
|
225
|
+
arr = np.transpose(arr, (1, 2, 0))
|
|
226
|
+
elif arr.ndim == 2:
|
|
227
|
+
print("Single-band dataset.")
|
|
228
|
+
self.data = arr
|
|
229
|
+
self._transform = src.transform
|
|
230
|
+
self._crs = src.crs
|
|
231
|
+
self.band_count = arr.shape[2] if arr.ndim == 3 else 1
|
|
232
|
+
self.band_index = 0
|
|
233
|
+
self.vmin, self.vmax = np.nanmin(arr), np.nanmax(arr)
|
|
234
|
+
return # ✅ Skip GDAL path if Rasterio succeeded
|
|
235
|
+
|
|
236
|
+
except Exception as e:
|
|
237
|
+
print(f"Rasterio could not open HDF directly: {e}")
|
|
238
|
+
print("Falling back to GDAL...")
|
|
239
|
+
|
|
240
|
+
try:
|
|
241
|
+
from osgeo import gdal
|
|
242
|
+
gdal.UseExceptions()
|
|
243
|
+
|
|
244
|
+
ds = gdal.Open(tif_path)
|
|
245
|
+
subs = ds.GetSubDatasets()
|
|
246
|
+
if not subs:
|
|
247
|
+
raise ValueError("No subdatasets found in HDF/HDF5 file.")
|
|
248
|
+
|
|
249
|
+
print(f"Found {len(subs)} subdatasets in {os.path.basename(tif_path)}:")
|
|
250
|
+
for i, (_, desc) in enumerate(subs):
|
|
251
|
+
print(f"[{i}] {desc}")
|
|
252
|
+
|
|
253
|
+
if subset is None:
|
|
254
|
+
print("\nUse --subset N to open a specific subdataset.")
|
|
255
|
+
sys.exit(0)
|
|
256
|
+
|
|
257
|
+
if subset < 0 or subset >= len(subs):
|
|
258
|
+
raise ValueError(f"Invalid subset index {subset}. Valid range: 0–{len(subs)-1}")
|
|
259
|
+
|
|
260
|
+
sub_name, desc = subs[subset]
|
|
261
|
+
print(f"\nOpening subdataset [{subset}]: {desc}")
|
|
262
|
+
sub_ds = gdal.Open(sub_name)
|
|
263
|
+
|
|
264
|
+
arr = sub_ds.ReadAsArray().astype(np.float32)
|
|
265
|
+
arr = np.squeeze(arr)
|
|
266
|
+
if arr.ndim == 3:
|
|
267
|
+
arr = np.transpose(arr, (1, 2, 0))
|
|
268
|
+
elif arr.ndim == 2:
|
|
269
|
+
print("Single-band dataset.")
|
|
270
|
+
else:
|
|
271
|
+
raise ValueError(f"Unexpected array shape {arr.shape}")
|
|
272
|
+
|
|
273
|
+
# Downsample large arrays for responsiveness
|
|
274
|
+
h, w = arr.shape[:2]
|
|
275
|
+
if h * w > 4_000_000:
|
|
276
|
+
step = max(2, int((h * w / 4_000_000) ** 0.5))
|
|
277
|
+
arr = arr[::step, ::step] if arr.ndim == 2 else arr[::step, ::step, :]
|
|
278
|
+
print(f"⚠️ Large dataset preview: downsampled by {step}x")
|
|
279
|
+
|
|
280
|
+
# Assign
|
|
281
|
+
self.data = arr
|
|
282
|
+
self._transform = None
|
|
283
|
+
self._crs = None
|
|
284
|
+
self.band_count = arr.shape[2] if arr.ndim == 3 else 1
|
|
285
|
+
self.band_index = 0
|
|
286
|
+
self.vmin, self.vmax = np.nanmin(arr), np.nanmax(arr)
|
|
215
287
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
sub_name, desc = subs[subset]
|
|
221
|
-
print(f"\nOpening subdataset [{subset}]: {desc}")
|
|
222
|
-
sub_ds = gdal.Open(sub_name)
|
|
223
|
-
|
|
224
|
-
# --- Read once ---
|
|
225
|
-
arr = sub_ds.ReadAsArray().astype(np.float32)
|
|
226
|
-
#print(f"Raw array shape from GDAL: {arr.shape} (ndim={arr.ndim})")
|
|
227
|
-
|
|
228
|
-
# --- Normalize shape ---
|
|
229
|
-
arr = np.squeeze(arr)
|
|
230
|
-
if arr.ndim == 3:
|
|
231
|
-
# Convert from (bands, rows, cols) → (rows, cols, bands)
|
|
232
|
-
arr = np.transpose(arr, (1, 2, 0))
|
|
233
|
-
#print(f"Transposed to {arr.shape} (rows, cols, bands)")
|
|
234
|
-
elif arr.ndim == 2:
|
|
235
|
-
print("Single-band dataset.")
|
|
236
|
-
else:
|
|
237
|
-
raise ValueError(f"Unexpected array shape {arr.shape}")
|
|
238
|
-
|
|
239
|
-
# --- Downsample large arrays for responsiveness ---
|
|
240
|
-
h, w = arr.shape[:2]
|
|
241
|
-
if h * w > 4_000_000:
|
|
242
|
-
step = max(2, int((h * w / 4_000_000) ** 0.5))
|
|
243
|
-
arr = arr[::step, ::step] if arr.ndim == 2 else arr[::step, ::step, :]
|
|
244
|
-
print(f"⚠️ Large dataset preview: downsampled by {step}x")
|
|
245
|
-
|
|
246
|
-
# --- Final assignments ---
|
|
247
|
-
self.data = arr
|
|
248
|
-
self._transform = None
|
|
249
|
-
self._crs = None
|
|
250
|
-
self.band_count = arr.shape[2] if arr.ndim == 3 else 1
|
|
251
|
-
self.band_index = 0
|
|
252
|
-
self.vmin, self.vmax = np.nanmin(arr), np.nanmax(arr)
|
|
253
|
-
|
|
254
|
-
if self.band_count > 1:
|
|
255
|
-
print(f"This subdataset has {self.band_count} bands — switch with [ and ] keys.")
|
|
256
|
-
else:
|
|
257
|
-
print("This subdataset has 1 band.")
|
|
288
|
+
if self.band_count > 1:
|
|
289
|
+
print(f"This subdataset has {self.band_count} bands — switch with [ and ] keys.")
|
|
290
|
+
else:
|
|
291
|
+
print("This subdataset has 1 band.")
|
|
258
292
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
print(f"Opening band {self.band}/{self.band_count}")
|
|
263
|
-
else:
|
|
264
|
-
self.band_index = 0
|
|
293
|
+
if self.band and self.band <= self.band_count:
|
|
294
|
+
self.band_index = self.band - 1
|
|
295
|
+
print(f"Opening band {self.band}/{self.band_count}")
|
|
265
296
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
297
|
+
except ImportError:
|
|
298
|
+
raise RuntimeError(
|
|
299
|
+
"HDF/HDF5 support requires GDAL (Python bindings).\n"
|
|
300
|
+
"Install it first (e.g., brew install gdal && pip install GDAL)"
|
|
301
|
+
)
|
|
271
302
|
|
|
272
303
|
# --------------------- Regular GeoTIFF --------------------- #
|
|
273
304
|
else:
|
|
@@ -487,8 +518,7 @@ class TiffViewer(QMainWindow):
|
|
|
487
518
|
rgb = np.zeros_like(arr)
|
|
488
519
|
if np.any(finite):
|
|
489
520
|
# Global 2–98 percentile stretch across all bands (QGIS-like)
|
|
490
|
-
global_min = np.nanpercentile(arr, 2)
|
|
491
|
-
global_max = np.nanpercentile(arr, 98)
|
|
521
|
+
global_min, global_max = np.nanpercentile(arr, (2, 98))
|
|
492
522
|
rng = max(global_max - global_min, 1e-12)
|
|
493
523
|
norm = np.clip((arr - global_min) / rng, 0, 1)
|
|
494
524
|
rgb = np.clip(norm * self.contrast, 0, 1)
|
|
@@ -640,8 +670,8 @@ class TiffViewer(QMainWindow):
|
|
|
640
670
|
super().keyPressEvent(ev)
|
|
641
671
|
|
|
642
672
|
|
|
643
|
-
# --------------------------------- CLI ----------------------------------- #
|
|
644
|
-
def
|
|
673
|
+
# --------------------------------- Legacy argparse CLI (not used by default) ----------------------------------- #
|
|
674
|
+
def legacy_argparse_main():
|
|
645
675
|
parser = argparse.ArgumentParser(description="TIFF viewer with RGB (2–98%) & shapefile overlays")
|
|
646
676
|
parser.add_argument("tif_path", nargs="?", help="Path to TIFF (optional if --rgbfiles is used)")
|
|
647
677
|
parser.add_argument("--scale", type=int, default=1, help="Downsample factor (1=full, 10=10x smaller)")
|
|
@@ -653,6 +683,8 @@ def main():
|
|
|
653
683
|
parser.add_argument("--shp-width", type=float, default=1.5, help="Overlay line width (screen pixels). Default: 1.5")
|
|
654
684
|
args = parser.parse_args()
|
|
655
685
|
|
|
686
|
+
from PySide6.QtCore import Qt
|
|
687
|
+
QApplication.setAttribute(Qt.ApplicationAttribute.AA_EnableHighDpiScaling, True)
|
|
656
688
|
app = QApplication(sys.argv)
|
|
657
689
|
win = TiffViewer(
|
|
658
690
|
args.tif_path,
|
|
@@ -677,9 +709,12 @@ def run_viewer(
|
|
|
677
709
|
shapefile=None,
|
|
678
710
|
shp_color=None,
|
|
679
711
|
shp_width=None,
|
|
680
|
-
subset=None
|
|
712
|
+
subset=None
|
|
681
713
|
):
|
|
714
|
+
|
|
682
715
|
"""Launch the TiffViewer app"""
|
|
716
|
+
from PySide6.QtCore import Qt
|
|
717
|
+
QApplication.setAttribute(Qt.ApplicationAttribute.AA_EnableHighDpiScaling, True)
|
|
683
718
|
app = QApplication(sys.argv)
|
|
684
719
|
win = TiffViewer(
|
|
685
720
|
tif_path,
|
|
@@ -690,7 +725,7 @@ def run_viewer(
|
|
|
690
725
|
shapefiles=shapefile,
|
|
691
726
|
shp_color=shp_color,
|
|
692
727
|
shp_width=shp_width,
|
|
693
|
-
subset=subset
|
|
728
|
+
subset=subset
|
|
694
729
|
)
|
|
695
730
|
win.show()
|
|
696
731
|
sys.exit(app.exec())
|
|
@@ -698,16 +733,17 @@ def run_viewer(
|
|
|
698
733
|
import click
|
|
699
734
|
|
|
700
735
|
@click.command()
|
|
701
|
-
@click.version_option("
|
|
736
|
+
@click.version_option("0.2.1", prog_name="viewtif")
|
|
702
737
|
@click.argument("tif_path", required=False)
|
|
703
738
|
@click.option("--band", default=1, show_default=True, type=int, help="Band number to display")
|
|
704
|
-
@click.option("--scale", default=1.0, show_default=True, type=
|
|
739
|
+
@click.option("--scale", default=1.0, show_default=True, type=int, help="Scale factor for display")
|
|
705
740
|
@click.option("--rgb", nargs=3, type=int, help="Three band numbers for RGB, e.g. --rgb 4 3 2")
|
|
706
741
|
@click.option("--rgbfiles", nargs=3, type=str, help="Three single-band TIFFs for RGB, e.g. --rgbfiles B4.tif B3.tif B2.tif")
|
|
707
742
|
@click.option("--shapefile", multiple=True, type=str, help="One or more shapefiles to overlay")
|
|
708
743
|
@click.option("--shp-color", default="white", show_default=True, help="Overlay color (name or #RRGGBB).")
|
|
709
744
|
@click.option("--shp-width", default=1.0, show_default=True, type=float, help="Overlay line width (screen pixels).")
|
|
710
745
|
@click.option("--subset", default=None, type=int, help="Open specific subdataset index in .hdf/.h5 file")
|
|
746
|
+
|
|
711
747
|
def main(tif_path, band, scale, rgb, rgbfiles, shapefile, shp_color, shp_width, subset):
|
|
712
748
|
"""Lightweight GeoTIFF viewer."""
|
|
713
749
|
# --- Warn early if shapefile requested but geopandas missing ---
|
|
@@ -727,7 +763,7 @@ def main(tif_path, band, scale, rgb, rgbfiles, shapefile, shp_color, shp_width,
|
|
|
727
763
|
shapefile=shapefile,
|
|
728
764
|
shp_color=shp_color,
|
|
729
765
|
shp_width=shp_width,
|
|
730
|
-
subset=subset
|
|
766
|
+
subset=subset
|
|
731
767
|
)
|
|
732
768
|
|
|
733
769
|
if __name__ == "__main__":
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|