Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
271 changes: 271 additions & 0 deletions python/demo/medical_imaging/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,271 @@
# Medical Image Denoising Demo

Anisotropic diffusion for medical image denoising using DOLFINx.

## Overview

This demo implements the Perona-Malik anisotropic diffusion model for medical image denoising. The method selectively smooths flat regions while preserving edges, making it suitable for medical imaging applications where preserving tissue boundaries and lesion edges is critical for diagnosis.

## File Structure

The demo consists of:
- demo/demo_anisotropic_diffusion_medical.py - Main standalone demo file
- test_demo.py - Test suite with 7 comprehensive tests
- README.md - This documentation file
- requirements.txt - Python dependencies
- data/ - Created automatically for synthetic test images
- output/ - Created automatically for denoised results

## Implementation

The implementation uses:
- Finite Element Method: P1 Lagrange elements on a triangular mesh
- Time Discretization: Backward Euler (implicit, unconditionally stable)
- Linear Solver: Conjugate Gradient with Jacobi preconditioner
- Mesh: Rectangular domain matching image dimensions

## Requirements

Core dependencies:
- fenics-dolfinx >= 0.9.0
- numpy >= 1.24.0
- mpi4py
- petsc4py

Image processing:
- Pillow >= 10.0.0
- scipy >= 1.10.0
- scikit-image >= 0.21.0

Testing:
- pytest >= 7.4.0
- pytest-mpi >= 0.6

## Installation

### Option 1: Using conda (recommended)

```bash
conda create -n fenicsx-env
conda activate fenicsx-env
conda install -c conda-forge fenics-dolfinx mpich
pip install pillow scipy scikit-image pytest pytest-mpi
```

### Option 2: Using requirements.txt

```bash
conda create -n fenicsx-env
conda activate fenicsx-env
conda install -c conda-forge fenics-dolfinx mpich
pip install -r requirements.txt
```

## Usage

### Basic Usage

Navigate to the medical_imaging directory and run:

```bash
python demo/demo_anisotropic_diffusion_medical.py
```

This will:
1. Create a synthetic test image in data/synthetic_test.png
2. Apply anisotropic diffusion denoising
3. Save the result to output/denoised.png
4. Print quality metrics (PSNR, SSIM, Edge Preservation Index)

### Run with your own image

```bash
python demo/demo_anisotropic_diffusion_medical.py --image path/to/your/image.jpg
```

### Custom parameters

```bash
python demo/demo_anisotropic_diffusion_medical.py --image data/mammogram.jpg --kappa 20.0 --iterations 30 --dt 0.05
```

### Parameters

--image: Path to input grayscale image (PNG, JPG, etc.)
If not provided, uses auto-generated synthetic test image

--kappa: Edge threshold parameter (default: 20.0)
Lower values (10-15): Preserve more edges, less smoothing
Higher values (30-40): More aggressive smoothing, may blur edges

--iterations: Number of time steps (default: 30)
Fewer iterations (10-20): Faster but may leave some noise
More iterations (40-50): Better denoising but risk of over-smoothing

--dt: Time step size (default: 0.05)
Smaller values (0.01-0.03): More stable but requires more iterations
Larger values (0.1+): Faster but may become unstable

## Example Output

```
Creating synthetic test image...
Loading image: data/synthetic_test.png
Image shape: (512, 512)

Initializing anisotropic diffusion solver...

Applying anisotropic diffusion...
Parameters: kappa=20.0, dt=0.05, iterations=30

Iteration 1/30
Iteration 2/30
...
Iteration 30/30

Quality Metrics:

PSNR: 35.52 dB
SSIM: 0.8739
Edge Preservation Index: 0.8042

Denoised image saved to: output/denoised.png
```

## Running Tests

The demo includes a comprehensive test suite with 7 tests.

### Run all tests

```bash
pytest test_demo.py -v
```

### Run specific test

```bash
pytest test_demo.py::test_anisotropic_diffusion_basic -v
```

### Sample test output

```
test_demo.py::test_synthetic_image_generation PASSED
test_demo.py::test_image_loading PASSED
test_demo.py::test_anisotropic_diffusion_basic PASSED
test_demo.py::test_quality_metrics PASSED
test_demo.py::test_mesh_creation PASSED
test_demo.py::test_diffusion_coefficient PASSED

6 passed in 8.23s
```

## Quality Metrics

The demo computes three metrics to assess denoising quality:

### PSNR (Peak Signal-to-Noise Ratio)
- Range: 0 to infinity (measured in dB)
- Good values: 30-40 dB
- Interpretation: Overall image quality; higher is better
- Our Sample Result: 35.52 dB

### SSIM (Structural Similarity Index)
- Range: 0 to 1
- Good values: greater than 0.8
- Interpretation: Preservation of structural features; 1.0 is perfect
- Our Sample Result: 0.8739

### Edge Preservation Index
- Range: 0 to 1
- Good values: greater than 0.7
- Interpretation: How well edges are preserved; critical for medical imaging
- Our Sample Result: 0.8042

All three metrics fall in the "good" range for medical image denoising.

## Tips for Different Medical Images

Recommended parameters for different imaging modalities:

Mammograms:
- kappa: 20-25
- iterations: 30

CT scans:
- kappa: 15-20
- iterations: 20-30

MRI:
- kappa: 25-30
- iterations: 30-40

High noise images:
- kappa: 20-25
- iterations: 40-50

Preserving Fine Details:
- kappa: 10-15
- iterations: 20-30

