Skip to content

Commit 82ed105

Browse files
committed
Updated handling of images of various formats when loading them, urban tour of the application, updated LLM output via a standard with a dictionary, a new model for detecting scale
1 parent 357c809 commit 82ed105

File tree

12 files changed

+102
-695
lines changed

12 files changed

+102
-695
lines changed

particleanalyzer/app.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,14 @@ def assets_path(name: str):
99

1010
def main(port=8000, api_key=""):
1111
demo = create_interface(api_key)
12-
demo.queue(default_concurrency_limit=5, api_open=False).launch(
12+
demo.queue(default_concurrency_limit=5, api_open=True).launch(
1313
server_name="127.0.0.1",
1414
server_port=port,
1515
pwa=True,
1616
favicon_path=f'{assets_path("")}/icon/favicon.png',
1717
i18n=i18n,
1818
ssr_mode=True,
19-
show_api=False,
19+
show_api=True,
2020
inbrowser=True,
2121
allowed_paths=[assets_path("")],
2222
)

particleanalyzer/cli.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ def main():
1414
help="Port to run the application on (default: 8000)",
1515
)
1616
run_parser.add_argument(
17-
"--api-key", type=str, default="", help="Hugging Face API key for LLM inference"
17+
"--api-key", type=str, default="", help="The OpenRouter or Hugging Face API key for LLM output"
1818
)
1919

2020
run_parser.set_defaults(func=run_app)

particleanalyzer/core/LLMAnalysis.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ def analyze(self, df: pd.DataFrame, model_llm: str) -> List[Tuple[None, str]]:
103103
self.lang = LanguageContext.get_language()
104104

105105
if df.empty:
106-
return [(None, "No particles detected")]
106+
return [{"role": "assistant", "content": "No particles detected"}]
107107

108108
# Вычисляем статистику
109109
stats = self._calculate_stats(df)
@@ -116,7 +116,7 @@ def analyze(self, df: pd.DataFrame, model_llm: str) -> List[Tuple[None, str]]:
116116
return self._format_response(response)
117117
except Exception as e:
118118
print(f"LLM analysis failed: {str(e)}")
119-
return [(None, f"Analysis error: {str(e)}")]
119+
return [{"role": "assistant", "content": f"Analysis error: {str(e)}"}]
120120

