Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix several issues in geoviewer #418

Merged
merged 19 commits into from
Jan 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't forget, for development-specific dependencies, you have to mirror them manually in "setup.cfg [options.extras_require]" (there's a reminder in the dev-environment file and another one in a PR template when you open one, but it's been a while here!)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I missed that info in the dev-environment.yml file !

- 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])
Loading