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

Cropreporter adding pam #1217

Merged
merged 12 commits into from
Aug 10, 2023
3 changes: 2 additions & 1 deletion docs/analyze_yii.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ The photosynthesis subpackage is dependent on a PSII_Data instance file structur
**returns** YII DataArray, YII histograms

- **Parameters:**
- ps_da - Photosynthesis xarray DataArray (either ojip_dark or ojip_light). Can either have a pair of frames F0,Fm or pair(s) of Fp,Fmp.
- ps_da - Photosynthesis xarray DataArray (either `ojip_dark`, `ojip_light`, `pam_dark`, or `pam_light`).
Can either have a pair of frames F0, Fm or pair(s) of Fp, Fmp.
- labeled_mask - Labeled mask of objects (32-bit).
- n_labels - Total number expected individual objects (default = 1).
- auto_fm - Automatically calculate the frame with maximum fluorescence per label, otherwise use a fixed frame for all labels (default = False).
Expand Down
4 changes: 2 additions & 2 deletions docs/photosynthesis_read_cropreporter.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ with labeled frames.
- **Context:**
- Reads in binary image files to be processed and does so using the metadata contained within a corresponding .INF
file.
- Measurements from dark-adapted plant state are stored in the attribute `ojip_dark`. Frames F0 and Fm are
- Measurements from dark-adapted plant state are stored in the attribute `ojip_dark` or `pam_dark`, depending on the measurement protocol. Frames F0 and Fm are
labeled according to the metadata in .INF. The default measurement label is 't0'.
- Measurements from light-adapted plant state are stored in the attribute `ojip_light`. Frames Fp and Fmp are
- Measurements from light-adapted plant state are stored in the attribute `ojip_light` or `pam_light`, depending on the measurement protocol. Frames Fp and Fmp are
labeled according to the metadata in .INF. The default measurement label is 't1'.
- Measurements from chlorophyll fluorescence are stored in the attribute `chlorophyll` and include a dark frame
(Fdark) and chlorophyll fluorescence frame (Chl).
Expand Down
9 changes: 4 additions & 5 deletions plantcv/plantcv/analyze/yii.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
"""Fluorescence Analysis (Fv/Fm parameter)."""