121121
def _build_prompt(self, stats, count_particles) -> str:
122122
return f"""
@@ -192,4 +192,4 @@ def _format_response(self, response) -> List[Tuple[None, str]]:
192192
analysis = response.choices[0].message.content
193193
elif self.provider == "huggingface":
194194
analysis = response.choices[0].message.content
195-
return [(None, analysis)]
195+
return [{"role": "assistant", "content": analysis}]

particleanalyzer/core/ParticleAnalyzer.py

Lines changed: 85 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -372,7 +372,7 @@ def _process_with_onnx(self, **config):
372372

373373
try:
374374
# Просто передаем numpy массив
375-
detections = self.model_manager.predict(
375+
results = self.model_manager.predict(
376376
model_name=config["model_change"],
377377
image_np=config["image"], # numpy массив
378378
confidence_threshold=config["confidence_threshold"],
@@ -383,10 +383,10 @@ def _process_with_onnx(self, **config):
383383
self._handle_error(e)
384384
return None, None, None
385385

386-
if len(detections) == 0:
386+
if len(results) == 0:
387387
gr.Info(self._get_translation("Объекты не обнаружены."))
388388
return None, None, None
389-
elif len(detections) == config["number_detections"]:
389+
elif len(results) == config["number_detections"]:
390390
gr.Info(
391391
self._get_translation(
392392
"Достигнут предел количества обнаружений. Увеличьте максимальное количество обнаружений в настройках."
@@ -405,8 +405,8 @@ def _process_with_onnx(self, **config):
405405
particle_counter, particle_data, annotations = 1, [], []
406406

407407
# Обработка детекций
408-
for i in range(len(detections.xyxy)):
409-
mask = detections.mask[i] if detections.mask is not None else None
408+
for i in range(len(results.xyxy)):
409+
mask = results.mask[i] if results.mask is not None else None
410410

411411
if mask is not None:
412412
contours, _ = cv2.findContours(
@@ -445,6 +445,7 @@ def _process_with_yolo(self, **config):
445445
with torch.no_grad():
446446
results = model(
447447
config["image"],
448+
imgsz=640,
448449
verbose=False,
449450
conf=config["confidence_threshold"],
450451
retina_masks=True,
@@ -900,37 +901,96 @@ def _cleanup(self, pbar: Optional[tqdm] = None):
900901
"""Очистка ресурсов"""
901902
if pbar:
902903
pbar.close()
904+
903905
gc.collect()
906+
904907
if torch.cuda.is_available():
908+
# Синхронизация перед очисткой
909+
torch.cuda.synchronize()
910+
# Очистка кэша CUDA
905911
torch.cuda.empty_cache()
912+
# Сброс статистики памяти
913+
torch.cuda.reset_peak_memory_stats()
906914

907-
def _img_to_numpy_array(self, file_path, max_size_kb=500, quality=85):
915+
def _img_to_numpy_array(self, file_path: str, max_size_kb: int = 500, quality: int = 85) -> Optional[np.ndarray]:
916+
"""
917+
Конвертирует изображение в numpy array с обработкой различных форматов и оптимизацией размера.
918+
919+
Args:
920+
file_path: Путь к файлу изображения
921+
max_size_kb: Максимальный размер в KB (по умолчанию 500)
922+
quality: Качество JPEG сжатия (по умолчанию 85)
923+
924+
Returns:
925+
np.ndarray или None в случае ошибки
926+
"""
908927
try:
909928
with Image.open(file_path) as img:
910-
img_byte_arr = io.BytesIO()
911-
912-
img.save(img_byte_arr, format="PNG", optimize=True)
913-
914-
current_size_kb = len(img_byte_arr.getvalue()) / 1024
915-
929+
original_mode = img.mode
930+
931+
# Обработка специальных режимов изображений
932+
if img.mode in ['I', 'I;16', 'I;16B', 'I;16L', 'F']:
933+
img_array = np.array(img)
934+
935+
# Нормализация в 8-бит для специальных форматов
936+
if img_array.dtype in [np.uint16, np.int16]:
937+
img_array = (img_array / 256).astype(np.uint8)
938+
elif img_array.dtype in [np.float32, np.float64]:
939+
img_array = (img_array * 255).astype(np.uint8)
940+
elif img_array.dtype in [np.int32, np.int64]:
941+
img_array = (img_array / (img_array.max() / 255)).astype(np.uint8)
942+
943+
img = Image.fromarray(img_array)
944+
945+
elif img.mode == 'CMYK':
946+
img = img.convert('RGB')
947+
948+
elif img.mode in ('RGBA', 'LA', 'P'):
949+
# Обработка прозрачности и палитровых изображений
950+
background = Image.new('RGB', img.size, (255, 255, 255))
951+
952+
if img.mode == 'P':
953+
img = img.convert('RGBA')
954+
955+
if img.mode in ('RGBA', 'LA'):
956+
bands = img.split()
957+
alpha = bands[-1]
958+
959+
# Черный фон для полностью прозрачных изображений
960+
if np.array(alpha).max() == 0:
961+
background = Image.new('RGB', img.size, (0, 0, 0))
962+
963+
background.paste(img, mask=img.split()[-1] if img.mode in ('RGBA', 'LA') else None)
964+
img = background
965+
966+
elif img.mode != 'RGB':
967+
img = img.convert('RGB')
968+
969+
# Проверка и коррекция белых изображений
970+
test_array = np.array(img)
971+
if np.all(test_array >= 250):
972+
with Image.open(file_path) as img_alt:
973+
alt_array = np.array(img_alt)
974+
if alt_array.max() > 0 and alt_array.max() <= 255:
975+
normalized = (alt_array / alt_array.max() * 255).astype(np.uint8)
976+
img = Image.fromarray(normalized)
977+
978+
# Оптимизация размера файла
979+
with io.BytesIO() as buffer:
980+
img.save(buffer, format='JPEG', quality=quality)
981+
current_size_kb = len(buffer.getvalue()) / 1024
982+
916983
if current_size_kb > max_size_kb:
917984
ratio = (max_size_kb / current_size_kb) ** 0.5
918-
new_size = (int(img.size[0] * ratio), int(img.size[1] * ratio))
985+
new_size = (max(1, int(img.size[0] * ratio)), max(1, int(img.size[1] * ratio)))
919986
img = img.resize(new_size, Image.Resampling.LANCZOS)
920-
921-
img_byte_arr = io.BytesIO()
922-
img.save(img_byte_arr, format="PNG", optimize=True)
923-
924-
img_byte_arr.seek(0)
925-
compressed_img = Image.open(img_byte_arr)
926-
927-
return np.array(compressed_img)
928-
929-
except (IOError, OSError, ValueError) as e:
987+
988+
return np.array(img)
989+
990+
except (IOError, OSError, ValueError, Exception):
930991
gr.Info(self._get_translation("Не поддерживаемый формат изображения."))
931-
print(f"Ошибка при загрузке изображения {file_path}: {e}")
932992
return None
933-
993+
934994
def handle_file_upload(self, file, scale_selector, request: gr.Request):
935995
# Быстрая проверка на None файл
936996
if file is None:

particleanalyzer/core/YOLOLoader.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ class YOLOLoader:
66
MODEL_MAPPING = {
77
"Yolo11 (dataset 9)": "Yolo11_d10_batch45.pt",
88
"Yolo12 (dataset 9)": "Yolo12_d10_batch45.pt",
9-
"ScaleProcessor": "ScaleProcessor_dataset8_RT-DETR.pt",
9+
"ScaleProcessor": "ScaleProcessor_dataset9_RT-DETR.pt",
1010
}
1111

1212
def __init__(self):

0 commit comments

Comments
 (0)