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

Reading TIF file #3

Merged
merged 75 commits into from
May 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
75 commits
Select commit Hold shift + click to select a range
3473b67
Reading TIF file
dhiraj-ms Feb 27, 2024
5c6c1b9
Updated code to read TIF file
dhiraj-ms Feb 27, 2024
72d05f0
Final Updated Code to Read TIF File
dhiraj-ms Feb 27, 2024
ca2c314
Final Version of TIF Reading Function
dhiraj-ms Feb 27, 2024
b536f48
Merge branch 'main' into tifreading
HaleySchuhl Mar 4, 2024
fa61c33
Merge branch 'main' into tifreading
nfahlgren Mar 4, 2024
40f0753
move python file into geospatial subpackage
HaleySchuhl Mar 7, 2024
370d521
add readimage function to init
HaleySchuhl Mar 7, 2024
b43f02d
start implementing Spectral_data object
HaleySchuhl Mar 7, 2024
1cbcd71
fill in unknowns of Spectral_data
HaleySchuhl Mar 7, 2024
9136003
update function name to read_geotif
HaleySchuhl Mar 11, 2024
84a12d9
Update read_geotif.py
HaleySchuhl Mar 11, 2024
fdc1d7c
implement wavelength dictionaries and pseudo_rgb
HaleySchuhl Mar 14, 2024
1e8f780
missing import of numpy
HaleySchuhl Mar 14, 2024
807a045
update the way bands are parsed
HaleySchuhl Mar 21, 2024
412ce84
Update read_geotif.py
HaleySchuhl Mar 21, 2024
8e18d49
remove print statement
HaleySchuhl Mar 21, 2024
e0837de
reshape datacube and add temporary plotting statement
HaleySchuhl Mar 21, 2024
c8032ca
for consistency rename filepath to filename
HaleySchuhl Mar 21, 2024
b4073d1
band band fatal error
HaleySchuhl Mar 26, 2024
d554525
Create 615.tif
HaleySchuhl Mar 26, 2024
bb3aa5b
Update conftest.py
HaleySchuhl Mar 26, 2024
4cc4c75
Create test_geospatial_read_geotif.py
HaleySchuhl Mar 26, 2024
12f99da
fix iteration for bad band input check
HaleySchuhl Mar 27, 2024
82a9d4f
Update test_geospatial_read_geotif.py
HaleySchuhl Mar 27, 2024
0099991
add bad input test
HaleySchuhl Mar 27, 2024
625ae59
deepsource fixes
HaleySchuhl Mar 28, 2024
bbf2822
minor syntax for more deepsource things
HaleySchuhl Mar 28, 2024
3edb7da
update pseudoRGB creation method
HaleySchuhl Apr 11, 2024
4c12de0
Merge branch 'main' into tifreading
nfahlgren Apr 11, 2024
2373e13
cast float32
HaleySchuhl Apr 11, 2024
ce75c91
Merge branch 'tifreading' of https://github.com/dhiraj-ms/plantcv-geo…
HaleySchuhl Apr 11, 2024
1f36507
integrate functionality for list of numbers
HaleySchuhl Apr 11, 2024
fd9384a
troubleshooting, will remove extra code soon
HaleySchuhl Apr 12, 2024
5790b22
remove pseudo rgb code
HaleySchuhl Apr 15, 2024
a79ee6f
Update read_geotif.md
HaleySchuhl Apr 15, 2024
24c5ac0
remove redundant code
HaleySchuhl Apr 15, 2024
23b2343
add in _debug and background masking
HaleySchuhl Apr 17, 2024
4b114de
trailing whitespace
HaleySchuhl Apr 18, 2024
ae6f63d
update masking method
HaleySchuhl Apr 18, 2024
0bc944b
Merge branch 'main' into tifreading
HaleySchuhl Apr 18, 2024
62416f0
add _find_closest_unsorted function
HaleySchuhl Apr 22, 2024
6cbef23
update param name in function call _find_closest_unssorted
HaleySchuhl Apr 22, 2024
0ffe132
check datatype and handle differently
HaleySchuhl Apr 22, 2024
f2d21b3
Update read_geotif.py
HaleySchuhl Apr 22, 2024
e3ae8fa
rearrage function and change handling of rgb tifs
HaleySchuhl Apr 23, 2024
8a71f54
try adding rescaling for printing purposes
HaleySchuhl Apr 23, 2024
2a971fb
Update test_geospatial_read_geotif.py
HaleySchuhl Apr 23, 2024
607a590
add doc image of rgb visualization
HaleySchuhl Apr 23, 2024
d9f16fb
add screenshot to doc page
HaleySchuhl Apr 23, 2024
2bf8373
Update read_geotif.py
HaleySchuhl Apr 23, 2024
cf20d49
Update conftest.py
HaleySchuhl Apr 23, 2024
9540ccf
Create rgb.tif
HaleySchuhl Apr 23, 2024
4f3919e
deepsource fixes
HaleySchuhl Apr 23, 2024
21e326f
rescale so that debug=print also works witht multispec
HaleySchuhl Apr 23, 2024
7c3d092
add multispec example to docs
HaleySchuhl Apr 23, 2024
d7370ae
mask background with 0s to avoid warnings
HaleySchuhl Apr 24, 2024
551a940
syntax fix and clarifications on doc page
HaleySchuhl Apr 24, 2024
b4d5ff4
Update changelog.md
HaleySchuhl Apr 24, 2024
1fd3400
remove unused import
HaleySchuhl Apr 24, 2024
e76c91d
small docs clarification about returned object type
HaleySchuhl Apr 25, 2024
69d375e
typo read_geotif
HaleySchuhl Apr 25, 2024
1588cce
Update read_geotif.md
HaleySchuhl Apr 26, 2024
a7a4251
reorder RGB getting returned not just the thing getting debugged
HaleySchuhl Apr 26, 2024
eb91f30
Removed trailing whitespaces
nfahlgren May 18, 2024
ac50d2e
Remove unnecessary else
nfahlgren May 18, 2024
049b178
in works on dict directly, remove keys list
nfahlgren May 18, 2024
eef5c48
Update docstrings to numpy format
nfahlgren May 18, 2024
b509160
Create helper function to simplify logical block
nfahlgren May 18, 2024
7d98fb9
Refactor to treat all images as Spectral_data
nfahlgren May 18, 2024
c373af8
Update test assertion due to data type change
nfahlgren May 18, 2024
03ec934
Change default band order to BGR
nfahlgren May 18, 2024
42ed1b7
Revise docs
nfahlgren May 18, 2024
bc4e22c
Restore versioning removed in PR
nfahlgren May 18, 2024
17eb3ed
Remove unused enumeration
nfahlgren May 18, 2024
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
4 changes: 4 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## Changelog

