lilferrit commited on
Commit
dfa545b
1 Parent(s): 2e87490

basic web-app implementation

Browse files
eggcount/app.py ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import dash_bootstrap_components as dbc
2
+ import dash
3
+
4
+ from eggcount.ui.ui_utils import (
5
+ get_navbar
6
+ )
7
+ from dash import Dash, html, dcc
8
+
9
+ app = Dash(
10
+ __name__,
11
+ use_pages = True,
12
+ external_stylesheets=[dbc.themes.BOOTSTRAP]
13
+ )
14
+
15
+ app.layout = dbc.Container(
16
+ children = [
17
+ get_navbar(),
18
+ dash.page_container
19
+ ],
20
+ class_name = "m-0 p-0 w-100 mw-100",
21
+ id = "content-container"
22
+ )
23
+
24
+ if __name__ == '__main__':
25
+ app.run(debug = True)
eggcount/cli.py ADDED
@@ -0,0 +1,131 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from eggcount.gradient import (
2
+ contour_thresh,
3
+ component_thesh,
4
+ component_filter_thresh
5
+ )
6
+ from os import PathLike
7
+ from PIL import Image
8
+ from pillow_heif import register_heif_opener
9
+ from typing import Optional
10
+
11
+ import os
12
+ import cv2
13
+ import numpy as np
14
+ import matplotlib.pyplot as plt
15
+ import fire
16
+
17
+ register_heif_opener()
18
+
19
+ def filter_connected_components(
20
+ img_dir: PathLike,
21
+ color_thresh: int = 75,
22
+ avg_area: float = 800,
23
+ vis: bool = False,
24
+ save_loc: PathLike = "",
25
+ kernal_size: tuple[int, int] = (3, 3),
26
+ max_eggs: Optional[int] = None
27
+ ) -> None:
28
+ # Open image, supports apple HEIC format
29
+ pil_img = Image.open(img_dir)
30
+
31
+ # Convert to standard RGB Image
32
+ img = np.array(pil_img)
33
+ res = component_filter_thresh(
34
+ img,
35
+ color_thresh = color_thresh,
36
+ avg_area = avg_area,
37
+ kernal_size = kernal_size,
38
+ max_eggs = max_eggs
39
+ )
40
+
41
+ res_vis = res["vis"]
42
+ res_stats = res["stats"]
43
+
44
+ for label, stat in res_stats.items():
45
+ print(f"{label.replace('-', ' ')}: {stat}")
46
+
47
+ if vis:
48
+ for label, curr_img in res_vis.items():
49
+ plt.imshow(curr_img)
50
+ plt.show()
51
+
52
+ if save_loc:
53
+ for label, curr_img in res_vis.items():
54
+ save_path = os.path.join(save_loc, label + ".png")
55
+ plt.imsave(save_path, curr_img)
56
+
57
+
58
+ def connected_components(
59
+ img_dir: PathLike,
60
+ color_thresh: int = 75,
61
+ avg_area: float = 800,
62
+ vis: bool = False,
63
+ save_loc: PathLike = "",
64
+ max_eggs: Optional[int] = None
65
+ ) -> None:
66
+ # Open Image
67
+ pil_img = Image.open(img_dir)
68
+
69
+ # Convert to standard RGB Image
70
+ img = np.array(pil_img)
71
+
72
+ res = component_thesh(
73
+ img,
74
+ color_thresh = color_thresh,
75
+ avg_area = avg_area,
76
+ max_eggs = max_eggs
77
+ )
78
+ res_vis = res["vis"]
79
+ res_stats = res["stats"]
80
+
81
+ for label, stat in res_stats.items():
82
+ print(f"{label.replace('-', ' ')}: {stat}")
83
+
84
+ if vis:
85
+ for label, curr_img in res_vis.items():
86
+ plt.imshow(curr_img)
87
+ plt.show()
88
+
89
+ if save_loc:
90
+ for label, curr_img in res_vis.items():
91
+ save_path = os.path.join(save_loc, label + ".png")
92
+ plt.imsave(save_path, curr_img)
93
+
94
+ def contour(
95
+ img_dir: PathLike,
96
+ color_thresh: int = 75,
97
+ avg_area: float = 800,
98
+ vis: bool = False,
99
+ save_loc: PathLike = "",
100
+ kernal_size: tuple[int, int] = (3, 3)
101
+ ) -> None:
102
+ # Open image, supports apple HEIC format
103
+ pil_img = Image.open(img_dir)
104
+
105
+ # Convert to standard RGB Image
106
+ img = np.array(pil_img)
107
+ res = contour_thresh(
108
+ img,
109
+ color_thresh = color_thresh,
110
+ avg_area = avg_area,
111
+ kernal_size = kernal_size
112
+ )
113
+
114
+ res_vis = res["vis"]
115
+ res_stats = res["stats"]
116
+
117
+ for label, stat in res_stats.items():
118
+ print(f"{label.replace('-', ' ')}: {stat}")
119
+
120
+ if vis:
121
+ for label, curr_img in res_vis.items():
122
+ plt.imshow(curr_img)
123
+ plt.show()
124
+
125
+ if save_loc:
126
+ for label, curr_img in res_vis.items():
127
+ save_path = os.path.join(save_loc, label + ".png")
128
+ plt.imsave(save_path, curr_img)
129
+
130
+ if __name__ == "__main__":
131
+ fire.Fire()
eggcount/demo.py DELETED
@@ -1,37 +0,0 @@
1
- from eggcount.eggcount import (
2
- count_eggs_contour_thresh
3
- )
4
- from os import PathLike
5
- from PIL import Image
6
- from pillow_heif import register_heif_opener
7
-
8
- import numpy as np
9
- import matplotlib.pyplot as plt
10
- import fire
11
-
12
- register_heif_opener()
13
-
14
- def demo(
15
- img_dir: PathLike,
16
- color_thresh: int = 75,
17
- avg_area: float = 800
18
- ) -> None:
19
- # Open image, suppoorts apple HEIC format
20
- pil_img = Image.open(img_dir)
21
-
22
- # Convert to standard RGB Image
23
- img = np.array(pil_img)
24
-
25
- num, processed_image = count_eggs_contour_thresh(
26
- img,
27
- color_thresh = color_thresh,
28
- avg_area = avg_area
29
- )
30
-
31
- print(num)
32
- plt.imshow(processed_image)
33
- plt.show()
34
-
35
- if __name__ == "__main__":
36
- fire.Fire(demo)
37
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
eggcount/eggcount.py DELETED
@@ -1,44 +0,0 @@
1
- from typing import Tuple
2
-
3
- import numpy as np
4
- import cv2
5
- import fire
6
-
7
- def contour_thresh(
8
- img: np.ndarray,
9
- color_thresh: int = 75,
10
- avg_area: float = 800
11
- ) -> Tuple[int, np.ndarray]:
12
- img_gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
13
- bin_mask = cv2.inRange(img_gray, 0, color_thresh)
14
-
15
- kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3,3))
16
- opening = cv2.morphologyEx(bin_mask, cv2.MORPH_OPEN, kernel, iterations = 1)
17
- close = cv2.morphologyEx(opening, cv2.MORPH_CLOSE, kernel, iterations = 2)
18
-
19
- cnts = cv2.findContours(close, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
20
- cnts = cnts[0] if len(cnts) == 2 else cnts[1]
21
- num = 0
22
-
23
- for cnt in cnts:
24
- area = cv2.contourArea(cnt)
25
-
26
- if area > avg_area / 2:
27
- cv2.drawContours(img, [cnt], -1, (255, 0, 0), 2)
28
- curr_num = round(area / avg_area)
29
- num += curr_num
30
-
31
- cv2.putText(
32
- img,
33
- str(curr_num),
34
- cnt[0, 0],
35
- cv2.FONT_HERSHEY_SIMPLEX,
36
- 1.5,
37
- (0, 0, 255),
38
- 3
39
- )
40
-
41
- return num, img
42
-
43
- if __name__ == "__main__":
44
- fire.Fire()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
eggcount/gradient.py ADDED
@@ -0,0 +1,184 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Tuple, Dict, Optional
2
+
3
+ import numpy as np
4
+ import cv2
5
+
6
+ def component_filter_thresh(
7
+ img: np.ndarray,
8
+ color_thresh: int = 75,
9
+ avg_area: float = 800,
10
+ kernal_size: tuple[int, int] = (3, 3),
11
+ max_eggs: Optional[int] = None
12
+ ) -> Dict:
13
+ # Clone image, get grayscale, and masc for candidate egg pixels
14
+ visualization_img = img.copy()
15
+ img_gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
16
+ bin_mask = cv2.inRange(img_gray, 0, color_thresh)
17
+
18
+ # Filter pixels not part of a elliptical region
19
+ kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, kernal_size)
20
+ opening = cv2.morphologyEx(bin_mask, cv2.MORPH_OPEN, kernel, iterations = 1)
21
+ close = cv2.morphologyEx(opening, cv2.MORPH_CLOSE, kernel, iterations = 1)
22
+
23
+ # Get connected components of filtered image
24
+ num_labels, labels, stats, centroids = cv2.connectedComponentsWithStats(
25
+ close,
26
+ )
27
+
28
+ num_eggs = 0
29
+
30
+ # Iterate over stats, calculating the number of eggs in each connected component
31
+ for curr_label, curr_stat in enumerate(stats):
32
+ left_x = curr_stat[cv2.CC_STAT_LEFT]
33
+ top_y = curr_stat[cv2.CC_STAT_TOP]
34
+ area = curr_stat[cv2.CC_STAT_AREA]
35
+
36
+ if (area < avg_area / 2) or curr_label == 0:
37
+ continue
38
+
39
+ # Calculate number of eggs
40
+ curr_num_eggs = round(area / avg_area)
41
+
42
+ if max_eggs and (curr_num_eggs > max_eggs):
43
+ continue
44
+
45
+ # Draw border around current component
46
+ component_mask = (labels == curr_label).astype(np.uint8)
47
+ contours, _ = cv2.findContours(component_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
48
+ cv2.drawContours(visualization_img, contours, -1, (255, 0, 0), 2)
49
+
50
+ # Label current component with number of eggs
51
+ cv2.putText(
52
+ visualization_img,
53
+ str(curr_num_eggs),
54
+ (left_x, top_y),
55
+ cv2.FONT_HERSHEY_SIMPLEX,
56
+ 1.5,
57
+ (0, 0, 255),
58
+ 3
59
+ )
60
+
61
+ num_eggs += curr_num_eggs
62
+
63
+ return {
64
+ "stats": {
65
+ "Num-Eggs": num_eggs
66
+ },
67
+ "vis": {
68
+ "Egg-Mask": bin_mask.astype(np.uint8),
69
+ "Ellipse-Filter": close.astype(np.uint8),
70
+ "Visualization": visualization_img.astype(np.uint8)
71
+ }
72
+ }
73
+
74
+ def component_thesh(
75
+ img: np.ndarray,
76
+ color_thresh: int = 75,
77
+ avg_area: float = 800,
78
+ max_eggs: Optional[int] = None
79
+ ) -> Dict:
80
+ # Clone image
81
+ visualization_img = img.copy()
82
+
83
+ # Convert to grayscale
84
+ img_gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
85
+
86
+ # Mask out egg pixels
87
+ bin_mask = cv2.inRange(img_gray, 0, color_thresh)
88
+
89
+ # Get connected components
90
+ num_labels, labels, stats, centroids = cv2.connectedComponentsWithStats(
91
+ bin_mask,
92
+ )
93
+
94
+ num_eggs = 0
95
+
96
+ # Iterate over stats, calculating the number of eggs in each connected component
97
+ for curr_label, curr_stat in enumerate(stats):
98
+ left_x = curr_stat[cv2.CC_STAT_LEFT]
99
+ top_y = curr_stat[cv2.CC_STAT_TOP]
100
+ area = curr_stat[cv2.CC_STAT_AREA]
101
+
102
+ if (area < avg_area / 2) or curr_label == 0:
103
+ continue
104
+
105
+ # Calculate number of eggs
106
+ curr_num_eggs = round(area / avg_area)
107
+
108
+ if max_eggs and (curr_num_eggs > max_eggs):
109
+ continue
110
+
111
+ # Draw border around current component
112
+ component_mask = (labels == curr_label).astype(np.uint8)
113
+ contours, _ = cv2.findContours(component_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
114
+ cv2.drawContours(visualization_img, contours, -1, (255, 0, 0), 2)
115
+
116
+ # Label current component with number of eggs
117
+ cv2.putText(
118
+ visualization_img,
119
+ str(curr_num_eggs),
120
+ (left_x, top_y),
121
+ cv2.FONT_HERSHEY_SIMPLEX,
122
+ 1.5,
123
+ (0, 0, 255),
124
+ 3
125
+ )
126
+
127
+ num_eggs += curr_num_eggs
128
+
129
+ return {
130
+ "stats": {
131
+ "Num-Eggs": num_eggs
132
+ },
133
+ "vis": {
134
+ "Egg-Mask": bin_mask.astype(np.uint8),
135
+ "Visualization": visualization_img.astype(np.uint8)
136
+ }
137
+ }
138
+
139
+ def contour_thresh(
140
+ img: np.ndarray,
141
+ color_thresh: int = 75,
142
+ avg_area: float = 800,
143
+ kernal_size: tuple[int, int] = (3, 3)
144
+ ) -> Dict:
145
+ visualization_img = img.copy()
146
+ img_gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
147
+ bin_mask = cv2.inRange(img_gray, 0, color_thresh)
148
+
149
+ kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, kernal_size)
150
+ opening = cv2.morphologyEx(bin_mask, cv2.MORPH_OPEN, kernel, iterations = 1)
151
+ close = cv2.morphologyEx(opening, cv2.MORPH_CLOSE, kernel, iterations = 2)
152
+
153
+ cnts = cv2.findContours(close, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
154
+ cnts = cnts[0] if len(cnts) == 2 else cnts[1]
155
+ num = 0
156
+
157
+ for cnt in cnts:
158
+ area = cv2.contourArea(cnt)
159
+
160
+ if area > avg_area / 2:
161
+ cv2.drawContours(visualization_img, [cnt], -1, (255, 0, 0), 2)
162
+ curr_num = round(area / avg_area)
163
+ num += curr_num
164
+
165
+ cv2.putText(
166
+ visualization_img,
167
+ str(curr_num),
168
+ cnt[0, 0],
169
+ cv2.FONT_HERSHEY_SIMPLEX,
170
+ 1.5,
171
+ (0, 0, 255),
172
+ 3
173
+ )
174
+
175
+ return {
176
+ "stats": {
177
+ "Num-Eggs": num
178
+ },
179
+ "vis": {
180
+ "Egg-Mask": bin_mask.astype(np.uint8),
181
+ "Ellipse-Filter": close.astype(np.uint8),
182
+ "Visualization": visualization_img.astype(np.uint8)
183
+ }
184
+ }
eggcount/pages/__init__.py ADDED
File without changes
eggcount/pages/home.py ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from dash import html, dcc, callback, Input, Output
2
+
3
+ import dash
4
+ import dash_bootstrap_components as dbc
5
+
6
+ dash.register_page(__name__, path = "/")
7
+
8
+ UPLOAD_HEIGHT = "25vh"
9
+
10
+ layout = dbc.Container(
11
+ children = dcc.Upload(
12
+ id = 'upload-data',
13
+ children = dbc.Container(
14
+ "Upload Image Here",
15
+ class_name = "w-100 border border-dark",
16
+ style = {
17
+ "height": UPLOAD_HEIGHT
18
+ }
19
+ )
20
+ ),
21
+ class_name = "text-center mt-3"
22
+ )
eggcount/ui/ui_utils.py ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import dash_bootstrap_components as dbc
2
+
3
+ NAVBAR_MIN_HEIGHT = "4rem"
4
+
5
+ def get_navbar() -> dbc.Nav:
6
+ return dbc.Nav(
7
+ children = [
8
+ dbc.NavItem(
9
+ dbc.NavLink("Home")
10
+ ),
11
+ dbc.NavItem(
12
+ dbc.NavLink("About")
13
+ )
14
+ ],
15
+ class_name = "bg-dark",
16
+ style = {
17
+ "min-height": NAVBAR_MIN_HEIGHT
18
+ }
19
+ )