
.. DO NOT EDIT.
.. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY.
.. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE:
.. "example_gallery/plot_snr.py"
.. LINE NUMBERS ARE GIVEN BELOW.

.. only:: html

    .. note::
        :class: sphx-glr-download-link-note

        :ref:`Go to the end <sphx_glr_download_example_gallery_plot_snr.py>`
        to download the full example code.

.. rst-class:: sphx-glr-example-title

.. _sphx_glr_example_gallery_plot_snr.py:


Signal-to-Noise Ratio (SNR) for Speckle Regions
===========================================================

This example demonstrates the Signal-to-Noise Ratio (SNR) metric for ultrasound speckle analysis.
We explore how SNR varies in different tissue regions and validate measurements against theoretical
Rayleigh distribution values.

Mathematical Definition
-----------------------

The Signal-to-Noise Ratio for ultrasound speckle is defined as the ratio of mean to standard deviation:

.. math::

    \text{SNR} = \frac{\mathbb{E}[X]}{\sqrt{\text{Var}(X)}} = \frac{\mu}{\sigma}

where :math:`\mathbb{E}[X]` is the mean and :math:`\sqrt{\text{Var}(X)}` is the standard deviation
of the envelope-detected signal values.

Fully developed speckle follows a Rayleigh distribution [1]_, so the theoretical SNR is:

.. math::

    \text{SNR}_{\text{Rayleigh}} = \sqrt{\frac{\pi}{4 - \pi}} \approx 1.91

This metric provides insight into speckle quality and can be used to:

- Assess fully developed speckle regions
- Compare different beamforming techniques
- Validate speckle statistics in phantom studies
- Quality control in ultrasound systems

**ROI Selection Best Practices:**

Following Perdios et al. [2]_, speckle patterns should be assessed within regions of
approximately 10λx10λ (where λ is the wavelength) to ensure sufficient statistical sampling
while maintaining spatial homogeneity. Regions should be placed away from boundaries and
avoid compounding artifacts near field edges.

References
----------
.. [1] C. B. Burckhardt, "Speckle in Ultrasound B-mode Scans," in IEEE Transactions
       on Sonics and Ultrasonics, vol. 25, no. 1, pp. 1-6, Jan. 1978,
       doi: 10.1109/T-SU.1978.30978.

.. [2] D. Perdios, M. Vonlanthen, F. Martinez, M. Arditi and J. -P. Thiran,
       "CNN-Based Image Reconstruction Method for Ultrafast Ultrasound Imaging,"
       in IEEE Transactions on Ultrasonics, Ferroelectrics, and Frequency Control,
       vol. 69, no. 4, pp. 1154-1168, April 2022, doi: 10.1109/TUFFC.2021.3131383.

.. [3] H. Liebgott, A. Rodriguez-Molares, F. Cervenansky, J. A. Jensen and O. Bernard,
       "Plane-Wave Imaging Challenge in Medical Ultrasound," 2016 IEEE International
       Ultrasonics Symposium (IUS), Tours, France, 2016, pp. 1-4,
       doi: 10.1109/ULTSYM.2016.7728908.

Example
-------

.. GENERATED FROM PYTHON SOURCE LINES 63-65

Import required modules and functions
------------------------------------------------------------------------

.. GENERATED FROM PYTHON SOURCE LINES 65-76

.. code-block:: Python


    import matplotlib.pyplot as plt
    import numpy as np
    import pandas as pd
    from matplotlib.patches import Circle

    from ultrasound_metrics.data import db_zero
    from ultrasound_metrics.data.uff import load_uff_dataset
    from ultrasound_metrics.metrics.snr import snr
    from ultrasound_metrics.roi.masks import build_mask








.. GENERATED FROM PYTHON SOURCE LINES 77-79

Load PICMUS datasets [3]_ for comparison
------------------------------------------------------------------------

.. GENERATED FROM PYTHON SOURCE LINES 79-88

.. code-block:: Python


    # Load contrast/speckle dataset (contains fully developed speckle regions)
    image_contrast, scan_contrast = load_uff_dataset("picmus_contrast_experiment")
    image_contrast = image_contrast.T

    # Load resolution dataset (contains point targets and speckle)
    image_resolution, scan_resolution = load_uff_dataset("picmus_resolution_experiment")
    image_resolution = image_resolution.T








.. GENERATED FROM PYTHON SOURCE LINES 89-91

Visualize datasets side by side
------------------------------------------------------------------------

.. GENERATED FROM PYTHON SOURCE LINES 91-120