## Troubleshooting

### ModuleNotFoundError: No module named 'dolfinx'

Make sure you have activated your conda environment:
```bash
conda activate fenicsx-env
```

If not installed:
```bash
conda install -c conda-forge fenics-dolfinx mpich
```

### Demo runs but output quality is poor

Try adjusting parameters:
- Increase iterations: --iterations 40
- Adjust kappa based on image type (see Tips section above)
- Ensure input image is grayscale (not RGB)

## Known Limitations

- Currently supports 2D grayscale images only (3D extension possible)
- Requires sufficient memory for mesh creation (large images may need downsampling)
- Processing time scales with image resolution and iteration count

## Validation

The method has been validated on:
- Synthetic test images with known noise levels
- CBIS-DDSM mammogram dataset
- Various parameter combinations

Sample Results:
- PSNR: approximately 35 dB
- SSIM: approximately 0.87
- EPI: approximately 0.80

## Performance Benchmarks

Image size: 512x512, iterations: 30, serial execution
- Mesh creation: 0.5 seconds
- Per iteration: 0.4 seconds
- Total runtime: 15-20 seconds
- Peak memory: 500 MB

Test suite:
- 7 tests total
- Runtime: 8-10 seconds
- Uses small test images (32x32, 64x64) for speed

## License

This demo follows the same license as DOLFINx (LGPL-3.0).

## Contact

For questions or issues with this demo, please open an issue on the DOLFINx GitHub repository:
https://github.com/FEniCS/dolfinx/issues
112 changes: 112 additions & 0 deletions python/demo/medical_imaging/data/download_cbis_ddsm.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import os
import numpy as np
import zipfile
from PIL import Image
from pathlib import Path
import argparse
import shutil

def download_dataset(output_dir="data/cbis_ddsm"):
output_path = Path(output_dir)
output_path.mkdir(parents=True, exist_ok=True)

os.system(f"kaggle datasets download -d awsaf49/cbis-ddsm-breast-cancer-image-dataset -p {output_dir}")

zip_file = output_path / "cbis-ddsm-breast-cancer-image-dataset.zip"
if zip_file.exists():
print("Extracting dataset...")
with zipfile.ZipFile(zip_file, 'r') as zip_ref:
zip_ref.extractall(output_path)
zip_file.unlink()
return True
else:
print("Error: Dataset download failed.")
return False

def prepare_sample_images(dataset_dir="data/cbis_ddsm", sample_dir="data/samples", n_samples=5):
sample_path = Path(sample_dir)
sample_path.mkdir(parents=True, exist_ok=True)

dataset_path = Path(dataset_dir)
image_files = list(dataset_path.rglob("*.png"))[:n_samples]

if not image_files:
print("No images found. Please download the dataset first.")
return

print(f"Copying {len(image_files)} sample images...")

for i, img_file in enumerate(image_files):
img = Image.open(img_file).convert('L')
max_size = 1024
if max(img.size) > max_size:
img.thumbnail((max_size, max_size), Image.Resampling.LANCZOS)
output_file = sample_path / f"sample_{i:02d}.png"
img.save(output_file)
print(f" Saved: {output_file}")

print(f"Sample images saved to {sample_dir}/")

def create_synthetic_test_image(output_file="data/synthetic_test.png", size=(512, 512)):
# Create clean image with geometric shapes
img = np.zeros(size, dtype=np.float64)

y, x = np.ogrid[:size[0], :size[1]]

# Circle 1 (top-left) - bright white
cx1, cy1, r1 = size[1]//4, size[0]//4, 60
mask1 = (x - cx1)**2 + (y - cy1)**2 <= r1**2
img[mask1] = 1.0

# Circle 2 (bottom-right) - slightly dimmer
cx2, cy2, r2 = 3*size[1]//4, 3*size[0]//4, 80
mask2 = (x - cx2)**2 + (y - cy2)**2 <= r2**2
img[mask2] = 0.9

# Rectangle (center)
img[size[0]//3:2*size[0]//3, size[1]//2-40:size[1]//2+40] = 0.8

# Add light Gaussian noise
np.random.seed(42) # Reproducible
noise = np.random.normal(0, 0.03, size)
noisy_img = img + noise

# Clip to [0, 1]
noisy_img = np.clip(noisy_img, 0, 1)

# Convert to 8-bit with high quality
img_uint8 = (noisy_img * 255).astype(np.uint8)

# Save with maximum quality
Image.fromarray(img_uint8).save(output_file, quality=100, optimize=False)

print(f"Improved synthetic test image saved to {output_file}")
print(f" Image size: {size}")
print(f" Normalized range: [0, 1]")
print(f" Noise level: LIGHT (std=0.03)")
print(f" Features: 2 circles + 1 rectangle")
print(f" Quality: HIGH (no compression)")

return output_file

if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Download and prepare CBIS-DDSM dataset")
parser.add_argument("--download", action="store_true", help="Download full dataset")
parser.add_argument("--samples", action="store_true", help="Create sample subset")
parser.add_argument("--synthetic", action="store_true", help="Create synthetic test image")
parser.add_argument("--all", action="store_true", help="Do all of the above")

args = parser.parse_args()

if args.all or args.synthetic:
create_synthetic_test_image()

if args.all or args.download:
success = download_dataset()
if success and (args.all or args.samples):
prepare_sample_images()
elif args.samples:
prepare_sample_images()

if not any([args.download, args.samples, args.synthetic, args.all]):
parser.print_help()
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading