viewinline 0.2.2__tar.gz → 0.3.0__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.
- viewinline-0.2.2/README.md → viewinline-0.3.0/PKG-INFO +59 -7
- viewinline-0.2.2/PKG-INFO → viewinline-0.3.0/README.md +26 -37
- {viewinline-0.2.2 → viewinline-0.3.0}/pyproject.toml +4 -4
- {viewinline-0.2.2 → viewinline-0.3.0}/src/viewinline/viewinline.py +282 -38
- {viewinline-0.2.2 → viewinline-0.3.0}/.github/FUNDING.yml +0 -0
- {viewinline-0.2.2 → viewinline-0.3.0}/.gitignore +0 -0
- {viewinline-0.2.2 → viewinline-0.3.0}/LICENSE +0 -0
- {viewinline-0.2.2 → viewinline-0.3.0}/src/viewinline/__init__.py +0 -0
- {viewinline-0.2.2 → viewinline-0.3.0}/viewinline_gif1.gif +0 -0
- {viewinline-0.2.2 → viewinline-0.3.0}/viewinline_gif2.gif +0 -0
|
@@ -1,10 +1,43 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: viewinline
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Quick look geospatial viewer for the terminal, with inline image previews
|
|
5
|
+
Project-URL: Homepage, https://github.com/nkeikon/viewinline
|
|
6
|
+
Project-URL: Repository, https://github.com/nkeikon/viewinline
|
|
7
|
+
Project-URL: Issues, https://github.com/nkeikon/viewinline/issues
|
|
8
|
+
Author: Keiko Nomura
|
|
9
|
+
License: Apache-2.0
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Requires-Python: >=3.9
|
|
12
|
+
Requires-Dist: geopandas
|
|
13
|
+
Requires-Dist: matplotlib
|
|
14
|
+
Requires-Dist: numpy
|
|
15
|
+
Requires-Dist: pandas
|
|
16
|
+
Requires-Dist: pillow
|
|
17
|
+
Requires-Dist: pyogrio
|
|
18
|
+
Requires-Dist: rasterio
|
|
19
|
+
Provides-Extra: all
|
|
20
|
+
Requires-Dist: duckdb; extra == 'all'
|
|
21
|
+
Requires-Dist: h5py; extra == 'all'
|
|
22
|
+
Requires-Dist: netcdf4; extra == 'all'
|
|
23
|
+
Requires-Dist: pyarrow; extra == 'all'
|
|
24
|
+
Provides-Extra: hdf5
|
|
25
|
+
Requires-Dist: h5py; extra == 'hdf5'
|
|
26
|
+
Provides-Extra: netcdf
|
|
27
|
+
Requires-Dist: netcdf4; extra == 'netcdf'
|
|
28
|
+
Provides-Extra: parquet
|
|
29
|
+
Requires-Dist: pyarrow; extra == 'parquet'
|
|
30
|
+
Provides-Extra: sql
|
|
31
|
+
Requires-Dist: duckdb; extra == 'sql'
|
|
32
|
+
Description-Content-Type: text/markdown
|
|
33
|
+
|
|
1
34
|
# viewinline
|
|
2
35
|
[](https://pepy.tech/project/viewinline)
|
|
3
36
|
[](https://pypi.org/project/viewinline/)
|
|
4
37
|
[](https://pypi.org/project/viewinline/)
|
|
5
38
|
|
|
6
39
|
**Quick-look geospatial viewer for compatible terminals.**
|
|
7
|
-
Displays rasters, vectors, and tabular data directly in the terminal with no GUI
|
|
40
|
+
Displays rasters, vectors, and tabular data directly in the terminal with no GUI.
|
|
8
41
|
|
|
9
42
|
<p align="center">
|
|
10
43
|
<a href="viewinline_gif1.gif"><img src="viewinline_gif1.gif" width="49%"></a>
|
|
@@ -17,7 +50,7 @@ Particularly useful on HPC systems and remote servers accessed via SSH. Images r
|
|
|
17
50
|
|
|
18
51
|
This tool combines the core display logic of `viewtif` and `viewgeom`, but is **non-interactive**: you can't zoom, pan, or switch colormaps on the fly. Instead, you control everything through command-line options (e.g. --display, --color-by, --colormap).
|
|
19
52
|
|
|
20
|
-
It uses the iTerm2 inline image protocol (OSC 1337) to
|
|
53
|
+
It uses the iTerm2 inline image protocol (OSC 1337) in supported terminals, and falls back to `chafa` in others, which displays real high-res images in terminals like kitty and Ghostty, and colored block-art (ASCII art) previews in Terminal.app, VS Code, and most Linux terminals. Without `chafa` installed, non-iTerm2-family terminals show an info message instead.
|
|
21
54
|
|
|
22
55
|
## Installation
|
|
23
56
|
Requires Python 3.9 or later.
|
|
@@ -31,6 +64,7 @@ pip install viewinline
|
|
|
31
64
|
# Rasters
|
|
32
65
|
viewinline path/to/file.tif
|
|
33
66
|
viewinline R.tif G.tif B.tif # RGB composite (also works with --rgbfiles)
|
|
67
|
+
viewinline hyperspectral.nc --band 50
|
|
34
68
|
viewinline path/to/multiband.tif --rgb 3 2 1
|
|
35
69
|
viewinline path/to/folder --gallery 4x3 # show image gallery (e.g. 4x3 grid)
|
|
36
70
|
|
|
@@ -39,6 +73,7 @@ viewinline file.nc # list variables
|
|
|
39
73
|
viewinline file.nc --subset 2 # display variable 2
|
|
40
74
|
viewinline file.nc --subset 1 --band 10 # variable 1, timestep 10 --band or --timestep
|
|
41
75
|
viewinline temp.nc --subset 1 --colormap plasma --vmin 273 --vmax 310
|
|
76
|
+
viewinline hyperspectral.nc --subset 1 --reduce NumberOfScanlines # override auto-detected axis
|
|
42
77
|
|
|
43
78
|
# Vectors
|
|
44
79
|
viewinline path/to/vector.geojson
|
|
@@ -63,18 +98,31 @@ viewinline data.geoparquet --table --where "POP > 100000" --sort POP --desc
|
|
|
63
98
|
|
|
64
99
|
## Compatible terminals
|
|
65
100
|
|
|
66
|
-
|
|
101
|
+
Native (no extra install required): images render via the iTerm2 inline image protocol on:
|
|
67
102
|
|
|
68
103
|
- **iTerm2** (macOS)
|
|
69
104
|
- **WezTerm** (cross-platform)
|
|
70
105
|
- **Konsole** (Linux/KDE)
|
|
71
106
|
- **Rio**, **Contour** (cross-platform)
|
|
72
107
|
|
|
73
|
-
|
|
108
|
+
Via `chafa` (recommended for everyone else): install chafa and viewinline works in nearly every terminal:
|
|
109
|
+
- kitty, Ghostty, foot — real high-resolution images via the kitty graphics protocol or sixel
|
|
110
|
+
- Terminal.app, VS Code, GNOME Terminal, Alacritty, Warp, Hyper — colored block-art previews with 24-bit color
|
|
111
|
+
|
|
112
|
+
Install chafa once (it's a system binary, available across all conda/virtualenv environments):
|
|
113
|
+
|
|
114
|
+
```
|
|
115
|
+
brew install chafa # macOS
|
|
116
|
+
sudo apt install chafa # Debian/Ubuntu
|
|
117
|
+
sudo dnf install chafa # Fedora
|
|
118
|
+
scoop install chafa # Windows
|
|
119
|
+
```
|
|
120
|
+
Without chafa, terminals outside the native list above show an info message instead of an image.
|
|
121
|
+
You can also force the chafa path on any terminal by setting `INLINE_VIEWER_ENGINE=chafa`.
|
|
74
122
|
|
|
75
123
|
**SSH/HPC usage:** Works over SSH when connecting from a compatible terminal. Images render on your local machine, not the remote server. No X11 forwarding or VNC required.
|
|
76
124
|
|
|
77
|
-
**tmux/screen:** Inline images
|
|
125
|
+
**tmux/screen:** Inline images work inside tmux only when the outer terminal is iTerm2 (or WezTerm/Konsole/Rio/Contour). In tmux with other outer terminals (kitty, Terminal.app, etc.), viewinline displays ASCII art previews instead of full-quality images.
|
|
78
126
|
|
|
79
127
|
**Fallback:** In terminals that do not support inline images, you can fallback to ASCII art by installing [`chafa`](https://hpjansson.org/chafa/) command-line tool. Install `chafa` with your package manager (e.g. `brew install chafa` or `sudo apt install chafa`). You can also force the use of `chafa` by setting the environment variable `INLINE_VIEWER_ENGINE=chafa`.
|
|
80
128
|
|
|
@@ -124,7 +172,9 @@ The iTerm2 inline image protocol (OSC 1337) is supported by:
|
|
|
124
172
|
|
|
125
173
|
**NetCDF/HDF notes:**
|
|
126
174
|
- viewinline lists only variables that can be displayed as 2D or 3D arrays
|
|
127
|
-
-
|
|
175
|
+
- 3D variables with time or known spatial dimensions are auto-handled (slices along the non-spatial axis)
|
|
176
|
+
- For 3D variables with non-standard dimensions (e.g., hyperspectral cubes like PICARD), viewinline auto-detects the band axis by smallest dimension. Use `--reduce DIM_NAME` to override.
|
|
177
|
+
- Variables with 4+ dimensions are not supported
|
|
128
178
|
- For a complete variable list, use `ncdump -h file.nc` or `viewtif`
|
|
129
179
|
|
|
130
180
|
## Dependencies
|
|
@@ -137,6 +187,7 @@ The iTerm2 inline image protocol (OSC 1337) is supported by:
|
|
|
137
187
|
- `numpy`, `pandas` — data handling
|
|
138
188
|
|
|
139
189
|
**Optional dependencies:**
|
|
190
|
+
- `chafa` — strongly recommended for terminal coverage beyond iTerm2/WezTerm/Konsole/Rio/Contour. System binary, not a Python package. See "Terminal support" above for install instructions.
|
|
140
191
|
- `duckdb` — required for `--where`, `--sort`, `--sql`, `--limit` with filtering
|
|
141
192
|
```bash
|
|
142
193
|
pip install duckdb
|
|
@@ -152,7 +203,7 @@ The iTerm2 inline image protocol (OSC 1337) is supported by:
|
|
|
152
203
|
|
|
153
204
|
**Note on HDF support:**
|
|
154
205
|
- **HDF5** (.h5, .hdf5): Supported via rasterio if GDAL has HDF5 support (most installations)
|
|
155
|
-
- **HDF4** (.hdf): Requires GDAL compiled with HDF4 support (
|
|
206
|
+
- **HDF4** (.hdf): Requires GDAL compiled with HDF4 support (the legacy format used by MODIS and older NASA products)
|
|
156
207
|
- **NetCDF** (.nc): Supported via rasterio (uses GDAL's NetCDF driver)
|
|
157
208
|
|
|
158
209
|
## Available options
|
|
@@ -164,6 +215,7 @@ Raster:
|
|
|
164
215
|
--band BAND Band number to display (single raster), or slice number for NetCDF. (default: 1)
|
|
165
216
|
--timestep INTEGER Alias for --band when working with NetCDF files.
|
|
166
217
|
--subset INTEGER Variable index for NetCDF/HDF files (e.g., --subset 1).
|
|
218
|
+
--reduce DIM_NAME For 3D NetCDF variables, specify which dimension to use as the band/slider axis. Auto-detected if omitted.
|
|
167
219
|
--colormap Apply colormap to single-band rasters. Flag without the color scheme → 'terrain'.
|
|
168
220
|
--rgb R G B Three band numbers for RGB display (e.g., --rgb 4 3 2). Overrides default 1 2 3.
|
|
169
221
|
--rgbfiles R G B Three single-band rasters for RGB composite. Can also provide as positional arguments.
|
|
@@ -1,40 +1,10 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: viewinline
|
|
3
|
-
Version: 0.2.2
|
|
4
|
-
Summary: Quick look geospatial viewer for iTerm2 compatible terminals
|
|
5
|
-
Project-URL: Homepage, https://github.com/nkeikon/viewinline
|
|
6
|
-
Project-URL: Repository, https://github.com/nkeikon/viewinline
|
|
7
|
-
Project-URL: Issues, https://github.com/nkeikon/viewinline/issues
|
|
8
|
-
Author: Keiko Nomura
|
|
9
|
-
License: Apache-2.0
|
|
10
|
-
License-File: LICENSE
|
|
11
|
-
Requires-Python: >=3.9
|
|
12
|
-
Requires-Dist: geopandas
|
|
13
|
-
Requires-Dist: matplotlib
|
|
14
|
-
Requires-Dist: numpy
|
|
15
|
-
Requires-Dist: pandas
|
|
16
|
-
Requires-Dist: pillow
|
|
17
|
-
Requires-Dist: pyogrio
|
|
18
|
-
Requires-Dist: rasterio
|
|
19
|
-
Provides-Extra: all
|
|
20
|
-
Requires-Dist: duckdb; extra == 'all'
|
|
21
|
-
Requires-Dist: h5py; extra == 'all'
|
|
22
|
-
Requires-Dist: pyarrow; extra == 'all'
|
|
23
|
-
Provides-Extra: hdf5
|
|
24
|
-
Requires-Dist: h5py; extra == 'hdf5'
|
|
25
|
-
Provides-Extra: parquet
|
|
26
|
-
Requires-Dist: pyarrow; extra == 'parquet'
|
|
27
|
-
Provides-Extra: sql
|
|
28
|
-
Requires-Dist: duckdb; extra == 'sql'
|
|
29
|
-
Description-Content-Type: text/markdown
|
|
30
|
-
|
|
31
1
|
# viewinline
|
|
32
2
|
[](https://pepy.tech/project/viewinline)
|
|
33
3
|
[](https://pypi.org/project/viewinline/)
|
|
34
4
|
[](https://pypi.org/project/viewinline/)
|
|
35
5
|
|
|
36
6
|
**Quick-look geospatial viewer for compatible terminals.**
|
|
37
|
-
Displays rasters, vectors, and tabular data directly in the terminal with no GUI
|
|
7
|
+
Displays rasters, vectors, and tabular data directly in the terminal with no GUI.
|
|
38
8
|
|
|
39
9
|
<p align="center">
|
|
40
10
|
<a href="viewinline_gif1.gif"><img src="viewinline_gif1.gif" width="49%"></a>
|
|
@@ -47,7 +17,7 @@ Particularly useful on HPC systems and remote servers accessed via SSH. Images r
|
|
|
47
17
|
|
|
48
18
|
This tool combines the core display logic of `viewtif` and `viewgeom`, but is **non-interactive**: you can't zoom, pan, or switch colormaps on the fly. Instead, you control everything through command-line options (e.g. --display, --color-by, --colormap).
|
|
49
19
|
|
|
50
|
-
It uses the iTerm2 inline image protocol (OSC 1337) to
|
|
20
|
+
It uses the iTerm2 inline image protocol (OSC 1337) in supported terminals, and falls back to `chafa` in others, which displays real high-res images in terminals like kitty and Ghostty, and colored block-art (ASCII art) previews in Terminal.app, VS Code, and most Linux terminals. Without `chafa` installed, non-iTerm2-family terminals show an info message instead.
|
|
51
21
|
|
|
52
22
|
## Installation
|
|
53
23
|
Requires Python 3.9 or later.
|
|
@@ -61,6 +31,7 @@ pip install viewinline
|
|
|
61
31
|
# Rasters
|
|
62
32
|
viewinline path/to/file.tif
|
|
63
33
|
viewinline R.tif G.tif B.tif # RGB composite (also works with --rgbfiles)
|
|
34
|
+
viewinline hyperspectral.nc --band 50
|
|
64
35
|
viewinline path/to/multiband.tif --rgb 3 2 1
|
|
65
36
|
viewinline path/to/folder --gallery 4x3 # show image gallery (e.g. 4x3 grid)
|
|
66
37
|
|
|
@@ -69,6 +40,7 @@ viewinline file.nc # list variables
|
|
|
69
40
|
viewinline file.nc --subset 2 # display variable 2
|
|
70
41
|
viewinline file.nc --subset 1 --band 10 # variable 1, timestep 10 --band or --timestep
|
|
71
42
|
viewinline temp.nc --subset 1 --colormap plasma --vmin 273 --vmax 310
|
|
43
|
+
viewinline hyperspectral.nc --subset 1 --reduce NumberOfScanlines # override auto-detected axis
|
|
72
44
|
|
|
73
45
|
# Vectors
|
|
74
46
|
viewinline path/to/vector.geojson
|
|
@@ -93,18 +65,31 @@ viewinline data.geoparquet --table --where "POP > 100000" --sort POP --desc
|
|
|
93
65
|
|
|
94
66
|
## Compatible terminals
|
|
95
67
|
|
|
96
|
-
|
|
68
|
+
Native (no extra install required): images render via the iTerm2 inline image protocol on:
|
|
97
69
|
|
|
98
70
|
- **iTerm2** (macOS)
|
|
99
71
|
- **WezTerm** (cross-platform)
|
|
100
72
|
- **Konsole** (Linux/KDE)
|
|
101
73
|
- **Rio**, **Contour** (cross-platform)
|
|
102
74
|
|
|
103
|
-
|
|
75
|
+
Via `chafa` (recommended for everyone else): install chafa and viewinline works in nearly every terminal:
|
|
76
|
+
- kitty, Ghostty, foot — real high-resolution images via the kitty graphics protocol or sixel
|
|
77
|
+
- Terminal.app, VS Code, GNOME Terminal, Alacritty, Warp, Hyper — colored block-art previews with 24-bit color
|
|
78
|
+
|
|
79
|
+
Install chafa once (it's a system binary, available across all conda/virtualenv environments):
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
brew install chafa # macOS
|
|
83
|
+
sudo apt install chafa # Debian/Ubuntu
|
|
84
|
+
sudo dnf install chafa # Fedora
|
|
85
|
+
scoop install chafa # Windows
|
|
86
|
+
```
|
|
87
|
+
Without chafa, terminals outside the native list above show an info message instead of an image.
|
|
88
|
+
You can also force the chafa path on any terminal by setting `INLINE_VIEWER_ENGINE=chafa`.
|
|
104
89
|
|
|
105
90
|
**SSH/HPC usage:** Works over SSH when connecting from a compatible terminal. Images render on your local machine, not the remote server. No X11 forwarding or VNC required.
|
|
106
91
|
|
|
107
|
-
**tmux/screen:** Inline images
|
|
92
|
+
**tmux/screen:** Inline images work inside tmux only when the outer terminal is iTerm2 (or WezTerm/Konsole/Rio/Contour). In tmux with other outer terminals (kitty, Terminal.app, etc.), viewinline displays ASCII art previews instead of full-quality images.
|
|
108
93
|
|
|
109
94
|
**Fallback:** In terminals that do not support inline images, you can fallback to ASCII art by installing [`chafa`](https://hpjansson.org/chafa/) command-line tool. Install `chafa` with your package manager (e.g. `brew install chafa` or `sudo apt install chafa`). You can also force the use of `chafa` by setting the environment variable `INLINE_VIEWER_ENGINE=chafa`.
|
|
110
95
|
|
|
@@ -154,7 +139,9 @@ The iTerm2 inline image protocol (OSC 1337) is supported by:
|
|
|
154
139
|
|
|
155
140
|
**NetCDF/HDF notes:**
|
|
156
141
|
- viewinline lists only variables that can be displayed as 2D or 3D arrays
|
|
157
|
-
-
|
|
142
|
+
- 3D variables with time or known spatial dimensions are auto-handled (slices along the non-spatial axis)
|
|
143
|
+
- For 3D variables with non-standard dimensions (e.g., hyperspectral cubes like PICARD), viewinline auto-detects the band axis by smallest dimension. Use `--reduce DIM_NAME` to override.
|
|
144
|
+
- Variables with 4+ dimensions are not supported
|
|
158
145
|
- For a complete variable list, use `ncdump -h file.nc` or `viewtif`
|
|
159
146
|
|
|
160
147
|
## Dependencies
|
|
@@ -167,6 +154,7 @@ The iTerm2 inline image protocol (OSC 1337) is supported by:
|
|
|
167
154
|
- `numpy`, `pandas` — data handling
|
|
168
155
|
|
|
169
156
|
**Optional dependencies:**
|
|
157
|
+
- `chafa` — strongly recommended for terminal coverage beyond iTerm2/WezTerm/Konsole/Rio/Contour. System binary, not a Python package. See "Terminal support" above for install instructions.
|
|
170
158
|
- `duckdb` — required for `--where`, `--sort`, `--sql`, `--limit` with filtering
|
|
171
159
|
```bash
|
|
172
160
|
pip install duckdb
|
|
@@ -182,7 +170,7 @@ The iTerm2 inline image protocol (OSC 1337) is supported by:
|
|
|
182
170
|
|
|
183
171
|
**Note on HDF support:**
|
|
184
172
|
- **HDF5** (.h5, .hdf5): Supported via rasterio if GDAL has HDF5 support (most installations)
|
|
185
|
-
- **HDF4** (.hdf): Requires GDAL compiled with HDF4 support (
|
|
173
|
+
- **HDF4** (.hdf): Requires GDAL compiled with HDF4 support (the legacy format used by MODIS and older NASA products)
|
|
186
174
|
- **NetCDF** (.nc): Supported via rasterio (uses GDAL's NetCDF driver)
|
|
187
175
|
|
|
188
176
|
## Available options
|
|
@@ -194,6 +182,7 @@ Raster:
|
|
|
194
182
|
--band BAND Band number to display (single raster), or slice number for NetCDF. (default: 1)
|
|
195
183
|
--timestep INTEGER Alias for --band when working with NetCDF files.
|
|
196
184
|
--subset INTEGER Variable index for NetCDF/HDF files (e.g., --subset 1).
|
|
185
|
+
--reduce DIM_NAME For 3D NetCDF variables, specify which dimension to use as the band/slider axis. Auto-detected if omitted.
|
|
197
186
|
--colormap Apply colormap to single-band rasters. Flag without the color scheme → 'terrain'.
|
|
198
187
|
--rgb R G B Three band numbers for RGB display (e.g., --rgb 4 3 2). Overrides default 1 2 3.
|
|
199
188
|
--rgbfiles R G B Three single-band rasters for RGB composite. Can also provide as positional arguments.
|
|
@@ -4,8 +4,8 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "viewinline"
|
|
7
|
-
version = "0.
|
|
8
|
-
description = "Quick look geospatial viewer for
|
|
7
|
+
version = "0.3.0"
|
|
8
|
+
description = "Quick look geospatial viewer for the terminal, with inline image previews"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { text = "Apache-2.0" }
|
|
11
11
|
authors = [
|
|
@@ -22,12 +22,12 @@ dependencies = [
|
|
|
22
22
|
"pyogrio",
|
|
23
23
|
"pandas"
|
|
24
24
|
]
|
|
25
|
-
|
|
26
25
|
[project.optional-dependencies]
|
|
27
26
|
sql = ["duckdb"]
|
|
28
27
|
parquet = ["pyarrow"]
|
|
29
28
|
hdf5 = ["h5py"]
|
|
30
|
-
|
|
29
|
+
netcdf = ["netCDF4"]
|
|
30
|
+
all = ["duckdb", "pyarrow", "h5py", "netCDF4"]
|
|
31
31
|
|
|
32
32
|
[project.scripts]
|
|
33
33
|
viewinline = "viewinline.viewinline:main"
|
|
@@ -28,12 +28,19 @@ from matplotlib import colormaps
|
|
|
28
28
|
import matplotlib as mpl
|
|
29
29
|
import subprocess
|
|
30
30
|
|
|
31
|
+
try:
|
|
32
|
+
import netCDF4
|
|
33
|
+
HAS_NETCDF4 = True
|
|
34
|
+
except ImportError:
|
|
35
|
+
HAS_NETCDF4 = False
|
|
36
|
+
|
|
31
37
|
import warnings
|
|
32
38
|
|
|
33
39
|
warnings.filterwarnings("ignore", message="More than one layer found", category=UserWarning)
|
|
34
40
|
warnings.filterwarnings("ignore", message="Dataset has no geotransform", category=UserWarning)
|
|
41
|
+
warnings.filterwarnings("ignore", message="invalid scale_factor or add_offset attribute", category=UserWarning)
|
|
35
42
|
|
|
36
|
-
__version__ = "0.
|
|
43
|
+
__version__ = "0.3.0"
|
|
37
44
|
|
|
38
45
|
AVAILABLE_COLORMAPS = [
|
|
39
46
|
"viridis", "inferno", "magma", "plasma",
|
|
@@ -41,12 +48,61 @@ AVAILABLE_COLORMAPS = [
|
|
|
41
48
|
"Spectral", "cubehelix", "tab10", "turbo"
|
|
42
49
|
]
|
|
43
50
|
|
|
51
|
+
# Terminals that don't natively support the iTerm2 OSC 1337 inline image
|
|
52
|
+
# protocol. Output for these is routed through chafa instead.
|
|
53
|
+
#
|
|
54
|
+
# Note: presence in this list does NOT mean "no images." Chafa auto-detects
|
|
55
|
+
# the terminal and picks the best output:
|
|
56
|
+
# - kitty (xterm-kitty) → real images via kitty graphics protocol
|
|
57
|
+
# - some others (e.g. foot, Ghostty) → may render real images via sixel
|
|
58
|
+
# or kitty protocol depending on chafa's detection
|
|
59
|
+
# - most others → Unicode block-art preview with 24-bit color
|
|
60
|
+
# (Terminal.app, VS Code, GNOME Terminal, Alacritty, Warp, etc.)
|
|
61
|
+
# Only terminals without chafa installed see no rendering at all.
|
|
62
|
+
|
|
44
63
|
_TERMINALS_WITHOUT_IMAGES = [
|
|
45
|
-
|
|
46
|
-
'
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
'
|
|
64
|
+
# macOS
|
|
65
|
+
'Apple_Terminal', # $TERM_PROGRAM for Terminal.app
|
|
66
|
+
|
|
67
|
+
# kitty (renders real images via chafa → kitty graphics protocol)
|
|
68
|
+
'xterm-kitty', # $TERM in kitty
|
|
69
|
+
|
|
70
|
+
# tmux / screen (TERM strings; $TMUX env var also signals tmux)
|
|
71
|
+
'screen', 'screen-256color',
|
|
72
|
+
'tmux', 'tmux-256color',
|
|
73
|
+
|
|
74
|
+
# Editors / IDE terminals
|
|
75
|
+
'vscode', # $TERM_PROGRAM in VS Code integrated terminal
|
|
76
|
+
|
|
77
|
+
# Cross-platform terminals known not to support OSC 1337
|
|
78
|
+
'alacritty',
|
|
79
|
+
'foot', # supports sixel → chafa renders real images
|
|
80
|
+
'ghostty', 'xterm-ghostty',
|
|
81
|
+
'WarpTerminal', # $TERM_PROGRAM in Warp
|
|
82
|
+
'Hyper', # $TERM_PROGRAM in Hyper
|
|
83
|
+
|
|
84
|
+
# Generic / legacy
|
|
85
|
+
'unknown',
|
|
86
|
+
'cygwin',
|
|
87
|
+
'rxvt', 'rxvt-unicode', 'rxvt-unicode-256color',
|
|
88
|
+
'st-256color', # suckless st
|
|
89
|
+
|
|
90
|
+
# Linux desktop terminals (most are VTE-based, no OSC 1337)
|
|
91
|
+
'gnome-terminal',
|
|
92
|
+
'xfce4-terminal',
|
|
93
|
+
'lxterminal',
|
|
94
|
+
'terminator',
|
|
95
|
+
'tilix',
|
|
96
|
+
'sakura',
|
|
97
|
+
'terminology',
|
|
98
|
+
'guake',
|
|
99
|
+
'tilda',
|
|
100
|
+
'deepin-terminal',
|
|
101
|
+
'eterm',
|
|
102
|
+
|
|
103
|
+
# Windows
|
|
104
|
+
'putty',
|
|
105
|
+
'Windows Terminal',
|
|
50
106
|
]
|
|
51
107
|
|
|
52
108
|
def detect_terminal() -> dict[str, str]:
|
|
@@ -135,41 +191,51 @@ def show_inline_image(image_array: np.ndarray, display_scale = None, is_vector:
|
|
|
135
191
|
|
|
136
192
|
if _TERMINAL_SUPPORTS_IMAGES:
|
|
137
193
|
sys.stdout.write(f"\033]1337;File=inline=1;width={width_pct}%:{encoded}\a\n")
|
|
138
|
-
else:
|
|
194
|
+
else:
|
|
139
195
|
if is_chafa_available():
|
|
196
|
+
# Inside tmux, force chafa to use block-art symbols instead of
|
|
197
|
+
# graphics protocols. Tmux mangles kitty graphics and sixel
|
|
198
|
+
# protocols, producing dot-character garbage on screen. Block-art
|
|
199
|
+
# passes through tmux reliably on every outer terminal.
|
|
200
|
+
chafa_args = ["chafa", "-"]
|
|
201
|
+
if os.environ.get("TMUX"):
|
|
202
|
+
chafa_args = ["chafa", "-f", "symbols", "-"]
|
|
140
203
|
chafa_output = subprocess.check_output(
|
|
141
|
-
|
|
204
|
+
chafa_args,
|
|
142
205
|
input=image_bytes
|
|
143
206
|
).decode()
|
|
144
207
|
|
|
145
208
|
sys.stdout.write(f"\n{chafa_output}\a\n")
|
|
209
|
+
|
|
146
210
|
else:
|
|
147
211
|
sys.stdout.write(f"[INFO] Use supported terminal or install 'chafa' for ascii art fallback. Detected: {_TERMINAL_INFO}\n")
|
|
148
212
|
|
|
149
213
|
sys.stdout.flush()
|
|
150
214
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
"""Attempt inline image display. No fallbacks, no detection.
|
|
215
|
+
def show_image_auto(img: np.ndarray, display_scale=None, is_vector: bool = False) -> None:
|
|
216
|
+
"""Render an image inline, with chafa fallback for non-iTerm2 terminals.
|
|
154
217
|
|
|
155
|
-
|
|
156
|
-
|
|
218
|
+
Cascade:
|
|
219
|
+
1. If terminal supports OSC 1337 → emit iTerm2 inline image sequence.
|
|
220
|
+
2. Else if chafa is installed → pipe through chafa (which auto-detects
|
|
221
|
+
and emits the terminal's native graphics protocol or block-art).
|
|
222
|
+
3. Else → print an info message suggesting chafa installation.
|
|
157
223
|
|
|
224
|
+
The branching happens inside show_inline_image(); this wrapper handles
|
|
225
|
+
status messaging and exception safety.
|
|
158
226
|
"""
|
|
159
|
-
if os.environ.get("TMUX"):
|
|
160
|
-
print("[WARN] Inside tmux — inline images won't display even with allow-passthrough on (known iTerm2/tmux issue). Use a plain terminal tab.")
|
|
161
|
-
return
|
|
162
|
-
|
|
163
227
|
try:
|
|
164
228
|
show_inline_image(img, display_scale, is_vector)
|
|
165
|
-
|
|
229
|
+
if _TERMINAL_SUPPORTS_IMAGES:
|
|
230
|
+
print("[VIEW] Inline render complete")
|
|
231
|
+
elif is_chafa_available():
|
|
232
|
+
print("[VIEW] Inline render complete via chafa")
|
|
233
|
+
# If neither path applies, show_inline_image already printed the info message
|
|
166
234
|
except Exception as e:
|
|
167
|
-
|
|
168
|
-
print(f"[ERROR] Failed to encode image: {e}")
|
|
235
|
+
print(f"[ERROR] Failed to render image: {e}")
|
|
169
236
|
import traceback
|
|
170
237
|
traceback.print_exc()
|
|
171
238
|
|
|
172
|
-
|
|
173
239
|
def resize_to_terminal(img: np.ndarray) -> tuple[np.ndarray, float]:
|
|
174
240
|
"""Resize image to fit terminal window (approx 8x16 pixel cells)."""
|
|
175
241
|
cols, rows = shutil.get_terminal_size((100, 40))
|
|
@@ -196,7 +262,6 @@ def load_csv_to_df(path: str) -> pd.DataFrame:
|
|
|
196
262
|
print(f"[ERROR] Failed to read CSV: {e}")
|
|
197
263
|
return pd.DataFrame()
|
|
198
264
|
|
|
199
|
-
|
|
200
265
|
# =============================================================
|
|
201
266
|
# Preview
|
|
202
267
|
# =============================================================
|
|
@@ -577,6 +642,177 @@ def render_simple_image(filepath: str, args) -> None:
|
|
|
577
642
|
except Exception as e:
|
|
578
643
|
print(f"[ERROR] Failed to load image: {e}")
|
|
579
644
|
|
|
645
|
+
def render_netcdf_via_netcdf4(path, args):
|
|
646
|
+
"""Read a NetCDF file via netCDF4 (bypassing GDAL). Handles hierarchical
|
|
647
|
+
groups and hyperspectral cubes where GDAL aborts or interprets axes wrong.
|
|
648
|
+
"""
|
|
649
|
+
if not HAS_NETCDF4:
|
|
650
|
+
print("[ERROR] netCDF4 not installed. Install with:")
|
|
651
|
+
print(" pip install netCDF4")
|
|
652
|
+
print(" or: pip install viewinline[netcdf]")
|
|
653
|
+
return
|
|
654
|
+
|
|
655
|
+
try:
|
|
656
|
+
nc = netCDF4.Dataset(path)
|
|
657
|
+
except Exception as e:
|
|
658
|
+
print(f"[ERROR] Could not open NetCDF file: {e}")
|
|
659
|
+
return
|
|
660
|
+
|
|
661
|
+
# Recursively collect (path, variable) pairs across all groups
|
|
662
|
+
def collect_vars(group, prefix=""):
|
|
663
|
+
out = []
|
|
664
|
+
for name, var in group.variables.items():
|
|
665
|
+
full_name = f"{prefix}{name}"
|
|
666
|
+
out.append((full_name, var))
|
|
667
|
+
for sub_name, sub in group.groups.items():
|
|
668
|
+
out.extend(collect_vars(sub, f"{prefix}{sub_name}/"))
|
|
669
|
+
return out
|
|
670
|
+
|
|
671
|
+
all_vars = collect_vars(nc)
|
|
672
|
+
|
|
673
|
+
if not all_vars:
|
|
674
|
+
print("[ERROR] No variables found in file.")
|
|
675
|
+
nc.close()
|
|
676
|
+
return
|
|
677
|
+
|
|
678
|
+
# If no --subset, list all variables and exit
|
|
679
|
+
if not args.subset:
|
|
680
|
+
print(f"Found {len(all_vars)} variables in {os.path.basename(path)}:")
|
|
681
|
+
for i, (name, var) in enumerate(all_vars, 1):
|
|
682
|
+
shape_str = "x".join(str(s) for s in var.shape)
|
|
683
|
+
print(f" [{i}] {name} ({shape_str}, {var.dtype})")
|
|
684
|
+
print(f"\nUse --subset <N> to display a specific variable.")
|
|
685
|
+
nc.close()
|
|
686
|
+
return
|
|
687
|
+
|
|
688
|
+
# Validate --subset
|
|
689
|
+
if args.subset < 1 or args.subset > len(all_vars):
|
|
690
|
+
print(f"[ERROR] --subset must be between 1 and {len(all_vars)}")
|
|
691
|
+
nc.close()
|
|
692
|
+
return
|
|
693
|
+
|
|
694
|
+
var_name, var = all_vars[args.subset - 1]
|
|
695
|
+
print(f"[INFO] Displaying variable {args.subset}: {var_name}")
|
|
696
|
+
print(f"[DATA] Shape: {var.shape} dtype: {var.dtype} dims: {var.dimensions}")
|
|
697
|
+
|
|
698
|
+
# Detect dimensionality and read the right slice
|
|
699
|
+
if var.ndim == 2:
|
|
700
|
+
data = np.asarray(var[:, :], dtype=np.float64)
|
|
701
|
+
slice_info = "2D variable"
|
|
702
|
+
|
|
703
|
+
elif var.ndim == 3:
|
|
704
|
+
spatial_dims = {'lat', 'lon', 'latitude', 'longitude', 'y', 'x'}
|
|
705
|
+
|
|
706
|
+
spectral_axis = None
|
|
707
|
+
|
|
708
|
+
# 1. User override via --reduce
|
|
709
|
+
if args.reduce_dim is not None:
|
|
710
|
+
if args.reduce_dim in var.dimensions:
|
|
711
|
+
spectral_axis = list(var.dimensions).index(args.reduce_dim)
|
|
712
|
+
print(f"[INFO] Using user-specified --reduce '{args.reduce_dim}'")
|
|
713
|
+
else:
|
|
714
|
+
print(f"[ERROR] --reduce '{args.reduce_dim}' is not a dimension of this variable.")
|
|
715
|
+
print(f"[INFO] Available dimensions: {list(var.dimensions)}")
|
|
716
|
+
nc.close()
|
|
717
|
+
return
|
|
718
|
+
|
|
719
|
+
# 2. Standard convention: reduce along the non-spatial dim
|
|
720
|
+
if spectral_axis is None:
|
|
721
|
+
has_standard_spatial = any(d in spatial_dims for d in var.dimensions)
|
|
722
|
+
if has_standard_spatial:
|
|
723
|
+
for i, dim_name in enumerate(var.dimensions):
|
|
724
|
+
if dim_name not in spatial_dims:
|
|
725
|
+
spectral_axis = i
|
|
726
|
+
break
|
|
727
|
+
|
|
728
|
+
# 3. Fallback heuristic: smallest dim is typically the band axis
|
|
729
|
+
if spectral_axis is None:
|
|
730
|
+
sizes = [(i, var.shape[i]) for i in range(3)]
|
|
731
|
+
spectral_axis = min(sizes, key=lambda x: x[1])[0]
|
|
732
|
+
print(f"[INFO] Non-standard dimensions detected: {list(var.dimensions)}")
|
|
733
|
+
print(f"[INFO] Reducing along '{var.dimensions[spectral_axis]}' (size {var.shape[spectral_axis]}, assumed band/spectral axis)")
|
|
734
|
+
print(f"[INFO] If this is not correct, use --reduce DIM_NAME to override.")
|
|
735
|
+
|
|
736
|
+
# Slice along chosen axis
|
|
737
|
+
band_count = var.shape[spectral_axis]
|
|
738
|
+
band_num = args.band if args.band is not None else 1
|
|
739
|
+
band_idx = max(0, min(band_num - 1, band_count - 1))
|
|
740
|
+
slicer = [slice(None)] * 3
|
|
741
|
+
slicer[spectral_axis] = band_idx
|
|
742
|
+
data = np.asarray(var[tuple(slicer)], dtype=np.float64)
|
|
743
|
+
slice_info = f"slice along axis {spectral_axis} ({var.dimensions[spectral_axis]}), band {band_idx + 1} of {band_count}"
|
|
744
|
+
|
|
745
|
+
else:
|
|
746
|
+
print(f"[ERROR] viewinline only supports 2D or 3D variables. This one is {var.ndim}D.")
|
|
747
|
+
nc.close()
|
|
748
|
+
return
|
|
749
|
+
|
|
750
|
+
print(f"[DATA] {slice_info}")
|
|
751
|
+
# Apply fill value
|
|
752
|
+
fill = getattr(var, '_FillValue', None)
|
|
753
|
+
if fill is not None:
|
|
754
|
+
data = np.where(data == fill, np.nan, data)
|
|
755
|
+
# Flip vertically if data is stored south-to-north so north appears at top.
|
|
756
|
+
# Determine which dims remain after slicing — for the 2D result, figure out
|
|
757
|
+
# which axis (0 or 1) corresponds to latitude, and check that dim's coord values.
|
|
758
|
+
if var.ndim == 2:
|
|
759
|
+
remaining_dims = list(var.dimensions)
|
|
760
|
+
elif var.ndim == 3:
|
|
761
|
+
if spectral_axis is not None:
|
|
762
|
+
remaining_dims = [d for i, d in enumerate(var.dimensions) if i != spectral_axis]
|
|
763
|
+
else:
|
|
764
|
+
remaining_dims = list(var.dimensions[1:]) # axis 0 was reduced
|
|
765
|
+
else:
|
|
766
|
+
remaining_dims = []
|
|
767
|
+
lat_names = {'lat', 'latitude', 'y'}
|
|
768
|
+
for axis_in_2d, dim_name in enumerate(remaining_dims):
|
|
769
|
+
if dim_name in lat_names and dim_name in nc.variables:
|
|
770
|
+
lat_vals = nc[dim_name][:]
|
|
771
|
+
if len(lat_vals) > 1 and lat_vals[0] < lat_vals[-1]:
|
|
772
|
+
data = np.flip(data, axis=axis_in_2d)
|
|
773
|
+
print(f"[INFO] Flipped along '{dim_name}' for display (data stored south-to-north).")
|
|
774
|
+
break
|
|
775
|
+
nc.close()
|
|
776
|
+
|
|
777
|
+
# Normalize and display
|
|
778
|
+
band_u8 = normalize_to_uint8(data, vmin=args.vmin, vmax=args.vmax,
|
|
779
|
+
nodata=args.nodata)
|
|
780
|
+
|
|
781
|
+
if args.colormap:
|
|
782
|
+
cmap = colormaps[args.colormap]
|
|
783
|
+
colored = cmap(band_u8 / 255.0)
|
|
784
|
+
img = (colored[:, :, :3] * 255).astype(np.uint8)
|
|
785
|
+
print(f"[INFO] Applying colormap: {args.colormap}")
|
|
786
|
+
else:
|
|
787
|
+
img = np.stack([band_u8] * 3, axis=-1)
|
|
788
|
+
print("[INFO] Displaying grayscale")
|
|
789
|
+
|
|
790
|
+
# Resize to terminal
|
|
791
|
+
H, W = img.shape[:2]
|
|
792
|
+
if args.display:
|
|
793
|
+
new_w, new_h = max(1, int(W * args.display)), max(1, int(H * args.display))
|
|
794
|
+
img = np.array(Image.fromarray(img).resize((new_w, new_h), Image.BILINEAR))
|
|
795
|
+
print(f"[VIEW] Manual resize ×{args.display:.2f} → {new_w}×{new_h}px")
|
|
796
|
+
# else:
|
|
797
|
+
# img, scale = resize_to_terminal(img)
|
|
798
|
+
# print(f"[VIEW] Rendered image size → {img.shape[1]}×{img.shape[0]}px (size={scale:.2f})")
|
|
799
|
+
else:
|
|
800
|
+
max_dim = 2000
|
|
801
|
+
if max(img.shape[:2]) > max_dim:
|
|
802
|
+
scale = max_dim / max(img.shape[:2])
|
|
803
|
+
new_w = int(img.shape[1] * scale)
|
|
804
|
+
new_h = int(img.shape[0] * scale)
|
|
805
|
+
img = np.array(Image.fromarray(img).resize((new_w, new_h), Image.BILINEAR))
|
|
806
|
+
print(f"[VIEW] Downsampled from {W}×{H}px to {new_w}×{new_h}px (scale={scale:.2f})")
|
|
807
|
+
print(f"[INFO] Use --display 1 for full resolution.")
|
|
808
|
+
else:
|
|
809
|
+
# (matches the width_pct logic in show_inline_image)
|
|
810
|
+
display_pct = args.display if args.display is not None else 0.33
|
|
811
|
+
|
|
812
|
+
print(f"[VIEW] Rendered image size → {img.shape[1]}×{img.shape[0]}px (size={display_pct:.2f})")
|
|
813
|
+
|
|
814
|
+
show_image_auto(img, getattr(args, "display", None), is_vector=False)
|
|
815
|
+
|
|
580
816
|
def render_raster(paths: list[str], args) -> None:
|
|
581
817
|
try:
|
|
582
818
|
import rasterio
|
|
@@ -589,10 +825,16 @@ def render_raster(paths: list[str], args) -> None:
|
|
|
589
825
|
|
|
590
826
|
if len(paths) == 1:
|
|
591
827
|
path = paths[0]
|
|
828
|
+
|
|
829
|
+
if path.lower().endswith('.nc'):
|
|
830
|
+
render_netcdf_via_netcdf4(path, args)
|
|
831
|
+
return
|
|
592
832
|
|
|
593
833
|
# Handle NetCDF/HDF with subdatasets
|
|
834
|
+
|
|
594
835
|
if path.lower().endswith(('.nc', '.hdf', '.hdf5', '.h5')):
|
|
595
836
|
try:
|
|
837
|
+
|
|
596
838
|
with rasterio.open(path) as src:
|
|
597
839
|
subdatasets = src.subdatasets
|
|
598
840
|
|
|
@@ -665,7 +907,7 @@ def render_raster(paths: list[str], args) -> None:
|
|
|
665
907
|
resampling=rasterio.enums.Resampling.bilinear
|
|
666
908
|
)
|
|
667
909
|
|
|
668
|
-
print(f"[
|
|
910
|
+
print(f"[VIEW] Downsampled for preview → {out_w}×{out_h}px (scale={scale:.3f})")
|
|
669
911
|
else:
|
|
670
912
|
data = ds.read()
|
|
671
913
|
|
|
@@ -677,8 +919,11 @@ def render_raster(paths: list[str], args) -> None:
|
|
|
677
919
|
print(f"[INFO] Multi-band raster detected ({band_count} bands)")
|
|
678
920
|
|
|
679
921
|
# MULTI BAND RGB (skip for NetCDF - treat as slices/timesteps, not RGB)
|
|
680
|
-
if band_count >= 3 and not paths[0].lower().endswith('.nc'):
|
|
681
|
-
|
|
922
|
+
# if band_count >= 3 and not paths[0].lower().endswith('.nc'):
|
|
923
|
+
# Auto-composite to RGB only when user didn't explicitly ask for a single band
|
|
924
|
+
# user_specified_band = args.band is not None and args.band != 1
|
|
925
|
+
user_specified_band = args.band is not None
|
|
926
|
+
if band_count >= 3 and not paths[0].lower().endswith('.nc') and not user_specified_band:
|
|
682
927
|
|
|
683
928
|
if getattr(args, "rgb", None):
|
|
684
929
|
try:
|
|
@@ -707,7 +952,8 @@ def render_raster(paths: list[str], args) -> None:
|
|
|
707
952
|
# SINGLE BAND
|
|
708
953
|
else:
|
|
709
954
|
|
|
710
|
-
|
|
955
|
+
band_num = args.band if args.band is not None else 1
|
|
956
|
+
band_idx = max(0, min(band_num - 1, band_count - 1))
|
|
711
957
|
# print(f"[INFO] Displaying band {band_idx + 1} of {band_count}")
|
|
712
958
|
raw_band = data[band_idx].astype(float)
|
|
713
959
|
|
|
@@ -784,7 +1030,7 @@ def render_raster(paths: list[str], args) -> None:
|
|
|
784
1030
|
print(f"[ERROR] Cannot display this variable.")
|
|
785
1031
|
print("[INFO] viewinline only supports 2D or 3D NetCDF variables")
|
|
786
1032
|
else:
|
|
787
|
-
print(f"[ERROR]
|
|
1033
|
+
print(f"[ERROR] Inline render failed: {e}")
|
|
788
1034
|
|
|
789
1035
|
|
|
790
1036
|
def render_gallery(folder: str, grid: str = "4x4", display_scale=None, is_vector=False) -> None:
|
|
@@ -889,13 +1135,6 @@ def render_vector(path, args):
|
|
|
889
1135
|
except Exception as e:
|
|
890
1136
|
print(f"[WARN] Could not list layers: {e}")
|
|
891
1137
|
|
|
892
|
-
# try:
|
|
893
|
-
# gdf = gpd.read_file(path, layer=getattr(args, "layer", None))
|
|
894
|
-
# print(f"[DATA] Vector loaded: {os.path.basename(path)} ({len(gdf)} features)")
|
|
895
|
-
# except Exception as e:
|
|
896
|
-
# print(f"[ERROR] Failed to read vector: {e}")
|
|
897
|
-
# return
|
|
898
|
-
|
|
899
1138
|
try:
|
|
900
1139
|
# Use read_parquet for parquet/geoparquet files
|
|
901
1140
|
if path.lower().endswith(('.parquet', '.geoparquet')):
|
|
@@ -1237,7 +1476,7 @@ def main() -> None:
|
|
|
1237
1476
|
|
|
1238
1477
|
# Raster options
|
|
1239
1478
|
parser.add_argument(
|
|
1240
|
-
"--band", type=int, default=
|
|
1479
|
+
"--band", type=int, default=None,
|
|
1241
1480
|
help="Band number to display (single raster case), or slice number for NetCDF."
|
|
1242
1481
|
)
|
|
1243
1482
|
parser.add_argument(
|
|
@@ -1253,6 +1492,10 @@ def main() -> None:
|
|
|
1253
1492
|
"--rgb", nargs=3, type=int, metavar=('R', 'G', 'B'), default=None,
|
|
1254
1493
|
help="Three band numbers for RGB display (e.g., --rgb 4 3 2). Overrides default 1 2 3."
|
|
1255
1494
|
)
|
|
1495
|
+
parser.add_argument(
|
|
1496
|
+
"--rgbfiles", nargs=3, type=str, metavar=('R', 'G', 'B'),
|
|
1497
|
+
help="Three single-band rasters for RGB composite (e.g., --rgbfiles R.tif G.tif B.tif). Can also provide as positional arguments without the flag."
|
|
1498
|
+
)
|
|
1256
1499
|
parser.add_argument(
|
|
1257
1500
|
"--vmin", type=float, default=None,
|
|
1258
1501
|
help="Minimum pixel value for raster display scaling."
|
|
@@ -1274,8 +1517,9 @@ def main() -> None:
|
|
|
1274
1517
|
help="Variable index for NetCDF files (e.g. --subset 1)."
|
|
1275
1518
|
)
|
|
1276
1519
|
parser.add_argument(
|
|
1277
|
-
"--
|
|
1278
|
-
|
|
1520
|
+
"--reduce", dest="reduce_dim", type=str, default=None,
|
|
1521
|
+
metavar="DIM_NAME",
|
|
1522
|
+
help="For 3D NetCDF variables, specify which dimension to use as the band axis (auto-detected if omitted)."
|
|
1279
1523
|
)
|
|
1280
1524
|
|
|
1281
1525
|
# CSV options
|
|
@@ -1359,7 +1603,7 @@ def main() -> None:
|
|
|
1359
1603
|
parser.add_argument(
|
|
1360
1604
|
"--table", action="store_true",
|
|
1361
1605
|
help="Display vector/parquet file as tabular data instead of rendering geometry."
|
|
1362
|
-
)
|
|
1606
|
+
)
|
|
1363
1607
|
|
|
1364
1608
|
parser.add_argument("--version", action="version", version=f"%(prog)s {__version__}")
|
|
1365
1609
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|