Skip to content

Commit

Permalink
Fix several issues in geoviewer (#418)
Browse files Browse the repository at this point in the history
  • Loading branch information
adehecq authored Jan 18, 2024
1 parent 920487d commit 27aba1b
Show file tree
Hide file tree
Showing 9 changed files with 118 additions and 21 deletions.
22 changes: 11 additions & 11 deletions bin/geoviewer.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
Geoviewer provides a command line tool for plotting raster and vector data.
TO DO:
- change so that only needed band is loaded
- include some options from imviewer: https://github.com/dshean/imview/blob/master/imview/imviewer.py
"""
from __future__ import annotations
Expand All @@ -19,7 +18,9 @@

def getparser() -> argparse.ArgumentParser:
# Set up description
parser = argparse.ArgumentParser(description="Visualisation tool for any image supported by GDAL.")
parser = argparse.ArgumentParser(
description="Visualisation tool for any image supported by GDAL. For single band plots (Single band rasters or with option -band) the image will be rendered as a pseudocolor image using the set or default colormap. For 3 or 4 band data, the image will be plotted as an RGB(A) image. For other band counts, an error will be raised and the option -band must be used."
)

# Positional arguments
parser.add_argument("filename", type=str, help="str, path to the image")
Expand All @@ -30,7 +31,7 @@ def getparser() -> argparse.ArgumentParser:
dest="cmap",
type=str,
default="default",
help="str, a matplotlib colormap string (default is from rcParams).",
help="str, a matplotlib colormap string (default is from rcParams). This parameter is ignored for multi-band rasters.",
)
parser.add_argument(
"-vmin",
Expand All @@ -39,7 +40,7 @@ def getparser() -> argparse.ArgumentParser:
default=None,
help=(
"float, the minimum value for colorscale, or can be expressed as a "
"percentile e.g. 5%% (default is calculated min value)."
"percentile e.g. 5%% (default is calculated min value). This parameter is ignored for multi-band rasters."
),
)
parser.add_argument(
Expand All @@ -49,20 +50,20 @@ def getparser() -> argparse.ArgumentParser:
default=None,
help=(
"float, the maximum value for colorscale, or can be expressed as a "
"percentile e.g. 95%% (default is calculated max value)."
"percentile e.g. 95%% (default is calculated max value). This parameter is ignored for multi-band rasters."
),
)
parser.add_argument(
"-band",
dest="band",
type=int,
default=None,
help="int, which band to display (start at 0) for multiband images (Default is 0).",
help="int, for multiband images, which band to display. Starts at 1. (Default is to load all bands and display as rasterio, i.e. asuming RGB(A) and with clipping outside [0-255] for int, [0-1] for float).",
)
parser.add_argument(
"-nocb",
dest="nocb",
help="If set, will not display a colorbar (Default is to display the colorbar).",
help="If set, will not display a colorbar (Default is to display the colorbar for single-band raster). This parameter is ignored for multi-band rasters.",
action="store_false",
)
parser.add_argument(
Expand Down Expand Up @@ -134,7 +135,7 @@ def main(test_args: Sequence[str] = None) -> None:
dfact = 1

# Read image
img = Raster(args.filename, downsample=dfact)
img = Raster(args.filename, downsample=dfact, indexes=args.band)

# Set no data value
if args.nodata == "default":
Expand All @@ -157,7 +158,7 @@ def main(test_args: Sequence[str] = None) -> None:
perc, _ = args.vmin.split("%")
try:
perc = float(perc)
vmin = np.percentile(img.data, perc)
vmin = np.percentile(img.data.compressed(), perc)
except ValueError: # Case no % sign
raise ValueError("vmin must be a float or percentage, currently set to %s" % args.vmin)

Expand All @@ -172,7 +173,7 @@ def main(test_args: Sequence[str] = None) -> None:
perc, _ = args.vmax.split("%")
try:
perc = float(perc)
vmax = np.percentile(img.data, perc)
vmax = np.percentile(img.data.compressed(), perc)
except ValueError: # Case no % sign
raise ValueError("vmax must be a float or percentage, currently set to %s" % args.vmax)

Expand Down Expand Up @@ -213,7 +214,6 @@ def main(test_args: Sequence[str] = None) -> None:
# plot
img.show(
ax=ax,
index=args.band,
cmap=cmap,
interpolation="nearest",
vmin=vmin,
Expand Down
1 change: 1 addition & 0 deletions dev-environment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ dependencies:
- sphinx-design
- sphinx-autodoc-typehints
- sphinxcontrib-programoutput
- sphinx-argparse
- autovizwidget
- graphviz
- myst-nb
Expand Down
14 changes: 14 additions & 0 deletions doc/source/cli.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
(cli)=
# Command Line Interface

This page lists all CLI functionalities of GeoUtils.
These commands can be run directly from a terminal, without having to launch a Python console.

## geoviewer.py

```{eval-rst}
.. argparse::
:filename: geoviewer.py
:func: getparser
:prog: geoviewer.py
```
1 change: 1 addition & 0 deletions doc/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
"sphinx.ext.intersphinx",
# "myst_parser", !! Not needed with myst_nb !! # Form of Markdown that works with sphinx, used a lot by the Sphinx Book Theme
"myst_nb", # MySt for rendering Jupyter notebook in documentation
"sphinxarg.ext", # To generate documentation for argparse tools
]

# For sphinx design to work properly
Expand Down
1 change: 1 addition & 0 deletions doc/source/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ analysis_examples/index
:maxdepth: 2
api
cli
background
```

Expand Down
4 changes: 4 additions & 0 deletions doc/source/quick_start.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,10 @@ rast_proximity_to_vec.show(cbar_title="Distance to glacier outline")
vect.show(rast_proximity_to_vec, fc="none")
```

```{tip}
To quickly visualize a raster directly from a terminal, without opening a Python console/notebook, check out our tool `geoviewer.py` in the {ref}`cli` documentation.
```

## Pythonic arithmetic and NumPy interface

All {class}`~geoutils.Raster` objects support Python arithmetic ({func}`+<operator.add>`, {func}`-<operator.sub>`, {func}`/<operator.truediv>`, {func}`//<operator.floordiv>`, {func}`*<operator.mul>`,
Expand Down
4 changes: 4 additions & 0 deletions geoutils/raster/raster.py
Original file line number Diff line number Diff line change
Expand Up @@ -2466,6 +2466,10 @@ def show(
if not self.is_loaded:
self.load()

# Set matplotlib interpolation to None by default, to avoid spreading gaps in plots
if "interpolation" not in kwargs.keys():
kwargs.update({"interpolation": "None"})

# Check if specific band selected, or take all
# rshow takes care of image dimensions
# if self.count=3 (4) => plotted as RGB(A)
Expand Down
1 change: 1 addition & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ doc =
sphinx-design
sphinx-autodoc-typehints
sphinxcontrib-programoutput
sphinx-argparse
autovizwidget
graphviz
myst-nb
Expand Down
91 changes: 81 additions & 10 deletions tests/test_geoviewer.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,10 @@
@pytest.mark.parametrize(
"option",
(
(),
("-cmap", "Reds"),
("-vmin", "-10", "-vmax", "10"),
("-vmin", "5%", "-vmax", "95%"),
("-band", "1"),
("-nocb",),
("-clabel", "Test"),
Expand All @@ -34,7 +36,7 @@
("-noresampl",),
),
) # type: ignore
def test_geoviewer_valid(capsys, monkeypatch, filename, option): # type: ignore
def test_geoviewer_valid_1band(capsys, monkeypatch, filename, option): # type: ignore
# To avoid having the plots popping up during execution
monkeypatch.setattr(plt, "show", lambda: None)

Expand All @@ -51,29 +53,98 @@ def test_geoviewer_valid(capsys, monkeypatch, filename, option): # type: ignore
assert output == ""

# Remove file if it was created
if option[0] == "-save":
if "-save" in option:
if os.path.exists("test.png"):
os.remove("test.png")


@pytest.mark.parametrize(
"filename", [gu.examples.get_path("everest_landsat_b4"), gu.examples.get_path("exploradores_aster_dem")]
) # type: ignore
@pytest.mark.parametrize(
"args",
(
(("-band", "0"), IndexError),
(("-band", "2"), IndexError),
(("-cmap", "Lols"), ValueError),
(("-cmap", "Lols"), ValueError),
(("-vmin", "lol"), ValueError),
(("-vmin", "lol2"), ValueError),
(("-vmax", "105%"), ValueError),
(("-figsize", "blabla"), ValueError),
(("-dpi", "300.5"), ValueError),
(("-nodata", "lol"), ValueError),
(("-nodata", "1e40"), ValueError),
),
) # type: ignore
def test_geoviewer_invalid_1band(capsys, monkeypatch, filename, args): # type: ignore
# To avoid having the plots popping up during execution
monkeypatch.setattr(plt, "show", lambda: None)

# To not get exception when testing generic functions such as --help
option, error = args
with pytest.raises(error):
geoviewer.main([filename, *option])


@pytest.mark.parametrize("filename", [gu.examples.get_path("everest_landsat_rgb")]) # type: ignore
@pytest.mark.parametrize(
"option",
(
("-cmap", "Lols"),
("-vmin", "lol"),
("-vmin", "lol2"),
("-figsize", "blabla"),
("-dpi", "300.5"),
("-nodata", "lol"),
(),
("-band", "1"),
("-band", "2"),
("-band", "3"),
("-clabel", "Test"),
("-figsize", "8,8"),
("-max_size", "1000"),
("-save", "test.png"),
("-dpi", "300"),
("-nodata", "99"),
("-noresampl",),
),
) # type: ignore
def test_geoviewer_valid_3band(capsys, monkeypatch, filename, option): # type: ignore
# To avoid having the plots popping up during execution
monkeypatch.setattr(plt, "show", lambda: None)

# To not get exception when testing generic functions such as --help
try:
geoviewer.main([filename, *option])
except SystemExit:
pass

# Capture error output (not stdout, just a plot)
output = capsys.readouterr().err

# No error should be raised
assert output == ""

# Remove file if it was created
if "-save" in option:
if os.path.exists("test.png"):
os.remove("test.png")


@pytest.mark.parametrize(
"filename",
[
gu.examples.get_path("everest_landsat_rgb"),
],
) # type: ignore
@pytest.mark.parametrize(
"args",
(
(("-band", "0"), IndexError),
(("-band", "4"), IndexError),
(("-nodata", "1e40"), ValueError),
),
) # type: ignore
def test_geoviewer_invalid(capsys, monkeypatch, filename, option): # type: ignore
def test_geoviewer_invalid_3band(capsys, monkeypatch, filename, args): # type: ignore
# To avoid having the plots popping up during execution
monkeypatch.setattr(plt, "show", lambda: None)

# To not get exception when testing generic functions such as --help
with pytest.raises(ValueError):
option, error = args
with pytest.raises(error):
geoviewer.main([filename, *option])

0 comments on commit 27aba1b

Please sign in to comment.