All notable changes to this project will be documented below.

#### geospatial.read_geotif

* v0.1dev: spectral = **geospatial.read_geotif**(*filename, bands="B,G,R"*)
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/documentation_images/rgb.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
22 changes: 16 additions & 6 deletions docs/read_geotif.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,35 @@

Read in data (from tif format, most likely georeferenced image data).

**plantcv.geospatial.read_geotif**(*filename, mode="rgb"*)
**plantcv.geospatial.read_geotif**(*filename, bands="B,G,R"*)

**returns** [PlantCV Spectral_data](https://plantcv.readthedocs.io/en/latest/Spectral_data/) object instance
**returns** [PlantCV Spectral_data](https://plantcv.readthedocs.io/en/latest/Spectral_data/) object instance.

- **Parameters:**
- filename - Filepath to .tif data
- mode - Mode for geotif reading
- filename - Path of the TIF image file.
- bands - Comma separated string representing the order of image bands (e.g., `bands="B,G,R"`), or a list of wavelengths (e.g., `bands=[650, 560, 480]`)
- Supported symbols and their default wavelengths:
- R (red) = 650nm
- G (green) = 560nm
- B (blue) = 480nm
- RE (rededge) = 717nm
- N or NIR (near infrared) = 842nm

- **Example use:**
- below


```python
import plantcv.plantcv as pcv
import plantcv.geospatial as geo

# Read geotif in
marker = geo.read_geotif(filename="./data/example_img.tif", mode="rgb")
ortho1 = geo.read_geotif(filename="./data/example_img.tif", bands="b,g,r,RE,NIR")
ortho2 = geo.read_geotif(filename="./data/example_rgb_img.tif", bands="B,G,R")

```

![Screenshot](documentation_images/multispec_pseudo_rgb.png)

![Screenshot](documentation_images/rgb.png)

**Source Code:** [Here](https://github.com/danforthcenter/plantcv-geospatial/blob/main/plantcv/geospatial/read_geotif.py)
6 changes: 6 additions & 0 deletions plantcv/geospatial/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
from importlib.metadata import version
from plantcv.geospatial.read_geotif import read_geotif

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

__all__ = [
"read_geotif"
]
124 changes: 124 additions & 0 deletions plantcv/geospatial/read_geotif.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
# Read georeferenced TIF files to Spectral Image data

import os
import cv2
import rasterio
import numpy as np
from plantcv.plantcv import params
from plantcv.plantcv import fatal_error
from plantcv.plantcv._debug import _debug
from plantcv.plantcv.classes import Spectral_data


def _find_closest_unsorted(array, target):
"""Find closest index of array item with smallest distance from the target.

Parameters
----------
array : numpy.ndarray
Array of wavelength labels
target : int, float
Target value

Returns
-------
int
Index of closest value to the target
"""
return min(range(len(array)), key=lambda i: abs(array[i]-target))


def _parse_bands(bands):
"""Parse bands.

Parameters
----------
bands : str
Comma separated string listing the order of bands

Returns
-------
list
List of bands
"""
# Numeric list of bands
band_list = []

# Parse bands
band_strs = bands.split(",")

# Default values for symbolic bands
default_wavelengths = {"R": 650, "G": 560, "B": 480, "RE": 717, "N": 842, "NIR": 842}

for band in band_strs:
# Check if the band symbols are supported
if band.upper() not in default_wavelengths:
fatal_error(f"Currently {band} is not supported, instead provide list of wavelengths in order.")
# Append the default wavelength for each band
band_list.append(default_wavelengths[band.upper()])

return band_list


def read_geotif(filename, bands="B,G,R"):
"""Read Georeferenced TIF image from file.

Parameters
----------
filename : str
Path of the TIF image file.
bands : str, list, optional
Comma separated string listing the order of bands or a list of wavelengths, by default "B,G,R"

Returns
-------
plantcv.plantcv.classes.Spectral_data
Orthomosaic image data
"""
img = rasterio.open(filename)
img_data = img.read()
img_data = img_data.transpose(1, 2, 0) # reshape such that z-dimension is last
height = img.height
width = img.width
wavelengths = {}

# Parse bands if input is a string
if isinstance(bands, str):
bands = _parse_bands(bands)

# Create a dictionary of wavelengths and their indices
for i, wl in enumerate(bands):
wavelengths[wl] = i

# Mask negative background values
img_data[img_data < 0.] = 0
# Make a list of wavelength keys
wl_keys = wavelengths.keys()
# Find which bands to use for red, green, and blue bands of the pseudo_rgb image
id_red = _find_closest_unsorted(array=np.array([float(i) for i in wl_keys]), target=630)
id_green = _find_closest_unsorted(array=np.array([float(i) for i in wl_keys]), target=540)
id_blue = _find_closest_unsorted(array=np.array([float(i) for i in wl_keys]), target=480)
# Stack bands together, BGR since plot_image will convert BGR2RGB automatically
pseudo_rgb = cv2.merge((img_data[:, :, [id_blue]],
img_data[:, :, [id_green]],
img_data[:, :, [id_red]]))
# Gamma correction
if pseudo_rgb.dtype != 'uint8':
pseudo_rgb = pseudo_rgb.astype('float32') ** (1 / 2.2)
pseudo_rgb = pseudo_rgb * 255
pseudo_rgb = pseudo_rgb.astype('uint8')

# Make a Spectral_data instance before calculating a pseudo-rgb
spectral_array = Spectral_data(array_data=img_data,
max_wavelength=None,
min_wavelength=None,
max_value=np.max(img_data), min_value=np.min(img_data),
d_type=img.dtypes[0],
wavelength_dict=wavelengths, samples=int(width),
lines=int(height), interleave=None,
wavelength_units="nm", array_type="datacube",
pseudo_rgb=pseudo_rgb, filename=filename, default_bands=[480, 540, 630])

_debug(visual=pseudo_rgb, filename=os.path.join(params.debug_outdir, f"{params.device}_pseudo_rgb.png"))

return spectral_array
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ dynamic = ["version"]
dependencies = [
"plantcv",
"matplotlib",
"rasterio",

]
requires-python = ">=3.6"
authors = [
Expand Down
19 changes: 19 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,23 @@
import os
import pytest
import matplotlib

# Disable plotting
matplotlib.use("Template")

class TestData:
def __init__(self):
"""Initialize simple variables."""
# Test data directory
self.datadir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "testdata")
# Flat image directory
self.snapshot_dir = os.path.join(self.datadir, "snapshot_dir")
# multispectral image
self.cropped_tif = os.path.join(self.datadir, "615.tif")
# rgb image
self.rgb_tif = os.path.join(self.datadir, "rgb.tif")

@pytest.fixture(scope="session")
def test_data():
"""Test data object for the main PlantCV-geospatial package."""
return TestData()
25 changes: 25 additions & 0 deletions tests/test_geospatial_read_geotif.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
"""Tests for geospatial.readd_geotif."""

import pytest
from plantcv.geospatial import read_geotif


def test_geospatial_read_geotif(test_data):
"""Test for plantcv-geospatial."""
# read in small 5-band tif image
img = read_geotif(filename=test_data.cropped_tif, bands="B,G,R,RE,N")
assert img.array_data.shape[2] == 5


def test_geospatial_read_geotif_rgb(test_data):
"""Test for plantcv-geospatial."""
# read in small 5-band tif image
img = read_geotif(filename=test_data.rgb_tif, bands="R,G,B")
assert img.array_data.shape[2] == 4


def test_geospatial_read_geotif_bad_input(test_data):
"""Test for plantcv-geospatial."""
# read in small 5-band tif image
with pytest.raises(RuntimeError):
_ = read_geotif(filename=test_data.cropped_tif, bands="p")
Binary file added tests/testdata/615.tif
Binary file not shown.
Binary file added tests/testdata/rgb.tif
Binary file not shown.