Author: Automated Analysis
Date: 2025-06-19

# ANTsPyMM: Blind Image Quality Control (QC) Summary

#### Automated Analysis

#### 2025-06-19

### 1. Introduction

This report demonstrates the `antspymm.blind_image_assessment` function for performing automated, no-reference Quality Control on medical images. We will first simulate a set of images with varying characteristics (noise, smoothness, etc.) and then run the QC analysis on them. The results are presented in interactive and static tables.

This script is designed to be fully portable and self-contained. It creates temporary files in a system-appropriate location and cleans up after itself upon completion.

### 2. Simulating Images for QC

First, we set up a Python environment and generate several test images. A temporary directory is created to store these images, ensuring the script does not leave files on the system.

    # --- Python Environment and Image Simulation ---
    import siq
    import ants
    import antspymm
    import os
    import tempfile
    import shutil
    import pandas as pd

    # Create a single, system-agnostic temporary directory for all output files.
    # This makes the script portable (works on Linux, macOS, Windows).
    temp_dir = tempfile.mkdtemp()
    print(f"Using temporary directory: {temp_dir}")

    ## Using temporary directory: /tmp/tmp0lt08_m8

    # List to hold the paths of all generated files for processing.
    generated_files = []

    # --- Image 1: Baseline Simulated Image ---
    print("Simulating baseline image...")

    ## Simulating baseline image...

    eximg = siq.simulate_image()
    exfn_base = os.path.join(temp_dir, 'baseline.nii.gz')
    ants.image_write(eximg, exfn_base)
    ants.plot( eximg )

    generated_files.append(exfn_base)

    # --- Image 2: Additive Gaussian Noise ---
    print("Simulating image with Gaussian noise...")

    ## Simulating image with Gaussian noise...

    img_gauss = siq.simulate_image().add_noise_to_image('additivegaussian', [0, 1])
    exfn_gauss = os.path.join(temp_dir, 'noise_gaussian.nii.gz')
    ants.image_write(img_gauss, exfn_gauss)
    ants.plot( img_gauss )

    generated_files.append(exfn_gauss)

    # --- Image 3: Salt & Pepper Noise ---
    print("Simulating image with Salt & Pepper noise...")

    ## Simulating image with Salt & Pepper noise...

    img_sp = siq.simulate_image()
    img_sp = ants.add_noise_to_image(img_sp, 'saltandpepper', [0.2, -1, 1])
    exfn_sp = os.path.join(temp_dir, 'noise_saltpepper.nii.gz')
    ants.image_write(img_sp, exfn_sp)
    ants.plot( img_sp )

    generated_files.append(exfn_sp)

    # --- Image 4: Smoothed Image ---
    print("Simulating a smoothed image...")

    ## Simulating a smoothed image...

    img_smooth = siq.simulate_image().smooth_image(3)
    exfn_smooth = os.path.join(temp_dir, 'smooth.nii.gz')
    ants.image_write(img_smooth, exfn_smooth)
    ants.plot( img_smooth )

    generated_files.append(exfn_smooth)

    # --- Image 5: Time Series Image ---
    print("Simulating a 4D time-series image...")

    ## Simulating a 4D time-series image...

    img_ts = siq.simulate_image([16, 16, 16, 4])
    exfn_ts = os.path.join(temp_dir, 'timeseries.nii.gz')
    ants.image_write(img_ts, exfn_ts)
    generated_files.append(exfn_ts)

    print(f"\nGenerated {len(generated_files)} files for analysis.")

    ##
    ## Generated 5 files for analysis.

### 3. Running Batch QC Analysis

Now we loop through the generated images and run `blind_image_assessment` on each. The results are collected into a single Pandas DataFrame.

    # --- Batch QC Processing ---
    # A more efficient way to build a DataFrame is to append dictionaries to a
    # list and then call the DataFrame constructor once.
    qc_results = []
    for f in generated_files:
        print(f"Running QC on: {os.path.basename(f)}...")
        myqc = antspymm.blind_image_assessment(f)
        qc_results.append(myqc)

    ## Running QC on: baseline.nii.gz...
    ## Running QC on: noise_gaussian.nii.gz...
    ## Running QC on: noise_saltpepper.nii.gz...
    ## Running QC on: smooth.nii.gz...
    ## Running QC on: timeseries.nii.gz...

    # Concatenate all results into a single DataFrame.
    qcdf = pd.concat(qc_results, axis=0)

    # The 'qcdf' DataFrame is now automatically available to R via reticulate as py$qcdf
    # No need to write to a temporary CSV file.

