diff --git a/docs/assets/screenshots/generate_volume.png b/docs/assets/screenshots/generate_volume.png
new file mode 100644
index 0000000000000000000000000000000000000000..02ee3b73643630316aec6f00c8d6e00866a06ec7
Binary files /dev/null and b/docs/assets/screenshots/generate_volume.png differ
diff --git a/docs/assets/screenshots/gui-annotation_tool.gif b/docs/assets/screenshots/gui-annotation_tool.gif
new file mode 100644
index 0000000000000000000000000000000000000000..c106bcd09bbb0701bed2dbe127133c651f10b59a
Binary files /dev/null and b/docs/assets/screenshots/gui-annotation_tool.gif differ
diff --git a/docs/assets/screenshots/operations-edge_fade_after.png b/docs/assets/screenshots/operations-edge_fade_after.png
new file mode 100644
index 0000000000000000000000000000000000000000..cd1977d5929188f3a78394fc666633886592656a
Binary files /dev/null and b/docs/assets/screenshots/operations-edge_fade_after.png differ
diff --git a/docs/assets/screenshots/operations-edge_fade_before.png b/docs/assets/screenshots/operations-edge_fade_before.png
new file mode 100644
index 0000000000000000000000000000000000000000..d0ce709ef28adc5844c5179375f0d36f0097f322
Binary files /dev/null and b/docs/assets/screenshots/operations-edge_fade_before.png differ
diff --git a/docs/assets/screenshots/viz-fade_mask.gif b/docs/assets/screenshots/viz-fade_mask.gif
new file mode 100644
index 0000000000000000000000000000000000000000..3ef2aba7b79af9306c1917a75d92d6cd3474d6ff
Binary files /dev/null and b/docs/assets/screenshots/viz-fade_mask.gif differ
diff --git a/docs/index.md b/docs/index.md
index 5a3ca251190a1d906061f4bd23871d19e599185e..ea6111ebbe6f4a893f5aaaf65c99f42418f43386 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -131,8 +131,21 @@ You can find us at Gitlab:
 [https://lab.compute.dtu.dk/QIM/tools/qim3d](https://lab.compute.dtu.dk/QIM/tools/qim3d
 )
 
-This project is licensed under the MIT License.
+This project is licensed under the [MIT License](https://lab.compute.dtu.dk/QIM/tools/qim3d/-/blob/main/LICENSE).
 
+### Contributors
+
+Below is a list of contributors to the project, arranged in chronological order of their first commit to the repository:
+
+| Author                    |   Commits | First commit |
+|:--------------------------|----------:|-------------:|
+| Felipe Delestro           |       170 | 2023-05-12   |
+| Stefan Engelmann Jensen   |        29 | 2023-06-29   |
+| Oskar Kristoffersen       |        15 | 2023-07-05   |
+| Christian Kento Rasmussen |        19 | 2024-02-01   |
+| Alessia Saccardo          |         7 | 2024-02-19   |
+| David Grundfest           |         4 | 2024-04-12   |
+| Anna Bøgevang Ekner       |         3 | 2024-04-18   |
 
 ## Support
 
diff --git a/docs/notebooks/Untitled.ipynb b/docs/notebooks/Untitled.ipynb
new file mode 100644
index 0000000000000000000000000000000000000000..c946f3b248ee0bf392a9239a3b744dccd6193555
--- /dev/null
+++ b/docs/notebooks/Untitled.ipynb
@@ -0,0 +1,71 @@
+{
+ "cells": [
+  {
+   "cell_type": "code",
+   "execution_count": 1,
+   "id": "0b73f2d8",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import qim3d"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 2,
+   "id": "73db6886",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "vol = qim3d.examples.bone_128x128x128"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 8,
+   "id": "22d86d4d",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "application/vnd.jupyter.widget-view+json": {
+       "model_id": "2fefeafbd89c4f9fa5a08dc1a5d503d1",
+       "version_major": 2,
+       "version_minor": 0
+      },
+      "text/plain": [
+       "HBox(children=(interactive(children=(IntSlider(value=64, description='Z', max=127), Output()), layout=Layout(a…"
+      ]
+     },
+     "execution_count": 8,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "qim3d.viz.orthogonal(vol)"
+   ]
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python 3 (ipykernel)",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.11.5"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/docs/notebooks/blob_detection.ipynb b/docs/notebooks/blob_detection.ipynb
index d5490b4bac824fcd66be93af99815a94a1ed482e..86f6d94691fed612a44439db08d3382fa2d47c6b 100644
--- a/docs/notebooks/blob_detection.ipynb
+++ b/docs/notebooks/blob_detection.ipynb
@@ -4,15 +4,7 @@
    "cell_type": "code",
    "execution_count": 1,
    "metadata": {},
-   "outputs": [
-    {
-     "name": "stderr",
-     "output_type": "stream",
-     "text": [
-      "WARNING:root:Could not load CuPy: No module named 'cupy'\n"
-     ]
-    }
-   ],
+   "outputs": [],
    "source": [
     "import qim3d"
    ]
@@ -30,7 +22,9 @@
    "source": [
     "This notebook shows how to do **blob detection** in a 3D volume using the `qim3d` library. \n",
     "\n",
-    "Blob detection is done by initializing a `qim3d.processing.Blob` object, and then calling the `qim3d.processing.Blob.detect` method. The `qim3d.processing.Blob.detect` method detects blobs by using the Difference of Gaussian (DoG) blob detection method, and returns an array `blobs` with the blobs found in the volume stored as `(p, r, c, radius)`. Subsequently, a binary mask of the volume can be retrieved with the `qim3d.processing.get_mask` method, in which the found blobs are marked as `True`."
+    "Blob detection is done by using the `qim3d.processing.blob_detection` method, which detects blobs by using the Difference of Gaussian (DoG) blob detection method, and returns two arrays:\n",
+    "- `blobs`: The blobs found in the volume stored as `(p, r, c, radius)`\n",
+    "- `binary_volume`: A binary mask of the volume with the blobs marked as `True`"
    ]
   },
   {
@@ -49,7 +43,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 4,
+   "execution_count": 2,
    "metadata": {},
    "outputs": [
     {
@@ -69,7 +63,7 @@
        "<Figure size 1000x200 with 5 Axes>"
       ]
      },
-     "execution_count": 4,
+     "execution_count": 2,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -97,9 +91,16 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 12,
+   "execution_count": 3,
    "metadata": {},
    "outputs": [
+    {
+     "name": "stderr",
+     "output_type": "stream",
+     "text": [
+      "Bright background selected, volume will be inverted.\n"
+     ]
+    },
     {
      "name": "stdout",
      "output_type": "stream",
@@ -109,31 +110,29 @@
     }
    ],
    "source": [
-    "# Initialize blob detector\n",
-    "blob_detector = qim3d.processing.Blob(\n",
-    "    background = \"bright\", \n",
-    "    min_sigma = 1, \n",
-    "    max_sigma = 8, \n",
-    "    threshold = 0.001, \n",
-    "    overlap = 0.1\n",
+    "# Detect blobs, and get binary mask\n",
+    "blobs, mask = qim3d.processing.blob_detection(\n",
+    "    cement_filtered,\n",
+    "    min_sigma=1,\n",
+    "    max_sigma=8,\n",
+    "    threshold=0.001,\n",
+    "    overlap=0.1,\n",
+    "    background=\"bright\"\n",
     "    )\n",
     "\n",
-    "# Detect blobs in filtered volume\n",
-    "blobs = blob_detector.detect(vol = cement_filtered)\n",
-    "\n",
     "# Number of blobs found\n",
     "print(f'Number of blobs found in the volume: {len(blobs)} blobs')"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 13,
+   "execution_count": 6,
    "metadata": {},
    "outputs": [
     {
      "data": {
       "application/vnd.jupyter.widget-view+json": {
-       "model_id": "4d128089a7e545d7a3fd8a4607c02085",
+       "model_id": "54d5e4864544453695c7d87287aa7cf9",
        "version_major": 2,
        "version_minor": 0
       },
@@ -141,32 +140,32 @@
        "interactive(children=(IntSlider(value=64, description='Slice', max=127), Output()), layout=Layout(align_items=…"
       ]
      },
-     "execution_count": 13,
+     "execution_count": 6,
      "metadata": {},
      "output_type": "execute_result"
     }
    ],
    "source": [
     "# Visualize blobs on slices of cement volume\n",
-    "qim3d.viz.detection.circles(blobs, cement, show = True)"
+    "qim3d.viz.detection.circles(blobs, cement, alpha = 0.8, show = True, color = 'red')"
    ]
   },
   {
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "**Get binary mask of detected blobs**"
+    "**Binary mask of detected blobs**"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 14,
+   "execution_count": 5,
    "metadata": {},
    "outputs": [
     {
      "data": {
       "application/vnd.jupyter.widget-view+json": {
-       "model_id": "2d8e5b955da948de8f2bea5bb19000b9",
+       "model_id": "11fd726e79a246c98948ec54b3c752e2",
        "version_major": 2,
        "version_minor": 0
       },
@@ -174,16 +173,13 @@
        "interactive(children=(IntSlider(value=64, description='Slice', max=127), Output()), layout=Layout(align_items=…"
       ]
      },
-     "execution_count": 14,
+     "execution_count": 5,
      "metadata": {},
      "output_type": "execute_result"
     }
    ],
    "source": [
-    "# Get binary mask of detected blobs\n",
-    "mask = blob_detector.get_mask()\n",
-    "\n",
-    "# Visualize mask\n",
+    "# Visualize binary mask\n",
     "qim3d.viz.slicer(mask)"
    ]
   }
