Skip to content

Commit

Permalink
Add base files
Browse files Browse the repository at this point in the history
  • Loading branch information
talmo committed Dec 13, 2022
1 parent 0576b50 commit b11a5b2
Show file tree
Hide file tree
Showing 18 changed files with 390 additions and 1 deletion.
36 changes: 36 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Package builds
name: Build

on:
release:
types:
- published

jobs:
pypi:
name: PyPI Wheel
runs-on: "ubuntu-22.04"
steps:

- name: Checkout repo
uses: actions/[email protected]

- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: 3.8

- name: Install dependencies
run: |
pip install --editable .[dev]
- name: Build wheel
run: |
python -m build --wheel
twine check dist/*
- name: Upload
env:
PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }}
run: |
twine upload -u __token__ -p "$PYPI_TOKEN" dist/* --non-interactive --skip-existing --disable-progress-bar
70 changes: 70 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# Continuous integration
name: CI

on:
push:
paths:
- "sleap_roots/**"
- "tests/**"
- ".github/workflows/ci.yml"
- "environment.yml"
- "setup.cfg"

jobs:
# Tests with pytest
tests:

strategy:
fail-fast: false
matrix:
os: ["ubuntu-22.04", "windows-2022"]
python: [3.8]

name: Tests (${{ matrix.os }}, Python ${{ matrix.python }})
runs-on: ${{ matrix.os }}

steps:

- name: Checkout repo
uses: actions/[email protected]

- name: Cache conda
uses: actions/cache@v1
env:
# Increase this value to reset cache if environment.yml has not changed
CACHE_NUMBER: 0
with:
path: ~/conda_pkgs_dir
key: ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-${{ hashFiles('environment.yml', 'setup.cfg') }}

- name: Setup Miniconda
# https://github.com/conda-incubator/setup-miniconda
uses: conda-incubator/[email protected]
with:
python-version: ${{ matrix.python }}
use-only-tar-bz2: true # IMPORTANT: This needs to be set for caching to work properly!
environment-file: environment.yml
activate-environment: sleap-roots

- name: Print environment info
shell: bash -l {0}
run: |
which python
conda info
conda list
- name: Test with pytest
if: ${{ !(startsWith(matrix.os, 'ubuntu') && matrix.python == 3.8) }}
shell: bash -l {0}
run: |
pytest
- name: Test with pytest (with coverage)
if: ${{ startsWith(matrix.os, 'ubuntu') && matrix.python == 3.8 }}
shell: bash -l {0}
run: |
pytest --cov=sleap_roots --cov-report=xml tests/
- name: Upload coverage
uses: codecov/codecov-action@v3
if: ${{ startsWith(matrix.os, 'ubuntu') && matrix.python == 3.8 }}
with:
fail_ci_if_error: true
verbose: false
41 changes: 41 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# This action runs:
#
# 1. Linting with black
#
# 2. Docstring style checking with pydocstyle
# Note: This uses Google-style docstring convention
# Ref: https://google.github.io/styleguide/pyguide.html

name: Lint

on:
push:
paths:
- "sleap_roots/**"
- "tests/**"
- ".github/workflows/lint.yml"

jobs:
lint:
name: Lint
runs-on: "ubuntu-22.04"
steps:

- name: Checkout repo
uses: actions/[email protected]

- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: 3.8

- name: Install dependencies
run: |
pip install --editable .[dev]
- name: Run Black
run: |
black --check sleap_roots tests
- name: Run pydocstyle
run: |
pydocstyle --convention=google sleap_roots/
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -127,3 +127,5 @@ dmypy.json

# Pyre type checker
.pyre/

.DS_Store
41 changes: 40 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,41 @@
# sleap-roots
Analysis tools for SLEAP-based root phenotyping.

[![CI](https://github.com/talmolab/sleap-roots/actions/workflows/ci.yml/badge.svg)](https://github.com/talmolab/sleap-roots/actions/workflows/ci.yml)
[![Lint](https://github.com/talmolab/sleap-roots/actions/workflows/lint.yml/badge.svg)](https://github.com/talmolab/sleap-roots/actions/workflows/lint.yml)
[![codecov](https://codecov.io/gh/talmolab/sleap-roots/branch/main/graph/badge.svg)](https://codecov.io/gh/talmolab/sleap-roots)
[![Release](https://img.shields.io/github/v/release/talmolab/sleap-roots?label=Latest)](https://github.com/talmolab/sleap-roots/releases/)
[![PyPI](https://img.shields.io/pypi/v/sleap-roots?label=PyPI)](https://pypi.org/project/sleap-roots)

Analysis tools for [SLEAP](https://sleap.ai)-based plant root phenotyping.

## Installation
```
pip install sleap-roots
```

If you are using conda:
```
conda create -n sleap-roots python=3.8
conda activate sleap-roots
pip install sleap-roots
```

### Development
For development, use the following syntax to install in editable mode:
```
conda env create -f environment.yml
```
This will create a conda environment called `sleap-roots`.

To run tests, first activate the environment:
```
conda activate sleap-roots
```
Then run `pytest` with:
```
pytest tests
```
To start fresh, just delete the environment:
```
conda env remove -n sleap-roots
```
12 changes: 12 additions & 0 deletions codecov.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
coverage:
status:
project: # Measures overall project coverage.
default:
target: auto
threshold: 0.05%
informational: true # Project coverage for stats only.
patch: # Only measures lines adjusted in the pull request.
default:
target: auto
threshold: 0.05%
informational: true # Patch coverage for stats only.
7 changes: 7 additions & 0 deletions environment.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
name: sleap-roots

dependencies:
- python=3.8
- pip
- pip:
- "--editable=.[dev]"
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[build-system]
requires = ["setuptools"]
build-backend = "setuptools.build_meta
45 changes: 45 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
[metadata]
name = sleap-roots
version = attr: sleap_roots.__version__
author = Talmo Pereira
author_email = [email protected]
maintainer = Talmo Pereira
maintainer_email = [email protected]
url = https://github.com/talmolab/sleap-roots
description = Analysis tools for SLEAP-based plant root phenotyping.
long_description = file: README.md, LICENSE
long_description_content_type = text/markdown
keywords = sleap, plants, roots
license = BSD 3-Clause License
classifiers =
Programming Language :: Python :: 3

[options]
packages = find:
install_requires =
numpy
attrs
matplotlib

[options.extras_require]
dev =
pytest
pytest-cov
black
pydocstyle
toml
twine
build

[options.exclude_package_data]
tests = *
docs = *

[options.packages.find]
exclude =
tests*
docs*

[pydocstyle]
convention = google
match-dir = 'sleap_roots'
7 changes: 7 additions & 0 deletions sleap_roots/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
"""High-level imports."""

from sleap_roots.series import Series

# Define package version.
# This is read dynamically by setuptools in setup.cfg to determine the release version.
__version__ = "0.0.1"
1 change: 1 addition & 0 deletions sleap_roots/bases.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Trait calculations that rely on bases (i.e., dicot-only)."""
1 change: 1 addition & 0 deletions sleap_roots/convhull.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Convex hull fitting and derived trait calculation."""
1 change: 1 addition & 0 deletions sleap_roots/ellipse.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Ellipse fitting and derived trait calculation."""
88 changes: 88 additions & 0 deletions sleap_roots/series.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
"""Series-level data loader."""

import attrs
from typing import Optional, Tuple
from pathlib import Path

try:
import sleap
except:
import sleap_io as sleap


@attrs.define
class Series:
h5_path: Optional[str] = None
primary_name: str = "primary_multi_day"
lateral_name: str = "lateral__nodes"
primary_labels: Optional[sleap.Labels] = None
lateral_labels: Optional[sleap.Labels] = None

@classmethod
def load(cls, h5_path: str, primary_name: str = "primary_multi_day", lateral_name: str = "lateral__nodes"):
primary_path = Path(h5_path).with_suffix(f".{primary_name}.predictions.slp").as_posix()
lateral_path = Path(h5_path).with_suffix(f".{lateral_name}.predictions.slp").as_posix()

return cls(
h5_path,
primary_name=primary_name,
lateral_name=lateral_name,
primary_labels=sleap.load_file(primary_path),
lateral_labels=sleap.load_file(lateral_path),
)

@property
def series_name(self) -> str:
return Path(self.h5_path).stem

@property
def video(self) -> sleap.Video:
return self.primary_labels.video

def __len__(self) -> int:
return len(self.video)

def __getitem__(self, idx: int) -> Tuple[sleap.LabeledFrame, sleap.LabeledFrame]:
return self.get_frame(idx)

def __iter__(self):
for i in range(len(self)):
yield self[i]

def get_frame(self, frame_idx: int) -> Tuple[sleap.LabeledFrame, sleap.LabeledFrame]:
"""Return labeled frames for primary and lateral predictions.
Args:
frame_idx: Integer frame number.
Returns:
Tuple of (primary_lf, lateral_lf) corresponding to the
sleap.LabeledFrames from each set of predictions on the same frame.
"""
lf_primary = self.primary_labels.find(self.primary_labels.video, frame_idx, return_new=True)[0]
lf_lateral = self.lateral_labels.find(self.lateral_labels.video, frame_idx, return_new=True)[0]
return lf_primary, lf_lateral

def plot(self, frame_idx: int, scale: float = 1.0, **kwargs):
primary_lf, lateral_lf = self.get_frame(frame_idx)
sleap.nn.viz.plot_img(primary_lf.image, scale=scale)
sleap.nn.viz.plot_instances(primary_lf.instances, cmap=["r"], **kwargs)
sleap.nn.viz.plot_instances(lateral_lf.instances, cmap=["g"], **kwargs)


def find_all_series(data_folders: list[str]) -> list[str]:
"""Find all .h5 series from a list of folders.
Args:
data_folders: List of paths to folders containing .h5 series.
Returns:
A list of filenames to .h5 series.
"""
if type(data_folders) != list:
data_folders = [data_folders]

h5_series = []
for data_folder in data_folders:
h5_series.extend([Path(p).as_posix() for p in Path(data_folder).glob("*.h5")])
return h5_series
Binary file not shown.
Binary file not shown.
Loading

0 comments on commit b11a5b2

Please sign in to comment.