From 2e06887b2ca5e8de37ed0ce57b4df93c05f8a190 Mon Sep 17 00:00:00 2001
From: s204159 <s204159@student.dtu.dk>
Date: Mon, 27 May 2024 09:18:29 +0200
Subject: [PATCH] Synthetic image gen

---
 qim3d/utils/__init__.py |  4 +-
 qim3d/utils/img.py      | 92 ++++++++++++++++++++++++++++++++++++++++-
 requirements.txt        |  3 +-
 setup.py                |  3 +-
 4 files changed, 97 insertions(+), 5 deletions(-)

diff --git a/qim3d/utils/__init__.py b/qim3d/utils/__init__.py
index 5b4866e2..3d9e2150 100644
--- a/qim3d/utils/__init__.py
+++ b/qim3d/utils/__init__.py
@@ -2,7 +2,7 @@
 from . import doi, internal_tools
 from .augmentations import Augmentation
 from .data import Dataset, prepare_dataloaders, prepare_datasets
-from .img import overlay_rgb_images
+from .img import generate_volume, overlay_rgb_images
 from .models import inference, model_summary, train_model
-from .system import Memory
 from .preview import image_preview
+from .system import Memory
diff --git a/qim3d/utils/img.py b/qim3d/utils/img.py
index 347e4cc2..c75aa93c 100644
--- a/qim3d/utils/img.py
+++ b/qim3d/utils/img.py
@@ -1,4 +1,7 @@
 import numpy as np
+import scipy.ndimage
+from noise import pnoise3
+
 
 def overlay_rgb_images(background, foreground, alpha=0.5):
     """Overlay a RGB foreground onto an RGB background using alpha blending.
@@ -35,7 +38,9 @@ def overlay_rgb_images(background, foreground, alpha=0.5):
 
     # Normalize if we have something
     if np.max(foreground_max_projection) > 0:
-        foreground_max_projection = foreground_max_projection / np.max(foreground_max_projection)
+        foreground_max_projection = foreground_max_projection / np.max(
+            foreground_max_projection
+        )
 
     composite = background * (1 - alpha) + foreground * alpha
     composite = np.clip(composite, 0, 255).astype("uint8")
@@ -44,3 +49,88 @@ def overlay_rgb_images(background, foreground, alpha=0.5):
     composite = composite + (background * (1 - alpha)) * (1 - foreground_max_projection)
 
     return composite.astype("uint8")
+
+
+def generate_volume(
+    base_shape=(128, 128, 128),
+    final_shape=(128, 128, 128),
+    noise_scale=0.05,
+    order=1,
+    gamma=1.0,
+    max_value=255,
+    threshold=0.5,
+    dtype="uint8",
+):
+     """
+    Generate a 3D volume with Perlin noise, spherical gradient, and optional scaling and gamma correction.
+
+    Args:
+        base_shape (tuple, optional): Shape of the initial volume to generate. Defaults to (128, 128, 128).
+        final_shape (tuple, optional): Desired shape of the final volume. Defaults to (128, 128, 128).
+        noise_scale (float, optional): Scale factor for Perlin noise. Defaults to 0.05.
+        order (int, optional): Order of the spline interpolation used in resizing. Defaults to 1.
+        gamma (float, optional): Gamma correction factor. Defaults to 1.0.
+        max_value (int, optional): Maximum value for the volume intensity. Defaults to 255.
+        threshold (float, optional): Threshold value for clipping low intensity values. Defaults to 0.5.
+        dtype (str, optional): Desired data type of the output volume. Defaults to "uint8".
+
+    Returns:
+        numpy.ndarray: Generated 3D volume with specified parameters.
+
+    Raises:
+        ValueError: If `final_shape` is not a tuple or does not have three elements.
+        ValueError: If `dtype` is not a valid numpy number type.
+
+    Example:
+        import qim3d
+        vol = qim3d.utils.generate_volume()
+        qim3d.viz.slices(vol, vmin=0, vmax=255)
+    """
+     
+    if not isinstance(final_shape, tuple) or len(final_shape) != 3:
+        raise ValueError("Size must be a tuple")
+    if not np.issubdtype(dtype, np.number):
+        raise ValueError("Invalid data type")
+
+    # Define the dimensions of the shape for generating Perlin noise
+
+    # Initialize the 3D array for the shape
+    volume = np.empty((base_shape[0], base_shape[1], base_shape[2]), dtype=np.float32)
+
+    # Fill the 3D array with values from the Perlin noise function
+    for i in range(base_shape[0]):
+        for j in range(base_shape[1]):
+            for k in range(base_shape[2]):
+                # Calculate the distance from the center of the shape
+                dist = np.sqrt(
+                    (i - base_shape[0] / 2) ** 2
+                    + (j - base_shape[1] / 2) ** 2
+                    + (k - base_shape[2] / 2) ** 2
+                ) / np.sqrt(3 * ((base_shape[0] / 2) ** 2))
+                # Generate Perlin noise and adjust the values based on the distance from the center
+                # This creates a spherical shape with noise
+                volume[i][j][k] = (1 + pnoise3(i * noise_scale, j * noise_scale, k * noise_scale)) * (
+                    1 - dist
+                )
+
+    # Normalize
+    volume = (volume - np.min(volume)) / (np.max(volume) - np.min(volume))
+
+    # Gamma correction
+    volume = np.power(volume, gamma)
+
+    # Scale the volume to the maximum value
+    volume = volume * max_value
+
+    # clip the low values of the volume to create a coherent volume
+    volume[volume < threshold * max_value] = 0
+
+    # Clip high values
+    volume[volume > max_value] = max_value
+
+    # Scale up the volume of volume to size
+    volume = scipy.ndimage.zoom(
+        volume, np.array(final_shape) / np.array(base_shape), order=order
+    )
+
+    return volume.astype(dtype)
diff --git a/requirements.txt b/requirements.txt
index b7508e72..c22d3ed7 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -23,4 +23,5 @@ dask>=2023.6.0,
 k3d>=2.16.1
 olefile>=0.46
 psutil>=5.9.0
-structure-tensor>=0.2.1
\ No newline at end of file
+structure-tensor>=0.2.1
+noise>=1.2.2
\ No newline at end of file
diff --git a/setup.py b/setup.py
index 34fa87fb..6cd31a6e 100644
--- a/setup.py
+++ b/setup.py
@@ -62,6 +62,7 @@ setup(
         "k3d>=2.16.1",
         "olefile>=0.46",
         "psutil>=5.9.0",
-        "structure-tensor>=0.2.1"
+        "structure-tensor>=0.2.1",
+        "noise>=1.2.2"
     ],
 )
-- 
GitLab