Sync: headless mode and pre-stack delete mode added to cdk installer. Minor pi model references fixed
326bb62 | """Tests for Review-tab redaction overlay export (drawing + PNG write).""" | |
| from __future__ import annotations | |
| import os | |
| os.environ.setdefault("PYTHONUTF8", "1") | |
| import numpy as np | |
| import pandas as pd | |
| from tools.file_redaction import ( | |
| add_redaction_label_legend, | |
| draw_rectangle_outline_pattern, | |
| ) | |
| from tools.redaction_review import ( | |
| _box_coords_to_pixel_rect, | |
| build_label_to_pattern_map, | |
| visualise_review_redaction_boxes, | |
| ) | |
| def test_box_coords_to_pixel_rect_gradio_pixels_vs_normalized(): | |
| """Gradio uses pixel coords; review data uses 0–1.""" | |
| assert _box_coords_to_pixel_rect(10, 20, 50, 60, 100, 100) == (10, 20, 50, 60) | |
| assert _box_coords_to_pixel_rect(0.1, 0.2, 0.5, 0.6, 100, 100) == ( | |
| 10, | |
| 20, | |
| 50, | |
| 60, | |
| ) | |
| def test_build_label_to_pattern_map_order_stable(): | |
| df = pd.DataFrame({"label": ["Zebra", "Alpha"]}) | |
| m = build_label_to_pattern_map(df, []) | |
| assert m["Alpha"] == "solid" | |
| assert m["Zebra"] == "dashed" | |
| def test_build_label_to_pattern_map_fallback_labels(): | |
| m = build_label_to_pattern_map(pd.DataFrame(), ["B", "A"]) | |
| assert m["A"] == "solid" | |
| assert m["B"] == "dashed" | |
| def test_draw_rectangle_outline_pattern_solid_changes_edge_pixels(): | |
| img = np.zeros((40, 40, 3), dtype=np.uint8) | |
| draw_rectangle_outline_pattern( | |
| img, 10, 10, 30, 30, (0, 255, 0), thickness=2, pattern="solid" | |
| ) | |
| assert int(img[10, 10, 1]) > 200 | |
| def test_add_redaction_label_legend_modifies_top_right(): | |
| img = np.ones((100, 160, 3), dtype=np.uint8) * 200 | |
| corner_before = img[8:25, 135:155].copy() | |
| add_redaction_label_legend( | |
| img, | |
| [((0, 0, 255), "solid", "TestLabel")], | |
| title="Legend", | |
| ) | |
| assert not np.array_equal(img[8:25, 135:155], corner_before) | |
| def test_visualise_review_redaction_boxes_writes_jpeg_under_size_cap(tmp_path): | |
| rgb = np.full((100, 100, 3), 240, dtype=np.uint8) | |
| page = { | |
| "image": rgb, | |
| "boxes": [ | |
| { | |
| "label": "PERSON", | |
| "color": "(255, 0, 0)", | |
| "xmin": 0.1, | |
| "ymin": 0.1, | |
| "xmax": 0.4, | |
| "ymax": 0.3, | |
| }, | |
| { | |
| "label": "EMAIL", | |
| "color": "(0, 128, 0)", | |
| "xmin": 0.5, | |
| "ymin": 0.5, | |
| "xmax": 0.95, | |
| "ymax": 0.85, | |
| }, | |
| ], | |
| } | |
| review_df = pd.DataFrame({"label": ["EMAIL", "PERSON"]}) | |
| out = visualise_review_redaction_boxes( | |
| page, | |
| review_df=review_df, | |
| output_folder=str(tmp_path), | |
| page_number=2, | |
| doc_base_name="mydoc.pdf", | |
| label_abbrev_chars=0, | |
| ) | |
| assert out is not None | |
| assert os.path.isfile(out) | |
| assert "redaction_overlay" in out.replace("\\", "/") | |
| assert out.endswith("_page2_redaction_overlay.jpg") | |
| assert os.path.getsize(out) <= 600 * 1024 | |
| def test_visualise_review_label_abbrev_drawn_when_requested(tmp_path): | |
| import cv2 | |
| rgb = np.full((120, 120, 3), 250, dtype=np.uint8) | |
| page = { | |
| "image": rgb, | |
| "boxes": [ | |
| { | |
| "label": "PERSON", | |
| "color": "(255, 0, 0)", | |
| "xmin": 20, | |
| "ymin": 40, | |
| "xmax": 90, | |
| "ymax": 90, | |
| } | |
| ], | |
| } | |
| out = visualise_review_redaction_boxes( | |
| page, | |
| output_folder=str(tmp_path), | |
| label_abbrev_chars=3, | |
| ) | |
| assert out is not None | |
| bgr = cv2.imread(out) | |
| assert bgr is not None | |
| # Above-box region should differ from flat background once label is drawn | |
| assert bgr[25:38, 45:75].std() > 2.0 | |
| def test_visualise_review_gradio_style_pixel_boxes_draw_visible(tmp_path): | |
| """Absolute pixel boxes (Gradio) must produce a visible outline, not clip to edge.""" | |
| import cv2 | |
| rgb = np.full((100, 100, 3), 250, dtype=np.uint8) | |
| page = { | |
| "image": rgb, | |
| "boxes": [ | |
| { | |
| "label": "R", | |
| "color": "(255, 0, 0)", | |
| "xmin": 10, | |
| "ymin": 10, | |
| "xmax": 45, | |
| "ymax": 40, | |
| } | |
| ], | |
| } | |
| out = visualise_review_redaction_boxes( | |
| page, | |
| output_folder=str(tmp_path), | |
| page_number=1, | |
| doc_base_name="t", | |
| label_abbrev_chars=0, | |
| ) | |
| assert out is not None | |
| bgr = cv2.imread(out) | |
| assert bgr is not None | |
| # Outline at top-left corner should differ from flat 250 background | |
| assert not np.allclose(bgr[10, 10], [250, 250, 250], atol=3) | |
| def test_visualise_review_redaction_boxes_returns_none_without_boxes(tmp_path): | |
| rgb = np.full((20, 20, 3), 200, dtype=np.uint8) | |
| page = {"image": rgb, "boxes": []} | |
| assert visualise_review_redaction_boxes(page, output_folder=str(tmp_path)) is None | |