Skip to content

Commit

Permalink
Merge pull request #8 from computervision-xray-testing/release/0.3.0
Browse files Browse the repository at this point in the history
Release/0.3.0
  • Loading branch information
cpieringer committed Jul 10, 2024
2 parents 84de539 + 3a8d8ff commit 370347a
Show file tree
Hide file tree
Showing 63 changed files with 55,130 additions and 38,685 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -102,3 +102,6 @@ venv.bak/

# mypy
.mypy_cache/

# Ruff
.ruff_cache
73 changes: 64 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,69 @@
# pybalu
<a id="readme-top"></a>

![Build and upload to PyPI](https://github.com/mbucchi/pybalu/workflows/Build%20and%20upload%20to%20PyPI/badge.svg)

Python implementation for Balu, a computer vision, pattern recognition and image processing library. Originally implemented in matlab by Domingo Mery.
<p align="right">(<a href="#readme-top">back to top</a>)</p>
<!-- PROJECT LOGO -->
<br />
<div align="center">
<!-- <a href="https://github.com/othneildrew/Best-README-Template">
<img src="images/logo.png" alt="Logo" width="80" height="80">
</a> -->

## Installing pybalu
<h3 align="center">pybalu</h3>

Python 3.6 or higher is required to use this package. In order to install pybalu, simply run
<p align="center">
Python3 implementation of the computer vision and pattern recognition library Balu.
<br />
<a href="https://github.com/computervision-xray-testing/pybalu"><strong>Explore the docs »</strong></a>
<br />
<br />
<a href="https://github.com/computervision-xray-testing/pybalu/issues/new?labels=bug&template=bug-report---.md">Report Bug</a>
·
<a href="https://github.com/computervision-xray-testing/pybalu/issues/new?labels=enhancement&template=feature-request---.md">Request Feature</a>
</p>
</div>


<!-- TABLE OF CONTENTS -->
<details>
<summary>Table of Contents</summary>
<ol>
<li>
<a href="#about-the-project">About The Project</a>
</li>
<li>
<a href="#getting-started">Getting Started</a>
<ul>
<!-- <li><a href="#prerequisites">Prerequisites</a></li> -->
<li><a href="#installation">Installation</a></li>
</ul>
</li>
<li><a href="#contributing">Contributing</a></li>
<li><a href="#Roadmap">Contributing</a></li>
</ol>
</details>


![Build and upload to PyPI](hhttps://github.com/computervision-xray-testing/pybalu/workflows/Build%20and%20upload%20to%20PyPI/badge.svg)


# About the Project

Python implementation for Balu, a computer vision, pattern recognition and image processing library. Originally implemented in Matlab&reg; by Domingo Mery.


# Installation

Python 3.10 or higher is required to use this package. In order to install pybalu, simply run

```bash
$ pip install pybalu
```

## Development and contributing

If you would like to contribute:
# Contributing

We follow github flow standard. If you would like to contribute:

- Fork the repo
- Create a new branch called `feature/<feature-desc>` or `fix/<fix-desc>` depending on the nature of your contribution
Expand All @@ -28,6 +77,12 @@ Possible and useful contributions:
- Documentation
- Examples

## Documentation
In case of contribution, once you have clone the repository, install the development version that includes optional dependencies specified in the pyproject.toml

```bash
$ pip install .[dev]
```

## Roadmap

_TODO_
- Documentation: _TODO_
34 changes: 18 additions & 16 deletions examples/feature_extraction/ellipses.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@
"source": [
"# Change directory to VSCode workspace root so that relative path loads work correctly. Turn this addition off with the DataScience.changeDirOnImportExport setting\n",
"import os\n",
"\n",
"try:\n",
" os.chdir(os.path.join(os.getcwd(), '..'))\n",
" os.chdir(os.path.join(os.getcwd(), \"..\"))\n",
"except:\n",
" pass"
]
Expand All @@ -34,7 +35,7 @@
"from pybalu.feature_extraction.geometric_utils import bbox as _bbox\n",
"from pybalu.feature_extraction import basic_geo_features\n",
"from pybalu.io import imread\n",
"from skimage.measure import label\n"
"from skimage.measure import label"
]
},
{
Expand All @@ -53,6 +54,7 @@
"outputs": [],
"source": [
"import matplotlib\n",
"\n",
"matplotlib.rcParams[\"figure.figsize\"] = (7, 7)\n",
"matplotlib.rcParams[\"axes.titlesize\"] = 20\n",
"matplotlib.rcParams[\"axes.titlepad\"] = 15\n",
Expand Down Expand Up @@ -118,13 +120,15 @@
" region = (labeled_T == idx).astype(int)\n",
" box = _bbox(region)\n",
" feats = basic_geo_features(region[box])\n",
" return np.array([\n",
" box[0].start + feats[0], # feats[0]: center of grav i [px]\n",
" box[1].start + feats[1], # feats[1]: center of grav j [px]\n",
" feats[10], # feats[10]: MajorAxisLength [px]\n",
" feats[11], # feats[11]: MinorAxisLength [px]\n",
" feats[12] # feats[12]: Orientation [deg]\n",
" ])\n",
" return np.array(\n",
" [\n",
" box[0].start + feats[0], # feats[0]: center of grav i [px]\n",
" box[1].start + feats[1], # feats[1]: center of grav j [px]\n",
" feats[10], # feats[10]: MajorAxisLength [px]\n",
" feats[11], # feats[11]: MinorAxisLength [px]\n",
" feats[12], # feats[12]: Orientation [deg]\n",
" ]\n",
" )\n",
"\n",
"\n",
"with Pool() as pool:\n",
Expand Down Expand Up @@ -161,8 +165,9 @@
"\n",
"\n",
"def draw_ellipse(x, y, height, width, angle, axes):\n",
" ell = Ellipse(xy=(x, y), height=height, width=width,\n",
" angle=angle, edgecolor=\"red\", facecolor=\"none\")\n",
" ell = Ellipse(\n",
" xy=(x, y), height=height, width=width, angle=angle, edgecolor=\"red\", facecolor=\"none\"\n",
" )\n",
" axes.add_artist(ell)\n",
" ell.set_clip_box(axes.bbox)\n",
" return ell\n",
Expand Down Expand Up @@ -205,8 +210,7 @@
"major_25 = np.percentile(ellipses[:, 3], 25)\n",
"major_75 = np.percentile(ellipses[:, 3], 75)\n",
"\n",
"valid_labels = 1 + \\\n",
" np.where((ellipses[:, 3] > major_25) & (ellipses[:, 3] < major_75))[0]\n",
"valid_labels = 1 + np.where((ellipses[:, 3] > major_25) & (ellipses[:, 3] < major_75))[0]\n",
"im_mean = np.array(im)\n",
"im_mean[np.where(~np.isin(labeled, valid_labels))] //= 7\n",
"plt.imshow(im_mean, cmap=\"gray\")\n",
Expand Down Expand Up @@ -240,9 +244,7 @@
],
"source": [
"def draw_at_angle(theta):\n",
" valid_labels = 1 + \\\n",
" np.where((ellipses[:, 4] > theta - 10) &\n",
" (ellipses[:, 4] < theta + 10))[0]\n",
" valid_labels = 1 + np.where((ellipses[:, 4] > theta - 10) & (ellipses[:, 4] < theta + 10))[0]\n",
" plt.title(f\"Orientation at {theta} deg\")\n",
" im_rotated = np.array(im)\n",
" im_rotated[np.where(~np.isin(labeled, valid_labels))] //= 7\n",
Expand Down
63 changes: 32 additions & 31 deletions examples/feature_extraction/ellipses.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,18 @@
from matplotlib.patches import Ellipse
from multiprocessing import Pool
from pybalu.feature_extraction.geometric_utils import bbox as _bbox
from pybalu.feature_extraction import basic_geo_features
from pybalu.feature_extraction.basic_geo import basic_geo_features
from pybalu.io import imread
from skimage.measure import label

# %% [markdown]
# ## Matplotlib setup
# The following code is used to set up the default parameters for all the
# The following code is used to set up the default parameters for all the
# plots shown by matplotlib

# %%
import matplotlib

matplotlib.rcParams["figure.figsize"] = (7, 7)
matplotlib.rcParams["axes.titlesize"] = 20
matplotlib.rcParams["axes.titlepad"] = 15
Expand All @@ -37,7 +38,7 @@
# ## Recognizing rice grains
# In order to recognize the grains, the following steps are followed:
#
# 1. Image is transformed to binary
# 1. Image is transformed to binary
# 2. Rice grains are separated and labeled accordingly
# 3. Geometric features are calculated for each grain by using `basic_geo_features` function
# 4. An `Ellipse` object is built for each grain
Expand All @@ -49,20 +50,22 @@


def calc_ellipse(idx):
region = (labeled_T == idx).astype(int)
box = _bbox(region)
feats = basic_geo_features(region[box])
return np.array([
box[0].start + feats[0], # feats[0]: center of grav i [px]
box[1].start + feats[1], # feats[1]: center of grav j [px]
feats[10], # feats[10]: MajorAxisLength [px]
feats[11], # feats[11]: MinorAxisLength [px]
feats[12] # feats[12]: Orientation [deg]
])
region = (labeled_T == idx).astype(int)
box = _bbox(region)
feats = basic_geo_features(region[box])
return np.array(
[
box[0].start + feats[0], # feats[0]: center of grav i [px]
box[1].start + feats[1], # feats[1]: center of grav j [px]
feats[10], # feats[10]: MajorAxisLength [px]
feats[11], # feats[11]: MinorAxisLength [px]
feats[12], # feats[12]: Orientation [deg]
]
)


with Pool() as pool:
ellipses = np.vstack(pool.map(calc_ellipse, range(1, n)))
ellipses = np.vstack(pool.map(calc_ellipse, range(1, n)))

# %% [markdown]
# ## Displaying the ellipses over the original image
Expand All @@ -74,15 +77,16 @@ def calc_ellipse(idx):


def draw_ellipse(x, y, height, width, angle, axes):
ell = Ellipse(xy=(x, y), height=height, width=width,
angle=angle, edgecolor="red", facecolor="none")
axes.add_artist(ell)
ell.set_clip_box(axes.bbox)
return ell
ell = Ellipse(
xy=(x, y), height=height, width=width, angle=angle, edgecolor="red", facecolor="none"
)
axes.add_artist(ell)
ell.set_clip_box(axes.bbox)
return ell


for ell in ellipses:
draw_ellipse(*ell, axes=ax)
draw_ellipse(*ell, axes=ax)

plt.show()

Expand All @@ -98,8 +102,7 @@ def draw_ellipse(x, y, height, width, angle, axes):
major_25 = np.percentile(ellipses[:, 3], 25)
major_75 = np.percentile(ellipses[:, 3], 75)

valid_labels = 1 + \
np.where((ellipses[:, 3] > major_25) & (ellipses[:, 3] < major_75))[0]
valid_labels = 1 + np.where((ellipses[:, 3] > major_25) & (ellipses[:, 3] < major_75))[0]
im_mean = np.array(im)
im_mean[np.where(~np.isin(labeled, valid_labels))] //= 7
plt.imshow(im_mean, cmap="gray")
Expand All @@ -108,21 +111,19 @@ def draw_ellipse(x, y, height, width, angle, axes):

# %% [markdown]
# ## Finding rice grains oriented at a specific angle
# Rice grains rotation is within 10deg of the given angle are highlighted. Just as
# Rice grains rotation is within 10deg of the given angle are highlighted. Just as
# before, this is done with the help of numpy matrix operations

# %%


def draw_at_angle(theta):
valid_labels = 1 + \
np.where((ellipses[:, 4] > theta - 10) &
(ellipses[:, 4] < theta + 10))[0]
plt.title(f"Orientation at {theta} deg")
im_rotated = np.array(im)
im_rotated[np.where(~np.isin(labeled, valid_labels))] //= 7
plt.imshow(im_rotated, cmap="gray")
plt.show()
valid_labels = 1 + np.where((ellipses[:, 4] > theta - 10) & (ellipses[:, 4] < theta + 10))[0]
plt.title(f"Orientation at {theta} deg")
im_rotated = np.array(im)
im_rotated[np.where(~np.isin(labeled, valid_labels))] //= 7
plt.imshow(im_rotated, cmap="gray")
plt.show()


draw_at_angle(45)
18 changes: 9 additions & 9 deletions examples/feature_selection/sfs.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@
"source": [
"# Change directory to VSCode workspace root so that relative path loads work correctly. Turn this addition off with the DataScience.changeDirOnImportExport setting\n",
"import os\n",
"\n",
"try:\n",
" os.chdir(os.path.join(os.getcwd(), '..'))\n",
" os.chdir(os.path.join(os.getcwd(), \"..\"))\n",
"except:\n",
" pass"
]
Expand Down Expand Up @@ -53,6 +54,7 @@
"outputs": [],
"source": [
"import matplotlib\n",
"\n",
"matplotlib.rcParams[\"figure.figsize\"] = (7, 7)\n",
"matplotlib.rcParams[\"axes.titlesize\"] = 20\n",
"matplotlib.rcParams[\"axes.titlepad\"] = 15\n",
Expand Down Expand Up @@ -98,7 +100,7 @@
"metadata": {},
"outputs": [],
"source": [
"idx_train, idx_test = stratify(classes, .90)\n",
"idx_train, idx_test = stratify(classes, 0.90)\n",
"f_train = features[idx_train]\n",
"c_train = classes[idx_train]\n",
"f_test = features[idx_test]\n",
Expand Down Expand Up @@ -146,8 +148,7 @@
"source": [
"N_FEATURES = 15\n",
"\n",
"selected_feats = sfs(f_train_norm, c_train, n_features=N_FEATURES,\n",
" method=\"fisher\", show=True)"
"selected_feats = sfs(f_train_norm, c_train, n_features=N_FEATURES, method=\"fisher\", show=True)"
]
},
{
Expand Down Expand Up @@ -189,13 +190,12 @@
" return performance(prediction, c_test)\n",
"\n",
"\n",
"values = [performance_for_features(selected_feats[:i]) * 100\n",
" for i in range(1, N_FEATURES + 1)]\n",
"values = [performance_for_features(selected_feats[:i]) * 100 for i in range(1, N_FEATURES + 1)]\n",
"\n",
"plt.bar(*zip(*enumerate(values)), tick_label=range(1, N_FEATURES+1))\n",
"plt.bar(*zip(*enumerate(values)), tick_label=range(1, N_FEATURES + 1))\n",
"plt.title(\"Performance vs. number of features\")\n",
"plt.xlabel('selected features')\n",
"plt.ylabel('accuracy [%]')\n",
"plt.xlabel(\"selected features\")\n",
"plt.ylabel(\"accuracy [%]\")\n",
"plt.show()"
]
},
Expand Down
Loading

0 comments on commit 370347a

Please sign in to comment.