.. code-block:: Python


    fig, axes = plt.subplots(1, 2, figsize=(12, 6))

    # Contrast/speckle dataset
    extent_contrast = [
        scan_contrast.x_axis.min(),
        scan_contrast.x_axis.max(),
        scan_contrast.z_axis.max(),
        scan_contrast.z_axis.min(),
    ]
    axes[0].imshow(db_zero(image_contrast), cmap="gray", extent=extent_contrast, vmin=-60, vmax=0)
    axes[0].set_title("PICMUS Contrast/Speckle Dataset\n(Fully developed speckle)")
    axes[0].set_xlabel("Lateral Position (m)")
    axes[0].set_ylabel("Axial Position (m)")

    # Resolution dataset
    extent_resolution = [
        scan_resolution.x_axis.min(),
        scan_resolution.x_axis.max(),
        scan_resolution.z_axis.max(),
        scan_resolution.z_axis.min(),
    ]
    axes[1].imshow(db_zero(image_resolution), cmap="gray", extent=extent_resolution, vmin=-60, vmax=0)
    axes[1].set_title("PICMUS Resolution Dataset\n(Point targets + speckle)")
    axes[1].set_xlabel("Lateral Position (m)")
    axes[1].set_ylabel("Axial Position (m)")

    plt.tight_layout()




.. image-sg:: /example_gallery/images/sphx_glr_plot_snr_001.png
   :alt: PICMUS Contrast/Speckle Dataset (Fully developed speckle), PICMUS Resolution Dataset (Point targets + speckle)
   :srcset: /example_gallery/images/sphx_glr_plot_snr_001.png
   :class: sphx-glr-single-img





.. GENERATED FROM PYTHON SOURCE LINES 121-123

Define ROI locations for speckle analysis
------------------------------------------------------------------------

.. GENERATED FROM PYTHON SOURCE LINES 123-164

.. code-block:: Python


    # Following Perdios et al., we use regions of approximately 10λx10λ
    # Assuming center frequency ~5 MHz and speed of sound ~1540 m/s
    # λ ≈ 1540/5e6 ≈ 0.3 mm, so 10λ ≈ 3 mm radius
    roi_radius = 0.003  # 3 mm radius

    # Equation S9 from Perdios et al. [2]_ and Equation 4 from Burckhardt [1]_
    theoretical_rayleigh_snr = np.sqrt(np.pi / (4 - np.pi))

    # Define ROI locations for different types of regions
    roi_specs = [
        {
            "name": "Fully Developed Speckle (contrast dataset)",
            "dataset": "contrast",
            "center": (-0.005, 0.035),
            "expected_snr": theoretical_rayleigh_snr,
            "description": "Homogeneous speckle region",
        },
        {
            "name": "Fully Developed Speckle (resolution dataset)",
            "dataset": "resolution",
            "center": (0.008, 0.030),
            "expected_snr": theoretical_rayleigh_snr,
            "description": "Homogeneous speckle region",
        },
        {
            "name": "Point Target Region",
            "dataset": "resolution",
            "center": (0.000, 0.0275),  # Near a point target
            "expected_snr": None,  # Should deviate from Rayleigh
            "description": "Point target + speckle",
        },
        {
            "name": "Lesion Boundary",
            "dataset": "contrast",
            "center": (-0.0025, 0.028),  # Near lesion boundary
            "expected_snr": None,  # Mixed statistics
            "description": "Lesion boundary region",
        },
    ]








.. GENERATED FROM PYTHON SOURCE LINES 165-167

Compute SNR for each ROI
------------------------------------------------------------------------

.. GENERATED FROM PYTHON SOURCE LINES 167-220

.. code-block:: Python


    results_data = []

    for spec in roi_specs:
        # Select the appropriate dataset and scan
        if spec["dataset"] == "contrast":
            image = image_contrast
            scan = scan_contrast
            extent = extent_contrast
        else:
            image = image_resolution
            scan = scan_resolution
            extent = extent_resolution

        # Create ROI mask
        mask = build_mask(
            position=spec["center"],
            dimension=roi_radius,
            x_axis=scan.x_axis,
            z_axis=scan.z_axis,
            shape="circle",
        )

        # Extract values in ROI
        roi_values = image[mask]

        # Compute SNR (function handles complex data internally)
        snr_linear = snr(roi_values, db=False)
        snr_db = snr(roi_values, db=True)

        # Calculate deviation from theoretical Rayleigh SNR if expected
        if spec["expected_snr"] is not None:
            deviation = abs(snr_linear - spec["expected_snr"])
            deviation_percent = (deviation / spec["expected_snr"]) * 100
        else:
            deviation = None
            deviation_percent = None

        results_data.append({
            "ROI": spec["name"],
            "Dataset": spec["dataset"],
            "SNR (linear)": snr_linear,
            "SNR (dB)": snr_db,
            "Expected SNR": spec["expected_snr"],
            "Deviation": deviation,
            "Deviation (%)": deviation_percent,
            "Description": spec["description"],
            "mask": mask,
            "center": spec["center"],
            "extent": extent,
            "image": image,
        })








