Skip to content

Commit 3c0513a

Browse files
committed
Added Filters, edited Hough Ellipse
Added Two types of filters (Min, Max)
1 parent c3005c6 commit 3c0513a

33 files changed

+132
-30
lines changed

App/Classes/Effects/Filter.py

Lines changed: 40 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,16 @@ def update_attributes(self):
7979
def calculate_filter(self):
8080
if self.type == "Mean":
8181
return self.mean_filter()
82-
elif self.type == "Median":
83-
return self.median_filter()
82+
elif self.type == "Weighed Average":
83+
return self.weighed_average_filter()
8484
elif self.type == "Gaussian":
8585
return self.gaussian_filter()
86+
elif self.type == "Median":
87+
return self.median_filter()
88+
elif self.type == "Max":
89+
return self.max_filter()
90+
elif self.type == "Min":
91+
return self.min_filter()
8692
else:
8793
raise ValueError("Unexpected filter type: " + self.type)
8894

@@ -158,15 +164,8 @@ def mean_filter(self):
158164
"""
159165
return self._apply_filter(np.mean)
160166

161-
def median_filter(self):
162-
"""
163-
Description:
164-
- Applies a median filter to an image.
165-
166-
Returns:
167-
- [numpy ndarray]: A filtered image using a median filter.
168-
"""
169-
return self._apply_filter(np.median)
167+
def weighed_average_filter(self):
168+
pass
170169

171170
def gaussian_filter(self):
172171
"""
@@ -192,3 +191,33 @@ def gaussian_filter(self):
192191
gaussian_filtered_image[i, j] = np.sum(window * kernel)
193192

194193
return gaussian_filtered_image
194+
195+
def median_filter(self):
196+
"""
197+
Description:
198+
- Applies a median filter to an image.
199+
200+
Returns:
201+
- [numpy ndarray]: A filtered image using a median filter.
202+
"""
203+
return self._apply_filter(np.median)
204+
205+
def max_filter(self):
206+
"""
207+
Description:
208+
- Applies a max filter to an image.
209+
210+
Returns:
211+
- A numpy array representing the filtered image.
212+
"""
213+
return self._apply_filter(np.max)
214+
215+
def min_filter(self):
216+
"""
217+
Description:
218+
- Applies a min filter to an image.
219+
220+
Returns:
221+
- A numpy array representing the filtered image.
222+
"""
223+
return self._apply_filter(np.min)

App/Classes/Effects/HoughTransform.py

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from Classes.EffectsWidgets.HoughTransformGroupBox import HoughTransformGroupBox
88
from Classes.ExtendedWidgets.DoubleClickPushButton import QDoubleClickPushButton
99
from PyQt5.QtCore import pyqtSignal
10-
from skimage import color, img_as_ubyte
10+
from skimage import color, data, img_as_ubyte
1111
from skimage.draw import ellipse_perimeter
1212
from skimage.feature import canny
1313
from skimage.transform import hough_ellipse
@@ -307,10 +307,15 @@ def hough_circle(self):
307307
return superimposed_circles_image
308308

309309
def hough_ellipse_using_scikit_image(self):
310-
image_gray = color.rgb2gray(self.original_image)
310+
image_rgb = data.coffee()[0:220, 160:420]
311+
image_gray = color.rgb2gray(image_rgb)
311312
edges = canny(image_gray, sigma=2.0, low_threshold=0.55, high_threshold=0.8)
313+
# result = hough_ellipse(
314+
# edges, accuracy=20, threshold=250, min_size=100, max_size=120
315+
# )
316+
312317
result = hough_ellipse(
313-
edges, accuracy=20, threshold=250, min_size=100, max_size=120
318+
self.edged_image, accuracy=20, threshold=250, min_size=100, max_size=120
314319
)
315320
result.sort(order="accumulator")
316321

@@ -387,22 +392,19 @@ def hough_ellipse_from_scratch(self, img, min2a=10, min_votes=10):
387392
parameters = [x0, y0, a, si, alpha]
388393
return parameters
389394

390-
print("No ellipses detected!")
391395
return None
392396

393397
def ellipse_output_image_from_scratch(self):
394398

