Skip to content

Commit

Permalink
Merge pull request #23 from danforthcenter/22-clicks-to-shapefile
Browse files Browse the repository at this point in the history
Clicked points to shape file
  • Loading branch information
HaleySchuhl authored Nov 6, 2024
2 parents bc7a157 + b6aa5ef commit c9052db
Show file tree
Hide file tree
Showing 8 changed files with 141 additions and 1 deletion.
4 changes: 4 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

All notable changes to this project will be documented below.

#### geospatial.points_to_geojson

* v0.1dev: **geosptial.points_to_geojson**(*img, viewer, out_path*)

#### geospatial.read_geotif

* v0.1dev: spectral = **geospatial.read_geotif**(*filename, bands="B,G,R", cropto=None*)
Expand Down
Binary file added docs/documentation_images/napari_clicks.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
36 changes: 36 additions & 0 deletions docs/points_to_geojson.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
## Output clicked points as a geojson shapefile

Using a Napari or PlantCV-annotate viewer object with clicked points, output a shapefile.

**geospatial.points_to_geojson**(*img, viewer, out_path*)

- **Parameters:**
- img - Spectral image object, likely read in with [`geo.read_geotif`](read_geotif.md)
- viewer - Napari viewer class object, possible created with PlantCV-Annotate.
- out_path - Path to save the geojson shapefile. Must be ".geojson" file type.

- **Context:**
- Saved points can be used downstream for generating circular ROIs or circles for use with rasterstats.
- **Example use:**
- below to click plant locations


```python
import plantcv.geospatial as geo
import plantcv.annotate as an

# Read geotif in
img = geo.read_geotif("../read_geotif/rgb.tif", bands="R,G,B")
viewer = an.napari_open(img=img.pseudo_rgb)
viewer.add_points()

# A napari viewer window will pop up, use the points function to add clicks
```
```python
# In a separate cell, save the output after clicking:
geo.points_to_geojson(img, viewer, out_path="./points_example.geojson")
```

![Screenshot](documentation_images/napari_clicks.png)

**Source Code:** [Here](https://github.com/danforthcenter/plantcv-geospatial/blob/main/plantcv/geospatial/points_to_geojson.py)
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ nav:
- Read Geo-tif Data: read_geotif.md
- Transform coordinate points: transform_points.md
- Transform coordinate polygons: transform_polygons.md
- Save clicked points as geojson: points_to_geojson.md
markdown_extensions:
- toc:
permalink: True
Expand Down
4 changes: 3 additions & 1 deletion plantcv/geospatial/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@
from plantcv.geospatial.read_geotif import read_geotif
from plantcv.geospatial.transform_points import transform_points
from plantcv.geospatial.transform_polygons import transform_polygons
from plantcv.geospatial.points_to_geojson import points_to_geojson

# Auto versioning
__version__ = version("plantcv-geospatial")

__all__ = [
"read_geotif",
"transform_points",
"transform_polygons"
"transform_polygons",
"points_to_geojson"
]
41 changes: 41 additions & 0 deletions plantcv/geospatial/points_to_geojson.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Save clicked points from Napari or PlantCV-annotate as a geojson points file.

import geojson
import rasterio
from plantcv.plantcv import fatal_error


def points_to_geojson(img, viewer, out_path):
"""Use clicks from a Napari or plantcv-annotate viewer to output a geojson shapefile.
Parameters
----------
img : PlantCV spectral_data class object
The image used for clicking on points, should be from read_geotif.
viewer: Napari viewer class object or plantcv-annotate Points class object.
The viewer used to make the clicks.
out_path : str
Path to save to shapefile. Must have "geojson" file extension
"""
# Napari output, points must be reversed
if hasattr(viewer, 'layers'):
points = [(img.metadata["transform"]*reversed(i)) for i in viewer.layers["Points"].data]
# Annotate output
elif hasattr(viewer, 'coords'):
points = [(img.metadata["transform"]*i) for i in viewer.coords['default']]
else:
fatal_error("Viewer class type not recognized. Currently, Napari and PlantCV-annotate viewers supported.")
features = [geojson.Feature(geometry=geojson.Point((lon, lat))) for lon, lat in points]
feature_collection = geojson.FeatureCollection(features)
# Make sure the coordinate system is the same as the original image
feature_collection['crs'] = {
"type": "name",
"properties": {
"name": rasterio.crs.CRS.to_string(img.metadata["crs"])
}
}
if ".geojson" in out_path:
with open(out_path, 'w') as f:
geojson.dump(feature_collection, f)
else:
fatal_error("File type not supported.")
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ dependencies = [
"rasterio",
"fiona",
"shapely",
"geojson",
"napari",
]
requires-python = ">=3.6"
authors = [
Expand Down
54 changes: 54 additions & 0 deletions tests/test_geospatial_points_to_geojson.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
"""Tests for geospatial.points_to_geojson"""

import pytest
import os
import napari
from plantcv.geospatial import read_geotif
from plantcv.geospatial import points_to_geojson

# Set up fake class just for testing the annotate output
# Don't want to have to have annotate as a dependency

class FakePoints:
def __init__(self):
self.coords = {}

def test_geospatial_points_to_geojson_napari(test_data, tmpdir):
"""Test for plantcv-geospatial."""
cache_dir = tmpdir.mkdir("cache")
img = read_geotif(filename=test_data.rgb_tif, bands="R,G,B")
viewer = napari.Viewer(show=False)
viewer.add_image(img.pseudo_rgb)
viewer.add_points()
filename = os.path.join(cache_dir, 'test_out.geojson')
points_to_geojson(img, viewer, out_path=filename)
assert os.path.exists(filename)

def test_geospatial_points_to_geojson_an(test_data, tmpdir):
"""Test for plantcv-geospatial."""
cache_dir = tmpdir.mkdir("cache")
img = read_geotif(filename=test_data.rgb_tif, bands="R,G,B")
viewer = FakePoints()
viewer.coords["default"] = []
filename = os.path.join(cache_dir, 'test_out.geojson')
points_to_geojson(img, viewer, out_path=filename)
assert os.path.exists(filename)

def test_geospatial_points_to_geojson_badviewer(test_data, tmpdir):
"""Test for plantcv-geospatial."""
cache_dir = tmpdir.mkdir("cache")
img = read_geotif(filename=test_data.rgb_tif, bands="R,G,B")
viewer = []
filename = os.path.join(cache_dir, 'test_out.geojson')
with pytest.raises(RuntimeError):
points_to_geojson(img, viewer, out_path=filename)

def test_geospatial_points_to_geojson_badfilename(test_data, tmpdir):
"""Test for plantcv-geospatial."""
cache_dir = tmpdir.mkdir("cache")
img = read_geotif(filename=test_data.rgb_tif, bands="R,G,B")
viewer = FakePoints()
viewer.coords["default"] = []
filename = os.path.join(cache_dir, 'test_out.txt')
with pytest.raises(RuntimeError):
points_to_geojson(img, viewer, out_path=filename)

0 comments on commit c9052db

Please sign in to comment.