.. GENERATED FROM PYTHON SOURCE LINES 221-223

Display results table
------------------------------------------------------------------------

.. GENERATED FROM PYTHON SOURCE LINES 223-244

.. code-block:: Python


    results_df = pd.DataFrame(results_data)

    print("=== SNR Analysis Results ===")
    display_cols = ["ROI", "Dataset", "SNR (linear)", "SNR (dB)", "Expected SNR", "Deviation (%)"]
    print(results_df[display_cols].round(3))

    print(f"\nTheoretical Rayleigh SNR: {theoretical_rayleigh_snr:.3f}")

    for _, row in results_df.iterrows():
        if row["Expected SNR"] is not None:
            if row["Deviation (%)"] < 10:
                status = "✅ Good agreement with theory (Rayleigh distribution)"
            elif row["Deviation (%)"] < 20:
                status = "⚠️  Moderate deviation from theory"
            else:
                status = "❌ Significant deviation from theory"
            print(f"{row['ROI']}: SNR = {row['SNR (linear)']:.3f}, {status}")
        else:
            print(f"{row['ROI']}: SNR = {row['SNR (linear)']:.3f} (non-Rayleigh distribution)")





.. rst-class:: sphx-glr-script-out

 .. code-block:: none

    === SNR Analysis Results ===
                                                ROI  ... Deviation (%)
    0    Fully Developed Speckle (contrast dataset)  ...         2.189
    1  Fully Developed Speckle (resolution dataset)  ...         1.115
    2                           Point Target Region  ...           NaN
    3                               Lesion Boundary  ...           NaN

    [4 rows x 6 columns]

    Theoretical Rayleigh SNR: 1.913
    Fully Developed Speckle (contrast dataset): SNR = 1.955, ✅ Good agreement with theory (Rayleigh distribution)
    Fully Developed Speckle (resolution dataset): SNR = 1.892, ✅ Good agreement with theory (Rayleigh distribution)
    Point Target Region: SNR = 0.534, ❌ Significant deviation from theory
    Lesion Boundary: SNR = 1.520, ❌ Significant deviation from theory




.. GENERATED FROM PYTHON SOURCE LINES 245-247

Visualize ROIs and SNR measurements
------------------------------------------------------------------------

.. GENERATED FROM PYTHON SOURCE LINES 247-279

.. code-block:: Python


    fig, axes = plt.subplots(2, 2, figsize=(15, 12))
    axes = axes.flatten()

    for i, (_, row) in enumerate(results_df.iterrows()):
        ax = axes[i]

        # Display the image
        ax.imshow(db_zero(row["image"]), cmap="gray", extent=row["extent"], vmin=-60, vmax=0)

        # Overlay ROI circle
        circle = Circle(row["center"], roi_radius, fill=False, color="red", linewidth=2, linestyle="--")
        ax.add_patch(circle)

        # Add SNR annotation
        ax.text(
            row["center"][0],
            row["center"][1] - 0.008,
            f"SNR = {row['SNR (linear)']:.2f}",
            ha="center",
            va="top",
            color="white",
            fontweight="bold",
            bbox={"boxstyle": "round,pad=0.3", "facecolor": "black", "alpha": 0.8},
        )

        ax.set_title(f"{row['ROI']}\n{row['Description']}")
        ax.set_xlabel("Lateral Position (m)")
        ax.set_ylabel("Axial Position (m)")

    plt.tight_layout()




.. image-sg:: /example_gallery/images/sphx_glr_plot_snr_002.png
   :alt: Fully Developed Speckle (contrast dataset) Homogeneous speckle region, Fully Developed Speckle (resolution dataset) Homogeneous speckle region, Point Target Region Point target + speckle, Lesion Boundary Lesion boundary region
   :srcset: /example_gallery/images/sphx_glr_plot_snr_002.png
   :class: sphx-glr-single-img





.. GENERATED FROM PYTHON SOURCE LINES 280-282

Statistical analysis of speckle regions
------------------------------------------------------------------------

.. GENERATED FROM PYTHON SOURCE LINES 282-349

