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

Add napari assisted warp #28

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
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
6 changes: 5 additions & 1 deletion docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@

All notable changes to this project will be documented below.

#### geospatial.napari_save_points

* v0.1dev: **geospatial.napari_save_points**(*images, num_points, outdir="./", bands="R,G,B", block=True, show=True*)

#### geospatial.points_to_geojson

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

#### geospatial.read_geotif

Expand Down
41 changes: 41 additions & 0 deletions docs/napari_save_points.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
## Save Napari clicked points

Uses Napari to open a list of images one at a time. The user then clicks a defined number of points on each image, which are then saved to a text file with the same name as the image. To be used upstream of warp for geospatial images that are not georeferenced.

**geospatial.napari_save_points**(*images, num_points, outdir="./", bands="R,G,B", block=True, show=True*)

- **Parameters:**
- images - Either a list of image paths or a directory where images are stored. Each image will be opened in sequence.
- num_points - Integer of expected number of clicked points. For warping downstream, this should be minimally 4.
- outdir - Directory to save output text files with saved points for each image. Defaults to "./".
- bands - If input images are geotifs, this is the required band order for `plantcv.geospatial.read_geotif`. Not required if image type is not geotif.
- block - True/False whether to stop function from advancing before user closes the viewer window.
- show - True/False whether to show the Napari viewer. Necessary for tests.

- **Outputs:**
- redo_list - list of images to redo because the number of clicks did not match num_points. Useful if the user makes a mistake and would like to retry for a subset of images.

- **Context:**
- Saved files can be used to warp all images to a specified reference image to bring all images to the same frame. This is useful for geospatial images that have not been georeferenced, as this approximates a version of what using ground control points achieves.

- **Example use:**
- below to click field corners


```python
import plantcv.geospatial as geo

# Make a list of images, can also be a directory
img_list = ["./image1.jpg", "./image2.jpg", "./image3.jpg", "./image4.jpg"]

# Opens a Napari window with a points layer for each image
# User should click reference points equal to num_points and then close the window, prompting the next one to open

redo_list = geo.napari_save_points(img_list, num_points=4, outdir="./")

# Text files with points are saved to outdir
# Output is a list of any images the user should redo

```

**Source Code:** [Here](https://github.com/danforthcenter/plantcv-geospatial/blob/main/plantcv/geospatial/napari_save_points.py)
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ nav:
- Transform coordinate points: transform_points.md
- Transform coordinate polygons: transform_polygons.md
- Save clicked points as geojson: points_to_geojson.md
- Save Napari points to text file: napari_save_points.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 @@ -3,6 +3,7 @@
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
from plantcv.geospatial.napari_save_points import napari_save_points

# Auto versioning
__version__ = version("plantcv-geospatial")
Expand All @@ -11,5 +12,6 @@
"read_geotif",
"transform_points",
"transform_polygons",
"points_to_geojson"
"points_to_geojson",
"napari_save_points"
]
71 changes: 71 additions & 0 deletions plantcv/geospatial/napari_save_points.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# Function to save out clicked points collected in napari

import plantcv.plantcv as pcv
from plantcv.plantcv import warn
from plantcv.geospatial import read_geotif
import napari
import os


def napari_save_points(images, num_points, outdir="./", bands="R,G,B", show_window=True):
"""Opens a set of images one at a time in a Napari window, waits for users
to click points and then saves those points to a file with the same name as the image.

Args:
images (list or str): Either a list of image paths or a directory name.
num_points (int): Number of points expected. If number of clicks received is different,
the image path is added to redo_list and returned.
outdir (str, optional): Directory to save text files with points. Defaults to "./".
bands (str, optional): Band list if input images are geotifs. Defaults to "R,G,B".
block (boolean): Whether to stop the function from advancing before user closes the viewer window.
show (boolean): Whether to show the Napari viewer. Necessary for tests.

Returns:
list: List of images to be redone due to a different number of clicked points than expected.
"""
# store debug
debug = pcv.params.debug

pcv.params.debug = None
# Store images with mistakes to a new list
redo_list = []

image_paths = images
if not isinstance(images, list):
image_paths = [os.path.join(images, i) for i in os.listdir(images)]
# Loop over each image
for image_path in image_paths:
# Load the current image
img_type = (image_path.split("/")[-1]).split(".")[-1]
if img_type == "tif":
geo_image = read_geotif(image_path, bands=bands)
image = geo_image.pseudo_rgb
else:
image, _, _ = pcv.readimage(image_path)
# Save image name for output file
img_name = (image_path.split("/")[-1]).split(".")[:-1]

viewer = napari.Viewer(show=show_window)

# Add the image and points layer
viewer.add_image(image)
viewer.add_points(name="points")
if show_window:
viewer.show(block=True)

# Save file if correct number of points
if len(viewer.layers["points"].data) == num_points:
with open(os.path.join(outdir, img_name[0]+"_warp.txt"), "w") as output:
for i in viewer.layers["points"].data:
point = [i[1], i[0]]
output.write(str(point) + '\t')
else:
redo_list.append(image_path)
warn('Image ' + str(image_path) + ' collected incorrect number of points. ' +
'Added to redo list.')
# Close the viewer in case it wasn't shown
if not show_window:
viewer.close()
# Reset debug
pcv.params.debug = debug
return redo_list
24 changes: 24 additions & 0 deletions tests/test_geospatial_napari_save_points.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
"""Tests for geospatial.napari_save_points"""

import os
from plantcv.geospatial import napari_save_points, read_geotif
from plantcv.plantcv import print_image


def test_geospatial_napari_save_points(test_data, tmpdir):
"""Test for plantcv-geospatial."""
cache_dir = tmpdir.mkdir("cache")
images = [test_data.rgb_tif]
redo_list = napari_save_points(images, num_points=4, outdir=cache_dir, show_window=False)
assert len(redo_list) == 1


def test_geospatial_napari_save_points_output(test_data, tmpdir):
"""Test for plantcv-geospatial."""
cache_dir = tmpdir.mkdir("cache")
# test with a png by creating a temp version of the rgb tif in cache_dir
img = read_geotif(filename=test_data.rgb_tif, bands="R,G,B")
print_image(img.pseudo_rgb, os.path.join(cache_dir, "rgb.png"))
images = [os.path.join(cache_dir, "rgb.png")]
_ = napari_save_points(images, num_points=0, outdir=cache_dir, show_window=False)
assert os.path.exists(os.path.join(cache_dir, "rgb_warp.txt"))
Loading