diff --git a/docs/processing.md b/docs/processing.md
index 550cb1001b6a51777e4790f41a3af58e30b87046..cd68efaba653a77ae1f938ca088e17e8b0f81ae7 100644
--- a/docs/processing.md
+++ b/docs/processing.md
@@ -5,14 +5,26 @@ Here, we provide functionalities designed specifically for 3D image analysis and
 ::: qim3d.processing
     options:
         members:
+            - test_blob_detection
+            - blob_detection
             - structure_tensor
             - local_thickness
             - get_3d_cc
-            - Pipeline
-            - Blob
+            - gaussian
+            - median
+            - maximum
+            - minimum
+            - tophat
+
+::: qim3d.processing.Pipeline
+    options:
+        members:
+            - append
 
 ::: qim3d.processing.operations
     options:
         members:
             - remove_background
             - watershed
+            - edge_fade
+            - fade_mask
diff --git a/docs/releases.md b/docs/releases.md
index 50372a1de82bd8730db03743793883544172125a..b6e158634f9908fc2901e353a8b4d83580b0beea 100644
--- a/docs/releases.md
+++ b/docs/releases.md
@@ -9,9 +9,17 @@ As the library is still in its early development stages, **there may be breaking
 
 And remember to keep your pip installation [up to date](/qim3d/#upgrade) so that you have the latest features!
 
+### v0.3.6 (30/05/2024)
+- Refactoring for performance improvement
+- Welcome message for the CLI
+- Introduction of `qim3d.processing.fade_mask` 🎉
+
+
 ### v0.3.5 (27/05/2024)
 - Added runtime and memory usage in the documentation
 - Introduction of `qim3d.utils.generate_volume` 🎉
+- Introduction of `preview` CLI 🎉
+
 
 ### v0.3.4 (22/05/2024)
 - Documentation for `qim3d.viz.plot_cc`
diff --git a/docs/utils.md b/docs/utils.md
index f86074f1b190446ad487521be309c3aa17515761..6425ef1ad29341cdeda23ac62c413ea604e47e1d 100644
--- a/docs/utils.md
+++ b/docs/utils.md
@@ -5,6 +5,7 @@ A set of tools to ease managment of the system, with the common needs for large
 ::: qim3d.utils.img
     options:
       members:
+        - generate_volume
         - overlay_rgb_images
 
 ::: qim3d.utils.system
diff --git a/docs/viz.md b/docs/viz.md
index 3ddd34a1c58617de014cf57e6479ad32fb826e72..691d31b496d472bd1725300f59ab7019d3155e4f 100644
--- a/docs/viz.md
+++ b/docs/viz.md
@@ -12,6 +12,7 @@ The `qim3d` library aims to provide easy ways to explore and get insights from v
             - vectors
             - plot_cc
             - colormaps
+            - interactive_fade_mask
             
 ::: qim3d.viz.colormaps
     options:
diff --git a/mkdocs.yml b/mkdocs.yml
index df02fdf5b76c459bb6035d146a84d808f00a8c5f..02e9b73b47a1dfdff8e6d6ed7831683ae8ec7a30 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -62,6 +62,7 @@ markdown_extensions:
   - admonition
   - attr_list
   - md_in_html
+  - tables
   - pymdownx.inlinehilite
   - pymdownx.snippets
   - pymdownx.details
@@ -84,5 +85,5 @@ plugins:
               show_root_full_path: true
               show_object_full_path: true
               show_symbol_type_heading: true
-              show_symbol_type_toc: true
+              show_symbol_type_toc: false
               separate_signature: true
\ No newline at end of file
diff --git a/qim3d/__init__.py b/qim3d/__init__.py
index 73714bf3730e46ae2be9e91c6062acb52a66de4f..c89b6fd07af948ddd1d126632e2713dd1c6e6f0e 100644
--- a/qim3d/__init__.py
+++ b/qim3d/__init__.py
@@ -1,14 +1,27 @@
+"""qim3d: A Python package for 3D image processing and visualization.
+
+The qim3d library is designed to make it easier to work with 3D imaging data in Python. 
+It offers a range of features, including data loading and manipulation,
+ image processing and filtering, visualization of 3D data, and analysis of imaging results.
+
+Documentation available at https://platform.qim.dk/qim3d/
+
+"""
+
+__version__ = "0.3.6"
+
 import logging
 
 logging.basicConfig(level=logging.ERROR)
 
-from qim3d import io
-from qim3d import gui
-from qim3d import viz
-from qim3d import utils
-from qim3d import models
-from qim3d import processing
+from . import io
+from . import gui
+from . import viz
+from . import utils
+from . import processing
+
+# Commenting out models because it takes too long to import
+# from . import models
 
-__version__ = "0.3.2"
 examples = io.ImgExamples()
 io.logger.set_level_info()
diff --git a/qim3d/gui/annotation_tool.py b/qim3d/gui/annotation_tool.py
index 571f53cce62630d280935f4086f210fe972af7ca..da66452814755d3af254a076b4c40063f4a66321 100644
--- a/qim3d/gui/annotation_tool.py
+++ b/qim3d/gui/annotation_tool.py
@@ -10,9 +10,14 @@ Or launched from a python script
 ```python
 import qim3d
 
-app = qim3d.gui.annotation_tool.Interface()
-app.launch()
+vol = qim3d.examples.NT_128x128x128
+annotation_tool = qim3d.gui.annotation_tool.Interface()
+
+# We can directly pass the image we loaded to the interface
+app = annotation_tool.launch(vol[0])
 ```
+![gui-annotation_tool](assets/screenshots/gui-annotation_tool.gif)
+
 """
 
 import getpass
diff --git a/qim3d/io/loading.py b/qim3d/io/loading.py
index bc9688320c3662fcb87ff1263e4f2a90070c122e..28638c80995f46ce162c8f5027de4bc6eb7b3671 100644
--- a/qim3d/io/loading.py
+++ b/qim3d/io/loading.py
@@ -31,7 +31,7 @@ from qim3d.io.logger import log
 from qim3d.utils.internal_tools import get_file_size, sizeof, stringify_path
 from qim3d.utils.system import Memory
 
-dask.config.set(scheduler="processes")  # Dask parallel goes brrrrr
+dask.config.set(scheduler="processes") 
 
 
 class DataLoader:
@@ -772,6 +772,16 @@ def load(
     """
     Load data from the specified file or directory.
 
+    Supported formats:
+
+    - `Tiff` (including file stacks)
+    - `HDF5`
+    - `TXRM`/`TXM`/`XRM`
+    - `NIfTI`
+    - `PIL` (including file stacks)
+    - `VOL`/`VGI`
+    - `DICOM`
+
     Args:
         path (str or os.PathLike): The path to the file or directory.
         virtual_stack (bool, optional): Specifies whether to use virtual
@@ -856,14 +866,17 @@ class ImgExamples:
         shell_225x128x128 (numpy.ndarray): A 3D volume of a shell.
 
     Tip:
-        Call `qim3d.examples.<name>` to access the image examples easily as this class is instantiated when importing `qim3d`
+        Simply call `qim3d.examples.<name>` to access the image examples.
 
     Example:
         ```python
         import qim3d
 
-        data = qim3d.examples.blobs_256x256
+        vol = qim3d.examples.shell_225x128x128
+        qim3d.viz.slices(vol, n_slices=15)
         ```
+        ![Grid of slices](assets/screenshots/viz-slices.png)
+
 
 
     """
diff --git a/qim3d/processing/__init__.py b/qim3d/processing/__init__.py
index 34dea0c4f0c556184277de549acf22dc9ad3f59e..79d9acf5f85d48c285d04424380ac455069330c6 100644
--- a/qim3d/processing/__init__.py
+++ b/qim3d/processing/__init__.py
@@ -1,6 +1,8 @@
+"Testing docstring"
+
 from .local_thickness_ import local_thickness
 from .structure_tensor_ import structure_tensor
+from .detection import blob_detection
 from .filters import *
-from .detection import *
 from .operations import *
 from .cc import get_3d_cc
diff --git a/qim3d/processing/detection.py b/qim3d/processing/detection.py
index b1397e4e36f7f5c4f49f67b9dc3f1e80fb0b6592..c5743a1501f8d2e20edda9f22c06b1eee76a48ad 100644
--- a/qim3d/processing/detection.py
+++ b/qim3d/processing/detection.py
@@ -1,60 +1,40 @@
+""" Blob detection using Difference of Gaussian (DoG) method """
+
 import numpy as np
 from qim3d.io.logger import log
 from skimage.feature import blob_dog
 
-__all__ = ["Blob"]
-
 
-class Blob:
+def blob_detection(
+    vol: np.ndarray,
+    background: str = "dark",
+    min_sigma: float = 1,
+    max_sigma: float = 50,
+    sigma_ratio: float = 1.6,
+    threshold: float = 0.5,
+    overlap: float = 0.5,
+    threshold_rel: float = None,
+    exclude_border: bool = False,
+) -> np.ndarray:
     """
-    Extract blobs from a volume using Difference of Gaussian (DoG) method
-    """
-    def __init__(
-        self,
-        background="dark",
-        min_sigma=1,
-        max_sigma=50,
-        sigma_ratio=1.6,
-        threshold=0.5,
-        overlap=0.5,
-        threshold_rel=None,
-        exclude_border=False,
-    ):
-        """
-        Initialize the blob detection object
-
-        Args:
-            background: 'dark' if background is darker than the blobs, 'bright' if background is lighter than the blobs
-            min_sigma: The minimum standard deviation for Gaussian kernel
-            max_sigma: The maximum standard deviation for Gaussian kernel
-            sigma_ratio: The ratio between the standard deviation of Gaussian Kernels
-            threshold: The absolute lower bound for scale space maxima. Reduce this to detect blobs with lower intensities.
-            overlap: The fraction of area of two blobs that overlap
-            threshold_rel: The relative lower bound for scale space maxima
-            exclude_border: If True, exclude blobs that are too close to the border of the image
-        """
-        self.background = background
-        self.min_sigma = min_sigma
-        self.max_sigma = max_sigma
-        self.sigma_ratio = sigma_ratio
-        self.threshold = threshold
-        self.overlap = overlap
-        self.threshold_rel = threshold_rel
-        self.exclude_border = exclude_border
-        self.vol_shape = None
-        self.blobs = None
-
-    def detect(self, vol):
-        """
-        Detect blobs in the volume
-
-        Args:
-            vol: The volume to detect blobs in
-
-        Returns:
-            blobs: The blobs found in the volume as (p, r, c, radius)
-
-        Example:
+    Extract blobs from a volume using Difference of Gaussian (DoG) method, and retrieve a binary volume with the blobs marked as True
+
+    Args:
+        vol: The volume to detect blobs in
+        background: 'dark' if background is darker than the blobs, 'bright' if background is lighter than the blobs
+        min_sigma: The minimum standard deviation for Gaussian kernel
+        max_sigma: The maximum standard deviation for Gaussian kernel
+        sigma_ratio: The ratio between the standard deviation of Gaussian Kernels
+        threshold: The absolute lower bound for scale space maxima. Reduce this to detect blobs with lower intensities
+        overlap: The fraction of area of two blobs that overlap
+        threshold_rel: The relative lower bound for scale space maxima
+        exclude_border: If True, exclude blobs that are too close to the border of the image
+
+    Returns:
+        blobs: The blobs found in the volume as (p, r, c, radius)
+        binary_volume: A binary volume with the blobs marked as True
+
+    Example:
             ```python
             import qim3d
 
@@ -62,8 +42,9 @@ class Blob:
             vol = qim3d.examples.cement_128x128x128
             vol_blurred = qim3d.processing.gaussian(vol, sigma=2)
 
-            # Initialize Blob detector
-            blob_detector = qim3d.processing.Blob(
+            # Detect blobs, and get binary mask
+            blobs, mask = qim3d.processing.blob_detection(
+                vol_blurred,
                 min_sigma=1,
                 max_sigma=8,
                 threshold=0.001,
@@ -71,88 +52,63 @@ class Blob:
                 background="bright"
                 )
 
-            # Detect blobs
-            blobs = blob_detector.detect(vol_blurred)
-
-            # Visualize results
-            qim3d.viz.circles(blobs,vol,alpha=0.8,color='blue')
+            # Visualize detected blobs
+            qim3d.viz.circles(blobs, vol, alpha=0.8, color='blue')
             ```
             ![blob detection](assets/screenshots/blob_detection.gif)
-        """
-        self.vol_shape = vol.shape
-        if self.background == "bright":
-            log.info("Bright background selected, volume will be inverted.")
-            vol = np.invert(vol)
-
-        blobs = blob_dog(
-            vol,
-            min_sigma=self.min_sigma,
-            max_sigma=self.max_sigma,
-            sigma_ratio=self.sigma_ratio,
-            threshold=self.threshold,
-            overlap=self.overlap,
-            threshold_rel=self.threshold_rel,
-            exclude_border=self.exclude_border,
-        )
-        blobs[:, 3] = blobs[:, 3] * np.sqrt(3)  # Change sigma to radius
-        self.blobs = blobs
-        return self.blobs
-    
-    def get_mask(self):
-        '''
-        Retrieve a binary volume with the blobs marked as True
-
-        Returns:
-            binary_volume: A binary volume with the blobs marked as True
-
-        Example:
-            ```python
-            import qim3d
-
-            # Get data
-            vol = qim3d.examples.cement_128x128x128
-            vol_blurred = qim3d.processing.gaussian(vol, sigma=2)
-
-            # Initialize Blob detector
-            blob_detector = qim3d.processing.Blob(
-                min_sigma=1,
-                max_sigma=8,
-                threshold=0.001,
-                overlap=0.1,
-                background="bright"
-                )
-
-                
-            # Detect blobs
-            blobs = blob_detector.detect(vol_blurred)
 
-            # Get mask and visualize
-            mask = blob_detector.get_mask()
+            ```python
+            # Visualize binary mask
             qim3d.viz.slicer(mask)
             ```
             ![blob detection](assets/screenshots/blob_get_mask.gif)
-        '''
-        binary_volume = np.zeros(self.vol_shape, dtype=bool)
-
-        for z, y, x, radius in self.blobs:
-            # Calculate the bounding box around the blob
-            z_start = max(0, int(z - radius))
-            z_end = min(self.vol_shape[0], int(z + radius) + 1)
-            y_start = max(0, int(y - radius))
-            y_end = min(self.vol_shape[1], int(y + radius) + 1)
-            x_start = max(0, int(x - radius))
-            x_end = min(self.vol_shape[2], int(x + radius) + 1)
-
-            z_indices, y_indices, x_indices = np.indices((z_end - z_start, y_end - y_start, x_end - x_start))
-            z_indices += z_start
-            y_indices += y_start
-            x_indices += x_start
-
-            # Calculate distances from the center of the blob to voxels within the bounding box
-            dist = np.sqrt((x_indices - x)**2 + (y_indices - y)**2 + (z_indices - z)**2)
+    """
 
-            binary_volume[z_start:z_end, y_start:y_end, x_start:x_end][dist <= radius] = True
+    if background == "bright":
+        log.info("Bright background selected, volume will be inverted.")
+        vol = np.invert(vol)
+
+    blobs = blob_dog(
+        vol,
+        min_sigma=min_sigma,
+        max_sigma=max_sigma,
+        sigma_ratio=sigma_ratio,
+        threshold=threshold,
+        overlap=overlap,
+        threshold_rel=threshold_rel,
+        exclude_border=exclude_border,
+    )
+
+    # Change sigma to radius
+    blobs[:, 3] = blobs[:, 3] * np.sqrt(3)
+
+    # Create binary mask of detected blobs
+    vol_shape = vol.shape
+    binary_volume = np.zeros(vol_shape, dtype=bool)
+
+    for z, y, x, radius in blobs:
+        # Calculate the bounding box around the blob
+        z_start = max(0, int(z - radius))
+        z_end = min(vol_shape[0], int(z + radius) + 1)
+        y_start = max(0, int(y - radius))
+        y_end = min(vol_shape[1], int(y + radius) + 1)
+        x_start = max(0, int(x - radius))
+        x_end = min(vol_shape[2], int(x + radius) + 1)
+
+        z_indices, y_indices, x_indices = np.indices(
+            (z_end - z_start, y_end - y_start, x_end - x_start)
+        )
+        z_indices += z_start
+        y_indices += y_start
+        x_indices += x_start
 
-        return binary_volume
+        # Calculate distances from the center of the blob to voxels within the bounding box
+        dist = np.sqrt(
+            (x_indices - x) ** 2 + (y_indices - y) ** 2 + (z_indices - z) ** 2
+        )
 
+        binary_volume[z_start:z_end, y_start:y_end, x_start:x_end][
+            dist <= radius
+        ] = True
 
+    return blobs, binary_volume
diff --git a/qim3d/processing/filters.py b/qim3d/processing/filters.py
index 354214c4d7e699de2c934cfbfed0710dd51e1c23..fc2d89134ded623e2e478f24f75a4e3735224169 100644
--- a/qim3d/processing/filters.py
+++ b/qim3d/processing/filters.py
@@ -265,11 +265,13 @@ def minimum(vol, **kwargs):
 
 def tophat(vol, **kwargs):
     """
-    Remove background from the volume
+    Remove background from the volume.
+
     Args:
         vol: The volume to remove background from
         radius: The radius of the structuring element (default: 3)
         background: color of the background, 'dark' or 'bright' (default: 'dark'). If 'bright', volume will be inverted.
+    
     Returns:
         vol: The volume with background removed
     """
diff --git a/qim3d/processing/local_thickness_.py b/qim3d/processing/local_thickness_.py
index 5e7b9416b53b0180623ffd60dd7ebe5f50396c99..f0de7e9954b4c4a3e5a7a7a3218bfbb0a6df0faa 100644
--- a/qim3d/processing/local_thickness_.py
+++ b/qim3d/processing/local_thickness_.py
@@ -5,8 +5,8 @@ import numpy as np
 from typing import Optional
 from skimage.filters import threshold_otsu
 from qim3d.io.logger import log
-from qim3d.viz import local_thickness as viz_local_thickness
-
+#from qim3d.viz import local_thickness as viz_local_thickness
+import qim3d
 
 def local_thickness(
     image: np.ndarray,
@@ -96,6 +96,6 @@ def local_thickness(
 
     # Visualize the local thickness if requested
     if visualize:
-        display(viz_local_thickness(image, local_thickness, **viz_kwargs))
+        display(qim3d.viz.local_thickness(image, local_thickness, **viz_kwargs))
 
     return local_thickness
diff --git a/qim3d/processing/operations.py b/qim3d/processing/operations.py
index b932c7f8d2d6cf599e04b4ac53a95005f2e16907..dfaeb42e41d2bb88f54f5d1ce5db1ecfff4a5d30 100644
--- a/qim3d/processing/operations.py
+++ b/qim3d/processing/operations.py
@@ -1,7 +1,8 @@
 import numpy as np
-import qim3d.processing.filters as filters
 import scipy
 import skimage
+
+import qim3d.processing.filters as filters
 from qim3d.io.logger import log
 
 
@@ -105,4 +106,82 @@ def watershed(
     num_labels = len(np.unique(labeled_volume))-1
     log.info(f"Total number of objects found: {num_labels}")
 
-    return labeled_volume, num_labels
\ No newline at end of file
+    return labeled_volume, num_labels
+
+def fade_mask(
+    vol: np.ndarray,
+    decay_rate: float = 10,
+    ratio: float = 0.5,
+    geometry: str = "sphere",
+    invert=False,
+    axis: int = 0,
+    ):
+    """
+    Apply edge fading to a volume.
+
+    Args:
+        vol (np.ndarray): The volume to apply edge fading to.
+        decay_rate (float, optional): The decay rate of the fading. Defaults to 10.
+        ratio (float, optional): The ratio of the volume to fade. Defaults to 0.
+        geometric (str, optional): The geometric shape of the fading. Can be 'spherical' or 'cylindrical'. Defaults to 'spherical'.
+        axis (int, optional): The axis along which to apply the fading. Defaults to 0.
+    
+    Returns:
+        np.ndarray: The volume with edge fading applied.
+
+    Example:
+        ```python
+        import qim3d
+        qim3d.viz.vol(vol)
+        ```
+        Image before edge fading has visible artifacts from the support. Which obscures the object of interest.
+        ![operations-edge_fade_before](assets/screenshots/operations-edge_fade_before.png)  
+
+        ```python
+        import qim3d
+        vol_faded = qim3d.processing.operations.edge_fade(vol, decay_rate=4, ratio=0.45, geometric='cylindrical')
+        qim3d.viz.vol(vol_faded)
+        ```
+        Afterwards the artifacts are faded out, making the object of interest more visible for visualization purposes.
+        ![operations-edge_fade_after](assets/screenshots/operations-edge_fade_after.png)
+
+    """
+    if 0 > axis or axis >= vol.ndim:
+        raise ValueError("Axis must be between 0 and the number of dimensions of the volume")
+
+    # Generate the coordinates of each point in the array
+    shape = vol.shape
+    z, y, x = np.indices(shape)
+    
+    # Calculate the center of the array
+    center = np.array([(s - 1) / 2 for s in shape])
+    
+    # Calculate the distance of each point from the center
+    if geometry == "sphere":
+        distance = np.linalg.norm([z - center[0], y - center[1], x - center[2]], axis=0)
+    elif geometry == "cilinder":
+        distance_list = np.array([z - center[0], y - center[1], x - center[2]])
+        # remove the axis along which the fading is not applied
+        distance_list = np.delete(distance_list, axis, axis=0)
+        distance = np.linalg.norm(distance_list, axis=0)
+    else:
+        raise ValueError("geometric must be 'spherical' or 'cylindrical'")
+    
+    # Normalize the distances so that they go from 0 at the center to 1 at the farthest point
+    max_distance = np.linalg.norm(center)
+    normalized_distance = distance / (max_distance*ratio)
+    
+    # Apply the decay rate
+    faded_distance = normalized_distance ** decay_rate
+    
+    # Invert the distances to have 1 at the center and 0 at the edges
+    fade_array = 1 - faded_distance
+    fade_array[fade_array<=0]=0
+    
+    if invert:
+        fade_array = -(fade_array-1)
+
+    # Apply the fading to the volume
+    vol_faded = vol * fade_array
+
+    return vol_faded
diff --git a/qim3d/utils/cli.py b/qim3d/utils/cli.py
index c48e477716322a4d4fb4050862610c2baffa482a..0e783c6dfcc5cc95c18bdf2ba0d1cc1bb2e03e25 100644
--- a/qim3d/utils/cli.py
+++ b/qim3d/utils/cli.py
@@ -1,40 +1,84 @@
 import argparse
 import webbrowser
 
-import qim3d
 from qim3d.gui import annotation_tool, data_explorer, iso3d, local_thickness
 from qim3d.io.loading import DataLoader
 from qim3d.utils import image_preview
+from qim3d import __version__ as version
+import qim3d
 
 
 def main():
-    parser = argparse.ArgumentParser(description='Qim3d command-line interface.')
-    subparsers = parser.add_subparsers(title='Subcommands', dest='subcommand')
+    parser = argparse.ArgumentParser(description="Qim3d command-line interface.")
+    subparsers = parser.add_subparsers(title="Subcommands", dest="subcommand")
 
     # GUIs
-    gui_parser = subparsers.add_parser('gui', help = 'Graphical User Interfaces.')
-
-    gui_parser.add_argument('--data-explorer', action='store_true', help='Run data explorer.')
-    gui_parser.add_argument('--iso3d', action='store_true', help='Run iso3d.')
-    gui_parser.add_argument('--annotation-tool', action='store_true', help='Run annotation tool.')
-    gui_parser.add_argument('--local-thickness', action='store_true', help='Run local thickness tool.')
-    gui_parser.add_argument('--host', default='0.0.0.0', help='Desired host.')
-    gui_parser.add_argument('--platform', action='store_true', help='Use QIM platform address')
-    gui_parser.add_argument('--no-browser', action='store_true', help='Do not launch browser.')
-
-    # K3D 
-    viz_parser = subparsers.add_parser('viz', help = 'Volumetric visualization.')
-    viz_parser.add_argument('--source', default=False, help='Path to the image file')
-    viz_parser.add_argument('--destination', default='k3d.html', help='Path to save html file.')
-    viz_parser.add_argument('--no-browser', action='store_true', help='Do not launch browser.')
+    gui_parser = subparsers.add_parser("gui", help="Graphical User Interfaces.")
+
+    gui_parser.add_argument(
+        "--data-explorer", action="store_true", help="Run data explorer."
+    )
+    gui_parser.add_argument("--iso3d", action="store_true", help="Run iso3d.")
+    gui_parser.add_argument(
+        "--annotation-tool", action="store_true", help="Run annotation tool."
+    )
+    gui_parser.add_argument(
+        "--local-thickness", action="store_true", help="Run local thickness tool."
+    )
+    gui_parser.add_argument("--host", default="0.0.0.0", help="Desired host.")
+    gui_parser.add_argument(
+        "--platform", action="store_true", help="Use QIM platform address"
+    )
+    gui_parser.add_argument(
+        "--no-browser", action="store_true", help="Do not launch browser."
+    )
+
+    # K3D
+    viz_parser = subparsers.add_parser("viz", help="Volumetric visualization.")
+    viz_parser.add_argument("--source", default=False, help="Path to the image file")
+    viz_parser.add_argument(
+        "--destination", default="k3d.html", help="Path to save html file."
+    )
+    viz_parser.add_argument(
+        "--no-browser", action="store_true", help="Do not launch browser."
+    )
 
     # Preview
-    preview_parser = subparsers.add_parser('preview', help= 'Preview of the image in CLI')
-    preview_parser.add_argument('filename',type = str, metavar = 'FILENAME', help = 'Path to image that will be displayed')
-    preview_parser.add_argument('--slice',type = int, metavar ='S', default = None, help = 'Specifies which slice of the image will be displayed.\nDefaults to middle slice. If number exceeds number of slices, last slice will be displayed.' )
-    preview_parser.add_argument('--axis', type = int, metavar = 'AX', default=0, help = 'Specifies from which axis will be the slice taken. Defaults to 0.')
-    preview_parser.add_argument('--resolution',type = int, metavar = 'RES', default = 80, help = 'Resolution of displayed image. Defaults to 80.')
-    preview_parser.add_argument('--absolute_values', action='store_false', help = 'By default set the maximum value to be 255 so the contrast is strong. This turns it off.')
+    preview_parser = subparsers.add_parser(
+        "preview", help="Preview of the image in CLI"
+    )
+    preview_parser.add_argument(
+        "filename",
+        type=str,
+        metavar="FILENAME",
+        help="Path to image that will be displayed",
+    )
+    preview_parser.add_argument(
+        "--slice",
+        type=int,
+        metavar="S",
+        default=None,
+        help="Specifies which slice of the image will be displayed.\nDefaults to middle slice. If number exceeds number of slices, last slice will be displayed.",
+    )
+    preview_parser.add_argument(
+        "--axis",
+        type=int,
+        metavar="AX",
+        default=0,
+        help="Specifies from which axis will be the slice taken. Defaults to 0.",
+    )
+    preview_parser.add_argument(
+        "--resolution",
+        type=int,
+        metavar="RES",
+        default=80,
+        help="Resolution of displayed image. Defaults to 80.",
+    )
+    preview_parser.add_argument(
+        "--absolute_values",
+        action="store_false",
+        help="By default set the maximum value to be 255 so the contrast is strong. This turns it off.",
+    )
 
     # File Convert
     preview_parser = subparsers.add_parser('convert', help= 'Convert files to different formats without loading the entire file into memory')
@@ -43,9 +87,9 @@ def main():
 
     args = parser.parse_args()
 
-    if args.subcommand == 'gui':
+    if args.subcommand == "gui":
         arghost = args.host
-        inbrowser = not args.no_browser # Should automatically open in browser
+        inbrowser = not args.no_browser  # Should automatically open in browser
         if args.data_explorer:
             if args.platform:
                 data_explorer.run_interface(arghost)
@@ -58,45 +102,77 @@ def main():
             else:
                 interface = iso3d.Interface()
                 interface.launch(inbrowser=inbrowser)
-        
+
         elif args.annotation_tool:
             if args.platform:
                 annotation_tool.run_interface(arghost)
             else:
                 interface = annotation_tool.Interface()
-                interface.launch(inbrowser=inbrowser)        
+                interface.launch(inbrowser=inbrowser)
         elif args.local_thickness:
             if args.platform:
                 local_thickness.run_interface(arghost)
             else:
                 interface = local_thickness.Interface()
                 interface.launch(inbrowser=inbrowser)
-
-
-    if args.subcommand == "viz":
+    elif args.subcommand == "viz":
         if not args.source:
-            print ("Please specify a source file using the argument --source")
+            print("Please specify a source file using the argument --source")
             return
         # Load the data
-        print (f"Loading data from {args.source}")
+        print(f"Loading data from {args.source}")
         volume = qim3d.io.load(str(args.source))
-        print (f"Done, volume shape: {volume.shape}")
+        print(f"Done, volume shape: {volume.shape}")
 
         # Make k3d plot
-        print ("\nGenerating k3d plot...")
+        print("\nGenerating k3d plot...")
         qim3d.viz.vol(volume, show=False, save=str(args.destination))
-        print (f"Done, plot available at <{args.destination}>")
+        print(f"Done, plot available at <{args.destination}>")
 
         if not args.no_browser:
             print("Opening in default browser...")
             webbrowser.open_new_tab(args.destination)
 
-    if args.subcommand == 'preview':
+    elif args.subcommand == "preview":
         image = DataLoader().load(args.filename)
-        image_preview(image, image_width = args.resolution, axis =  args.axis, slice = args.slice, relative_intensity= args.absolute_values)
-
-    if args.subcommand == 'convert':
+        image_preview(
+            image,
+            image_width=args.resolution,
+            axis=args.axis,
+            slice=args.slice,
+            relative_intensity=args.absolute_values,
+        )
+
+    elif args.subcommand is None:
+        welcome_text = (
+            "\n"
+            "         _          _____     __ \n"
+            "  ____ _(_)___ ___ |__  /____/ / \n"
+            " / __ `/ / __ `__ \ /_ </ __  /  \n"
+            "/ /_/ / / / / / / /__/ / /_/ /   \n"
+            "\__, /_/_/ /_/ /_/____/\__,_/    \n"
+            "  /_/                            \n"
+            "\n"
+            "--- Welcome to qim3d command-line interface ---\n"
+            "qim3d is a Python package for 3D image processing and visualization.\n"
+            "For more information, please visit: https://platform.qim.dk/qim3d/\n"
+            f"Current version of qim3d: {version}\n"
+            " \n"
+            "The qim3d command-line interface provides the following subcommands:\n"
+            "- gui: Graphical User Interfaces\n"
+            "- viz: Volumetric visualizations of volumes\n"
+            "- preview: Preview of an volume directly in the terminal\n"
+            " \n"
+            "For more information on each subcommand, type 'qim3d <subcommand> --help'.\n"
+        )
+        print(welcome_text)
+        print("--- Help page for qim3d command-line interface shown below ---\n")
+        parser.print_help()
+        print("\n")
+
+    elif args.subcommand == 'convert':
         qim3d.io.convert(args.input_path, args.output_path)
-        
-if __name__ == '__main__':
-    main()
\ No newline at end of file
+
+
+if __name__ == "__main__":
+    main()
diff --git a/qim3d/utils/img.py b/qim3d/utils/img.py
index 13f181faef80baa52dae596969d3d8a8c90e4651..6b91af384311662885897dcd6c5938fd18c67a84 100644
--- a/qim3d/utils/img.py
+++ b/qim3d/utils/img.py
@@ -82,9 +82,18 @@ def generate_volume(
         ValueError: If `dtype` is not a valid numpy number type.
 
     Example:
+        ```python
         import qim3d
-        vol = qim3d.utils.generate_volume()
-        qim3d.viz.slices(vol, vmin=0, vmax=255)
+        vol = qim3d.utils.generate_volume(noise_scale=0.05, threshold=0.4)
+        qim3d.viz.slices(vol, vmin=0, vmax=255, n_slices=15)
+        ```
+        ![generate_volume](assets/screenshots/generate_volume.png)
+
+        ```python
+        qim3d.viz.vol(vol)
+        ```
+        <iframe src="https://platform.qim.dk/k3d/synthetic_volume.html" width="100%" height="500" frameborder="0"></iframe>
+
     """
 
     if not isinstance(final_shape, tuple) or len(final_shape) != 3:
diff --git a/qim3d/viz/__init__.py b/qim3d/viz/__init__.py
index c11df18022667389276dfafaf077b5579f388801..080a4ebd7a812cb659c5b817d18767a3d287568a 100644
--- a/qim3d/viz/__init__.py
+++ b/qim3d/viz/__init__.py
@@ -1,10 +1,16 @@
-from .visualizations import plot_metrics
-from .img import grid_pred, grid_overview, slices, slicer, orthogonal, vol_masked
-from .k3d import vol
-from .structure_tensor import vectors
-from .local_thickness_ import local_thickness
-from .cc import plot_cc
-#from .colormaps import objects
 from . import colormaps
-
+from .cc import plot_cc
 from .detection import circles
+from .img import (
+    grid_overview,
+    grid_pred,
+    interactive_fade_mask,
+    orthogonal,
+    slicer,
+    slices,
+    vol_masked,
+)
+from .k3d import vol
+from .local_thickness_ import local_thickness
+from .structure_tensor import vectors
+from .visualizations import plot_metrics
diff --git a/qim3d/viz/cc.py b/qim3d/viz/cc.py
index 7efee4923463ee44419b54443d06896c9cbd8436..8daee7dca7cbeb0f206ac215954521b4564b2393 100644
--- a/qim3d/viz/cc.py
+++ b/qim3d/viz/cc.py
@@ -3,9 +3,8 @@ import numpy as np
 
 from qim3d.io.logger import log
 from qim3d.processing.cc import CC
-from qim3d.viz import slices
 from qim3d.viz.colormaps import objects as qim3dCmap
-
+import qim3d
 
 def plot_cc(
     connected_components,
@@ -66,18 +65,18 @@ def plot_cc(
                 overlay_crop = overlay[bb]
                 # use cc as mask for overlay_crop, where all values in cc set to 0 should be masked out, cc contains integers
                 overlay_crop = np.where(cc == 0, 0, overlay_crop)
-                fig = slices(overlay_crop, show=show, **kwargs)
+                fig = qim3d.viz.slices(overlay_crop, show=show, **kwargs)
             else:
                 cc = connected_components.get_cc(component, crop=False)
                 overlay_crop = np.where(cc == 0, 0, overlay)
-                fig = slices(overlay_crop, show=show, **kwargs)
+                fig = qim3d.viz.slices(overlay_crop, show=show, **kwargs)
         else:
             # assigns discrete color map to each connected component if not given 
             if "cmap" not in kwargs:
                 kwargs["cmap"] = qim3dCmap(len(component_indexs))
         
             # Plot the connected component without overlay
-            fig = slices(connected_components.get_cc(component, crop=crop), show=show, **kwargs)
+            fig = qim3d.viz.slices(connected_components.get_cc(component, crop=crop), show=show, **kwargs)
 
         figs.append(fig)
 
diff --git a/qim3d/viz/colormaps.py b/qim3d/viz/colormaps.py
index d72c94d9ec1f104ec605f487b89dbbdf9b5cc609..547e10fb8f1afd0e70b1993d9c73fc7c902f4683 100644
--- a/qim3d/viz/colormaps.py
+++ b/qim3d/viz/colormaps.py
@@ -10,8 +10,6 @@ from matplotlib.colors import LinearSegmentedColormap
 from matplotlib import colormaps
 from skimage import color
 
-from qim3d.io.logger import log
-
 def rearrange_colors(randRGBcolors_old, min_dist = 0.5):
     # Create new list for re-arranged colors
     randRGBcolors_new = [randRGBcolors_old.pop(0)]
diff --git a/qim3d/viz/detection.py b/qim3d/viz/detection.py
index acfffa70c5e09ebc535a5823ffa207da099de779..f6ff1bf102c44106696b2eda97b868e83d4dff67 100644
--- a/qim3d/viz/detection.py
+++ b/qim3d/viz/detection.py
@@ -1,10 +1,9 @@
 import matplotlib.pyplot as plt
-from qim3d.viz import slices
 from qim3d.io.logger import log
 import numpy as np
 import ipywidgets as widgets
 from IPython.display import clear_output, display
-
+import qim3d
 
 def circles(blobs, vol, alpha=0.5, color="#ff9900", **kwargs):
     """
@@ -16,7 +15,7 @@ def circles(blobs, vol, alpha=0.5, color="#ff9900", **kwargs):
 
     Args:
         blobs (array-like): An array-like object of blobs, where each blob is represented
-            as a 4-tuple (p, r, c, radius). Usally the result of `qim3d.processing.Blob().detect()`
+            as a 4-tuple (p, r, c, radius). Usually the result of `qim3d.processing.blob_detection(vol)`
         vol (array-like): The 3D volume on which to plot the blobs.
         alpha (float, optional): The transparency of the blobs. Defaults to 0.5.
         color (str, optional): The color of the blobs. Defaults to "#ff9900".
@@ -29,7 +28,7 @@ def circles(blobs, vol, alpha=0.5, color="#ff9900", **kwargs):
 
     def _slicer(z_slice):
         clear_output(wait=True)
-        fig = slices(
+        fig = qim3d.viz.slices(
             vol,
             n_slices=1,
             position=z_slice,
diff --git a/qim3d/viz/img.py b/qim3d/viz/img.py
index 769db8cdbc2832c6cd189b1b887d6a9489bf0b05..f2293565675af6b6fc7cf76b4577d6fbedd2fa22 100644
--- a/qim3d/viz/img.py
+++ b/qim3d/viz/img.py
@@ -5,14 +5,15 @@ Provides a collection of visualization functions.
 import math
 from typing import List, Optional, Union
 
+import dask.array as da
 import ipywidgets as widgets
 import matplotlib.pyplot as plt
 import numpy as np
 import torch
 from matplotlib import colormaps
 from matplotlib.colors import LinearSegmentedColormap
-import dask.array as da
 
+import qim3d
 from qim3d.io.logger import log
 
 
@@ -265,8 +266,8 @@ def slices(
         ```python
         import qim3d
 
-        img = qim3d.examples.shell_225x128x128
-        qim3d.viz.slices(img, n_slices=15)
+        vol = qim3d.examples.shell_225x128x128
+        qim3d.viz.slices(vol, n_slices=15)
         ```
         ![Grid of slices](assets/screenshots/viz-slices.png)
     """
@@ -557,3 +558,83 @@ def vol_masked(vol, vol_mask, viz_delta=128):
     vol_masked = background + foreground
 
     return vol_masked
+
+def interactive_fade_mask(vol: np.ndarray, axis: int = 0):
+    """ Interactive widget for visualizing the effect of edge fading on a 3D volume.
+
+    This can be used to select the best parameters before applying the mask.
+
+    Args:
+        vol (np.ndarray): The volume to apply edge fading to.
+        axis (int, optional): The axis along which to apply the fading. Defaults to 0.
+
+    Example:
+        ```python
+        import qim3d
+        vol = qim3d.examples.cement_128x128x128
+        qim3d.viz.interactive_fade_mask(vol) 
+        ```
+        ![operations-edge_fade_before](assets/screenshots/viz-fade_mask.gif)  
+
+    """
+
+    # Create the interactive widget
+    def _slicer(position, decay_rate, ratio, geometry, invert):
+        fig, axes = plt.subplots(1, 3, figsize=(9, 3))
+
+        axes[0].imshow(vol[position, :, :], cmap='viridis')
+        axes[0].set_title('Original')
+        axes[0].axis('off')
+
+        mask = qim3d.processing.operations.fade_mask(np.ones_like(vol), decay_rate=decay_rate, ratio=ratio, geometry=geometry, axis=axis, invert=invert)
+        axes[1].imshow(mask[position, :, :], cmap='viridis')
+        axes[1].set_title('Mask')
+        axes[1].axis('off')
+
+        masked_vol = qim3d.processing.operations.fade_mask(vol, decay_rate=decay_rate, ratio=ratio,  geometry=geometry, axis=axis, invert=invert)
+        axes[2].imshow(masked_vol[position, :, :], cmap='viridis')
+        axes[2].set_title('Masked')
+        axes[2].axis('off')
+
+        return fig
+    
+    shape_dropdown = widgets.Dropdown(
+        options=['sphere', 'cilinder'],
+        value='sphere',  # default value
+        description='Geometry',
+    )
+
+    position_slider = widgets.IntSlider(
+        value=vol.shape[0] // 2,
+        min=0,
+        max=vol.shape[0] - 1,
+        description="Slice",
+        continuous_update=False,
+    )
+    decay_rate_slider = widgets.FloatSlider(
+        value=10,
+        min=1,
+        max=50,
+        step=1.0,
+        description="Decay Rate",
+        continuous_update=False,
+    )
+    ratio_slider = widgets.FloatSlider(
+        value=0.5,
+        min=0.1,
+        max=1,
+        step=0.01, 
+        description="Ratio",
+        continuous_update=False,
+    )
+
+    # Create the Checkbox widget
+    invert_checkbox = widgets.Checkbox(
+        value=False,  # default value
+        description='Invert'
+    )
+
+    slicer_obj = widgets.interactive(_slicer, position=position_slider, decay_rate=decay_rate_slider, ratio=ratio_slider, geometry=shape_dropdown, invert=invert_checkbox)
+    slicer_obj.layout = widgets.Layout(align_items="flex-start")
+
+    return slicer_obj
diff --git a/qim3d/viz/k3d.py b/qim3d/viz/k3d.py
index 9271c56717b6f1ca95f674d16001c07ac3569dab..e612568b95e5a70b40ec99ccc2b914ff6ae71288 100644
--- a/qim3d/viz/k3d.py
+++ b/qim3d/viz/k3d.py
@@ -25,6 +25,7 @@ def vol(
     cmap=None,
     samples="auto",
     max_voxels=412**3,
+    data_type="scaled_float16",
     **kwargs,
 ):
     """
@@ -72,6 +73,7 @@ def vol(
         ```
 
     """
+
     pixel_count = img.shape[0] * img.shape[1] * img.shape[2]
     # target is 60fps on m1 macbook pro, using test volume: https://data.qim.dk/pages/foam.html
     if samples == "auto":
@@ -88,10 +90,10 @@ def vol(
 
     if aspectmode.lower() not in ["data", "cube"]:
         raise ValueError("aspectmode should be either 'data' or 'cube'")
-
     # check if image should be downsampled for visualization
     original_shape = img.shape
     img = downscale_img(img, max_voxels=max_voxels)
+
     new_shape = img.shape
 
     if original_shape != new_shape:
@@ -99,6 +101,17 @@ def vol(
             f"Downsampled image for visualization. From {original_shape} to {new_shape}"
         )
 
+    # Scale the image to float16 if needed
+    if save:
+        # When saving, we need float64
+        img = img.astype(np.float64)
+    else:
+
+        if data_type == "scaled_float16":
+            img = scale_to_float16(img)
+        else:
+            img = img.astype(data_type)
+
     # Set color ranges
     color_range = [np.min(img), np.max(img)]
     if vmin:
@@ -106,8 +119,9 @@ def vol(
     if vmax:
         color_range[1] = vmax
 
+    # Create the volume plot
     plt_volume = k3d.volume(
-        scale_to_float16(img),
+        img,
         bounds=(
             [0, img.shape[2], 0, img.shape[1], 0, img.shape[0]]
             if aspectmode.lower() == "data"
@@ -119,7 +133,6 @@ def vol(
     )
     plot = k3d.plot(grid_visible=grid_visible, **kwargs)
     plot += plt_volume
-
     if save:
         # Save html to disk
         with open(str(save), "w", encoding="utf-8") as fp:
diff --git a/setup.py b/setup.py
index 40caf8e9001803be246912181eb9ba47a18995d1..017d277a0acb04bd4f63301c31186d0432e97d9b 100644
--- a/setup.py
+++ b/setup.py
@@ -9,7 +9,7 @@ with open("README.md", "r", encoding="utf-8") as f:
 
 setup(
     name="qim3d",
-    version="0.3.5",
+    version="0.3.6",
     author="Felipe Delestro",
     author_email="fima@dtu.dk",
     description="QIM tools and user interfaces",