.. code-block:: Python


    # Focus on the fully developed speckle regions
    speckle_rows = results_df[results_df["Expected SNR"].notna()]

    fig, axes = plt.subplots(1, 2, figsize=(12, 5))

    colors = ["skyblue", "lightcoral"]
    for i, (_, row) in enumerate(speckle_rows.iterrows()):
        # Extract ROI values and convert to envelope (magnitude)
        roi_values_complex = row["image"][row["mask"]]
        roi_values = np.abs(roi_values_complex)  # Take envelope for histogram

        # Create histogram of envelope values
        axes[0].hist(
            roi_values,
            bins=50,
            alpha=0.7,
            density=True,
            color=colors[i],
            label=f"{row['ROI']} (SNR={row['SNR (linear)']:.2f})",
        )

    # Overlay theoretical Rayleigh distribution
    x_theory = np.linspace(0, roi_values.max(), 1000)
    # For comparison, use sigma that gives the same mean as our data
    sigma_rayleigh = np.mean(roi_values) / np.sqrt(np.pi / 2)
    rayleigh_pdf = (x_theory / sigma_rayleigh**2) * np.exp(-(x_theory**2) / (2 * sigma_rayleigh**2))
    axes[0].plot(x_theory, rayleigh_pdf, "k--", linewidth=2, label="Theoretical Rayleigh")

    axes[0].set_xlabel("Envelope Amplitude")
    axes[0].set_ylabel("Probability Density")
    axes[0].set_title("Envelope Distribution vs Rayleigh Distribution")
    axes[0].legend()
    axes[0].grid(True, alpha=0.3)

    # SNR comparison bar chart
    roi_names = [row["ROI"] for _, row in speckle_rows.iterrows()]
    snr_values = [row["SNR (linear)"] for _, row in speckle_rows.iterrows()]

    bars = axes[1].bar(roi_names, snr_values, alpha=0.7, color=colors)
    axes[1].axhline(
        y=theoretical_rayleigh_snr,
        color="red",
        linestyle="--",
        linewidth=2,
        label="Theoretical (Rayleigh distribution)",
    )
    axes[1].set_ylabel("SNR")
    axes[1].set_title("Measured SNR vs Theoretical")
    axes[1].legend()
    axes[1].grid(True, alpha=0.3)

    # Add value labels on bars
    for bar, value in zip(bars, snr_values):
        height = bar.get_height()
        axes[1].text(
            bar.get_x() + bar.get_width() / 2.0,
            height + 0.01,
            f"{value:.2f}",
            ha="center",
            va="bottom",
            fontweight="bold",
        )

    plt.xticks(rotation=45, ha="right")
    plt.tight_layout()




.. image-sg:: /example_gallery/images/sphx_glr_plot_snr_003.png
   :alt: Envelope Distribution vs Rayleigh Distribution, Measured SNR vs Theoretical
   :srcset: /example_gallery/images/sphx_glr_plot_snr_003.png
   :class: sphx-glr-single-img





.. GENERATED FROM PYTHON SOURCE LINES 350-352

%%
Show all figures (if run as a script)

.. GENERATED FROM PYTHON SOURCE LINES 352-354

.. code-block:: Python

    plt.show()








.. GENERATED FROM PYTHON SOURCE LINES 355-389

Best Practices for SNR Measurement
-----------------------------------

Based on the analysis above, here are key guidelines for SNR measurement:

1. ROI Size Guidelines:

   - Use regions of approximately 10λx10λ for adequate statistical sampling
   - For 5 MHz transducers: ≈3mm x 3mm ROI provides good sampling
   - Larger ROIs provide more stable statistics but may include spatial variations

2. Region Selection Criteria:

   - Choose homogeneous speckle areas away from boundaries
   - Avoid edges with incomplete plane wave coverage

3. Regions to Avoid:

   - Lesion edges (mixed Rayleigh/non-Rayleigh statistics)
   - Point targets (coherent scattering, non-Rayleigh)
   - Field edges (incomplete acoustic data)
   - Areas with obvious artifacts

4. Expected Values for Quality Assessment:

   - Fully developed speckle: SNR ≈ 1.91 (Rayleigh distribution) [1]_ [2]_
   - Point targets: SNR significantly < 1.91
   - Mixed regions: Variable SNR depending on content mixture

5. Clinical and Research Applications:

   - Speckle quality assessment in different tissue types
   - Beamforming algorithm evaluation and optimization
   - System validation and quality control


.. rst-class:: sphx-glr-timing

   **Total running time of the script:** (0 minutes 1.103 seconds)


.. _sphx_glr_download_example_gallery_plot_snr.py:

.. only:: html

  .. container:: sphx-glr-footer sphx-glr-footer-example

    .. container:: sphx-glr-download sphx-glr-download-jupyter

      :download:`Download Jupyter notebook: plot_snr.ipynb <plot_snr.ipynb>`

    .. container:: sphx-glr-download sphx-glr-download-python

      :download:`Download Python source code: plot_snr.py <plot_snr.py>`

    .. container:: sphx-glr-download sphx-glr-download-zip

      :download:`Download zipped: plot_snr.zip <plot_snr.zip>`


.. only:: html

 .. rst-class:: sphx-glr-signature

    `Gallery generated by Sphinx-Gallery <https://sphinx-gallery.github.io>`_
