Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 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
5 changes: 5 additions & 0 deletions docs/updating.md
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,11 @@ pages for more details on the input and output variable types.
* post v3.7: index_array = **plantcv.hyperspectral.extract_index**(*array, index="NDVI", distance=20*)
* post v3.8: DEPRECATED see plantcv.spectral_index

#### plantcv.hyperspectral.write_data

* pre v4.0: NA
* post v4.0: **plantcv.hyperspectral.write_data**(*filename, spectral_data*)

#### plantcv.image_add

* pre v3.0dev2: device, added_img = **plantcv.image_add**(*img1, img2, device, debug=None*)
Expand Down
42 changes: 42 additions & 0 deletions docs/write_data.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
## Write data

Write a hyperspectral image in ENVI format to the specified file.
It creates a text header file with extension .hdr and a binary file with
extension .raw. This function only supports Band-interleaved-by-line (BIL)
interleave.

**plantcv.hyperspectral.write_data**(*filename, spectral_data*):

**returns** Bool

- **Parameters:**
- filename- desired name of the hyperspectral image file. The extensions are ignored and .hdr and .raw are used.
- spectral_data- Hyperspectral data object

- **Context:**
- Used to save a modified hyperspectral image

- **Example use:**

```python
from plantcv import plantcv as pcv

modified_spectral = pcv.Spectral_data(array_data=modified_array_data,
max_wavelength=list(source_spectral.wavelength_dict.keys())[-1],
min_wavelength=list(source_spectral.wavelength_dict.keys())[0],
max_value=float(np.amax(modified_array_data)),
min_value=float(np.amin(modified_array_data)),
d_type=modified_array_data.dtype,
wavelength_dict=source_spectral.wavelength_dict,
samples=modified_array_data.shape[1],
lines=modified_array_data.shape[0], interleave='bil',
wavelength_units=source_spectral.wavelength_units,
array_type="datacube",
pseudo_rgb=None,
filename=source_spectral.filename,
default_bands=None)

pcv.hyperspectral.write_data('test-hyperspectral', modified_spectral)
```

**Source Code:** [Here](https://github.com/danforthcenter/plantcv/blob/master/plantcv/plantcv/hyperspectral/write_data.py)
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ nav:
- 'Spectral Index': spectral_index.md
- 'Extract wavelength': extract_wavelength.md
- 'Spectral data objects': Spectral_data.md
- 'Write data': write_data.md
- 'Image Add': image_add.md
- 'Image Subtract': image_subtract.md
- 'Image Fusion': image_fusion.md
Expand Down
4 changes: 3 additions & 1 deletion plantcv/plantcv/hyperspectral/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
from plantcv.plantcv.hyperspectral.calibrate import calibrate
from plantcv.plantcv.hyperspectral._avg_reflectance import _avg_reflectance
from plantcv.plantcv.hyperspectral._inverse_covariance import _inverse_covariance
from plantcv.plantcv.hyperspectral.write_data import write_data

# add new functions to end of lists
__all__ = ["read_data", "_find_closest", "analyze_spectral", "analyze_index", "calibrate",
"_make_pseudo_rgb", "extract_wavelength", "_avg_reflectance", "_inverse_covariance"]
"_make_pseudo_rgb", "extract_wavelength", "_avg_reflectance", "_inverse_covariance",
"write_data"]
48 changes: 48 additions & 0 deletions plantcv/plantcv/hyperspectral/write_data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import os
import numpy as np

from plantcv.plantcv import _version

__version__ = _version.get_versions()['version']

def write_data(filename, spectral_data):
"""Write hyperspectral image data to a file.
Inputs:
filename = Name of file to write
spectral_data = Hyperspectral data object

Returns:

:param filename: str
:param spectral_data: __main__.Spectral_data
"""

filename = os.path.splitext(filename)[0]