### 4. QC Results

The following tables display the QC metrics for all simulated images.

#### 4.1. Interactive QC Data Table

This table is sortable and searchable, which is useful for exploring the results dynamically.

    ## Warning in py_to_r.pandas.core.frame.DataFrame(x): index contains duplicated
    ## values: row names not set

#### 4.2. Static QC Data Table

This is a clean, static view of the same data, suitable for printing or including in a non-interactive document.

Static table of blind QC metrics.
filename | dimensionality | noise | snr | cnr | psnr | ssim | mi | reflection_err | EVR | msk_vol | spc0 | spc1 | spc2 | org0 | org1 | org2 | dimx | dimy | dimz | dimt | slice | modality | mriseries | mrimfg | mrimodel | mriMagneticFieldStrength | mriSAR | mriPixelBandwidth | mriPixelBandwidthPE | dti_bvalueMax | dti_bvecnorm
--- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | ---
baseline | 3 | 0.03687 | 25.65 | 20.98 | 23.16 | 0.9421 | -1.023 | 0.1575 | 0.6699 | 32494 | 1 | 1 | 1 | 0 | 0 | 0 | 32 | 32 | 32 | 1 | 0 | unknown | NA | NA | NA | NA | NA | NA | NA | NA | NA
noise_gaussian | 3 | 0.09391 | NA | NA | 17.13 | 0.6415 | -0.2906 | 0.137 | NA | 0 | 1 | 1 | 1 | 0 | 0 | 0 | 32 | 32 | 32 | 1 | 0 | unknown | NA | NA | NA | NA | NA | NA | NA | NA | NA
noise_saltpepper | 3 | 0.07676 | Inf | Inf | 14.12 | 0.5467 | -0.7279 | 0.2088 | 0.834 | 32767 | 1 | 1 | 1 | 0 | 0 | 0 | 32 | 32 | 32 | 1 | 0 | unknown | NA | NA | NA | NA | NA | NA | NA | NA | NA
smooth | 3 | 0.03274 | 13.09 | 11.16 | 34.76 | 0.9956 | -1.893 | 0.2581 | 0.02344 | 31356 | 1 | 1 | 1 | 0 | 0 | 0 | 32 | 32 | 32 | 1 | 0 | unknown | NA | NA | NA | NA | NA | NA | NA | NA | NA
timeseries | 4 | 0.08569 | 32222380 | 22635400 | 21.9 | 0.9365 | -1.184 | 0.1029 | 0.6025 | 4052 | 1 | 1 | 1 | 0 | 0 | 0 | 16 | 16 | 16 | 4 | 0 | unknown | NA | NA | NA | NA | NA | NA | NA | NA | NA

### 5. QC Metric Definitions

The metrics reported in the tables above are defined as follows:

`fn`
  The filename of the processed image.
`noise`
  Estimated noise level, calculated as the mean absolute difference between the original image and a smoothed version of it.
`snr`
  Signal-to-Noise Ratio. Defined from automatically generated foreground (fg) and background (bg) masks as: `mean(fg) / std(bg)`.
`cnr`
  Contrast-to-Noise Ratio. Defined as: `(mean(fg) - mean(bg)) / std(bg)`.
`psnr`
  Peak Signal-to-Noise Ratio. Calculated between the original and a smoothed version of the image.
`ssim`
  Structural Similarity. Measures the similarity between the original and a smoothed version of the image. Higher values indicate greater similarity.
`mi`
  Mutual Information. Measures the statistical dependency between the original and smoothed image.
`reflection_error`
  Mean absolute error between the original image and a version reflected across the mid-sagittal plane. Useful for detecting severe spatial artifacts or asymmetries.
`EVR`
  Eigenvalue Ratio. A measure of image texture and complexity. Values near 0 suggest very smooth images, while values near 1 suggest pure noise. Typical medical images fall in the 0.6-0.7 range.
`msk_vol`
  The volume of the automatically detected foreground mask, in cubic millimeters.
`spc*`
  The voxel spacing (e.g., `spcx`, `spcy`, `spcz`).
`org*`
  The image origin coordinates.
`dim*`
  The image dimensions (number of voxels along each axis).
`slice`
  The slice index, primarily used for 4D time-series data.
`modality`
  Inferred image modality, typically based on the filename.
`mriseries`, `mrimfg`, `mrimodel`
  Metadata fields (series description, manufacturer, model) read from a sidecar JSON file, if available.