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

Refactor pipelines into classes #51

Merged
merged 104 commits into from
Aug 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
104 commits
Select commit Hold shift + click to select a range
c896369
Reorganizing the trait_map and modify ellipse, network functions.
Jul 20, 2023
f874be3
Update ellipse argument default setting.
Jul 20, 2023
c9f4931
Reorganize ellipse and network functions by reducing arguments.
Jul 24, 2023
98ce00c
Map convex hull traits
eberrigan Jul 25, 2023
d6bd4bb
Change primary to lateral when monocots is True
eberrigan Jul 25, 2023
4d7bc4d
Get tips x and y coordinates uses network map
eberrigan Jul 25, 2023
51d60ab
Change "stem" to "root"
eberrigan Jul 25, 2023
a73ebcd
Fix tip y map
eberrigan Jul 25, 2023
fc155f8
Change root width back to take lateral_pts
eberrigan Jul 25, 2023
ff9530d
Changing the order of positional arguments to match others (primary i…
eberrigan Jul 25, 2023
c6e58a3
Fix plotting for using sleap-io API
eberrigan Jul 26, 2023
1bd4ddd
Merge branch 'elizabeth/fix_plotting' into elizabeth/pipeline_cache
eberrigan Jul 26, 2023
25e1047
Make positional arguments consistent
eberrigan Jul 26, 2023
eb973bd
Refactor `get_base_xs` to use graph
eberrigan Jul 26, 2023
fd30b78
Map `scanline_intersection_counts` and use keyword arguments
eberrigan Jul 26, 2023
4b529ae
Refactor `get_base_ys`, `get_base_length`, and `base_ct_density` to u…
eberrigan Jul 27, 2023
296fcc7
Clean up dependencies. Fix tip_ys.
eberrigan Jul 27, 2023
8f8a39a
Refactor `get_root_lengths_max` for use with graph
eberrigan Jul 27, 2023
a25e8b9
Refactor `get_base_tip_dist` to make base and tip pts or all points o…
eberrigan Jul 27, 2023
01ea8bc
Delete `primary_depth`
eberrigan Jul 27, 2023
617f379
Delete traitsgraph
eberrigan Jul 27, 2023
383d454
Delete traitsgraph dependencies
eberrigan Jul 27, 2023
132ac7e
Refactor base-related traits to use graph optionally
eberrigan Jul 27, 2023
3f3967f
Delete traits graph dependency
eberrigan Jul 27, 2023
2f9465f
Use `get_primary_pts` from series class
eberrigan Jul 27, 2023
7cf6817
Delete `get_primary_depth` tests
eberrigan Jul 27, 2023
2f64aaf
Fix trait map for base traits
eberrigan Jul 28, 2023
a88dafc
Delete test for traitsgraph.py
eberrigan Jul 28, 2023
2294d14
Standardize trait definition in trait map
eberrigan Jul 31, 2023
7d4c612
Change "graph" to "trait"
eberrigan Jul 31, 2023
393ee9a
Fix docstrings in `get_bases`
eberrigan Aug 1, 2023
519ec93
Use `TraitDef` class
eberrigan Aug 1, 2023
2b54f73
Fix docstrings
eberrigan Aug 1, 2023
b3430da
Add argument to class `TraitDef` whether to include in csv or if scalar
eberrigan Aug 1, 2023
3b67c58
Change `attr` to `attrs`
eberrigan Aug 1, 2023
d352e86
Add `lengths.py` for length-related traits.
eberrigan Aug 1, 2023
676d055
Add `primary_max_length_pts` to trait definitions
eberrigan Aug 1, 2023
2d437fd
Add `pts_all_array` and `convex_hull` trait definitions
eberrigan Aug 2, 2023
1412c8a
Fix docstring
eberrigan Aug 2, 2023
0979969
Import base-related trait to `lengths.py`
eberrigan Aug 2, 2023
c00fa12
Make sure arrays of points are 2-dimensional
eberrigan Aug 2, 2023
2033e08
Streamline point-related functions
eberrigan Aug 2, 2023
f37a40c
Vectorize `get_node_ind`
eberrigan Aug 2, 2023
a603943
Add trait definitions until `lateral_lengths`
eberrigan Aug 2, 2023
716b670
Delete unnecessary code
eberrigan Aug 2, 2023
752d0a5
Merge branch 'main' into lin/pipeline_cache
Aug 2, 2023
72577c8
Merge remote-tracking branch 'origin/Elizabeth/pipeline_cache' into l…
Aug 2, 2023
6f04244
Use node_ind for `get_root_angle` function.
Aug 2, 2023
e21f57d
Modify base functions by assuming primary_pts as the primary_length_max.
Aug 2, 2023
a84b2f8
Modify argument pts as Optional in `get_base_tip_dist` function
Aug 2, 2023
e90a8eb
Modify argument pts as Optional in `get_grav_index` function
Aug 2, 2023
8cbce05
Draft the trait_definitions using the defined TraitDef class.
Aug 2, 2023
f9316ab
Uppercase the `get_root_angle` function arg description.
Aug 3, 2023
0143e14
Add test_lengths module for lengths-related functions.
Aug 3, 2023
1dbc7c4
Remove lengths-related functions from test_bases.
Aug 3, 2023
6e41394
Set pts as Optional argument for `get_grav_index` function.
Aug 3, 2023
43a5e90
Change the module name for importing lengths-related functions.
Aug 3, 2023
a08a2b6
Remove importing the points functions, only keep `get_all_pts_array`.
Aug 3, 2023
95519da
Test ellipse-related functions.
Aug 3, 2023
b6f0867
Redo the function `get_node_ind`.
Aug 4, 2023
8c249a8
Test function `get_node_ind`.
Aug 4, 2023
8b8c523
Angle function reset node_ind to array if only one value.
Aug 4, 2023
da05663
Angle function return nan if all Nan node, return value if single array.
Aug 4, 2023
bdf1ed4
Test angle functions.
Aug 4, 2023
c8aaad1
Add network_width_depth_ratio in trait_definitions.
Aug 4, 2023
555d5b0
Reorganize arguments of `get_network_distribution_ratio` function.
Aug 4, 2023
dff789c
Add `network_length` trait before calculating `network_solidity`.
Aug 4, 2023
2e8099a
Update `primary_root_length` function with calculated lengths.
Aug 4, 2023
38ed487
Update `get_network_solidity` function with calculated network_length.
Aug 4, 2023
6bd8bef
Test network-related functions.
Aug 4, 2023
e676d08
Test points function (`get_all_pts_array`).
Aug 4, 2023
81a52f7
Update and test scanline functions using calculated scanline counts.
Aug 4, 2023
34381ff
Refactor `get_root_pair_widths_projections` to take in `primary_max_l…
eberrigan Aug 9, 2023
fea073e
Cleanup trait map
eberrigan Aug 9, 2023
38a2a7d
Fix tests for base-related traits
eberrigan Aug 9, 2023
aa48229
Add test for `get_max_lengths_pts`
eberrigan Aug 9, 2023
7290d61
Refactored `get_base_ct_density` to take `primary_length_max` and `la…
eberrigan Aug 9, 2023
beedc74
Fixed multi-line strings
eberrigan Aug 9, 2023
ec2621b
Refactor base-related traits
eberrigan Aug 9, 2023
6246eaa
Refactor base-related traits and tests
eberrigan Aug 10, 2023
4c523c4
Test root-length-related traits
eberrigan Aug 10, 2023
79519a2
Test tip-related traits
eberrigan Aug 10, 2023
e8601c2
Refactor convex-hull-related traits
eberrigan Aug 10, 2023
d7b0b1e
Test convex-hull functions
eberrigan Aug 10, 2023
d0f5e55
Lint
eberrigan Aug 10, 2023
4dadb33
Lint
eberrigan Aug 10, 2023
502b154
Lint
eberrigan Aug 10, 2023
6ada71b
Lint
eberrigan Aug 10, 2023
61a879a
Lint
eberrigan Aug 10, 2023
ae2ff5a
Fix kwargs involving `get_tips` in trait map
eberrigan Aug 10, 2023
3d2e5b7
Fix input for pipeline tests
eberrigan Aug 10, 2023
8b88a02
Refactor network related functions
eberrigan Aug 14, 2023
0db548f
Test pipeline
eberrigan Aug 14, 2023
12049d4
Refactor scanline function
eberrigan Aug 14, 2023
d4de693
Start refactoring pipeline into classes
talmo Aug 17, 2023
48d9247
Finish refactoring trait pipelines into classes
talmo Aug 17, 2023
31ef1f2
Runtime fixes
talmo Aug 17, 2023
f71999c
More refactoring to minimize redundant code across pipeline types
talmo Aug 17, 2023
4267a43
Rename module and fix tests
talmo Aug 17, 2023
7caf85c
Add missing renamed modules
talmo Aug 17, 2023
cf2a999
Fix summary tests
talmo Aug 17, 2023
d50b2cd
Fix Series to load video directly to bypass path resolution issues
talmo Aug 17, 2023
fa56d71
Lint
talmo Aug 17, 2023
e71e8d9
Lint
talmo Aug 17, 2023
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: 2 additions & 3 deletions sleap_roots/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,8 @@
import sleap_roots.scanline
import sleap_roots.series
import sleap_roots.summary
import sleap_roots.traitsgraph
import sleap_roots.graphpipeline
from sleap_roots.graphpipeline import get_all_plants_traits
import sleap_roots.trait_pipelines
from sleap_roots.trait_pipelines import DicotPipeline, TraitDef
from sleap_roots.series import Series

# Define package version.
Expand Down
103 changes: 82 additions & 21 deletions sleap_roots/angle.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,51 +4,112 @@
import math


def get_node_ind(pts: np.ndarray, proximal=True) -> np.ndarray:
"""Find nproximal/distal node index.
def get_node_ind(pts: np.ndarray, proximal: bool = True) -> np.ndarray:
"""Find proximal/distal node index.
Args:
pts: Numpy array of points of shape (instances, nodes, 2).
pts: Numpy array of points of shape (instances, nodes, 2) or (nodes, 2).
proximal: Boolean value, where true is proximal (default), false is distal.
Returns:
An array of shape (instances,) of proximal or distal node index.
"""
node_ind = []
for i in range(pts.shape[0]):
ind = 1 if proximal else pts.shape[1] - 1 # set initial proximal/distal node
while np.isnan(pts[i, ind]).any():
ind += 1 if proximal else -1
if (ind == pts.shape[1] and proximal) or (ind == 0 and not proximal):
break
node_ind.append(ind)
# Check if pts is a numpy array
if not isinstance(pts, np.ndarray):
raise TypeError("Input pts should be a numpy array.")

Check warning on line 19 in sleap_roots/angle.py

View check run for this annotation

Codecov / codecov/patch

sleap_roots/angle.py#L19

Added line #L19 was not covered by tests

# Check if pts has 2 or 3 dimensions
if pts.ndim not in [2, 3]:
raise ValueError("Input pts should have 2 or 3 dimensions.")

Check warning on line 23 in sleap_roots/angle.py

View check run for this annotation

Codecov / codecov/patch

sleap_roots/angle.py#L23

Added line #L23 was not covered by tests

# Check if the last dimension of pts has size 2
if pts.shape[-1] != 2:
raise ValueError(

Check warning on line 27 in sleap_roots/angle.py

View check run for this annotation

Codecov / codecov/patch

sleap_roots/angle.py#L27

Added line #L27 was not covered by tests
"The last dimension of the input pts should have size 2,"
"representing x and y coordinates."
)

# Check if pts is 2D, if so, reshape to 3D
if pts.ndim == 2:
pts = pts[np.newaxis, ...]

# Identify where NaN values exist
nan_mask = np.isnan(pts).any(axis=-1)

# If only NaN values, return NaN
if nan_mask.all():
return np.nan

if proximal:
# For proximal, we want the first non-NaN node in the first half root
# get the first half nan mask (exclude the base node)
node_proximal = nan_mask[:, 1 : int((nan_mask.shape[1] + 1) / 2)]
# get the nearest non-Nan node index
node_ind = np.argmax(~node_proximal, axis=-1)
# if there is no non-Nan node, set value of 99
node_ind[node_proximal.all(axis=1)] = 99
node_ind = node_ind + 1 # adjust indices by adding one (base node)
else:
# For distal, we want the last non-NaN node in the last half root
# get the last half nan mask
node_distal = nan_mask[:, int(nan_mask.shape[1] / 2) :]
# get the farest non-Nan node
node_ind = (node_distal[:, ::-1] == False).argmax(axis=1)
node_ind[node_distal.all(axis=1)] = -95 # set value if no non-Nan node
node_ind = pts.shape[1] - node_ind - 1 # adjust indices by reversing

# reset indices of 0 (base node) if no non-Nan node
node_ind[node_ind == 100] = 0

# If pts was originally 2D, return a scalar instead of a single-element array
if pts.shape[0] == 1:
return node_ind[0]

# If only one root, return a scalar instead of a single-element array
if node_ind.shape[0] == 1:
return node_ind[0]

Check warning on line 70 in sleap_roots/angle.py

View check run for this annotation

Codecov / codecov/patch

sleap_roots/angle.py#L70

Added line #L70 was not covered by tests

return node_ind


def get_root_angle(pts: np.ndarray, proximal=True, base_ind=0) -> np.ndarray:
def get_root_angle(
pts: np.ndarray, node_ind: np.ndarray, proximal: bool = True, base_ind=0
) -> np.ndarray:
"""Find angles for each root.
Args:
pts: Numpy array of points of shape (instances, nodes, 2).
node_ind: Primary or lateral root node index.
proximal: Boolean value, where true is proximal (default), false is distal.
base_ind: Index of base node in the skeleton (default: 0).
Returns:
An array of shape (instances,) of angles in degrees, modulo 360.
"""
node_ind = get_node_ind(pts, proximal) # get proximal or distal node index
# if node_ind is a single int value, make it as array to keep consistent
if not isinstance(node_ind, np.ndarray):
node_ind = [node_ind]

if np.isnan(node_ind).all():
return np.nan

if pts.ndim == 2:
pts = np.expand_dims(pts, axis=0)

angs_root = []
for i in range(len(node_ind)):
# filter out the cases if all nan nodes in last/first half part
# to calculate proximal/distal angle
if (node_ind[i] < math.ceil(pts.shape[1] / 2) and proximal) or (
node_ind[i] >= math.floor(pts.shape[1] / 2) and not (proximal)
):
# if the node_ind is 0, do NOT calculate angs
if node_ind[i] == 0:
angs = np.nan
else:
xy = pts[i, node_ind[i], :] - pts[i, base_ind, :] # center on base node
# calculate the angle and convert to the start with gravity direction
ang = np.arctan2(-xy[1], xy[0]) * 180 / np.pi
angs = abs(ang + 90) if ang < 90 else abs(-(360 - 90 - ang))
else:
angs = np.nan
angs_root.append(angs)
return np.array(angs_root)
angs_root = np.array(angs_root)

# If only one root, return a scalar instead of a single-element array
if angs_root.shape[0] == 1:
return angs_root[0]
return angs_root
Loading