Skip to content

Commit 370347a

Browse files
authored
Merge pull request #8 from computervision-xray-testing/release/0.3.0
Release/0.3.0
2 parents 84de539 + 3a8d8ff commit 370347a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

63 files changed

+55130
-38685
lines changed

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -102,3 +102,6 @@ venv.bak/
102102

103103
# mypy
104104
.mypy_cache/
105+
106+
# Ruff
107+
.ruff_cache

README.md

+64-9
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,69 @@
1-
# pybalu
1+
<a id="readme-top"></a>
22

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

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

7-
## Installing pybalu
12+
<h3 align="center">pybalu</h3>
813

9-
Python 3.6 or higher is required to use this package. In order to install pybalu, simply run
14+
<p align="center">
15+
Python3 implementation of the computer vision and pattern recognition library Balu.
16+
<br />
17+
<a href="https://github.com/computervision-xray-testing/pybalu"><strong>Explore the docs »</strong></a>
18+
<br />
19+
<br />
20+
<a href="https://github.com/computervision-xray-testing/pybalu/issues/new?labels=bug&template=bug-report---.md">Report Bug</a>
21+
·
22+
<a href="https://github.com/computervision-xray-testing/pybalu/issues/new?labels=enhancement&template=feature-request---.md">Request Feature</a>
23+
</p>
24+
</div>
25+
26+
27+
<!-- TABLE OF CONTENTS -->
28+
<details>
29+
<summary>Table of Contents</summary>
30+
<ol>
31+
<li>
32+
<a href="#about-the-project">About The Project</a>
33+
</li>
34+
<li>
35+
<a href="#getting-started">Getting Started</a>
36+
<ul>
37+
<!-- <li><a href="#prerequisites">Prerequisites</a></li> -->
38+
<li><a href="#installation">Installation</a></li>
39+
</ul>
40+
</li>
41+
<li><a href="#contributing">Contributing</a></li>
42+
<li><a href="#Roadmap">Contributing</a></li>
43+
</ol>
44+
</details>
45+
46+
47+
![Build and upload to PyPI](hhttps://github.com/computervision-xray-testing/pybalu/workflows/Build%20and%20upload%20to%20PyPI/badge.svg)
48+
49+
50+
# About the Project
51+
52+
Python implementation for Balu, a computer vision, pattern recognition and image processing library. Originally implemented in Matlab&reg; by Domingo Mery.
53+
54+
55+
# Installation
56+
57+
Python 3.10 or higher is required to use this package. In order to install pybalu, simply run
1058

1159
```bash
1260
$ pip install pybalu
1361
```
1462

15-
## Development and contributing
1663

17-
If you would like to contribute:
64+
# Contributing
65+
66+
We follow github flow standard. If you would like to contribute:
1867

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

31-
## Documentation
80+
In case of contribution, once you have clone the repository, install the development version that includes optional dependencies specified in the pyproject.toml
81+
82+
```bash
83+
$ pip install .[dev]
84+
```
85+
86+
## Roadmap
3287

33-
_TODO_
88+
- Documentation: _TODO_

examples/feature_extraction/ellipses.ipynb

+18-16
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@
88
"source": [
99
"# Change directory to VSCode workspace root so that relative path loads work correctly. Turn this addition off with the DataScience.changeDirOnImportExport setting\n",
1010
"import os\n",
11+
"\n",
1112
"try:\n",
12-
" os.chdir(os.path.join(os.getcwd(), '..'))\n",
13+
" os.chdir(os.path.join(os.getcwd(), \"..\"))\n",
1314
"except:\n",
1415
" pass"
1516
]
@@ -34,7 +35,7 @@
3435
"from pybalu.feature_extraction.geometric_utils import bbox as _bbox\n",
3536
"from pybalu.feature_extraction import basic_geo_features\n",
3637
"from pybalu.io import imread\n",
37-
"from skimage.measure import label\n"
38+
"from skimage.measure import label"
3839
]
3940
},
4041
{
@@ -53,6 +54,7 @@
5354
"outputs": [],
5455
"source": [
5556
"import matplotlib\n",
57+
"\n",
5658
"matplotlib.rcParams[\"figure.figsize\"] = (7, 7)\n",
5759
"matplotlib.rcParams[\"axes.titlesize\"] = 20\n",
5860
"matplotlib.rcParams[\"axes.titlepad\"] = 15\n",
@@ -118,13 +120,15 @@
118120
" region = (labeled_T == idx).astype(int)\n",
119121
" box = _bbox(region)\n",
120122
" feats = basic_geo_features(region[box])\n",
121-
" return np.array([\n",
122-
" box[0].start + feats[0], # feats[0]: center of grav i [px]\n",
123-
" box[1].start + feats[1], # feats[1]: center of grav j [px]\n",
124-
" feats[10], # feats[10]: MajorAxisLength [px]\n",
125-
" feats[11], # feats[11]: MinorAxisLength [px]\n",
126-
" feats[12] # feats[12]: Orientation [deg]\n",
127-
" ])\n",
123+
" return np.array(\n",
124+
" [\n",
125+
" box[0].start + feats[0], # feats[0]: center of grav i [px]\n",
126+
" box[1].start + feats[1], # feats[1]: center of grav j [px]\n",
127+
" feats[10], # feats[10]: MajorAxisLength [px]\n",
128+
" feats[11], # feats[11]: MinorAxisLength [px]\n",
129+
" feats[12], # feats[12]: Orientation [deg]\n",
130+
" ]\n",
131+
" )\n",
128132
"\n",
129133
"\n",
130134
"with Pool() as pool:\n",
@@ -161,8 +165,9 @@
161165
"\n",
162166
"\n",
163167
"def draw_ellipse(x, y, height, width, angle, axes):\n",
164-
" ell = Ellipse(xy=(x, y), height=height, width=width,\n",
165-
" angle=angle, edgecolor=\"red\", facecolor=\"none\")\n",
168+
" ell = Ellipse(\n",
169+
" xy=(x, y), height=height, width=width, angle=angle, edgecolor=\"red\", facecolor=\"none\"\n",
170+
" )\n",
166171
" axes.add_artist(ell)\n",
167172
" ell.set_clip_box(axes.bbox)\n",
168173
" return ell\n",
@@ -205,8 +210,7 @@
205210
"major_25 = np.percentile(ellipses[:, 3], 25)\n",
206211
"major_75 = np.percentile(ellipses[:, 3], 75)\n",
207212
"\n",
208-
"valid_labels = 1 + \\\n",
209-
" np.where((ellipses[:, 3] > major_25) & (ellipses[:, 3] < major_75))[0]\n",
213+
"valid_labels = 1 + np.where((ellipses[:, 3] > major_25) & (ellipses[:, 3] < major_75))[0]\n",
210214
"im_mean = np.array(im)\n",
211215
"im_mean[np.where(~np.isin(labeled, valid_labels))] //= 7\n",
212216
"plt.imshow(im_mean, cmap=\"gray\")\n",
@@ -240,9 +244,7 @@
240244
],
241245
"source": [
242246
"def draw_at_angle(theta):\n",
243-
" valid_labels = 1 + \\\n",
244-
" np.where((ellipses[:, 4] > theta - 10) &\n",
245-
" (ellipses[:, 4] < theta + 10))[0]\n",
247+
" valid_labels = 1 + np.where((ellipses[:, 4] > theta - 10) & (ellipses[:, 4] < theta + 10))[0]\n",
246248
" plt.title(f\"Orientation at {theta} deg\")\n",
247249
" im_rotated = np.array(im)\n",
248250
" im_rotated[np.where(~np.isin(labeled, valid_labels))] //= 7\n",

examples/feature_extraction/ellipses.py

+32-31
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,18 @@
77
from matplotlib.patches import Ellipse
88
from multiprocessing import Pool
99
from pybalu.feature_extraction.geometric_utils import bbox as _bbox
10-
from pybalu.feature_extraction import basic_geo_features
10+
from pybalu.feature_extraction.basic_geo import basic_geo_features
1111
from pybalu.io import imread
1212
from skimage.measure import label
1313

1414
# %% [markdown]
1515
# ## Matplotlib setup
16-
# The following code is used to set up the default parameters for all the
16+
# The following code is used to set up the default parameters for all the
1717
# plots shown by matplotlib
1818

1919
# %%
2020
import matplotlib
21+
2122
matplotlib.rcParams["figure.figsize"] = (7, 7)
2223
matplotlib.rcParams["axes.titlesize"] = 20
2324
matplotlib.rcParams["axes.titlepad"] = 15
@@ -37,7 +38,7 @@
3738
# ## Recognizing rice grains
3839
# In order to recognize the grains, the following steps are followed:
3940
#
40-
# 1. Image is transformed to binary
41+
# 1. Image is transformed to binary
4142
# 2. Rice grains are separated and labeled accordingly
4243
# 3. Geometric features are calculated for each grain by using `basic_geo_features` function
4344
# 4. An `Ellipse` object is built for each grain
@@ -49,20 +50,22 @@
4950

5051

5152
def calc_ellipse(idx):
52-
region = (labeled_T == idx).astype(int)
53-
box = _bbox(region)
54-
feats = basic_geo_features(region[box])
55-
return np.array([
56-
box[0].start + feats[0], # feats[0]: center of grav i [px]
57-
box[1].start + feats[1], # feats[1]: center of grav j [px]
58-
feats[10], # feats[10]: MajorAxisLength [px]
59-
feats[11], # feats[11]: MinorAxisLength [px]
60-
feats[12] # feats[12]: Orientation [deg]
61-
])
53+
region = (labeled_T == idx).astype(int)
54+
box = _bbox(region)
55+
feats = basic_geo_features(region[box])
56+
return np.array(
57+
[
58+
box[0].start + feats[0], # feats[0]: center of grav i [px]
59+
box[1].start + feats[1], # feats[1]: center of grav j [px]
60+
feats[10], # feats[10]: MajorAxisLength [px]
61+
feats[11], # feats[11]: MinorAxisLength [px]
62+
feats[12], # feats[12]: Orientation [deg]
63+
]
64+
)
6265

6366

6467
with Pool() as pool:
65-
ellipses = np.vstack(pool.map(calc_ellipse, range(1, n)))
68+
ellipses = np.vstack(pool.map(calc_ellipse, range(1, n)))
6669

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

7578

7679
def draw_ellipse(x, y, height, width, angle, axes):
77-
ell = Ellipse(xy=(x, y), height=height, width=width,
78-
angle=angle, edgecolor="red", facecolor="none")
79-
axes.add_artist(ell)
80-
ell.set_clip_box(axes.bbox)
81-
return ell
80+
ell = Ellipse(
81+
xy=(x, y), height=height, width=width, angle=angle, edgecolor="red", facecolor="none"
82+
)
83+
axes.add_artist(ell)
84+
ell.set_clip_box(axes.bbox)
85+
return ell
8286

8387

8488
for ell in ellipses:
85-
draw_ellipse(*ell, axes=ax)
89+
draw_ellipse(*ell, axes=ax)
8690

8791
plt.show()
8892

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

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

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

114117
# %%
115118

116119

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

127128

128129
draw_at_angle(45)

examples/feature_selection/sfs.ipynb

+9-9
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@
88
"source": [
99
"# Change directory to VSCode workspace root so that relative path loads work correctly. Turn this addition off with the DataScience.changeDirOnImportExport setting\n",
1010
"import os\n",
11+
"\n",
1112
"try:\n",
12-
" os.chdir(os.path.join(os.getcwd(), '..'))\n",
13+
" os.chdir(os.path.join(os.getcwd(), \"..\"))\n",
1314
"except:\n",
1415
" pass"
1516
]
@@ -53,6 +54,7 @@
5354
"outputs": [],
5455
"source": [
5556
"import matplotlib\n",
57+
"\n",
5658
"matplotlib.rcParams[\"figure.figsize\"] = (7, 7)\n",
5759
"matplotlib.rcParams[\"axes.titlesize\"] = 20\n",
5860
"matplotlib.rcParams[\"axes.titlepad\"] = 15\n",
@@ -98,7 +100,7 @@
98100
"metadata": {},
99101
"outputs": [],
100102
"source": [
101-
"idx_train, idx_test = stratify(classes, .90)\n",
103+
"idx_train, idx_test = stratify(classes, 0.90)\n",
102104
"f_train = features[idx_train]\n",
103105
"c_train = classes[idx_train]\n",
104106
"f_test = features[idx_test]\n",
@@ -146,8 +148,7 @@
146148
"source": [
147149
"N_FEATURES = 15\n",
148150
"\n",
149-
"selected_feats = sfs(f_train_norm, c_train, n_features=N_FEATURES,\n",
150-
" method=\"fisher\", show=True)"
151+
"selected_feats = sfs(f_train_norm, c_train, n_features=N_FEATURES, method=\"fisher\", show=True)"
151152
]
152153
},
153154
{
@@ -189,13 +190,12 @@
189190
" return performance(prediction, c_test)\n",
190191
"\n",
191192
"\n",
192-
"values = [performance_for_features(selected_feats[:i]) * 100\n",
193-
" for i in range(1, N_FEATURES + 1)]\n",
193+
"values = [performance_for_features(selected_feats[:i]) * 100 for i in range(1, N_FEATURES + 1)]\n",
194194
"\n",
195-
"plt.bar(*zip(*enumerate(values)), tick_label=range(1, N_FEATURES+1))\n",
195+
"plt.bar(*zip(*enumerate(values)), tick_label=range(1, N_FEATURES + 1))\n",
196196
"plt.title(\"Performance vs. number of features\")\n",
197-
"plt.xlabel('selected features')\n",
198-
"plt.ylabel('accuracy [%]')\n",
197+
"plt.xlabel(\"selected features\")\n",
198+
"plt.ylabel(\"accuracy [%]\")\n",
199199
"plt.show()"
200200
]
201201
},

0 commit comments

Comments
 (0)