395-
img = cv2.Canny(self.original_image, 100, 200)
396-
397-
parameters = self.hough_ellipse_from_scratch(img)
399+
parameters = self.hough_ellipse_from_scratch(self.edged_image)
398400
# Define the parameters of the ellipse
399401
center = (
400402
int(parameters[0]),
401403
int(parameters[1]),
402404
) # Convert center coordinates to integers
403405
axes = (
404-
int(parameters[2]) * 2,
405-
parameters[3] * 2,
406+
int(parameters[2]),
407+
parameters[3],
406408
) # Convert major axis length to integer
407409
angle = np.degrees(parameters[4]) # Convert angle from radians to degrees
408410

@@ -411,8 +413,10 @@ def ellipse_output_image_from_scratch(self):
411413

412414
# Draw the ellipse on the image
413415
self.output_image = cv2.ellipse(
414-
image, center, axes, 255 - angle, 0, 360, (0, 255, 0), 2
416+
image, center, axes, angle, 0, 360, (0, 255, 0), 2
415417
)
418+
419+
self.output_image[int(parameters[1]), int(parameters[0])] = 255
416420
return self.output_image
417421

418422
# Line Transform Helper Functions
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

App/Classes/EffectsWidgets/FilterGroupBox.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@ def initUI(self):
2424
filter_type_layout = QHBoxLayout()
2525
self.filter_type_label = QLabel("Filter Type")
2626
self.filter_type_comb = QComboBox()
27-
self.filter_type_comb.addItems(["Mean", "Median", "Gaussian"])
27+
self.filter_type_comb.addItems(
28+
["Mean", "Weighted Average", "Gaussian", "Median", "Max", "Min"]
29+
)
2830
self.filter_type_comb.currentIndexChanged.connect(self.update_filter_options)
2931
filter_type_layout.addWidget(self.filter_type_label)
3032
filter_type_layout.addWidget(self.filter_type_comb)
@@ -55,8 +57,8 @@ def initUI(self):
5557

5658
def update_filter_options(self, index):
5759
if index == 2: # Gaussian
58-
self.sigma_spinbox.setVisible(True)
5960
self.sigma_label.setVisible(True)
61+
self.sigma_spinbox.setVisible(True)
6062
else: # Mean or Median
61-
self.sigma_spinbox.setVisible(False)
6263
self.sigma_label.setVisible(False)
64+
self.sigma_spinbox.setVisible(False)

App/Classes/EffectsWidgets/HoughTransformGroupBox.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ def initUI(self):
134134
self.ellipse_detector_type_label = QLabel("Detector Type")
135135
self.ellipse_detector_type_combobox = QComboBox()
136136
self.ellipse_detector_type_combobox.addItems(["From Scratch", "Scikit-image"])
137-
self.ellipse_detector_type_combobox.setCurrentIndex(1)
137+
self.ellipse_detector_type_combobox.setCurrentIndex(0)
138138
self.ellipse_attributes_hbox.addWidget(self.ellipse_detector_type_label)
139139
self.ellipse_attributes_hbox.addWidget(self.ellipse_detector_type_combobox)
140140

Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

App/ImageAlchemyBackend.py

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,14 @@ def __init__(self, file_path):
4646
self.img_data = None
4747
self.grayscale_img = None
4848

49-
self.output_img = None
50-
self.cumulative_output = None
49+
self.output_img = None # The output image of the last appied effect
50+
self.cumulative_output = (
51+
None # The output image of appliying all the effects as "chain"
52+
)
5153

