Skip to content
Merged
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: 3 additions & 3 deletions docs/photosynthesis_read_cropreporter.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ PSII_data instance containing [xarray DataArrays](http://xarray.pydata.org/en/st
- **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` or `pam_dark`, depending on the measurement protocol. Frames F0 and Fm are
- Measurements from dark-adapted plant state are stored in the attribute `psd` (ojip) 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` or `pam_light`, depending on the measurement protocol. Frames Fp and Fmp are
- Measurements from light-adapted plant state are stored in the attribute `psl` (ojip) 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'.
- Time-resolved PAM fluorescence measurements are stored in the attribute `pam_time`. Frames F0, Fm, Fp, Fmp, F0pp, and Fmpp are
labeled according to the metadata in .INF, with measurement labels starting at 't0' (e.g. t0, t1, t2, ...).
Expand Down Expand Up @@ -48,7 +48,7 @@ ps = pcv.photosynthesis.read_cropreporter(filename="PSII_HDR_020321_WT_TOP_1.INF
ps

# to see the frames you imported use xarray plot methods e.g.
ps.ojip_dark.plot(col='frame_label', col_wrap=4)
ps.psd.load().plot(col='frame_label', col_wrap=4)

```

Expand Down
5 changes: 3 additions & 2 deletions plantcv/plantcv/classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,9 @@ def __init__(self, metadata: dict = None):
self.aph = None
self.chl = None
self.clr = None
self.ojip_dark = None
self.ojip_light = None
self.ojip = None
self.psd = None
self.psl = None
self.pam_dark = None
self.pam_light = None
self.pam_time = None
Expand Down
217 changes: 112 additions & 105 deletions plantcv/plantcv/photosynthesis/read_cropreporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,114 @@ def _load(self):
self._color = img_as_ubyte(img_cube[:, :, [2, 1, 0]])


class PSD:
"""OJIP dark-adapted measurements dataset. Stores the file path at init; image data is loaded on first access."""

def __init__(self, filepath, height, width, metadata):
"""Initialize PSD dataset with file path and image dimensions."""
self._filepath = filepath
self._height = height
self._width = width
self._metadata = metadata

def __bool__(self):
"""The existence of the PSD class is true."""
return True

def __repr__(self):
"""String representation of the PSD dataset, indicating whether the data has been loaded."""
return f"PSD(filepath={self._filepath!r})"

def load(self):
"""Load the OJIP dark-adapted measurements from the .DAT file."""
img_cube, frame_labels, frame_nums = _read_dat_file(
dataset="PSD",
filename=str(self._filepath),
height=self._height,
width=self._width,
)
# If not all frames are saved the order is fixed
# Phenovation does not update the framenumbers in the references.
# Default frames (when SaveAllFrames == 0)
f0_frame = 1
fm_frame = 2
# F0 and Fm keys
f0_key = "FvFmFrameF0" if "FvFmFrameF0" in self._metadata else "DkOjipFrameF0"
fm_key = "FvFmFrameFm" if "FvFmFrameFm" in self._metadata else "DkOjipFrameFm"
# Get the F0 and Fm frames based on the metadata if all frames are saved
if f0_key in self._metadata and self._metadata["SaveAllFrames"] != "0":
f0_frame = int(self._metadata[f0_key]) + 1
fm_frame = int(self._metadata[fm_key]) + 1
frame_labels[0] = 'Fdark'
frame_labels[f0_frame] = 'F0'
frame_labels[fm_frame] = 'Fm'
# Replace frame_num with time, skip timepoint 0
for i in range(len(frame_nums) - 1):
frame_nums[i + 1] = int(self._metadata.get(f"FvFmTimePoint{i}", frame_nums[i + 1]))
return 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='ojip_dark'
)


class PSL:
"""OJIP light-adapted measurements dataset. Stores the file path at init; image data is loaded on first access."""

def __init__(self, filepath, height, width, metadata):
"""Initialize PSL dataset with file path and image dimensions."""
self._filepath = filepath
self._height = height
self._width = width
self._metadata = metadata

def __bool__(self):
"""The existence of the PSL class is true."""
return True

def __repr__(self):
"""String representation of the PSL dataset, indicating whether the data has been loaded."""
return f"PSL(filepath={self._filepath!r})"

def load(self):
"""Load the OJIP light-adapted measurements from the .DAT file."""
img_cube, frame_labels, frame_nums = _read_dat_file(
dataset="PSL",
filename=str(self._filepath),
height=self._height,
width=self._width,
)
# If not all frames are saved the order is fixed
# Phenovation does not update the framenumbers in the references.
# Default frames (when SaveAllFrames == 0)
fsp_frame = 1
fmp_frame = 2
# F' and Fm' keys
fsp_key = "FqFmFrameFsp" if "FqFmFrameFsp" in self._metadata else "LtOjipFrameFsp"
fmp_key = "FqFmFrameFmp" if "FqFmFrameFmp" in self._metadata else "LtOjipFrameFmp"
# Get the F' and Fm' frames based on the metadata if all frames are saved
if fsp_key in self._metadata and self._metadata["SaveAllFrames"] != "0":
fsp_frame = int(self._metadata[fsp_key]) + 1
fmp_frame = int(self._metadata[fmp_key]) + 1
frame_labels[0] = "Flight"
frame_labels[fsp_frame] = 'Fp'
frame_labels[fmp_frame] = 'Fmp'
# Replace frame_num with time, skip timepoint 0
for i in range(len(frame_nums) - 1):
frame_nums[i + 1] = int(self._metadata.get(f"FqFmTimePoint{i}", frame_nums[i + 1]))
return xr.DataArray(
data=img_cube[..., None],
dims=('x', 'y', 'frame_label', 'measurement'),
coords={'frame_label': frame_labels,
'frame_num': ('frame_label', frame_nums),
'measurement': ['t1']},
name='ojip_light'
)


def read_cropreporter(filename):
"""Read datacubes from PhenoVation B.V. CropReporter or PlantExplorer cameras into a PSII_data instance.

Expand Down Expand Up @@ -174,6 +282,10 @@ def read_cropreporter(filename):
"CHL": lambda fp: CHL(filepath=fp, height=height, width=width),
# Color data
"CLR": lambda fp: CLR(filepath=fp, height=height, width=width),
# OJIP dark data
"PSD": lambda fp: PSD(filepath=fp, height=height, width=width, metadata=ps.metadata),
# OJIP light data
"PSL": lambda fp: PSL(filepath=fp, height=height, width=width, metadata=ps.metadata),
}

# Process datasets
Expand All @@ -186,15 +298,8 @@ def read_cropreporter(filename):
# Get the class constructor
constructor = dataset_classes.get(dataset)
if constructor is not None:
# Set the attribute on the PSII_data instance to a lazy-loading object
setattr(ps, key, constructor(bin_filepath))

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

# Light-adapted measurements
_process_psl_data(ps=ps, metadata=metadata_dict)

# NPQ measurements
_process_npq_data(ps=ps, metadata=metadata_dict)

Expand Down Expand Up @@ -222,104 +327,6 @@ def read_cropreporter(filename):
return ps


def _process_psd_data(ps, metadata):
"""
Create an xarray DataArray for a PSD 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="PSD", datapath=ps.datapath, filename=ps.filename)
if os.path.exists(bin_filepath):
img_cube, frame_labels, frame_nums = _read_dat_file(dataset="PSD", filename=bin_filepath,
height=int(metadata["ImageRows"]),
width=int(metadata["ImageCols"]))
# If not all frames are saved the order is fixed
# Phenovation does not update the framenumbers in the references.
# Default frames (when SaveAllFrames == 0)
f0_frame = 1
fm_frame = 2
# If the method is labeled FvFm
if 'FvFmFrameF0' in metadata and metadata["SaveAllFrames"] != "0":
f0_frame = int(metadata["FvFmFrameF0"]) + 1
fm_frame = int(metadata["FvFmFrameFm"]) + 1
# If the method is labeled DkOjip
if 'DkOjipFrameF0' in metadata and metadata["SaveAllFrames"] != "0":
f0_frame = int(metadata["DkOjipFrameF0"]) + 1 # data cube includes Fdark at the beginning
fm_frame = int(metadata["DkOjipFrameFm"]) + 1
frame_labels[0] = 'Fdark'
frame_labels[f0_frame] = 'F0'
frame_labels[fm_frame] = 'Fm'
psd = 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='ojip_dark'
)
psd.attrs["long_name"] = "OJIP dark-adapted measurements"
ps.ojip_dark = psd

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


def _process_psl_data(ps, metadata):
"""
Create an xarray DataArray for a PSL 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="PSL", datapath=ps.datapath, filename=ps.filename)
if os.path.exists(bin_filepath):
img_cube, frame_labels, frame_nums = _read_dat_file(dataset="PSL", filename=bin_filepath,
height=int(metadata["ImageRows"]),
width=int(metadata["ImageCols"]))
# If not all frames are saved the order is fixed
# Phenovation does not update the framenumbers in the references.
# Default frames (when SaveAllFrames == 0)
fsp_frame = 1
fmp_frame = 2
# If the method is labeled FqFm
if 'FqFmFrameFsp' in metadata and metadata["SaveAllFrames"] != "0":
fsp_frame = int(metadata["FqFmFrameFsp"]) + 1
fmp_frame = int(metadata["FqFmFrameFmp"]) + 1
# If the method is labeled LtOjip
if 'LtOjipFrameFsp' in metadata and metadata["SaveAllFrames"] != "0":
fsp_frame = int(metadata["LtOjipFrameFsp"]) + 1
fmp_frame = int(metadata["LtOjipFrameFmp"]) + 1
frame_labels[0] = "Flight"
frame_labels[fsp_frame] = 'Fp'
frame_labels[fmp_frame] = 'Fmp'
psl = xr.DataArray(
data=img_cube[..., None],
dims=('x', 'y', 'frame_label', 'measurement'),
coords={'frame_label': frame_labels,
'frame_num': ('frame_label', frame_nums),
'measurement': ['t1']},
name='ojip_light'
)
psl.attrs["long_name"] = "OJIP light-adapted measurements"
ps.ojip_light = psl

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


def _process_npq_data(ps, metadata):
"""
Create an xarray DataArray for a NPQ dataset.
Expand Down
4 changes: 2 additions & 2 deletions plantcv/plantcv/visualize/chlorophyll_fluorescence.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ def chlorophyll_fluorescence(ps_da, labeled_mask, n_labels=1, label="object"):
fluor_values = ps_da.where(submask[..., None, None]).mean(['x', 'y', 'measurement']).values

# Append fluorescence values to data dictionary
data["Timepoints"].extend(range(0, ind_size))
data["Timepoints"].extend(list(ps_da.frame_num.values))
data["Fluorescence"].extend(list(fluor_values))
data["Labels"].extend(list(ps_da.frame_label.values))
data["Group"].extend([f"{label}{i}"] * ind_size)
Expand All @@ -91,7 +91,7 @@ def chlorophyll_fluorescence(ps_da, labeled_mask, n_labels=1, label="object"):
rule = (
alt.Chart(df)
.mark_rule(strokeDash=[10, 10], color="gray")
.encode(x=alt.datum(idx))
.encode(x=alt.datum(ps_da.frame_num.values[idx]))
)
# Label the chart with the Fm or Fm' value
text = (
Expand Down
4 changes: 2 additions & 2 deletions tests/plantcv/photosynthesis/test_psii_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@
def test_psii_data(photosynthesis_test_data):
"""Test for PlantCV."""
psii = PSII_data()
psii.ojip_dark = photosynthesis_test_data.psii_cropreporter('ojip_dark')
assert psii.ojip_dark.shape == (10, 10, 4, 1)
psii.psd = photosynthesis_test_data.psii_cropreporter('ojip_dark')
assert psii.psd.shape == (10, 10, 4, 1)
Loading