mirror of
https://github.com/kuhyx/testsAndMisc-archive.git
synced 2026-07-04 16:43:08 +02:00
- Add comprehensive tests for all packages (3572 tests, 100% branch coverage) - Split oversized test files to stay under 500-line limit - Add per-file ruff ignores for test-appropriate suppressions - Fix _cache_decks.py to properly convert JSON lists to tuples - Add session-scoped conftest fixture for logging handler cleanup (Python 3.14) - Update ruff pre-commit hook to v0.15.2 - Add codespell ignore words for test data - Add generated output files to .gitignore
496 lines
14 KiB
Python
496 lines
14 KiB
Python
"""Tests for Q23 image-segmentation diagram modules (BATCH 3 / GROUP 1).
|
|
|
|
Covers:
|
|
- _q23_common.py (constants, _save_figure, _render_text_lines)
|
|
- _q23_architectures.py (generate_fcn, generate_unet)
|
|
- _q23_diy_unet.py (generate_diy_unet, _draw_unet_layer_stack,
|
|
_draw_unet_pseudocode)
|
|
- _q23_mean_shift_ncuts.py (generate_mean_shift, generate_normalized_cuts,
|
|
_draw_ncuts_pixel_grid, _draw_ncuts_edges)
|
|
- _q23_mnemonics.py (generate_mnemonics)
|
|
- _q23_nn_basics.py (generate_relu, generate_dot_product)
|
|
- _q23_otsu_watershed.py (generate_otsu_bimodal, generate_watershed,
|
|
_draw_otsu_variance_panel, _draw_watershed_result_panel)
|
|
- _q23_receptive_transformer.py (generate_receptive_field, generate_transformer)
|
|
- _q23_region_diy.py (generate_region_growing, generate_diy_thresholding,
|
|
_draw_region_growing_grid, _draw_bfs_expansion,
|
|
_draw_otsu_variance_and_pseudocode)
|
|
- generate_q23_diagrams.py (__all__, imports, __main__ block)
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import matplotlib as mpl
|
|
|
|
mpl.use("Agg")
|
|
import matplotlib.pyplot as plt
|
|
import numpy as np
|
|
import pytest
|
|
|
|
pytestmark = pytest.mark.usefixtures("_no_savefig")
|
|
|
|
|
|
# ── _q23_common ───────────────────────────────────────────────────────
|
|
|
|
|
|
class TestQ23Constants:
|
|
"""Module-level constants and singletons."""
|
|
|
|
def test_dpi(self) -> None:
|
|
from _q23_common import DPI
|
|
|
|
assert DPI == 300
|
|
|
|
def test_output_dir_is_str(self) -> None:
|
|
from _q23_common import OUTPUT_DIR
|
|
|
|
assert isinstance(OUTPUT_DIR, str)
|
|
|
|
def test_color_constants(self) -> None:
|
|
from _q23_common import (
|
|
ACCENT,
|
|
ACCENT_LIGHT,
|
|
BLACK,
|
|
GRAY1,
|
|
GRAY2,
|
|
GRAY3,
|
|
GRAY4,
|
|
GRAY5,
|
|
GRAY6,
|
|
GREEN_ACCENT,
|
|
RED_ACCENT,
|
|
WHITE,
|
|
)
|
|
|
|
colors = [
|
|
BLACK,
|
|
WHITE,
|
|
GRAY1,
|
|
GRAY2,
|
|
GRAY3,
|
|
GRAY4,
|
|
GRAY5,
|
|
GRAY6,
|
|
ACCENT,
|
|
ACCENT_LIGHT,
|
|
RED_ACCENT,
|
|
GREEN_ACCENT,
|
|
]
|
|
assert all(isinstance(c, str) and c.startswith("#") for c in colors)
|
|
|
|
def test_font_size_constants(self) -> None:
|
|
from _q23_common import FS, FS_SMALL, FS_TINY, FS_TITLE
|
|
|
|
assert FS_TITLE > FS > FS_SMALL > FS_TINY
|
|
|
|
def test_threshold_constants(self) -> None:
|
|
from _q23_common import (
|
|
_BRIGHT_THRESHOLD,
|
|
_DARK_PIXEL_THRESHOLD,
|
|
_GRID_LAST_IDX,
|
|
_HIGHLIGHT_END,
|
|
_HIGHLIGHT_START,
|
|
_OTSU_THRESHOLD,
|
|
_RIDGE_X,
|
|
_VALLEY2_END,
|
|
)
|
|
|
|
assert _DARK_PIXEL_THRESHOLD == 100
|
|
assert _GRID_LAST_IDX == 3
|
|
assert _HIGHLIGHT_START == 3
|
|
assert _HIGHLIGHT_END == 5
|
|
assert _BRIGHT_THRESHOLD == 170
|
|
assert _OTSU_THRESHOLD == 128
|
|
assert _RIDGE_X == 5
|
|
assert _VALLEY2_END == 9
|
|
|
|
def test_rng_exists(self) -> None:
|
|
from _q23_common import rng
|
|
|
|
assert rng is not None
|
|
|
|
|
|
class TestQ23SaveFigure:
|
|
"""_save_figure from _q23_common."""
|
|
|
|
def test_runs(self) -> None:
|
|
from _q23_common import _save_figure
|
|
|
|
_fig, _ax = plt.subplots()
|
|
_save_figure("test_q23_save.png")
|
|
|
|
|
|
class TestQ23RenderTextLines:
|
|
"""_render_text_lines from _q23_common."""
|
|
|
|
def test_basic_lines(self) -> None:
|
|
from _q23_common import _render_text_lines
|
|
|
|
fig, ax = plt.subplots()
|
|
ax.set_xlim(0, 10)
|
|
ax.set_ylim(0, 10)
|
|
lines = [
|
|
("Hello", 10, "black", "bold"),
|
|
("World", 8, "gray", "normal"),
|
|
]
|
|
_render_text_lines(ax, lines, start_y=9.0)
|
|
plt.close(fig)
|
|
|
|
def test_empty_line_gaps(self) -> None:
|
|
from _q23_common import _render_text_lines
|
|
|
|
fig, ax = plt.subplots()
|
|
ax.set_xlim(0, 10)
|
|
ax.set_ylim(0, 10)
|
|
lines = [
|
|
("First", 10, "black", "bold"),
|
|
("", 0, "", ""),
|
|
("After gap", 10, "black", "normal"),
|
|
]
|
|
_render_text_lines(ax, lines, start_y=9.0, y_step=0.5, y_empty_step=0.3)
|
|
plt.close(fig)
|
|
|
|
def test_custom_x_pos(self) -> None:
|
|
from _q23_common import _render_text_lines
|
|
|
|
fig, ax = plt.subplots()
|
|
ax.set_xlim(0, 10)
|
|
ax.set_ylim(0, 10)
|
|
lines = [("Test", 10, "red", "normal")]
|
|
_render_text_lines(ax, lines, x_pos=0.3, start_y=8.0)
|
|
plt.close(fig)
|
|
|
|
|
|
# ── _q23_architectures ───────────────────────────────────────────────
|
|
|
|
|
|
class TestGenerateFCN:
|
|
"""generate_fcn from _q23_architectures."""
|
|
|
|
def test_runs(self) -> None:
|
|
from _q23_architectures import generate_fcn
|
|
|
|
generate_fcn()
|
|
|
|
|
|
class TestGenerateUNet:
|
|
"""generate_unet from _q23_architectures."""
|
|
|
|
def test_runs(self) -> None:
|
|
from _q23_architectures import generate_unet
|
|
|
|
generate_unet()
|
|
|
|
|
|
# ── _q23_diy_unet ────────────────────────────────────────────────────
|
|
|
|
|
|
class TestDrawUnetLayerStack:
|
|
"""_draw_unet_layer_stack from _q23_diy_unet."""
|
|
|
|
def test_without_skip(self) -> None:
|
|
from _q23_diy_unet import _draw_unet_layer_stack
|
|
|
|
fig, ax = plt.subplots()
|
|
_draw_unet_layer_stack(
|
|
ax,
|
|
[(64, 3), (32, 64), (16, 128)],
|
|
face_color="#B3D4FC",
|
|
edge_color="#4A90D9",
|
|
arrow_color="#4A90D9",
|
|
arrow_label="Conv+Pool",
|
|
)
|
|
plt.close(fig)
|
|
|
|
def test_with_skip(self) -> None:
|
|
from _q23_diy_unet import _draw_unet_layer_stack
|
|
|
|
fig, ax = plt.subplots()
|
|
_draw_unet_layer_stack(
|
|
ax,
|
|
[(8, 256), (16, 128), (32, 64)],
|
|
face_color="#C8E6C9",
|
|
edge_color="#388E3C",
|
|
arrow_color="#388E3C",
|
|
arrow_label="UpConv+Concat",
|
|
add_skip=True,
|
|
)
|
|
plt.close(fig)
|
|
|
|
def test_single_layer_no_arrows(self) -> None:
|
|
from _q23_diy_unet import _draw_unet_layer_stack
|
|
|
|
fig, ax = plt.subplots()
|
|
_draw_unet_layer_stack(
|
|
ax,
|
|
[(64, 3)],
|
|
face_color="#B3D4FC",
|
|
edge_color="#4A90D9",
|
|
arrow_color="#4A90D9",
|
|
arrow_label="X",
|
|
)
|
|
plt.close(fig)
|
|
|
|
|
|
class TestDrawUnetPseudocode:
|
|
"""_draw_unet_pseudocode from _q23_diy_unet."""
|
|
|
|
def test_runs(self) -> None:
|
|
from _q23_diy_unet import _draw_unet_pseudocode
|
|
|
|
fig, ax = plt.subplots()
|
|
_draw_unet_pseudocode(ax)
|
|
plt.close(fig)
|
|
|
|
|
|
class TestGenerateDiyUnet:
|
|
"""generate_diy_unet from _q23_diy_unet."""
|
|
|
|
@pytest.mark.filterwarnings("ignore::UserWarning")
|
|
def test_runs(self) -> None:
|
|
from _q23_diy_unet import generate_diy_unet
|
|
|
|
generate_diy_unet()
|
|
|
|
|
|
# ── _q23_mean_shift_ncuts ────────────────────────────────────────────
|
|
|
|
|
|
class TestGenerateMeanShift:
|
|
"""generate_mean_shift from _q23_mean_shift_ncuts."""
|
|
|
|
def test_runs(self) -> None:
|
|
from _q23_mean_shift_ncuts import generate_mean_shift
|
|
|
|
generate_mean_shift()
|
|
|
|
|
|
class TestDrawNcutsPixelGrid:
|
|
"""_draw_ncuts_pixel_grid from _q23_mean_shift_ncuts."""
|
|
|
|
def test_runs(self) -> None:
|
|
from _q23_mean_shift_ncuts import _draw_ncuts_pixel_grid
|
|
|
|
fig, ax = plt.subplots()
|
|
ax.set_xlim(-0.5, 4.5)
|
|
ax.set_ylim(-0.5, 4.5)
|
|
pixel_vals = np.array(
|
|
[
|
|
[30, 35, 180, 190],
|
|
[40, 30, 185, 200],
|
|
[170, 180, 40, 35],
|
|
[190, 175, 30, 45],
|
|
]
|
|
)
|
|
_draw_ncuts_pixel_grid(ax, pixel_vals)
|
|
plt.close(fig)
|
|
|
|
def test_bright_pixels(self) -> None:
|
|
"""All pixels above dark threshold → black text."""
|
|
from _q23_mean_shift_ncuts import _draw_ncuts_pixel_grid
|
|
|
|
fig, ax = plt.subplots()
|
|
ax.set_xlim(-0.5, 4.5)
|
|
ax.set_ylim(-0.5, 4.5)
|
|
bright = np.full((4, 4), 200)
|
|
_draw_ncuts_pixel_grid(ax, bright)
|
|
plt.close(fig)
|
|
|
|
def test_dark_pixels(self) -> None:
|
|
"""All pixels below dark threshold → white text."""
|
|
from _q23_mean_shift_ncuts import _draw_ncuts_pixel_grid
|
|
|
|
fig, ax = plt.subplots()
|
|
ax.set_xlim(-0.5, 4.5)
|
|
ax.set_ylim(-0.5, 4.5)
|
|
dark = np.full((4, 4), 50)
|
|
_draw_ncuts_pixel_grid(ax, dark)
|
|
plt.close(fig)
|
|
|
|
|
|
class TestDrawNcutsEdges:
|
|
"""_draw_ncuts_edges from _q23_mean_shift_ncuts."""
|
|
|
|
def test_runs(self) -> None:
|
|
from _q23_mean_shift_ncuts import _draw_ncuts_edges
|
|
|
|
fig, ax = plt.subplots()
|
|
ax.set_xlim(-0.5, 4.5)
|
|
ax.set_ylim(-0.5, 4.5)
|
|
pixel_vals = np.array(
|
|
[
|
|
[30, 35, 180, 190],
|
|
[40, 30, 185, 200],
|
|
[170, 180, 40, 35],
|
|
[190, 175, 30, 45],
|
|
]
|
|
)
|
|
_draw_ncuts_edges(ax, pixel_vals)
|
|
plt.close(fig)
|
|
|
|
def test_uniform_values(self) -> None:
|
|
"""All same values → max similarity everywhere."""
|
|
from _q23_mean_shift_ncuts import _draw_ncuts_edges
|
|
|
|
fig, ax = plt.subplots()
|
|
ax.set_xlim(-0.5, 4.5)
|
|
ax.set_ylim(-0.5, 4.5)
|
|
uniform = np.full((4, 4), 128)
|
|
_draw_ncuts_edges(ax, uniform)
|
|
plt.close(fig)
|
|
|
|
|
|
class TestGenerateNormalizedCuts:
|
|
"""generate_normalized_cuts from _q23_mean_shift_ncuts."""
|
|
|
|
def test_runs(self) -> None:
|
|
from _q23_mean_shift_ncuts import generate_normalized_cuts
|
|
|
|
generate_normalized_cuts()
|
|
|
|
|
|
# ── _q23_mnemonics ───────────────────────────────────────────────────
|
|
|
|
|
|
class TestGenerateMnemonics:
|
|
"""generate_mnemonics from _q23_mnemonics."""
|
|
|
|
def test_runs(self) -> None:
|
|
from _q23_mnemonics import generate_mnemonics
|
|
|
|
generate_mnemonics()
|
|
|
|
|
|
# ── _q23_nn_basics ───────────────────────────────────────────────────
|
|
|
|
|
|
class TestGenerateRelu:
|
|
"""generate_relu from _q23_nn_basics."""
|
|
|
|
def test_runs(self) -> None:
|
|
from _q23_nn_basics import generate_relu
|
|
|
|
generate_relu()
|
|
|
|
|
|
class TestGenerateDotProduct:
|
|
"""generate_dot_product from _q23_nn_basics."""
|
|
|
|
def test_runs(self) -> None:
|
|
from _q23_nn_basics import generate_dot_product
|
|
|
|
generate_dot_product()
|
|
|
|
|
|
# ── _q23_otsu_watershed ──────────────────────────────────────────────
|
|
|
|
|
|
class TestDrawOtsuVariancePanel:
|
|
"""_draw_otsu_variance_panel from _q23_otsu_watershed."""
|
|
|
|
def test_runs(self) -> None:
|
|
from _q23_otsu_watershed import _draw_otsu_variance_panel
|
|
|
|
fig, ax = plt.subplots()
|
|
ax.set_xlim(0, 10)
|
|
ax.set_ylim(0, 10)
|
|
_draw_otsu_variance_panel(ax)
|
|
plt.close(fig)
|
|
|
|
|
|
class TestGenerateOtsuBimodal:
|
|
"""generate_otsu_bimodal from _q23_otsu_watershed."""
|
|
|
|
def test_runs(self) -> None:
|
|
from _q23_otsu_watershed import generate_otsu_bimodal
|
|
|
|
generate_otsu_bimodal()
|
|
|
|
|
|
class TestDrawWatershedResultPanel:
|
|
"""_draw_watershed_result_panel from _q23_otsu_watershed."""
|
|
|
|
def test_runs(self) -> None:
|
|
from _q23_otsu_watershed import _draw_watershed_result_panel
|
|
|
|
fig, ax = plt.subplots()
|
|
ax.set_xlim(0, 10)
|
|
ax.set_ylim(0, 10)
|
|
_draw_watershed_result_panel(ax)
|
|
plt.close(fig)
|
|
|
|
|
|
class TestGenerateWatershed:
|
|
"""generate_watershed from _q23_otsu_watershed."""
|
|
|
|
def test_runs(self) -> None:
|
|
from _q23_otsu_watershed import generate_watershed
|
|
|
|
generate_watershed()
|
|
|
|
|
|
# ── _q23_receptive_transformer ───────────────────────────────────────
|
|
|
|
|
|
class TestGenerateReceptiveField:
|
|
"""generate_receptive_field from _q23_receptive_transformer."""
|
|
|
|
def test_runs(self) -> None:
|
|
from _q23_receptive_transformer import generate_receptive_field
|
|
|
|
generate_receptive_field()
|
|
|
|
|
|
class TestGenerateTransformer:
|
|
"""generate_transformer from _q23_receptive_transformer."""
|
|
|
|
def test_runs(self) -> None:
|
|
from _q23_receptive_transformer import generate_transformer
|
|
|
|
generate_transformer()
|
|
|
|
|
|
# ── _q23_region_diy ──────────────────────────────────────────────────
|
|
|
|
|
|
class TestDrawRegionGrowingGrid:
|
|
"""_draw_region_growing_grid from _q23_region_diy."""
|
|
|
|
def test_runs(self) -> None:
|
|
from _q23_region_diy import _draw_region_growing_grid
|
|
|
|
fig, ax = plt.subplots()
|
|
_draw_region_growing_grid(ax)
|
|
plt.close(fig)
|
|
|
|
def test_bright_pixels_in_region(self) -> None:
|
|
"""Hit elif branch: masked pixel >= _BRIGHT_THRESHOLD."""
|
|
from unittest.mock import patch
|
|
|
|
from _q23_region_diy import _draw_region_growing_grid
|
|
|
|
fig, ax = plt.subplots()
|
|
with patch("_q23_region_diy._BRIGHT_THRESHOLD", 0):
|
|
_draw_region_growing_grid(ax)
|
|
plt.close(fig)
|
|
|
|
|
|
class TestDrawBfsExpansion:
|
|
"""_draw_bfs_expansion from _q23_region_diy."""
|
|
|
|
def test_runs(self) -> None:
|
|
from _q23_region_diy import _draw_bfs_expansion
|
|
|
|
fig, ax = plt.subplots()
|
|
_draw_bfs_expansion(ax)
|
|
plt.close(fig)
|
|
|
|
|
|
class TestGenerateRegionGrowing:
|
|
"""generate_region_growing from _q23_region_diy."""
|
|
|
|
def test_runs(self) -> None:
|
|
from _q23_region_diy import generate_region_growing
|
|
|
|
generate_region_growing()
|