viewtif 0.1.5__tar.gz → 0.1.6__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.6/.gitignore +8 -0
- {viewtif-0.1.5 → viewtif-0.1.6}/PKG-INFO +48 -18
- viewtif-0.1.6/README.md +103 -0
- {viewtif-0.1.5 → viewtif-0.1.6}/pyproject.toml +1 -1
- {viewtif-0.1.5 → viewtif-0.1.6}/src/viewtif/tif_viewer.py +177 -43
- viewtif-0.1.5/.gitignore +0 -4
- viewtif-0.1.5/README.md +0 -73
- {viewtif-0.1.5 → viewtif-0.1.6}/examples/README.md +0 -0
- {viewtif-0.1.5 → viewtif-0.1.6}/examples/sample_data/ECOSTRESS_LST.tif +0 -0
- {viewtif-0.1.5 → viewtif-0.1.6}/examples/sample_data/HLS_B02.tif +0 -0
- {viewtif-0.1.5 → viewtif-0.1.6}/examples/sample_data/HLS_B03.tif +0 -0
- {viewtif-0.1.5 → viewtif-0.1.6}/examples/sample_data/HLS_B04.tif +0 -0
- {viewtif-0.1.5 → viewtif-0.1.6}/examples/sample_data/Zip_Codes.cpg +0 -0
- {viewtif-0.1.5 → viewtif-0.1.6}/examples/sample_data/Zip_Codes.dbf +0 -0
- {viewtif-0.1.5 → viewtif-0.1.6}/examples/sample_data/Zip_Codes.prj +0 -0
- {viewtif-0.1.5 → viewtif-0.1.6}/examples/sample_data/Zip_Codes.shp +0 -0
- {viewtif-0.1.5 → viewtif-0.1.6}/examples/sample_data/Zip_Codes.shx +0 -0
viewtif-0.1.6/.gitignore
ADDED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: viewtif
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.6
|
|
4
4
|
Summary: Simple GeoTIFF viewer with optional shapefile overlay.
|
|
5
5
|
Project-URL: Homepage, https://github.com/nkeikon/tifviewer
|
|
6
6
|
Project-URL: Source, https://github.com/nkeikon/tifviewer
|
|
@@ -19,6 +19,9 @@ Requires-Dist: shapely>=2.0; extra == 'geo'
|
|
|
19
19
|
Description-Content-Type: text/markdown
|
|
20
20
|
|
|
21
21
|
# viewtif
|
|
22
|
+
[](https://pepy.tech/project/viewtif)
|
|
23
|
+
[](https://pypi.org/project/viewtif/)
|
|
24
|
+
[](https://pypi.org/project/viewtif/)
|
|
22
25
|
|
|
23
26
|
A lightweight GeoTIFF viewer for quick visualization directly from the command line.
|
|
24
27
|
|
|
@@ -31,35 +34,61 @@ You can visualize single-band GeoTIFFs, RGB composites, and shapefile overlays i
|
|
|
31
34
|
```bash
|
|
32
35
|
pip install viewtif
|
|
33
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).
|
|
34
41
|
|
|
35
|
-
|
|
42
|
+
### Optional features
|
|
43
|
+
#### Shapefile overlay support
|
|
36
44
|
```bash
|
|
37
45
|
pip install "viewtif[geo]"
|
|
38
46
|
```
|
|
39
|
-
Note
|
|
40
|
-
Make sure to include the quotes, or zsh will interpret it as a pattern.
|
|
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.`
|
|
41
57
|
|
|
42
58
|
## Quick Start
|
|
43
59
|
```bash
|
|
44
60
|
# View a GeoTIFF
|
|
45
|
-
viewtif
|
|
46
|
-
|
|
47
|
-
# View with shapefile overlay
|
|
48
|
-
viewtif examples/sample_data/ECOSTRESS_LST.tif \
|
|
49
|
-
--shapefile examples/sample_data/Zip_Codes.shp
|
|
61
|
+
viewtif ECOSTRESS_LST.tif
|
|
50
62
|
|
|
51
63
|
# View an RGB composite
|
|
52
64
|
viewtif --rgbfiles \
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
|
56
81
|
|
|
82
|
+
# View a specific subdataset and band
|
|
83
|
+
viewtif AG100.v003.33.-107.0001.h5 --subset 1 --band 3
|
|
57
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.`
|
|
58
87
|
|
|
59
88
|
## Controls
|
|
60
89
|
| Key | Action |
|
|
61
90
|
| -------------------- | --------------------------------------- |
|
|
62
|
-
| `+` / `-` | Zoom in / out |
|
|
91
|
+
| `+` / `-` or mouse / trackpad | Zoom in / out |
|
|
63
92
|
| Arrow keys or `WASD` | Pan |
|
|
64
93
|
| `C` / `V` | Increase / decrease contrast |
|
|
65
94
|
| `G` / `H` | Increase / decrease gamma |
|
|
@@ -68,8 +97,8 @@ viewtif --rgbfiles \
|
|
|
68
97
|
| `R` | Reset view |
|
|
69
98
|
|
|
70
99
|
## Features
|
|
71
|
-
- Command-line driven GeoTIFF viewer
|
|
72
|
-
- Supports single-band
|
|
100
|
+
- Command-line driven GeoTIFF viewer.
|
|
101
|
+
- Supports single-band, RGB composite, and HDF/HDF5 subdatasets.
|
|
73
102
|
- Optional shapefile overlay for geographic context.
|
|
74
103
|
- Adjustable contrast, gamma, and colormap.
|
|
75
104
|
- Fast preview using rasterio and PySide6.
|
|
@@ -78,14 +107,15 @@ viewtif --rgbfiles \
|
|
|
78
107
|
- ECOSTRESS_LST.tif
|
|
79
108
|
- Zip_Codes.shp and associated files
|
|
80
109
|
- HLS_B04.tif, HLS_B03.tif, HLS_B02.tif (RGB sample)
|
|
110
|
+
- AG100.v003.33.-107.0001.h5 (HDF5 file)
|
|
81
111
|
|
|
82
112
|
## Credit & License
|
|
83
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.
|
|
84
114
|
|
|
85
|
-
|
|
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.
|
|
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.
|
|
87
117
|
|
|
88
|
-
|
|
118
|
+
## License
|
|
89
119
|
This project is released under the MIT License.
|
|
90
120
|
|
|
91
121
|
## Contributors
|
viewtif-0.1.6/README.md
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# viewtif
|
|
2
|
+
[](https://pepy.tech/project/viewtif)
|
|
3
|
+
[](https://pypi.org/project/viewtif/)
|
|
4
|
+
[](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
|
|
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
|
+
## Controls
|
|
69
|
+
| Key | Action |
|
|
70
|
+
| -------------------- | --------------------------------------- |
|
|
71
|
+
| `+` / `-` or mouse / trackpad | Zoom in / out |
|
|
72
|
+
| Arrow keys or `WASD` | Pan |
|
|
73
|
+
| `C` / `V` | Increase / decrease contrast |
|
|
74
|
+
| `G` / `H` | Increase / decrease gamma |
|
|
75
|
+
| `M` | Toggle colormap (`viridis` ↔ `magma`) |
|
|
76
|
+
| `[` / `]` | Previous / next band (single-band only) |
|
|
77
|
+
| `R` | Reset view |
|
|
78
|
+
|
|
79
|
+
## Features
|
|
80
|
+
- Command-line driven GeoTIFF viewer.
|
|
81
|
+
- Supports single-band, RGB composite, and HDF/HDF5 subdatasets.
|
|
82
|
+
- Optional shapefile overlay for geographic context.
|
|
83
|
+
- Adjustable contrast, gamma, and colormap.
|
|
84
|
+
- Fast preview using rasterio and PySide6.
|
|
85
|
+
|
|
86
|
+
## Example Data
|
|
87
|
+
- ECOSTRESS_LST.tif
|
|
88
|
+
- Zip_Codes.shp and associated files
|
|
89
|
+
- HLS_B04.tif, HLS_B03.tif, HLS_B02.tif (RGB sample)
|
|
90
|
+
- AG100.v003.33.-107.0001.h5 (HDF5 file)
|
|
91
|
+
|
|
92
|
+
## Credit & License
|
|
93
|
+
`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.
|
|
94
|
+
|
|
95
|
+
## Citation
|
|
96
|
+
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.
|
|
97
|
+
|
|
98
|
+
## License
|
|
99
|
+
This project is released under the MIT License.
|
|
100
|
+
|
|
101
|
+
## Contributors
|
|
102
|
+
- [@HarshShinde0](https://github.com/HarshShinde0) — added mouse-wheel and trackpad zoom support
|
|
103
|
+
|
|
@@ -56,6 +56,7 @@ 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)
|
|
@@ -101,6 +102,7 @@ class TiffViewer(QMainWindow):
|
|
|
101
102
|
shapefiles: list[str] | None = None,
|
|
102
103
|
shp_color: str = "white",
|
|
103
104
|
shp_width: float = 2,
|
|
105
|
+
subset: int | None = None,
|
|
104
106
|
):
|
|
105
107
|
super().__init__()
|
|
106
108
|
|
|
@@ -141,38 +143,118 @@ class TiffViewer(QMainWindow):
|
|
|
141
143
|
self.tif_path = self.tif_path or (os.path.commonprefix([red, green, blue]) or red)
|
|
142
144
|
|
|
143
145
|
elif tif_path:
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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 ---
|
|
154
198
|
self.data = arr
|
|
155
|
-
self.
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
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
|
+
|
|
176
258
|
else:
|
|
177
259
|
raise ValueError("Provide a TIFF path or --rgbfiles.")
|
|
178
260
|
|
|
@@ -339,6 +421,10 @@ class TiffViewer(QMainWindow):
|
|
|
339
421
|
self.setWindowTitle(f"RGB ({', '.join(names)})")
|
|
340
422
|
elif self.rgb_mode and self.rgb:
|
|
341
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
|
+
)
|
|
342
428
|
else:
|
|
343
429
|
self.setWindowTitle(f"Band {self.band}/{self.band_count} — {os.path.basename(self.tif_path)}")
|
|
344
430
|
|
|
@@ -371,10 +457,44 @@ class TiffViewer(QMainWindow):
|
|
|
371
457
|
return rgb
|
|
372
458
|
|
|
373
459
|
def update_pixmap(self):
|
|
374
|
-
|
|
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
|
+
|
|
375
495
|
h, w, _ = rgb.shape
|
|
376
496
|
self._last_rgb = rgb
|
|
377
|
-
qimg = QImage(
|
|
497
|
+
qimg = QImage(rgb.data, w, h, 3 * w, QImage.Format.Format_RGB888)
|
|
378
498
|
pix = QPixmap.fromImage(qimg)
|
|
379
499
|
if self.pixmap_item is None:
|
|
380
500
|
self.pixmap_item = QGraphicsPixmapItem(pix)
|
|
@@ -433,13 +553,24 @@ class TiffViewer(QMainWindow):
|
|
|
433
553
|
self.cmap_name, self.alt_cmap_name = self.alt_cmap_name, self.cmap_name
|
|
434
554
|
self.update_pixmap()
|
|
435
555
|
|
|
436
|
-
# Band switch
|
|
437
|
-
elif
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
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)
|
|
443
574
|
|
|
444
575
|
elif k == Qt.Key.Key_R:
|
|
445
576
|
self.contrast = 1.0
|
|
@@ -488,6 +619,7 @@ def run_viewer(
|
|
|
488
619
|
shapefile=None,
|
|
489
620
|
shp_color=None,
|
|
490
621
|
shp_width=None,
|
|
622
|
+
subset=None,
|
|
491
623
|
):
|
|
492
624
|
"""Launch the TiffViewer app"""
|
|
493
625
|
app = QApplication(sys.argv)
|
|
@@ -500,6 +632,7 @@ def run_viewer(
|
|
|
500
632
|
shapefiles=shapefile,
|
|
501
633
|
shp_color=shp_color,
|
|
502
634
|
shp_width=shp_width,
|
|
635
|
+
subset=subset,
|
|
503
636
|
)
|
|
504
637
|
win.show()
|
|
505
638
|
sys.exit(app.exec())
|
|
@@ -507,6 +640,7 @@ def run_viewer(
|
|
|
507
640
|
import click
|
|
508
641
|
|
|
509
642
|
@click.command()
|
|
643
|
+
@click.version_option("1.0.6", prog_name="viewtif")
|
|
510
644
|
@click.argument("tif_path", required=False)
|
|
511
645
|
@click.option("--band", default=1, show_default=True, type=int, help="Band number to display")
|
|
512
646
|
@click.option("--scale", default=1.0, show_default=True, type=float, help="Scale factor for display")
|
|
@@ -515,9 +649,9 @@ import click
|
|
|
515
649
|
@click.option("--shapefile", multiple=True, type=str, help="One or more shapefiles to overlay")
|
|
516
650
|
@click.option("--shp-color", default="white", show_default=True, help="Overlay color (name or #RRGGBB).")
|
|
517
651
|
@click.option("--shp-width", default=1.0, show_default=True, type=float, help="Overlay line width (screen pixels).")
|
|
518
|
-
|
|
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):
|
|
519
654
|
"""Lightweight GeoTIFF viewer."""
|
|
520
|
-
|
|
521
655
|
# --- Warn early if shapefile requested but geopandas missing ---
|
|
522
656
|
if shapefile and not HAVE_GEO:
|
|
523
657
|
print(
|
|
@@ -535,9 +669,9 @@ def main(tif_path, band, scale, rgb, rgbfiles, shapefile, shp_color, shp_width):
|
|
|
535
669
|
shapefile=shapefile,
|
|
536
670
|
shp_color=shp_color,
|
|
537
671
|
shp_width=shp_width,
|
|
672
|
+
subset=subset,
|
|
538
673
|
)
|
|
539
674
|
|
|
540
|
-
|
|
541
675
|
if __name__ == "__main__":
|
|
542
676
|
main()
|
|
543
677
|
|
viewtif-0.1.5/.gitignore
DELETED
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
|
|
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
|