52-
self.applied_effects = {} # Dictionary to store the applied effects.
54+
self.applied_effects = (
55+
{}
56+
) # Dictionary to store the applied effects and its parameters.
5357
# They will be shown in the tree and the table widgets.
5458
Image.all_images.append(self)
5559
# To facilitate the access to the images, we will store them in a list
@@ -83,6 +87,8 @@ def add_applied_effect(self, effect_name, effect_attributes):
8387
Args:
8488
- effect_name [String]: Name of the effect, the key in the dictionary.
8589
- effect_attributes [Dictionary]: Attributes of the effect (e.g., type, value1, value2).
90+
91+
Note that these are formed inside each effect class and we are just passing it to the class to set/store it.
8692
"""
8793
self.applied_effects[effect_name] = effect_attributes
8894

@@ -184,6 +190,9 @@ def __init__(self, ui):
184190
]
185191
### End Effects Library ###
186192

193+
## === Cumulative Boolean === ##
194+
self.is_cumulative = False
195+
187196
self.init_ui_connections()
188197

189198
def init_ui_connections(self):
@@ -1103,7 +1112,9 @@ def update_edged_image(new_edged_image):
11031112
nonlocal edged_image
11041113
edged_image = new_edged_image
11051114
# Update the Hough Transform with the new edged image
1106-
hough_effect.update_images(self.current_image_data, self.grayscale_image, edged_image)
1115+
hough_effect.update_images(
1116+
self.current_image_data, self.grayscale_image, edged_image
1117+
)
11071118
# Update the displayed image
11081119
self.current_image.set_output_image(self.output_image)
11091120
self.display_image(self.current_image_data, self.output_image)
@@ -1264,7 +1275,7 @@ def cumulative_pipeline(self):
12641275
Description:
12651276
- Toggles the cumulative pipeline of the applied effects.
12661277
"""
1267-
pass
1278+
self.is_cumulative = not self.is_cumulative
12681279

12691280
# Added Effects table widget functionalities #
12701281
# ========================================== #

App/ImageAlchemyUI.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,12 @@ def setupUi(self, ImgAlchemy):
4848
self.app_grid_layout.setSpacing(0)
4949
self.app_grid_layout.setObjectName("gridLayout")
5050

51+
# # Create the splitter to make the app resizable
52+
# self.splitter = QtWidgets.QSplitter(self.centralwidget)
53+
# self.splitter.setOrientation(QtCore.Qt.Horizontal)
54+
# self.splitter.setObjectName("splitter")
55+
# self.app_grid_layout.addWidget(self.splitter, 0, 0, 1, 1)
56+
5157
# Side Bar: Left Sidebar -> Closed by default #
5258
# =========================================== #
5359

@@ -78,6 +84,18 @@ def setupUi(self, ImgAlchemy):
7884
self.left_bar_expanded = QtWidgets.QWidget(self.centralwidget)
7985
self.left_bar_expanded.setObjectName("left_bar_expanded")
8086
self.left_bar_expanded_VLayout = QtWidgets.QVBoxLayout(self.left_bar_expanded)
87+
# # To add the feature of manual resizing
88+
# self.left_bar_full = QtWidgets.QWidget()
89+
# self.left_bar_full.setObjectName("left_bar_full")
90+
# self.left_bar_full_VLayout = QtWidgets.QVBoxLayout(self.left_bar_full)
91+
# self.left_bar_full_VLayout.setContentsMargins(0, 0, 0, 0)
92+
# self.left_bar_full_VLayout.addWidget(self.left_bar_header)
93+
# ## Make a horizontal layout that holds the views of the left bar and place it into the left bar full layout
94+
# self.left_bar_views_HLayout = QtWidgets.QHBoxLayout()
95+
# self.left_bar_views_HLayout.setContentsMargins(0, 0, 0, 0)
96+
# self.left_bar_views_HLayout.addWidget(self.left_bar_collapsed)
97+
# self.left_bar_views_HLayout.addWidget(self.left_bar_expanded)
98+
# self.left_bar_full_VLayout.addLayout(self.left_bar_views_HLayout)
8199

82100
# Viewport: Image Workspace #
83101
# ========================= #
@@ -156,6 +174,19 @@ def setupUi(self, ImgAlchemy):
156174
self.effect_menu_expanded_VLayout.setContentsMargins(0, 0, 0, 0)
157175
self.effect_menu_expanded.setMinimumWidth(200)
158176

