/
sarus
/
photo-processor
Обзор
Документация
Войти
/
sarus
/
photo-processor
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
tests/test_reporting.py
209 строк
6 KB
otcheskiy
feat: add simple background segmentation and mask quality
01 авг 2026, 12:34
01 авг 2026, 12:34
03c5abe
Код
Авторство
О чём код?
"""Тесты CSV-отчёта и pipeline (этапы 1–2).""" from __future__ import annotations import hashlib from pathlib import Path from PIL import Image, ImageDraw from photo_processor.config import load_config from photo_processor.mask_quality import STAGE2_OK_WARNING from photo_processor.pipeline import STAGE1_WARNING, run_pipeline from photo_processor.reporting import CSV_DELIMITER, ReportRow, write_report from tests.helpers import VALID_CONFIG_TEXT, make_light_scene_jpeg UNICODE_NAME = "WK+ 5111 c.1 55-14-145 мужская, пластик, темная.jpg" def test_csv_utf8_bom_and_delimiter(tmp_path: Path) -> None: report_path = tmp_path / "processing_report.csv" rows = [ ReportRow( timestamp="2026-08-01 10:30:15", source_file=UNICODE_NAME, output_file="", status="SKIPPED", original_width="100", original_height="80", cropped_width="90", cropped_height="74", warning=STAGE1_WARNING, error="", processing_time_ms=1, mask_method="simple", ) ] write_report(report_path, rows) raw = report_path.read_bytes() assert raw.startswith(b"\xef\xbb\xbf") text = report_path.read_text(encoding="utf-8-sig") lines = text.strip().splitlines() assert CSV_DELIMITER in lines[0] assert "original_width" in lines[0] assert "cropped_height" in lines[0] assert "background_brightness" in lines[0] assert "mask_quality_score" in lines[0] assert UNICODE_NAME in lines[1] assert "SKIPPED" in lines[1] def test_pipeline_unicode_and_save_cropped( tmp_path: Path, project_config: Path, ) -> None: input_dir = tmp_path / "input" output_dir = tmp_path / "output" input_dir.mkdir() output_dir.mkdir() source = make_light_scene_jpeg( input_dir / UNICODE_NAME, size=(200, 100), object_box=(60, 30, 140, 70), ) source_hash = hashlib.sha256(source.read_bytes()).hexdigest() config = load_config(project_config) assert config.debug.save_cropped is True result = run_pipeline( input_dir=input_dir, output_dir=output_dir, config=config, ) run_path = result.run_dir.path assert result.stats.total == 1 assert result.stats.errors == 0 assert result.stats.skipped + result.stats.reviews == 1 cropped_path = run_path / "debug" / "cropped" / UNICODE_NAME assert cropped_path.is_file() with Image.open(cropped_path) as cropped: assert cropped.mode == "RGB" assert cropped.size == (180, 94) report_text = result.run_dir.report_path.read_text(encoding="utf-8-sig") assert UNICODE_NAME in report_text assert "200" in report_text assert "180" in report_text assert "simple" in report_text assert STAGE2_OK_WARNING in report_text or "REVIEW" in report_text assert hashlib.sha256(source.read_bytes()).hexdigest() == source_hash def test_pipeline_save_cropped_false(tmp_path: Path) -> None: input_dir = tmp_path / "input" output_dir = tmp_path / "output" input_dir.mkdir() output_dir.mkdir() make_light_scene_jpeg( input_dir / "plain.jpg", size=(100, 100), object_box=(30, 30, 70, 70), ) config_path = tmp_path / "config.yaml" text = VALID_CONFIG_TEXT for key in ( "save_cropped", "save_raw_mask", "save_clean_mask", "save_bbox", "save_foreground_preview", ): text = text.replace(f"{key}: true", f"{key}: false") config_path.write_text(text + "\n", encoding="utf-8") config = load_config(config_path) result = run_pipeline(input_dir=input_dir, output_dir=output_dir, config=config) cropped_dir = result.run_dir.debug_dir / "cropped" assert not cropped_dir.exists() or not any(cropped_dir.iterdir()) report_text = result.run_dir.report_path.read_text(encoding="utf-8-sig") data_line = [line for line in report_text.splitlines() if "plain.jpg" in line][-1] parts = data_line.split(";") assert parts[1] == "plain.jpg" assert parts[2] == "" assert parts[3] in {"SKIPPED", "REVIEW"} assert "90" in parts def test_corrupted_then_valid_continues(tmp_path: Path, project_config: Path) -> None: input_dir = tmp_path / "input" output_dir = tmp_path / "output" input_dir.mkdir() output_dir.mkdir() bad = input_dir / "aaa_broken.jpg" bad.write_bytes(b"not-an-image") make_light_scene_jpeg( input_dir / "bbb_ok.jpg", size=(160, 120), object_box=(50, 40, 110, 80), ) source_ok = input_dir / "bbb_ok.jpg" source_hash = hashlib.sha256(source_ok.read_bytes()).hexdigest() config = load_config(project_config) result = run_pipeline(input_dir=input_dir, output_dir=output_dir, config=config) assert result.stats.total == 2 assert result.stats.errors == 1 assert result.stats.skipped + result.stats.reviews == 1 report_text = result.run_dir.report_path.read_text(encoding="utf-8-sig") assert "aaa_broken.jpg" in report_text assert "bbb_ok.jpg" in report_text assert "ERROR" in report_text log_text = result.run_dir.log_path.read_text(encoding="utf-8") assert "aaa_broken.jpg" in log_text cropped_ok = result.run_dir.debug_dir / "cropped" / "bbb_ok.jpg" assert cropped_ok.is_file() assert hashlib.sha256(source_ok.read_bytes()).hexdigest() == source_hash assert bad.is_file() def test_pipeline_creates_structure( tmp_path: Path, project_config: Path, ) -> None: input_dir = tmp_path / "input" output_dir = tmp_path / "output" input_dir.mkdir() output_dir.mkdir() png_path = input_dir / "plain.png" image = Image.new("RGB", (120, 100), (245, 245, 245)) ImageDraw.Draw(image).rectangle((35, 30, 85, 70), fill=(20, 20, 20)) image.save(png_path, format="PNG") make_light_scene_jpeg( input_dir / "other.jpg", size=(120, 100), object_box=(35, 30, 85, 70), ) (input_dir / "ignore.txt").write_text("nope", encoding="utf-8") config = load_config(project_config) result = run_pipeline( input_dir=input_dir, output_dir=output_dir, config=config, ) run_path = result.run_dir.path assert (run_path / "images").is_dir() assert (run_path / "review").is_dir() assert (run_path / "debug").is_dir() assert (run_path / "processing_report.csv").is_file() assert (run_path / "run.log").is_file() assert result.stats.total == 2 assert result.stats.errors == 0