# create header
lines, samples, bands = spectral_data.array_data.shape
dtype_dict = {'B': "1", 'h': "2", 'i': "3", 'f': "4", 'd': "5", 'F': "6",
'D': "9", 'H': "12", 'I': "13", 'l': "14", 'L': "15"}
wavelenghths = list(spectral_data.wavelength_dict.keys())
with open(filename+'.hdr', mode='w') as f:
f.write('ENVI\n')
f.write(f'; this file was created using PlantCV version {__version__}\n')
f.write(f'; original file: {spectral_data.filename}\n')
f.write('interleave = bil\n')
f.write(f'samples = {samples}\n')
f.write(f'lines = {lines}\n')
f.write(f'bands = {bands}\n')
f.write(f'data type = {dtype_dict[spectral_data.array_data.dtype.char]}\n')
f.write(f'wavelength units = {spectral_data.wavelength_units}\n')
f.write(f'default bands ={spectral_data.default_bands}\n')
f.write('wavelength = {\n')
for wl in wavelenghths[:-1]:
f.write(f'{wl},\n')
f.write(f'{wavelenghths[-1]}\n')
f.write('}')

# create raw binary file containing the hyperspectral array values
with open(filename+'.raw', mode='w+b') as f:
f.write(spectral_data.array_data.transpose(0,2,1).tobytes(order='C'))

return True
52 changes: 52 additions & 0 deletions tests/plantcv/hyperspectral/test_write_data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import os
import pytest
import numpy as np

from plantcv.plantcv import Spectral_data
from plantcv.plantcv.hyperspectral import read_data
from plantcv.plantcv.hyperspectral import write_data

def test_write_data_default(tmpdir):
"""Test for PlantCV."""
rng = np.random.default_rng()

# Create a test tmp directory
cache_dir = tmpdir.mkdir("cache")

lines = 32
samples = 32
bands = 5

# Create random array data in the interval [0-65535] and wavelengths in the
# interval [400-1000)
rand_array = rng.integers(0, 65535, size=(lines, samples, bands), dtype=np.uint16, endpoint=True)
rand_wavelengths = np.sort(600.0*rng.random(size=bands) + 400.0)
# Create dictionary of wavelengths
wavelength_dict = {}
for j, wavelength in enumerate(rand_wavelengths):
wavelength_dict.update({wavelength: float(j)})

# Create spectral data object
rand_spectral_array = Spectral_data(array_data=rand_array,
max_wavelength=rand_wavelengths[-1],
min_wavelength=rand_wavelengths[0],
max_value=float(np.amax(rand_array)),
min_value=float(np.amin(rand_array)),
d_type=rand_array.dtype,
wavelength_dict=wavelength_dict,
samples=samples,
lines=lines,
interleave='bil',
wavelength_units='nm',
array_type="datacube",
pseudo_rgb=None,
filename='random_hyperspectral_test',
default_bands=None)


filename = os.path.join(cache_dir, 'plantcv_hyperspectral_write_data.raw')
out = write_data(filename=filename, spectral_data=rand_spectral_array)

# Read written hyperspectral image
array_data = read_data(filename=filename)
assert np.shape(array_data.array_data) == (lines, samples, bands)
Copy link
Contributor

Choose a reason for hiding this comment

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

Would going beyond checking if the dimensions are the same to see if some of the values are the same be helpful? Or is just checking that they're the same shape usually sufficient

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That is a good question. We could check some values or also verify that it's the exact array. I think it depends on what can go wrong. Since the array is vectorized and written as a binary array, in order to have the correct dimensions the data type and the interleave format should be correct.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ok cool, would just a simple assert np.array_equal(array_data.array_data, rand_spectral_array) be good or do you think that would take up more time than it's worth?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think both ways are good options

Copy link
Contributor

Choose a reason for hiding this comment

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

Would going beyond checking if the dimensions are the same to see if some of the values are the same be helpful? Or is just checking that they're the same shape usually sufficient