177+
# # To add the feature of manual resizing
178+
# self.effect_menu_full = QtWidgets.QWidget()
179+
# self.effect_menu_full.setObjectName("effect_menu_full")
180+
# self.effect_menu_full_VLayout = QtWidgets.QVBoxLayout(self.effect_menu_full)
181+
# self.effect_menu_full_VLayout.addWidget(self.effect_menu_header)
182+
# self.effect_menu_full_VLayout.setContentsMargins(0, 0, 0, 0)
183+
# ## Make a horizontal layout that holds the views of the effect menu and place it into the effect menu full layout
184+
# self.effect_menu_full_HLayout = QtWidgets.QHBoxLayout()
185+
# self.effect_menu_full_HLayout.setContentsMargins(0, 0, 0, 0)
186+
# self.effect_menu_full_HLayout.addWidget(self.effect_menu_collapsed)
187+
# self.effect_menu_full_HLayout.addWidget(self.effect_menu_expanded)
188+
# self.effect_menu_full_VLayout.addLayout(self.effect_menu_full_HLayout)
189+
159190
# Effect Menu: Expanded View Content #
160191
# ================================== #
161192
# Create a scroll area
@@ -222,6 +253,18 @@ def setupUi(self, ImgAlchemy):
222253
self.control_panel_expanded
223254
)
224255
self.control_panel_expanded_VLayout.setContentsMargins(0, 0, 0, 0)
256+
# # To add the feature of manual resizing
257+
# self.control_panel_full = QtWidgets.QWidget()
258+
# self.control_panel_full.setObjectName("control_panel_full")
259+
# self.control_panel_full_VLayout = QtWidgets.QVBoxLayout(self.control_panel_full)
260+
# self.control_panel_full_VLayout.addWidget(self.control_panel_header)
261+
# self.control_panel_full_VLayout.setContentsMargins(0, 0, 0, 0)
262+
# ## Make a horizontal layout that holds the views of the control panel and place it into the control panel full layout
263+
# self.control_panel_full_HLayout = QtWidgets.QHBoxLayout()
264+
# self.control_panel_full_HLayout.setContentsMargins(0, 0, 0, 0)
265+
# self.control_panel_full_HLayout.addWidget(self.control_panel_collapsed)
266+
# self.control_panel_full_HLayout.addWidget(self.control_panel_expanded)
267+
# self.control_panel_full_VLayout.addLayout(self.control_panel_full_HLayout)
225268

226269
# Control Panel Collapsed Content #
227270
# =============================== #
@@ -514,6 +557,8 @@ def setupUi(self, ImgAlchemy):
514557

515558
# Manage of the app main layout #
516559
# ============================= #
560+
561+
# Before adding the resizability feature
517562
self.app_grid_layout.addWidget(self.left_bar_header, 0, 0, 1, 2)
518563
self.app_grid_layout.addWidget(self.left_bar_collapsed, 1, 0, 1, 1)
519564
self.app_grid_layout.addWidget(self.left_bar_expanded, 1, 1, 1, 1)
@@ -528,6 +573,15 @@ def setupUi(self, ImgAlchemy):
528573
self.app_grid_layout.addWidget(self.control_panel_collapsed, 1, 5, 1, 1)
529574
self.app_grid_layout.addWidget(self.control_panel_expanded, 1, 6, 1, 1)
530575

576+
# # Add widgets to splitter
577+
# self.splitter.addWidget(self.left_bar_full)
578+
# self.splitter.addWidget(self.image_workspace)
579+
# self.splitter.addWidget(self.effect_menu_full)
580+
# self.splitter.addWidget(self.control_panel_full)
581+
582+
# # Set sizes for the sections
583+
# self.splitter.setSizes([200, 400, 200, 300])
584+
531585
self.init_bars_signals_and_slots()
532586
self.retranslateUi(ImgAlchemy)
533587
self.image_workspace.setCurrentIndex(0)

App/TO_DO.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ The app UI meant to be similar to Photoshop UI. It has different sections, each
1818
- When you expand or collapse the control panel, the expanded or the collapsed view of the effects menu appears for a second.
1919
- Themes Feature.
2020
- Make it responsive.
21+
- The error message due to invalid types.
2122

2223
### Digital Image Processing Functions (DIPFs):
2324
_That means "Is this effect was integrated in the app or not?"._
@@ -49,6 +50,7 @@ _That means "Is this effect was integrated in the app or not?"._
4950
- Openning multiple histogram tabs for the same image
5051
- Defining to which image does the tab belong to in its title probably
5152
- In the groupboxes of the effects, the parameters controllers (e.g. sliders and spinboxes) are not actually set to the default values defined in the effect class
53+
- When I used SNAKE, it saved the result. In the same run, I opened buildings image to see the Hough line algorithm. It updated the SNAKE gif with the buildings input and output image.
5254

5355
## Done:
5456

-8 Bytes
Binary file not shown.
217 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)