Skip to content

Commit 6fa27f9

Browse files
committed
The individual particle analysis feature has been updated, adding the ability to turn on/off the borders and fill of segmented particles, and adding the ability to select the outline color.
1 parent 2ba8891 commit 6fa27f9

File tree

9 files changed

+281
-151
lines changed

9 files changed

+281
-151
lines changed

Images/Filtering data.mp4

7.75 MB
Binary file not shown.

particleanalyzer/core/ParticleAnalyzer.py

Lines changed: 117 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
from tqdm import tqdm
1010
from typing import Optional, Tuple
1111
from scipy.spatial.distance import pdist
12+
import random
13+
import re
1214

1315
try:
1416
from detectron2.engine import DefaultPredictor
@@ -122,7 +124,6 @@ def _create_error_return(self) -> Tuple:
122124
None,
123125
gr.update(visible=False),
124126
gr.update(visible=False),
125-
gr.update(visible=False),
126127
None,
127128
None,
128129
None,
@@ -152,8 +153,10 @@ def analyze_image(
152153
overlap_width_ratio: float,
153154
sahi_mode: bool,
154155
number_of_bins: int,
155-
segment_mode: bool,
156156
show_Feret_diametr: bool,
157+
outline_color,
158+
show_fillPoly,
159+
show_polylines,
157160
api_key: bool,
158161
request: gr.Request,
159162
pr=gr.Progress(),
@@ -223,6 +226,9 @@ def analyze_image(
223226
"scale": scale,
224227
"scale_factor_glob": scale_factor_glob,
225228
"show_Feret_diametr": show_Feret_diametr,
229+
"outline_color": outline_color,
230+
"show_fillPoly": show_fillPoly,
231+
"show_polylines": show_polylines,
226232
}
227233

228234
# Выбор стратегии обработки
@@ -233,6 +239,8 @@ def analyze_image(
233239

234240
output_image = cv2.cvtColor(output_image, cv2.COLOR_BGR2RGB)
235241
df = pd.DataFrame(particle_data)
242+
points_df = pd.DataFrame(df["points"])
243+
df = df.drop(columns=["points"])
236244

237245
pbar.set_description(self._get_translation("Построение таблицы..."))
238246
pr(0.75, desc=self._get_translation("Построение таблицы..."))
@@ -245,20 +253,42 @@ def analyze_image(
245253
lang=self.lang,
246254
)
247255
stats_df = builder.build_stats_table()
248-
d_max_max = stats_df.data.iloc[1, 3] + stats_df.data.iloc[0, 3] * 0.01
249-
d_max_min = stats_df.data.iloc[1, 4]
250-
d_min_max = stats_df.data.iloc[2, 3] + stats_df.data.iloc[1, 3] * 0.01
251-
d_min_min = stats_df.data.iloc[2, 4]
252-
theta_max_max = stats_df.data.iloc[4, 3] + stats_df.data.iloc[4, 3] * 0.01
253-
theta_max_min = stats_df.data.iloc[4, 4]
254-
theta_min_max = stats_df.data.iloc[5, 3] + stats_df.data.iloc[5, 3] * 0.01
255-
theta_min_min = stats_df.data.iloc[5, 4]
256-
S_max = stats_df.data.iloc[6, 3] + stats_df.data.iloc[6, 3] * 0.01
257-
S_min = stats_df.data.iloc[6, 4]
258-
P_max = stats_df.data.iloc[7, 3] + stats_df.data.iloc[7, 3] * 0.01
259-
P_min = stats_df.data.iloc[7, 4]
260-
I_max = stats_df.data.iloc[9, 3] + stats_df.data.iloc[9, 3] * 0.01
261-
I_min = stats_df.data.iloc[9, 4]
256+
257+
d_max_max = round(
258+
stats_df.data.iloc[1, 3] + stats_df.data.iloc[0, 3] * 0.01,
259+
config["round_value"],
260+
)
261+
d_max_min = round(stats_df.data.iloc[1, 4], config["round_value"])
262+
d_min_max = round(
263+
stats_df.data.iloc[2, 3] + stats_df.data.iloc[1, 3] * 0.01,
264+
config["round_value"],
265+
)
266+
d_min_min = round(stats_df.data.iloc[2, 4], config["round_value"])
267+
theta_max_max = round(
268+
stats_df.data.iloc[4, 3] + stats_df.data.iloc[4, 3] * 0.01,
269+
config["round_value"],
270+
)
271+
theta_max_min = round(stats_df.data.iloc[4, 4], config["round_value"])
272+
theta_min_max = round(
273+
stats_df.data.iloc[5, 3] + stats_df.data.iloc[5, 3] * 0.01,
274+
config["round_value"],
275+
)
276+
theta_min_min = round(stats_df.data.iloc[5, 4], config["round_value"])
277+
S_max = round(
278+
stats_df.data.iloc[6, 3] + stats_df.data.iloc[6, 3] * 0.01,
279+
config["round_value"],
280+
)
281+
S_min = round(stats_df.data.iloc[6, 4], config["round_value"])
282+
P_max = round(
283+
stats_df.data.iloc[7, 3] + stats_df.data.iloc[7, 3] * 0.01,
284+
config["round_value"],
285+
)
286+
P_min = round(stats_df.data.iloc[7, 4], config["round_value"])
287+
I_max = round(
288+
stats_df.data.iloc[9, 3] + stats_df.data.iloc[9, 3] * 0.01,
289+
config["round_value"],
290+
)
291+
I_min = round(stats_df.data.iloc[9, 4], config["round_value"])
262292

263293
pbar.update(1)
264294

@@ -270,30 +300,51 @@ def analyze_image(
270300
return (
271301
output_image,
272302
df,
303+
points_df,
273304
fig,
274305
stats_df,
275-
(orig_image, annotations) if segment_mode else None,
276-
gr.update(visible=segment_mode),
277306
gr.update(visible=api_key),
278307
gr.update(visible=True),
279-
gr.update(minimum=d_max_min, maximum=d_max_max, value=(d_max_min, d_max_max), label=f"Dₘₐₓ [{self._get_translation(scale_selector['unit'])}]"),
280-
gr.update(minimum=d_min_min, maximum=d_min_max, value=(d_min_min, d_min_max), label=f"Dₘᵢₙ [{self._get_translation(scale_selector['unit'])}]"),
281308
gr.update(
282-
minimum=theta_max_min, maximum=theta_max_max, value=(theta_max_min, theta_max_max)
309+
minimum=d_max_min,
310+
maximum=d_max_max,
311+
value=(d_max_min, d_max_max),
312+
step=d_max_max * 0.01,
313+
label=f"Dₘₐₓ [{self._get_translation(scale_selector['unit'])}]",
283314
),
284315
gr.update(
285-
minimum=theta_min_min, maximum=theta_min_max, value=(theta_min_min, theta_min_max)
316+
minimum=d_min_min,
317+
maximum=d_min_max,
318+
value=(d_min_min, d_min_max),
319+
step=d_min_max * 0.01,
320+
label=f"Dₘᵢₙ [{self._get_translation(scale_selector['unit'])}]",
286321
),
287-
gr.update(minimum=0, maximum=1, value=(0, 1)),
288322
gr.update(
289-
minimum=S_min, maximum=S_max, value=(S_min, S_max), label=f"S [{self._get_translation(scale_selector['unit'])}²]"
323+
minimum=theta_max_min,
324+
maximum=theta_max_max,
325+
value=(theta_max_min, theta_max_max),
290326
),
291327
gr.update(
292-
minimum=P_min, maximum=P_max, value=(P_min, P_max), label=f"P [{self._get_translation(scale_selector['unit'])}]"
328+
minimum=theta_min_min,
329+
maximum=theta_min_max,
330+
value=(theta_min_min, theta_min_max),
293331
),
332+
gr.update(minimum=0, maximum=1, value=(0, 1)),
294333
gr.update(
295-
minimum=I_min, maximum=I_max, value=(I_min, I_max)
334+
minimum=S_min,
335+
maximum=S_max,
336+
value=(S_min, S_max),
337+
step=S_max * 0.01,
338+
label=f"S [{self._get_translation(scale_selector['unit'])}²]",
296339
),
340+
gr.update(
341+
minimum=P_min,
342+
maximum=P_max,
343+
value=(P_min, P_max),
344+
step=P_max * 0.01,
345+
label=f"P [{self._get_translation(scale_selector['unit'])}]",
346+
),
347+
gr.update(minimum=I_min, maximum=I_max, value=(I_min, I_max)),
297348
gr.update(visible=True),
298349
)
299350
except Exception as e:
@@ -570,13 +621,27 @@ def get_feret(contour, angles=np.arange(0, 180, 1)):
570621
mean_intensity = cv2.mean(config["gray_image"], mask=mask_img)[0]
571622

572623
# Отрисовка контура и Feret-линий (опционально)
573-
cv2.polylines(
574-
config["output_image"],
575-
[points],
576-
isClosed=True,
577-
color=(0, 255, 0),
578-
thickness=config["thickness"],
579-
)
624+
if config["show_fillPoly"]:
625+
overlay = config["output_image"].copy()
626+
cv2.fillPoly(overlay, [points], self.get_random_color())
627+
alpha = 0.3
628+
cv2.addWeighted(
629+
overlay,
630+
alpha,
631+
config["output_image"],
632+
1 - alpha,
633+
0,
634+
config["output_image"],
635+
)
636+
if config["show_polylines"]:
637+
cv2.polylines(
638+
config["output_image"],
639+
[points],
640+
isClosed=True,
641+
color=self.rgba_to_bgr(config["outline_color"]),
642+
thickness=config["thickness"],
643+
)
644+
580645
if config["show_Feret_diametr"]:
581646
self._draw_feret_lines(
582647
config["output_image"], points, angle_max, (0, 255, 255)
@@ -610,8 +675,6 @@ def get_feret(contour, angles=np.arange(0, 180, 1)):
610675
config["particle_data"].append(
611676
{
612677
"№": round(config["particle_counter"], config["round_value"]),
613-
"centroid_x": round(centroid_x, config["round_value"]),
614-
"centroid_y": round(centroid_y, config["round_value"]),
615678
"D [{}]".format(unit): round(
616679
diameter * scale_factor, config["round_value"]
617680
),
@@ -634,6 +697,8 @@ def get_feret(contour, angles=np.arange(0, 180, 1)):
634697
f'I [{self._get_translation("ед.")}]': round(
635698
mean_intensity, config["round_value"]
636699
),
700+
"centroid_x": round(centroid_x, config["round_value"]),
701+
"centroid_y": round(centroid_y, config["round_value"]),
637702
"points": points.tolist(),
638703
}
639704
)
@@ -700,6 +765,23 @@ def _handle_gpu_error(self, error: Exception):
700765
)
701766
)
702767

768+
@staticmethod
769+
def get_random_color():
770+
"""Генерирует случайный цвет в BGR формате"""
771+
return (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
772+
773+
@staticmethod
774+
def rgba_to_bgr(rgba_str):
775+
numbers = re.findall(r"[\d.]+", rgba_str)
776+
if len(numbers) < 3:
777+
raise ValueError("Неправильный формат rgba")
778+
779+
r = int(float(numbers[0]))
780+
g = int(float(numbers[1]))
781+
b = int(float(numbers[2]))
782+
783+
return (b, g, r)
784+
703785
def _cleanup(self, pbar: Optional[tqdm] = None):
704786
"""Очистка ресурсов"""
705787
if pbar:

particleanalyzer/core/languages.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,9 @@
131131
Я – ваш виртуальный ассистент в сканирующей электронной микроскопии. Готов провести детальный анализ морфологии и размерных характеристик частиц.""",
132132
"Видео инструкция": "Видео инструкция",
133133
"Параметры фильтрации": "Параметры фильтрации",
134+
"Включить контур?": "Включить контур?",
135+
"Цвет контура": "Цвет контура",
136+
"Включить заливку?": "Включить заливку?",
134137
},
135138
"en": {
136139
"Подготовка...": "Initializing...",
@@ -250,6 +253,9 @@
250253
I am your virtual assistant for scanning electron microscopy. Ready to perform detailed analysis of particle morphology and size characteristics.""",
251254
"Видео инструкция": "Video instructions",
252255
"Параметры фильтрации": "Filtering Options",
256+
"Включить контур?": "Enable outline?",
257+
"Цвет контура": "Outline color",
258+
"Включить заливку?": "Enable fill?",
253259
},
254260
"zh-cn": {
255261
"Подготовка...": "初始化中...",
@@ -369,7 +375,9 @@
369375
我是您的电子显微镜虚拟助手,可对颗粒形貌和尺寸特征进行详细分析。""",
370376
"Видео инструкция": "视频说明",
371377
"Параметры фильтрации": "过滤选项",
372-
378+
"Включить контур?": "启用轮廓?",
379+
"Цвет контура": "轮廓颜色",
380+
"Включить заливку?": "启用填充?",
373381
},
374382
"zh-tw": {
375383
"Подготовка...": "初始化中...",
@@ -497,6 +505,9 @@
497505
我是您的電子顯微鏡虛擬助手,可對顆粒形貌和尺寸特徵進行詳細分析。""",
498506
"Видео инструкция": "影片說明",
499507
"Параметры фильтрации": "過濾選項",
508+
"Включить контур?": "啟用輪廓?",
509+
"Цвет контура": "輪廓顏色",
510+
"Включить заливку?": "啟用填充?",
500511
},
501512
}
502513

0 commit comments

Comments
 (0)