import os
import numpy as np
import pandas as pd
Expand All @@ -14,7 +13,7 @@ def yii(ps_da, labeled_mask, n_labels=1, auto_fm=False, measurement_labels=None,
Calculate and analyze PSII efficiency estimates from fluorescence image data.

Inputs:
ps_da = Photosynthesis xarray DataArray (either ojip_dark or ojip_light)
ps_da = Photosynthesis xarray DataArray (either ojip_dark, ojip_light, pam_dark, or pam_light)
labeled_mask = Labeled mask of objects (32-bit).
n_labels = Total number expected individual objects (default = 1).
auto_fm = Automatically calculate the frame with maximum fluorescence per label, otherwise
Expand Down Expand Up @@ -47,7 +46,7 @@ def yii(ps_da, labeled_mask, n_labels=1, auto_fm=False, measurement_labels=None,
var = ps_da.name.lower()

# Validate that var is a supported type
if var not in ['ojip_dark', 'ojip_light']:
if var not in ['ojip_dark', 'ojip_light', 'pam_dark', 'pam_light']:
fatal_error(f"Unsupported DataArray type: {var}")

# Make an zeroed array of the same shape as the input DataArray
Expand Down Expand Up @@ -78,12 +77,12 @@ def yii(ps_da, labeled_mask, n_labels=1, auto_fm=False, measurement_labels=None,
yii_masked = ps_da.astype('float').where(submask > 0, other=np.nan)

# Dark-adapted datasets (Fv/Fm)
if var == 'ojip_dark':
if var in ['ojip_dark', 'pam_dark']:
# Calculate Fv/Fm
yii_lbl = (yii_masked.sel(frame_label='Fm') - yii_masked.sel(frame_label='F0')) / yii_masked.sel(frame_label='Fm')

# Light-adapted datasets (Fq'/Fm')
if var == 'ojip_light':
if var in ['ojip_light', 'pam_light']:
# Calculate Fq'/Fm'
yii_lbl = yii_masked.groupby('measurement', squeeze=False).map(_calc_yii)

Expand Down
77 changes: 75 additions & 2 deletions plantcv/plantcv/photosynthesis/read_cropreporter.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
# Read in fluorescence images from a .DAT file

"""Read in fluorescence images from a .DAT file."""
import os
import numpy as np
import xarray as xr
Expand Down Expand Up @@ -46,6 +45,12 @@ def read_cropreporter(filename):
# Light-adapted measurements
_process_psl_data(ps=ps, metadata=metadata_dict)

# Dark-adapted PAM measurements
_process_pmd_data(ps=ps, metadata=metadata_dict)

# Light-adapted PAM Pmeasurements
_process_pml_data(ps=ps, metadata=metadata_dict)

# Chlorophyll fluorescence data
_process_chl_data(ps=ps, metadata=metadata_dict)

Expand Down Expand Up @@ -153,6 +158,74 @@ def _process_psl_data(ps, metadata):
col_wrap=int(np.ceil(ps.ojip_light.frame_label.size / 4)))


def _process_pmd_data(ps, metadata):
"""
Create an xarray DataArray for a PMD dataset.

Inputs:
ps = PSII_data instance
metadata = INF file metadata dictionary

:param ps: plantcv.plantcv.classes.PSII_data
:param metadata: dict
"""
bin_filepath = _dat_filepath(dataset="PMD", datapath=ps.datapath, filename=ps.filename)
if os.path.exists(bin_filepath):
img_cube, frame_labels, frame_nums = _read_dat_file(dataset="PMD", filename=bin_filepath,
height=int(metadata["ImageRows"]),
width=int(metadata["ImageCols"]))
frame_labels = ["Fdark", "F0", "Fm", "Fs"]
pmd = xr.DataArray(
data=img_cube[..., None],
dims=('x', 'y', 'frame_label', 'measurement'),
coords={'frame_label': frame_labels,
'frame_num': ('frame_label', frame_nums),
'measurement': ['t0']},
name='pam_dark'
)
pmd.attrs["long_name"] = "pam dark-adapted measurements"
ps.add_data(pmd)

_debug(visual=ps.pam_dark.squeeze('measurement', drop=True),
filename=os.path.join(params.debug_outdir, f"{str(params.device)}_PMD-frames.png"),
col='frame_label',
col_wrap=int(np.ceil(ps.pam_dark.frame_label.size / 4)))


def _process_pml_data(ps, metadata):
"""
Create an xarray DataArray for a PML dataset.

Inputs:
ps = PSII_data instance
metadata = INF file metadata dictionary

:param ps: plantcv.plantcv.classes.PSII_data
:param metadata: dict
"""
bin_filepath = _dat_filepath(dataset="PML", datapath=ps.datapath, filename=ps.filename)
if os.path.exists(bin_filepath):
img_cube, frame_labels, frame_nums = _read_dat_file(dataset="PML", filename=bin_filepath,
height=int(metadata["ImageRows"]),
width=int(metadata["ImageCols"]))
frame_labels = ["Flight", "Fp", "Fmp", "Fs"]
pml = xr.DataArray(
data=img_cube[..., None],
dims=('x', 'y', 'frame_label', 'measurement'),
coords={'frame_label': frame_labels,
'frame_num': ('frame_label', frame_nums),
'measurement': ['t0']},
name='pam_light'
)
pml.attrs["long_name"] = "pam light-adapted measurements"
ps.add_data(pml)

_debug(visual=ps.pam_light.squeeze('measurement', drop=True),
filename=os.path.join(params.debug_outdir, f"{str(params.device)}_PML-frames.png"),
col='frame_label',
col_wrap=int(np.ceil(ps.pam_light.frame_label.size / 4)))


def _process_chl_data(ps, metadata):
"""
Create an xarray DataArray for a CHL dataset.
Expand Down
2 changes: 1 addition & 1 deletion tests/plantcv/photosynthesis/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def __init__(self):
self.datadir_v653 = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "..", "testdata","cropreporter_v653")
# CropReporter data file
self.cropreporter = os.path.join(self.datadir_v441, "PSII_HDR_020321_WT_TOP_1.INF")
self.cropreporter_v653_ojip = os.path.join(self.datadir_v653, "HDR_OJIP_dark_light.INF")
self.cropreporter_v653 = os.path.join(self.datadir_v653, "HDR_dark_light.INF")
# Mask image
self.ps_mask = os.path.join(self.datadir_v441, "PSII_HDR_020321_WT_TOP_1_mask.png")

Expand Down
11 changes: 8 additions & 3 deletions tests/plantcv/photosynthesis/test_read_cropreporter.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
"""Tests for plantcv.photosynthesis.read_cropreporter."""
import os
import shutil
from plantcv.plantcv import PSII_data
Expand All @@ -10,16 +11,16 @@ def test_read_cropreporter(photosynthesis_test_data, tmpdir):
assert isinstance(ps, PSII_data) and ps.ojip_dark.shape == (966, 1296, 21, 1)

# Check with different naming conventioned of phenovation
ps = read_cropreporter(filename=photosynthesis_test_data.cropreporter_v653_ojip)
ps = read_cropreporter(filename=photosynthesis_test_data.cropreporter_v653)
assert isinstance(ps, PSII_data) and ps.ojip_dark.shape == (1500, 2048, 7, 1)
# check labels
true_labels = ['Fdark', 'F0', 'PSD2', 'PSD3', 'Fm', 'PSD5', 'PSD6']
assert all([a == b for a, b in zip(ps.ojip_dark.coords['frame_label'].to_dict()['data'], true_labels)])

# Create dataset with only 3 frames
cache_dir = os.path.join(tmpdir, "sub")
shutil.copytree(os.path.dirname(photosynthesis_test_data.cropreporter_v653_ojip), cache_dir)
inffilename = os.path.join(cache_dir, photosynthesis_test_data.cropreporter_v653_ojip.split(os.sep)[-1])
shutil.copytree(os.path.dirname(photosynthesis_test_data.cropreporter_v653), cache_dir)
inffilename = os.path.join(cache_dir, photosynthesis_test_data.cropreporter_v653.split(os.sep)[-1])

# Modify the inf file
metadata_dict = {}
Expand All @@ -41,6 +42,10 @@ def test_read_cropreporter(photosynthesis_test_data, tmpdir):
assert all([a == b for a, b in zip(ps.ojip_dark.coords['frame_label'].to_dict()['data'], true_dark_labels)])
true_light_labels = ['Flight', 'Fp', 'Fmp', 'PSL3', 'PSL4', 'PSL5', 'PSL6']
assert all([a == b for a, b in zip(ps.ojip_light.coords['frame_label'].to_dict()['data'], true_light_labels)])
true_pam_dark_labels = ["Fdark", "F0", "Fm", "Fs"]
assert all([a == b for a, b in zip(ps.pam_dark.coords['frame_label'].to_dict()['data'], true_pam_dark_labels)])
true_pam_light_labels = ["Flight", "Fp", "Fmp", "Fs"]
assert all([a == b for a, b in zip(ps.pam_light.coords['frame_label'].to_dict()['data'], true_pam_light_labels)])


def test_read_cropreporter_spc_only(photosynthesis_test_data, tmpdir):
Expand Down
Loading