From 1b9f3737084e915f64bf63028ed2eac743a75389 Mon Sep 17 00:00:00 2001 From: Josh Sumner <51797700+joshqsumner@users.noreply.github.com> Date: Fri, 5 Dec 2025 13:52:12 -0600 Subject: [PATCH 1/4] including docs --- docs/analyze_size.md | 32 +++++++++++++++++++ docs/binary.md | 28 +++++++++++++++++ docs/fill.md | 24 +++++++++++++++ docs/index.md | 73 ++++++++++++++++++++++++++++++++++++++++++++ docs/plot_image.md | 22 +++++++++++++ docs/readimage.md | 25 +++++++++++++++ docs/rgb2gray_lab.md | 25 +++++++++++++++ mkdocs.yml | 21 +++++++++++++ 8 files changed, 250 insertions(+) create mode 100644 docs/analyze_size.md create mode 100644 docs/binary.md create mode 100644 docs/fill.md create mode 100644 docs/index.md create mode 100644 docs/plot_image.md create mode 100644 docs/readimage.md create mode 100644 docs/rgb2gray_lab.md create mode 100644 mkdocs.yml diff --git a/docs/analyze_size.md b/docs/analyze_size.md new file mode 100644 index 0000000..2e1ff56 --- /dev/null +++ b/docs/analyze_size.md @@ -0,0 +1,32 @@ +## Analyze the Size and Shape Characteristics of Objects + +Size and shape analysis outputs numeric properties for individual plants, seeds, leaves, etc. + +**littlecv.analyze.size**(*img, mask, label="littlecv_test", plot=True*) + +**returns** analysis_image + +- **Parameters:** + - img - numpy.ndarray, RGB or grayscale image data for plotting. + - mask - numpy.ndarray, binary mask of object. + - label - str, label for outputs + - plot - Logical, should a debug image be printed? Defaults to True. +- **Context:** + - Used to output size and shape characteristics of individual objects (labeled regions). + - About the analysis image: We draw some of the measured shape characteristics on the input `img`. The height, width, + longest path, convex hull, and centroid are drawn in magenta. The edges of the object is drawn in blue. +- **Output data stored:** Data ('area', 'convex_hull_area', 'solidity', 'perimeter', 'width', 'height', 'longest_path', +'center_of_mass, 'convex_hull_vertices', 'object_in_frame', 'ellipse_center', 'ellipse_major_axis', 'ellipse_minor_axis', +'ellipse_angle', 'ellipse_eccentricity', 'total_edge_length') are returned as an Outputs class object, which is basically a dictionary. + + +```python + +from littlecv import littlecv as lcv + +# Characterize object shapes +outputs, analysis_image = lcv.size(img=img, mask=mask) + +``` + +**Source Code:** [Here](https://github.com/danforthcenter/contributing_functions_tutorial/blob/main/littlecv/littlecv/analyze_size.py) diff --git a/docs/binary.md b/docs/binary.md new file mode 100644 index 0000000..b5ca010 --- /dev/null +++ b/docs/binary.md @@ -0,0 +1,28 @@ +## Binary Threshold + +Creates a binary image from a gray image based on the threshold values. +The object target can be specified as dark or light. + +**littlecv.threshold.binary**(*gray_img, threshold, object_type="light"*) + +**returns** thresholded/binary image + +- **Parameters:** + - gray_img - Grayscale image data + - threshold - Threshold value (0-255). Values lighter than this are kept. + - plot - Logical, should a debug image be plotted? Defaults to True. +- **Context:** + - Used to help differentiate plant and background +- **Example use:** + - Below + + +```python + +from littlecv import littlecv as lcv + +threshold_light = lcv.threshold.binary(gray_img=gray_img, threshold=36) + +``` + +**Source Code:** [Here](https://github.com/danforthcenter/contributing_functions_tutorial/blob/main/littlecv/littlecv/threshold_binary.py) diff --git a/docs/fill.md b/docs/fill.md new file mode 100644 index 0000000..12c5111 --- /dev/null +++ b/docs/fill.md @@ -0,0 +1,24 @@ +## Fill + +Identifies objects and fills objects that are less than specified size + +**littlecv.fill**(*bin_img, size, roi=None*) + +**returns** fill_image + +- **Parameters:** + - bin_img - Binary image data + - size - minimum object area size in pixels (integer), smaller objects will be filled + - plot - Logical, should a debug image be plotted? Defaults to True. + - **Context:** + - Used to reduce image noise + +```python + +from littlecv import littlecv as lcv + +fill_image = lcv.fill(bin_img=binary_img, size=200) + +``` + +**Source Code:** [Here](https://github.com/danforthcenter/contributing_functions_tutorial/blob/main/littlecv/littlecv/fill.py) diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..63a67f7 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,73 @@ +# Contributing New Functions to PlantCV + +This repository is a place for you to learn to contribute to PlantCV in one of our guided new contributor workshops. For full documentation or asynchronous learning about contributing to PlantCV please see the [contribution docs](http://plantcv.readthedocs.io/en/latest/CONTRIBUTING/). + +## Getting started + +To get started you will need an environment with at least `numpy`, `cv2`, `matplotlib`, `pandas`, `python-dateutil`, and `skikit-image` Python libraries installed. We recommend installing `PlantCV` in editable mode if you have not already done so as that will be best for contributing and will install everything you need for this activity. If you are looking to contribute to PlantCV then you may already have a conda environment set up for `PlantCV` and that would work for this workshop as well, whether `PlantCV` is editable or not in that installation. + +### Installing littlecv + +* First, Clone this repository (`git clone https://github.com/danforthcenter/contributing_tutorial.git`). Throughout this readme we include git CLI commands but [Github Desktop](https://github.com/apps/desktop), [Git Kraken](https://www.gitkraken.com/), or Git through VSCode/other IDEs are all friendlier options and worth learning to use. +* Next, `cd` to the cloned repo (`cd contributing_tutorial`) +* Finally, with your `PlantCV` or equivalent environment active, install `littlecv` by running `pip install -e .`. + +Now you will have an editable version of `littlecv` installed locally. You can check your installation by running this command, which should tell you that you'll be an author soon: + +``` +python -c "from littlecv import littlecv as lcv; print(lcv.__author__)" +``` + +## The task + +In this repository there is a small Python library called `littleCV`, these are just a handful of `PlantCV` functions with some of the more complicated features removed. + +There is also an iPython notebook running a simple workflow using `littleCV`. The functions that exist in `littleCV` work in this tutorial, but you'll need a new function to get what you want. In the [`contributing_tutorial`](https://github.com/danforthcenter/contributing_tutorial) activity your task was just to find an error in existing code, which is an important kind of contribution to a codebase like `PlantCV`. In this tutorial your task is to add a totally new outward facing (exported) function. The point of this task is not to do complex Python coding but to familiarize with making edits to the files that define a Python package, writing clean code to match a codebase's style requirements, and making useful docstrings. If you get stuck on the Python coding aspect of this exercise that is okay, everyone contributing to `PlantCV` has different background and experience in Python so some tasks will be easier or harder for different people, you can check the example at the bottom of this readme file for guidance. + +The function that you are adding should crop an image using a top left coordinate (x, y), height of the cropped section, and width of the cropped section. Your function should be callable by a user after the standard `from littlecv import littlecv as lcv` import syntax as `lcv.crop` or something similarly named. Other than that, just make a function that crops an image and make the tests pass on github actions (note that this includes making sure that you cover your new function with tests). + + + +#### Steps + +* 1: Open a branch with your name (`git checkout -b first_last`). +* 2: Set up an image object to crop in jupyter (run `jupyter-lab` and use GUI). +* 3: Edit `littlecv` code to add your function to `littlecv` OR write your function in jupyter. Some of our team likes to prototype functions in jupyter cells first, others prefer to edit python files and restart the kernel after each set of changes. Which you choose is up to you. +* 4: Restart Jupyter Kernel (or run the cell with your function in it) and test your function, alternating between edits and interactive testing until you are satisfied. +* 5: Write tests for your function. In `PlantCV` this would mean a new `test_crop.py` file, but you can add it to `tests/test_littlecv.py` or make another file, that is up to you. When you are satisfied use `py.test --cov littlecv --cov-report="term-missing"`. +* 6: Commit your changes (`git commit -m "your commit message here"`). This is like saving the files on your branch. How often you do this and how granular commits are is mostly up to you. +* 7: Push your changes to github (`git push origin main`). This syncs the changes you've committed to github so that other people could pull them and use them locally. +* 8: Open a Pull Request. These are a feature of Github, not of Git itself, so we'll have to do this online or through a software like Github Desktop. Go to `https://github.com/danforthcenter/contributing_functions_tutorial/tree/YOUR_BRANCH_NAME` or to [`https://github.com/danforthcenter/contributing_functions_tutorial`](https://github.com/danforthcenter/contributing_functions_tutorial) and use the branch dropdown menu to select your branch. On your branch there should be a green `Compare and pull request` button, click that and fill out the template. There is a sidebar of options for labels, assignees, reviewers, etc. In `PlantCV` you'll; use those to describe the purpose of your PR at a very high level, mark it for a certain version milestone or request certain reviewers based on who has domain expertise or last edited those files. Once you are done with the description click `create pull request`. Once the pull request is created the automated checks will run, if those pass then you are done. You might see deepsource problems that do not show up on your local tests since deepsource is more particular about code style, etc. + +### The Functions + +`littlecv` only has 6 exposed functions, they are: + +* [`readimage`](readimage.md): Reads an image into a numpy.ndarray. Simplified from `plantcv.plantcv.read_image` +* [`rgb2gray_lab`](rgb2gray_lab.md): Converts an RGB image to grayscale with either L, A, or B channel. Simplified from `plantcv.plantcv.rgb2gray_lab`. +* [`binary`](binary.md): Applies a binary threshold. Simplified from `plantcv.plantcv.threshold.binary`. +* [`fill`](fill.md): Filters small objects. Simplified from `plantcv.plantcv.fill`. +* [`size`](analyze_size.md): Calculates single value phenotypes given a mask and image. Simplified from `plantcv.analyze.size` +* [`plot_image`](plot_image.md): Plots an image. Simplified from `plantcv.plantcv.plot_image`. + +There is also a minimal `Outputs` class called by `size` to store results which is based on the `plantcv.plantcv.Outputs` class. + + +### If you get stuck + +If you get stuck with the Python coding part of this exercise then start from something like this example to familiarize with subsetting numpy arrays: + +``` +import numpy as np +# images are numpy arrays +# numpy arrays can have each dimension subset with a range [inclusive, exclusive) +a1 = np.array([[1,2,3,4], [5,6,7,8], [9,10,11,12]]) +print(a1[0:1, 0:3]) # first "row", first, second, and third "columns". Remember Python is 0 indexed. + +# many images will have at least 3 channels +a2 = np.zeros((100, 100,3)) +# ":" is shorthand for "all" +a2[:,:,0] = 255 +# print a small piece of this array +a2[0:2, 0:2, 0:4] +``` diff --git a/docs/plot_image.md b/docs/plot_image.md new file mode 100644 index 0000000..c842f38 --- /dev/null +++ b/docs/plot_image.md @@ -0,0 +1,22 @@ +## Plot Image + +Open the image in a window or plot in a Jupyter notebook using [matplotlib](https://matplotlib.org/). + +**plot_image**(*img, plot=True*) + +**returns** none + +- **Parameters:** + - img - image object +- **Context:** + - Often used to debug new image processing workflows + - Used to view images in Jupyter notebooks (or in a window) + +```python + +from littlecv import littlecv as lcv +lcv.plot_image(img) + +``` + +**Source Code:** [Here](https://github.com/danforthcenter/contributing_functions_tutorial/tree/main/littlecv/littlecv/plot_image.py) diff --git a/docs/readimage.md b/docs/readimage.md new file mode 100644 index 0000000..43213d9 --- /dev/null +++ b/docs/readimage.md @@ -0,0 +1,25 @@ +## Read Image + +Reads image into numpy ndarray and splits the path and image filename (*see note about when `mode='envi'`). Most modes of use of this function is a wrapper for the OpenCV function [imread](http://docs.opencv.org/modules/highgui/doc/reading_and_writing_images_and_video.html). + +**littlecv.readimage**(*filename, plot=True*) + +**returns** img, path, image filename + +- **Parameters:** + - filename - image file to be read (possibly including a path) + - plot - Logical, should the image be plotted when read? Defaults to True. +- **Context:** + - Reads in file to be processed +- **Notes:** + - Simplified from `plantcv.plantcv.readimage`. + +```python +from littlecv import littlecv as lcv + +#read in image +img, path, img_filename = lcv.readimage(filename="home/user/images/test-image.png") + +``` + +**Source Code:** [Here](https://github.com/danforthcenter/contributing_functions_tutorial/blob/main/littlecv/littlecv/readimage.py) diff --git a/docs/rgb2gray_lab.md b/docs/rgb2gray_lab.md new file mode 100644 index 0000000..0b44f13 --- /dev/null +++ b/docs/rgb2gray_lab.md @@ -0,0 +1,25 @@ +## RGB to LAB + +Convert image from RGB color space to LAB color space and split the channels. + +**littlecv.rgb2gray_lab**(*rgb_img, channel, plot=True*) + +**returns** split image (l, a, or b channel) + +- **Parameters:** + - rgb_img - RGB image data + - channel - Split 'l' (lightness), 'a' (green-magenta), or 'b' (blue-yellow) channel + - plot - Logical, should a debug image be printed? Defaults to True. + +- **Context:** + - Used to help differentiate plant and background + +```python + +from littlecv import littlecv as lcv + +l_channel = lcv.rgb2gray_lab(rgb_img=rgb_img, channel='l') + +``` + +**Source Code:** [Here](https://github.com/danforthcenter/contributing_functions_tutorial/blob/main/littlecv/littlecv/rgb2gray_lab.py) diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000..de93a84 --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,21 @@ +site_name: LittleCV +site_url: https://littlecv.readthedocs.io/ +site_description: Learn to contribute to PlantCV +site_author: PlantCV Development Team +repo_url: https://github.com/danforthcenter/contributing_functions_tutorial +copyright: Copyright 2025, Donald Danforth Plant Science Center +theme: readthedocs + +nav: +- Home: index.md +- Documentation: + - 'Read an Image': readimage.md + - 'Plot an Image': plot_image.md + - 'RGB to Grayscale': rgb2gray_lab.md + - 'Binary Thresholding': binary.md + - 'Fill Holes': fill.md + - 'Shape Analysis': analyze_size.md +markdown_extensions: + - toc: + permalink: True + - admonition From 53c71dc4d3206e0f07373d3b5d06477e7eaadd46 Mon Sep 17 00:00:00 2001 From: Josh Sumner <51797700+joshqsumner@users.noreply.github.com> Date: Fri, 5 Dec 2025 13:52:23 -0600 Subject: [PATCH 2/4] removing extra kwargs --- littlecv/littlecv/plot_image.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/littlecv/littlecv/plot_image.py b/littlecv/littlecv/plot_image.py index d02d5cb..e4c6be5 100644 --- a/littlecv/littlecv/plot_image.py +++ b/littlecv/littlecv/plot_image.py @@ -5,13 +5,12 @@ from matplotlib import pyplot as plt -def plot_image(img, plot=True, **kwargs): +def plot_image(img, plot=True): """Plot an image to the screen. :param img: numpy.ndarray, ggplot, xarray.core.dataarray.DataArray :param plot: bool, make a plot (defaults True, only used internally in place of pcv.params.debug) - :param kwargs: key-value arguments to xarray.plot method :return: """ dimensions = numpy.shape(img) From 3083f51b3b5b90e8a5b76146c6b890b8b1620798 Mon Sep 17 00:00:00 2001 From: Josh Sumner <51797700+joshqsumner@users.noreply.github.com> Date: Fri, 5 Dec 2025 14:01:01 -0600 Subject: [PATCH 3/4] adding steps --- docs/index.md | 9 +++++---- readme.md | 10 ++++++---- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/docs/index.md b/docs/index.md index 63a67f7..5f2b86e 100644 --- a/docs/index.md +++ b/docs/index.md @@ -32,12 +32,13 @@ The function that you are adding should crop an image using a top left coordinat * 1: Open a branch with your name (`git checkout -b first_last`). * 2: Set up an image object to crop in jupyter (run `jupyter-lab` and use GUI). -* 3: Edit `littlecv` code to add your function to `littlecv` OR write your function in jupyter. Some of our team likes to prototype functions in jupyter cells first, others prefer to edit python files and restart the kernel after each set of changes. Which you choose is up to you. +* 3: Edit `littlecv` code to add your function to `littlecv` OR write your function in jupyter. Some of our team likes to prototype functions in jupyter cells first, others prefer to edit python files and restart the kernel after each set of changes. Which you choose is up to you. Either way you will eventually need a module that defines your function and to add that module to `__init__.py`. * 4: Restart Jupyter Kernel (or run the cell with your function in it) and test your function, alternating between edits and interactive testing until you are satisfied. * 5: Write tests for your function. In `PlantCV` this would mean a new `test_crop.py` file, but you can add it to `tests/test_littlecv.py` or make another file, that is up to you. When you are satisfied use `py.test --cov littlecv --cov-report="term-missing"`. -* 6: Commit your changes (`git commit -m "your commit message here"`). This is like saving the files on your branch. How often you do this and how granular commits are is mostly up to you. -* 7: Push your changes to github (`git push origin main`). This syncs the changes you've committed to github so that other people could pull them and use them locally. -* 8: Open a Pull Request. These are a feature of Github, not of Git itself, so we'll have to do this online or through a software like Github Desktop. Go to `https://github.com/danforthcenter/contributing_functions_tutorial/tree/YOUR_BRANCH_NAME` or to [`https://github.com/danforthcenter/contributing_functions_tutorial`](https://github.com/danforthcenter/contributing_functions_tutorial) and use the branch dropdown menu to select your branch. On your branch there should be a green `Compare and pull request` button, click that and fill out the template. There is a sidebar of options for labels, assignees, reviewers, etc. In `PlantCV` you'll; use those to describe the purpose of your PR at a very high level, mark it for a certain version milestone or request certain reviewers based on who has domain expertise or last edited those files. Once you are done with the description click `create pull request`. Once the pull request is created the automated checks will run, if those pass then you are done. You might see deepsource problems that do not show up on your local tests since deepsource is more particular about code style, etc. +* 6: Write documentation for your new function (see the `docs` folder in this repo) and add the new docs page to `mkdocs.yml`. +* 7: Commit your changes (`git commit -m "your commit message here"`). This is like saving the files on your branch. How often you do this and how granular commits are is mostly up to you. +* 8: Push your changes to github (`git push origin main`). This syncs the changes you've committed to github so that other people could pull them and use them locally. +* 9: Open a Pull Request. These are a feature of Github, not of Git itself, so we'll have to do this online or through a software like Github Desktop. Go to `https://github.com/danforthcenter/contributing_functions_tutorial/tree/YOUR_BRANCH_NAME` or to [`https://github.com/danforthcenter/contributing_functions_tutorial`](https://github.com/danforthcenter/contributing_functions_tutorial) and use the branch dropdown menu to select your branch. On your branch there should be a green `Compare and pull request` button, click that and fill out the template. There is a sidebar of options for labels, assignees, reviewers, etc. In `PlantCV` you'll; use those to describe the purpose of your PR at a very high level, mark it for a certain version milestone or request certain reviewers based on who has domain expertise or last edited those files. Once you are done with the description click `create pull request`. Once the pull request is created the automated checks will run, if those pass then you are done. You might see deepsource problems that do not show up on your local tests since deepsource is more particular about code style, etc. ### The Functions diff --git a/readme.md b/readme.md index 4abec24..5d571d5 100644 --- a/readme.md +++ b/readme.md @@ -32,12 +32,14 @@ The function that you are adding should crop an image using a top left coordinat * 1: Open a branch with your name (`git checkout -b first_last`). * 2: Set up an image object to crop in jupyter (run `jupyter-lab` and use GUI). -* 3: Edit `littlecv` code to add your function to `littlecv` OR write your function in jupyter. Some of our team likes to prototype functions in jupyter cells first, others prefer to edit python files and restart the kernel after each set of changes. Which you choose is up to you. +* 3: Edit `littlecv` code to add your function to `littlecv` OR write your function in jupyter. Some of our team likes to prototype functions in jupyter cells first, others prefer to edit python files and restart the kernel after each set of changes. Which you choose is up to you. Either way you will eventually need a module that defines your function and to add that module to `__init__.py`. * 4: Restart Jupyter Kernel (or run the cell with your function in it) and test your function, alternating between edits and interactive testing until you are satisfied. * 5: Write tests for your function. In `PlantCV` this would mean a new `test_crop.py` file, but you can add it to `tests/test_littlecv.py` or make another file, that is up to you. When you are satisfied use `py.test --cov littlecv --cov-report="term-missing"`. -* 6: Commit your changes (`git commit -m "your commit message here"`). This is like saving the files on your branch. How often you do this and how granular commits are is mostly up to you. -* 7: Push your changes to github (`git push origin main`). This syncs the changes you've committed to github so that other people could pull them and use them locally. -* 8: Open a Pull Request. These are a feature of Github, not of Git itself, so we'll have to do this online or through a software like Github Desktop. Go to `https://github.com/danforthcenter/contributing_functions_tutorial/tree/YOUR_BRANCH_NAME` or to [`https://github.com/danforthcenter/contributing_functions_tutorial`](https://github.com/danforthcenter/contributing_functions_tutorial) and use the branch dropdown menu to select your branch. On your branch there should be a green `Compare and pull request` button, click that and fill out the template. There is a sidebar of options for labels, assignees, reviewers, etc. In `PlantCV` you'll; use those to describe the purpose of your PR at a very high level, mark it for a certain version milestone or request certain reviewers based on who has domain expertise or last edited those files. Once you are done with the description click `create pull request`. Once the pull request is created the automated checks will run, if those pass then you are done. You might see deepsource problems that do not show up on your local tests since deepsource is more particular about code style, etc. +* 6: Write documentation for your new function (see the `docs` folder in this repo) and add the new docs page to `mkdocs.yml`. +* 7: Commit your changes (`git commit -m "your commit message here"`). This is like saving the files on your branch. How often you do this and how granular commits are is mostly up to you. +* 8: Push your changes to github (`git push origin main`). This syncs the changes you've committed to github so that other people could pull them and use them locally. +* 9: Open a Pull Request. These are a feature of Github, not of Git itself, so we'll have to do this online or through a software like Github Desktop. Go to `https://github.com/danforthcenter/contributing_functions_tutorial/tree/YOUR_BRANCH_NAME` or to [`https://github.com/danforthcenter/contributing_functions_tutorial`](https://github.com/danforthcenter/contributing_functions_tutorial) and use the branch dropdown menu to select your branch. On your branch there should be a green `Compare and pull request` button, click that and fill out the template. There is a sidebar of options for labels, assignees, reviewers, etc. In `PlantCV` you'll; use those to describe the purpose of your PR at a very high level, mark it for a certain version milestone or request certain reviewers based on who has domain expertise or last edited those files. Once you are done with the description click `create pull request`. Once the pull request is created the automated checks will run, if those pass then you are done. You might see deepsource problems that do not show up on your local tests since deepsource is more particular about code style, etc. + ### The Functions From b7ff80394fdb45b075fded0dd9dbe66d19e13328 Mon Sep 17 00:00:00 2001 From: Josh Sumner <51797700+joshqsumner@users.noreply.github.com> Date: Fri, 5 Dec 2025 14:01:09 -0600 Subject: [PATCH 4/4] pointing at DS website --- mkdocs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mkdocs.yml b/mkdocs.yml index de93a84..d9b370f 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -1,5 +1,5 @@ site_name: LittleCV -site_url: https://littlecv.readthedocs.io/ +site_url: https://https://datasci.danforthcenter.org/littlecv/ site_description: Learn to contribute to PlantCV site_author: PlantCV Development Team repo_url: https://github.com/danforthcenter/contributing_functions_tutorial