File size: 5,027 Bytes
3c4c67b 4eeba46 3c4c67b aef1f5a 3c4c67b 987c4be 3c4c67b aef1f5a |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 |
"""Shared test fixtures."""
from __future__ import annotations
import tempfile
from pathlib import Path
from typing import TYPE_CHECKING
import nibabel as nib
import numpy as np
import pytest
from stroke_deepisles_demo.core.types import CaseFiles
if TYPE_CHECKING:
from collections.abc import Generator
@pytest.fixture
def temp_dir() -> Generator[Path, None, None]:
"""Create a temporary directory for test outputs."""
with tempfile.TemporaryDirectory() as td:
yield Path(td)
@pytest.fixture
def synthetic_nifti_3d(temp_dir: Path) -> Path:
"""Create a minimal synthetic 3D NIfTI file."""
data = np.random.rand(10, 10, 10).astype(np.float32)
img = nib.Nifti1Image(data, affine=np.eye(4)) # type: ignore
path = temp_dir / "synthetic.nii.gz"
nib.save(img, path) # type: ignore
return path
@pytest.fixture
def synthetic_case_files(temp_dir: Path) -> CaseFiles:
"""Create a complete set of synthetic case files."""
# Create DWI
dwi_data = np.random.rand(64, 64, 30).astype(np.float32)
dwi_img = nib.Nifti1Image(dwi_data, affine=np.eye(4)) # type: ignore
dwi_path = temp_dir / "dwi.nii.gz"
nib.save(dwi_img, dwi_path) # type: ignore
# Create ADC
adc_data = np.random.rand(64, 64, 30).astype(np.float32) * 2000
adc_img = nib.Nifti1Image(adc_data, affine=np.eye(4)) # type: ignore
adc_path = temp_dir / "adc.nii.gz"
nib.save(adc_img, adc_path) # type: ignore
# Create mask
mask_data = (np.random.rand(64, 64, 30) > 0.9).astype(np.uint8)
mask_img = nib.Nifti1Image(mask_data, affine=np.eye(4)) # type: ignore
mask_path = temp_dir / "mask.nii.gz"
nib.save(mask_img, mask_path) # type: ignore
return CaseFiles(
dwi=dwi_path,
adc=adc_path,
ground_truth=mask_path,
)
@pytest.fixture
def synthetic_probability_mask(temp_dir: Path) -> Path:
"""
Create a synthetic probability mask (float values 0.0-1.0).
This simulates model output that may contain probability values
rather than binary 0/1 masks. Used to test visualization handling
of probability-valued segmentation masks.
The mask has values ONLY at slice 5 to ensure get_slice_at_max_lesion selects it:
- Outer region with low probability (0.3) - below 0.5 threshold
- Inner region with high probability (0.8) - above 0.5 threshold
See: docs/specs/23-slice-comparison-overlay-bug.md
"""
mask_data = np.zeros((10, 10, 10), dtype=np.float32)
# Only populate slice 5 to ensure it's selected as max lesion slice
# Outer region: low confidence (below 0.5 threshold)
mask_data[2:8, 2:8, 5] = 0.3
# Inner region: high confidence (above 0.5 threshold) - this should be visible
mask_data[3:7, 3:7, 5] = 0.8
img = nib.Nifti1Image(mask_data, affine=np.eye(4)) # type: ignore
path = temp_dir / "probability_mask.nii.gz"
nib.save(img, path) # type: ignore
return path
@pytest.fixture
def synthetic_binary_mask(temp_dir: Path) -> Path:
"""Create a synthetic binary mask (0 or 1 values only)."""
mask_data = np.zeros((10, 10, 10), dtype=np.uint8)
mask_data[3:7, 3:7, 4:6] = 1 # Binary lesion region
img = nib.Nifti1Image(mask_data, affine=np.eye(4)) # type: ignore
path = temp_dir / "binary_mask.nii.gz"
nib.save(img, path) # type: ignore
return path
@pytest.fixture
def synthetic_isles_dir(temp_dir: Path) -> Path:
"""
Create synthetic ISLES24-like directory structure.
Structure:
temp_dir/
βββ Images-DWI/
β βββ sub-stroke0001_ses-02_dwi.nii.gz
β βββ sub-stroke0002_ses-02_dwi.nii.gz
βββ Images-ADC/
β βββ sub-stroke0001_ses-02_adc.nii.gz
β βββ sub-stroke0002_ses-02_adc.nii.gz
βββ Masks/
βββ sub-stroke0001_ses-02_lesion-msk.nii.gz
βββ sub-stroke0002_ses-02_lesion-msk.nii.gz
"""
dwi_dir = temp_dir / "Images-DWI"
adc_dir = temp_dir / "Images-ADC"
mask_dir = temp_dir / "Masks"
dwi_dir.mkdir()
adc_dir.mkdir()
mask_dir.mkdir()
for subject_num in [1, 2]:
subject_id = f"sub-stroke{subject_num:04d}"
# Create DWI
dwi_data = np.random.rand(10, 10, 5).astype(np.float32)
dwi_img = nib.Nifti1Image(dwi_data, affine=np.eye(4)) # type: ignore
nib.save(dwi_img, dwi_dir / f"{subject_id}_ses-02_dwi.nii.gz") # type: ignore
# Create ADC
adc_data = np.random.rand(10, 10, 5).astype(np.float32) * 2000
adc_img = nib.Nifti1Image(adc_data, affine=np.eye(4)) # type: ignore
nib.save(adc_img, adc_dir / f"{subject_id}_ses-02_adc.nii.gz") # type: ignore
# Create Mask
mask_data = (np.random.rand(10, 10, 5) > 0.9).astype(np.uint8)
mask_img = nib.Nifti1Image(mask_data, affine=np.eye(4)) # type: ignore
nib.save(mask_img, mask_dir / f"{subject_id}_ses-02_lesion-msk.nii.gz") # type: ignore
return temp_dir
|