diff --git a/docs/assets/screenshots/blob_detection.gif b/docs/assets/screenshots/blob_detection.gif
new file mode 100644
index 0000000000000000000000000000000000000000..3f001f7ef1db6a4143961a11ecf03316bb0215f9
Binary files /dev/null and b/docs/assets/screenshots/blob_detection.gif differ
diff --git a/docs/assets/screenshots/blob_get_mask.gif b/docs/assets/screenshots/blob_get_mask.gif
new file mode 100644
index 0000000000000000000000000000000000000000..2377a9055871f81d5503a622317c8087c4add159
Binary files /dev/null and b/docs/assets/screenshots/blob_get_mask.gif differ
diff --git a/docs/assets/screenshots/filter_original.png b/docs/assets/screenshots/filter_original.png
new file mode 100644
index 0000000000000000000000000000000000000000..81532fac3317ba9faa89554d3b10bf1c84759a21
Binary files /dev/null and b/docs/assets/screenshots/filter_original.png differ
diff --git a/docs/assets/screenshots/filter_processed.png b/docs/assets/screenshots/filter_processed.png
new file mode 100644
index 0000000000000000000000000000000000000000..699c29def9340bfef18f256acb7ceb3025205f22
Binary files /dev/null and b/docs/assets/screenshots/filter_processed.png differ
diff --git a/docs/assets/screenshots/local_thickness_2d.png b/docs/assets/screenshots/local_thickness_2d.png
new file mode 100644
index 0000000000000000000000000000000000000000..de3b9bc38c230efca50a6fd2d6743ffa583837af
Binary files /dev/null and b/docs/assets/screenshots/local_thickness_2d.png differ
diff --git a/docs/assets/screenshots/local_thickness_3d.gif b/docs/assets/screenshots/local_thickness_3d.gif
new file mode 100644
index 0000000000000000000000000000000000000000..fb55580eeb1a6bdd036b4ae472e1821d47e6e7d8
Binary files /dev/null and b/docs/assets/screenshots/local_thickness_3d.gif differ
diff --git a/docs/assets/screenshots/structure_tensor.gif b/docs/assets/screenshots/structure_tensor.gif
new file mode 100644
index 0000000000000000000000000000000000000000..1195d2084b9bdde5616fafc44442005dd54c22c9
Binary files /dev/null and b/docs/assets/screenshots/structure_tensor.gif differ
diff --git a/docs/io.md b/docs/io.md
index 3d15f97b206949d26a314edc9a8ec065a4c18b09..da1364ecf4492c2337a2d44af0111e24fd5b3b4e 100644
--- a/docs/io.md
+++ b/docs/io.md
@@ -3,32 +3,10 @@ Dealing with volumetric data can be done by `qim3d` for the most common image fo
 
 Currently, it is possible to directly load `tiff`, `h5`, `nii`,`txm`, `vol` and common `PIL` formats using one single function.
 
-
-!!! Example
-    ```python
-    import qim3d
-
-    # Get some data from examples
-    vol = qim3d.examples.blobs_256x256x256
-
-    # Save in a local file
-    qim3d.io.save("blobs.tif", vol)
-
-    # Load data from file
-    loaded_vol = qim3d.io.load("blobs.tif")
-    ```
-
-::: qim3d.io.load
+::: qim3d.io
     options:
         members:
             - load
-
-::: qim3d.io.save
-    options:
-        members:
             - save
-
-::: qim3d.io.downloader
-    options:
-        members:
-            - Downloader
\ No newline at end of file
+            - Downloader
+            - ImgExamples
\ No newline at end of file
diff --git a/docs/processing.md b/docs/processing.md
index 114e38d8da59d227f30058670f479e22dce0601d..b469a21c6bf8cf1c9115e9efa0f9404dd62c9b53 100644
--- a/docs/processing.md
+++ b/docs/processing.md
@@ -1,16 +1,12 @@
 # Processing data
 
-::: qim3d.processing.filters
-    options:
-        members:
-            - 
-
+`qim3d` provides various tools for 3D image processing. Here, we provide a suite of powerful functionalities designed specifically for 3D image analysis and processing. From filter pipelines to structure tensor computation and blob detection, `qim3d` equips you with the tools you need to extract meaningful insights from your data.
 
-::: qim3d.processing.filters.Pipeline
+::: qim3d.processing
     options:
         members:
-            - append
-
-
-
-::: qim3d.processing.structure_tensor
\ No newline at end of file
+            - structure_tensor
+            - local_thickness
+            - get_3d_cc
+            - Pipeline
+            - Blob
\ No newline at end of file
diff --git a/docs/releases.md b/docs/releases.md
index e029f4ad88c5c56fd7db4a8b199a51b57e6316cc..3f611b8c1992b6f31560b58bb54f1e59725a2bca 100644
--- a/docs/releases.md
+++ b/docs/releases.md
@@ -12,6 +12,12 @@ And remember to keep your pip installation [up to date](/qim3d/#upgrade) so that
 ### v0.3.3 (coming soon!)
 - Introduction of `qim3d.viz.slicer` (and also `qim3d.viz.orthogonal` ) 🎉
 - Introduction of `qim3d.gui.annotation_tool` 🎉
+- Introduction of `qim3d.processing.Blob` for blob detection 🎉
+- Introduction of `qim3d.processing.local_thickness` 🎉
+- Introduction of `qim3d.processing.structure_tensor` 🎉
+- Support for loading DICOM files with `qim3d.io.load`🎉
+- Introduction of `qim3d.processing.get_3d_cc` for 3D connected components and `qim3d.viz.plot_cc` for associated visualization 🎉
+- Introduction of `qim3d.viz.colormaps` for easy visualization of e.g. multi-label segmentation results 🎉
 
 ### v0.3.2 (23/02/2024)
 
@@ -20,9 +26,9 @@ This version focus on the increased usability of the `qim3d` library
 - Online documentation available at [https://platform.qim.dk/qim3d](https://platform.qim.dk/qim3d)
 - Virtual stacks also available for `txm` files
 - Updated GUI launch pipeline
-- New functionalities for `qim3d.vix.slices`
+- New functionalities for `qim3d.viz.slices`
 - Introduction of `qim3d.processing.filters` 🎉 
-- Introduction of `qim3d.viz.k3d` 🎉 
+- Introduction of `qim3d.viz.vol` 🎉 
 
 ### v0.3.1 (01/02/2024)
 
diff --git a/docs/viz.md b/docs/viz.md
index 042b73314d652d66b5a004cfb65f4fa3b28dc945..bc729bc1505713f463a682ed6b9701ef5dc0f98f 100644
--- a/docs/viz.md
+++ b/docs/viz.md
@@ -1,54 +1,14 @@
 # Data visualization
-The `qim3d`libray aims to provide easy ways to explore and get insights from volumetric data. 
+The `qim3d` library aims to provide easy ways to explore and get insights from volumetric data. 
 
-!!! Example
-    ```python
-    import qim3d
-
-    img = qim3d.examples.shell_225x128x128
-    qim3d.viz.slices(img, n_slices=15)
-    ```
-
-    ![Grid of slices](assets/screenshots/viz-slices.png)
-
-
-!!! Example
-    ```python
-    import qim3d
-
-    vol = qim3d.examples.bone_128x128x128
-    qim3d.viz.slicer(vol)
-    ```
-    ![viz slicer](assets/screenshots/viz-slicer.gif)
-
-
-!!! Example
-    ```python
-    import qim3d
-
-    vol = qim3d.examples.fly_150x256x256
-    qim3d.viz.orthogonal(vol, cmap="magma")
-    ```
-    ![viz orthogonal](assets/screenshots/viz-orthogonal.gif)
-
-
-!!! Example
-    ```python
-    import qim3d
-
-    vol = qim3d.examples.bone_128x128x128
-    qim3d.viz.vol(vol) 
-    ```
-
-    <iframe src="https://platform.qim.dk/k3d/fima-bone_128x128x128-20240221113459.html" width="100%" height="500" frameborder="0"></iframe>
-::: qim3d.viz.img
+::: qim3d.viz
     options:
         members:
             - slices
             - slicer
             - orthogonal
-
-::: qim3d.viz.k3d
-    options:
-        members:
             - vol
+            - local_thickness
+            - vectors
+            - plot_cc
+            - objects
diff --git a/qim3d/io/__init__.py b/qim3d/io/__init__.py
index 3384ec71aa400a9ff34debdd75d6b0a1c5dc8bb9..f97cb7d4df45d2d32243a99b9449b749c8c24868 100644
--- a/qim3d/io/__init__.py
+++ b/qim3d/io/__init__.py
@@ -1,5 +1,5 @@
+from .loading import DataLoader, load, ImgExamples
 from .downloader import Downloader
-from .load import DataLoader, load, ImgExamples
-from .save import DataSaver, save 
+from .saving import DataSaver, save 
 from .sync import Sync
 from . import logger
\ No newline at end of file
diff --git a/qim3d/io/downloader.py b/qim3d/io/downloader.py
index a57871dd294e51977bc52266c3d97f26130aa312..32ea5489c099f5a6566864696fbeda5c83afe03d 100644
--- a/qim3d/io/downloader.py
+++ b/qim3d/io/downloader.py
@@ -7,16 +7,16 @@ from urllib.parse import quote
 from tqdm import tqdm
 from pathlib import Path
 
-from qim3d.io.load import load
+from qim3d.io import load
 from qim3d.io.logger import log
 import outputformat as ouf
 
 
 class Downloader:
-    """Class for downloading large data files available on the [QIM data repository](https://data.qim.dk/data-repository/).
+    """Class for downloading large data files available on the [QIM data repository](https://data.qim.dk/).
 
     Attributes:
-        [folder_name] (str): folder class with the name of the folder in https://data.qim.dk/data-repository/
+        [folder_name] (str): folder class with the name of the folder in <https://data.qim.dk/>
 
     Example:
         ```python
diff --git a/qim3d/io/load.py b/qim3d/io/loading.py
similarity index 93%
rename from qim3d/io/load.py
rename to qim3d/io/loading.py
index 2a48536aabb8ddba00560389695d42391d448bae..83b4c3d50415d32c576689a3ce8955ba25049fb0 100644
--- a/qim3d/io/load.py
+++ b/qim3d/io/loading.py
@@ -480,9 +480,11 @@ class DataLoader:
             path (str or os.PathLike): The path to the file or directory.
 
         Returns:
-            numpy.ndarray, numpy.memmap, h5py._hl.dataset.Dataset, nibabel.arrayproxy.ArrayProxy or tuple: The loaded volume.
-                If 'self.virtual_stack' is True, returns numpy.memmap, h5py._hl.dataset.Dataset or nibabel.arrayproxy.ArrayProxy depending on file format
-                If 'self.return_metadata' is True and file format is either HDF5, NIfTI or TXRM/TXM/XRM, returns a tuple (volume, metadata).
+            vol (numpy.ndarray, numpy.memmap, h5py._hl.dataset.Dataset, nibabel.arrayproxy.ArrayProxy or tuple): The loaded volume
+        
+                If `virtual_stack=True`, returns `numpy.memmap`, `h5py._hl.dataset.Dataset` or `nibabel.arrayproxy.ArrayProxy` depending on file format
+                If `return_metadata=True` and file format is either HDF5, NIfTI or TXRM/TXM/XRM, returns `tuple` (volume, metadata).
+
         Raises:
             ValueError: If the format is not supported
             ValueError: If the file or directory does not exist.
@@ -585,11 +587,10 @@ def load(
         to the DataLoader constructor.
 
     Returns:
-        numpy.ndarray, numpy.memmap, h5py._hl.dataset.Dataset, nibabel.arrayproxy.ArrayProxy or tuple: The loaded volume.
-
-        If 'virtual_stack' is True, returns numpy.memmap, h5py._hl.dataset.Dataset or nibabel.arrayproxy.ArrayProxy depending on file format
-        
-        If 'return_metadata' is True and file format is either HDF5, NIfTI or TXRM/TXM/XRM, returns a tuple (volume, metadata).
+        vol (numpy.ndarray, numpy.memmap, h5py._hl.dataset.Dataset, nibabel.arrayproxy.ArrayProxy or tuple): The loaded volume
+    
+            If `virtual_stack=True`, returns `numpy.memmap`, `h5py._hl.dataset.Dataset` or `nibabel.arrayproxy.ArrayProxy` depending on file format
+            If `return_metadata=True` and file format is either HDF5, NIfTI or TXRM/TXM/XRM, returns `tuple` (volume, metadata).
 
     Example:
         ```python
@@ -635,7 +636,30 @@ def load(
 
 
 class ImgExamples:
-    """Image examples"""
+    """Image examples
+
+    Attributes:
+        blobs_256x256 (numpy.ndarray): A 2D image of blobs.
+        blobs_256x256x256 (numpy.ndarray): A 3D volume of blobs.
+        bone_128x128x128 (numpy.ndarray): A 3D volume of bone.
+        cement_128x128x128 (numpy.ndarray): A 3D volume of cement.
+        fly_150x256x256 (numpy.ndarray): A 3D volume of a fly.
+        NT_10x200x100 (numpy.ndarray): A 3D volume of a neuron.
+        NT_128x128x128 (numpy.ndarray): A 3D volume of a neuron.
+        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`
+    
+    Example:
+        ```python
+        import qim3d
+
+        data = qim3d.examples.blobs_256x256
+        ```
+
+
+    """
 
     def __init__(self):
         img_examples_path = Path(qim3d.__file__).parents[0] / "img_examples"
@@ -647,4 +671,4 @@ class ImgExamples:
 
         # Generate loader for each image found
         for idx, name in enumerate(img_names):
-            exec(f"self.{name} = qim3d.io.load(path = img_paths[idx])")
\ No newline at end of file
+            exec(f"self.{name} = load(path = img_paths[idx])")
\ No newline at end of file
diff --git a/qim3d/io/save.py b/qim3d/io/saving.py
similarity index 100%
rename from qim3d/io/save.py
rename to qim3d/io/saving.py
diff --git a/qim3d/processing/__init__.py b/qim3d/processing/__init__.py
index 7d251a6b1b976e33ee5d2ada0d1fa0308428756a..18b959f02996a87d8e4a2c46c0577c02419ecae7 100644
--- a/qim3d/processing/__init__.py
+++ b/qim3d/processing/__init__.py
@@ -1,4 +1,5 @@
+from .local_thickness_ import local_thickness
+from .structure_tensor_ import structure_tensor
 from .filters import *
-from .local_thickness import local_thickness
-from .structure_tensor import structure_tensor
 from .detection import *
+from .cc import get_3d_cc
diff --git a/qim3d/utils/cc.py b/qim3d/processing/cc.py
similarity index 100%
rename from qim3d/utils/cc.py
rename to qim3d/processing/cc.py
diff --git a/qim3d/processing/detection.py b/qim3d/processing/detection.py
index b74cf36e9e0eacb957fb66975c9a5809dc06abe3..94136e090ad41f40cc2ec617db6cb08abf4a7a11 100644
--- a/qim3d/processing/detection.py
+++ b/qim3d/processing/detection.py
@@ -6,6 +6,9 @@ __all__ = ["Blob"]
 
 
 class Blob:
+    """
+    Extract blobs from a volume using Difference of Gaussian (DoG) method
+    """
     def __init__(
         self,
         background="dark",
@@ -19,6 +22,7 @@ class Blob:
     ):
         """
         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
@@ -43,10 +47,37 @@ class Blob:
     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:
+            ```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)
+
+            # Visualize results
+            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":
@@ -70,8 +101,32 @@ class Blob:
     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"
+                )
+
+            # Get mask and visualize
+            mask = blob_detector.get_mask()
+            qim3d.viz.slicer(mask)
+            ```
+            ![blob detection](assets/screenshots/blob_get_mask.gif)
         '''
         binary_volume = np.zeros(self.vol_shape, dtype=bool)
 
diff --git a/qim3d/processing/filters.py b/qim3d/processing/filters.py
index 4390ba69aa41a9281c36662ceea30eb5d5eed72e..c7d39ef901f834aa12828c7f9cad014dd416b73a 100644
--- a/qim3d/processing/filters.py
+++ b/qim3d/processing/filters.py
@@ -87,6 +87,37 @@ class Minimum(FilterBase):
 
 
 class Pipeline:
+    """
+    Example:
+        ```python
+        import qim3d
+        from qim3d.processing import Pipeline, Median, Gaussian, Maximum, Minimum
+
+        # Get data
+        vol = qim3d.examples.fly_150x256x256
+
+        # Show original
+        qim3d.viz.slices(vol, axis=0, show=True)
+
+        # Create filter pipeline
+        pipeline = Pipeline(
+            Median(size=5),
+            Gaussian(sigma=3)
+        )
+
+        # Append a third filter to the pipeline
+        pipeline.append(Maximum(size=3))
+
+        # Apply filter pipeline
+        vol_filtered = pipeline(vol)
+
+        # Show filtered
+        qim3d.viz.slices(vol_filtered, axis=0)
+        ```
+        ![original volume](assets/screenshots/filter_original.png)
+        ![filtered volume](assets/screenshots/filter_processed.png)
+            
+        """
     def __init__(self, *args: Type[FilterBase]):
         """
         Represents a sequence of image filters.
@@ -125,6 +156,20 @@ class Pipeline:
 
         Args:
             fn: An instance of a FilterBase subclass to be appended.
+        
+        Example:
+            ```python
+            import qim3d
+            from qim3d.processing import Pipeline, Maximum, Median
+
+            # Create filter pipeline
+            pipeline = Pipeline(
+                Maximum(size=3)
+            )
+
+            # Append a second filter to the pipeline
+            pipeline.append(Median(size=5))
+            ```
         """
         self._add_filter(str(len(self.filters)), fn)
 
diff --git a/qim3d/processing/local_thickness.py b/qim3d/processing/local_thickness_.py
similarity index 77%
rename from qim3d/processing/local_thickness.py
rename to qim3d/processing/local_thickness_.py
index e581c01376d96c428dcb1b39b2fc47a56f9898f2..8090ad5be561027819c4f84b877d08b6a1df1ec0 100644
--- a/qim3d/processing/local_thickness.py
+++ b/qim3d/processing/local_thickness_.py
@@ -15,7 +15,7 @@ def local_thickness(
     visualize=False,
     **viz_kwargs
 ) -> np.ndarray:
-    """Wrapper for the local thickness function from the localthickness package (https://github.com/vedranaa/local-thickness)
+    """Wrapper for the local thickness function from the [local thickness package](https://github.com/vedranaa/local-thickness)
 
     Args:
         image (np.ndarray): 2D or 3D NumPy array representing the image/volume.
@@ -26,11 +26,27 @@ def local_thickness(
         mask (np.ndarray, optional): binary mask of the same size of the image defining parts of the
             image to be included in the computation of the local thickness. Default is None.
         visualize (bool, optional): Whether to visualize the local thickness. Default is False.
-        **viz_kwargs: Additional keyword arguments for the visualization function. Only used if visualize=True.
+        **viz_kwargs: Additional keyword arguments passed to `qim3d.viz.local_thickness`. Only used if `visualize=True`.
 
     Returns:
         local_thickness (np.ndarray): 2D or 3D NumPy array representing the local thickness of the input image/volume.
+    
+    Example:
+        ```python
+        import qim3d
 
+        fly = qim3d.examples.fly_150x256x256 # 3D volume
+        lt_fly = qim3d.processing.local_thickness(fly, visualize=True, axis=0)
+        ```
+        ![local thickness 3d](assets/screenshots/local_thickness_3d.gif)
+
+        ```python
+        import qim3d
+
+        blobs = qim3d.examples.blobs_256x256 # 2D image
+        lt_blobs = qim3d.processing.local_thickness(blobs, visualize=True)
+        ```
+        ![local thickness 2d](assets/screenshots/local_thickness_2d.png)
 
     !!! quote "Reference"
         Dahl, V. A., & Dahl, A. B. (2023, June). Fast Local Thickness. 2023 IEEE/CVF Conference on Computer Vision and Pattern Recognition Workshops (CVPRW).
diff --git a/qim3d/processing/structure_tensor.py b/qim3d/processing/structure_tensor_.py
similarity index 76%
rename from qim3d/processing/structure_tensor.py
rename to qim3d/processing/structure_tensor_.py
index abd37cc6c6828a0fd9fd86e321be439a37c640c5..2699fa70c5427d848e45fa28466f3c5b8a35773b 100644
--- a/qim3d/processing/structure_tensor.py
+++ b/qim3d/processing/structure_tensor_.py
@@ -18,11 +18,12 @@ def structure_tensor(
 
     Args:
         vol (np.ndarray): 3D NumPy array representing the volume.
-        sigma (float): A noise scale, structures smaller than sigma will be removed by smoothing.
-        rho (float): An integration scale giving the size over the neighborhood in which the orientation is to be analysed.
-        full: A flag indicating that all three eigenvalues should be returned. Default is False.
+        sigma (float, optional): A noise scale, structures smaller than sigma will be removed by smoothing.
+        rho (float, optional): An integration scale giving the size over the neighborhood in which the orientation is to be analysed.
+        full (bool, optional): A flag indicating that all three eigenvalues should be returned. Default is False.
         visualize (bool, optional): Whether to visualize the structure tensor. Default is False.
-        **viz_kwargs: Additional keyword arguments for the visualization function. Only used if visualize=True.
+        **viz_kwargs: Additional keyword arguments for passed to `qim3d.viz.vectors`. Only used if `visualize=True`.
+
     Raises:
         ValueError: If the input volume is not 3D.
 
@@ -30,6 +31,15 @@ def structure_tensor(
         val: An array with shape `(3, *vol.shape)` containing the eigenvalues of the structure tensor.
         vec: An array with shape `(3, *vol.shape)` if `full` is `False`, otherwise `(3, 3, *vol.shape)` containing eigenvectors.
 
+    Example:
+        ```python
+        import qim3d
+
+        vol = qim3d.examples.NT_128x128x128
+        val, vec = qim3d.processing.structure_tensor(vol, visualize=True, axis=2)
+        ```
+        ![structure tensor](assets/screenshots/structure_tensor.gif)
+
     !!! quote "Reference"
         Jeppesen, N., et al. "Quantifying effects of manufacturing methods on fiber orientation in unidirectional composites using structure tensor analysis." Composites Part A: Applied Science and Manufacturing 149 (2021): 106541.
         <https://doi.org/10.1016/j.compositesa.2021.106541>
diff --git a/qim3d/tests/utils/test_connected_components.py b/qim3d/tests/processing/test_connected_components.py
similarity index 97%
rename from qim3d/tests/utils/test_connected_components.py
rename to qim3d/tests/processing/test_connected_components.py
index cb2b21089b1db5b2517a58524aa5df264809ce06..0e972123f11b4b9f163127afc110872155e84e95 100644
--- a/qim3d/tests/utils/test_connected_components.py
+++ b/qim3d/tests/processing/test_connected_components.py
@@ -1,7 +1,7 @@
 import numpy as np
 import pytest
 
-from qim3d.utils.cc import get_3d_cc
+from qim3d.processing.cc import get_3d_cc
 
 
 @pytest.fixture(scope="module")
diff --git a/qim3d/tests/viz/test_img.py b/qim3d/tests/viz/test_img.py
index 2ac9eed9bb09418f888b9ec9bf0e3a0bf54d8979..1179a15f5c03f002632c097e1c58fad019b2321b 100644
--- a/qim3d/tests/viz/test_img.py
+++ b/qim3d/tests/viz/test_img.py
@@ -241,5 +241,3 @@ def test_local_thickness_3d_max_projection():
 
     # Assert that returned object is an interactive widget
     assert isinstance(fig, plt.Figure)
-
-
diff --git a/qim3d/utils/__init__.py b/qim3d/utils/__init__.py
index 9b08365be27631d622fbf6edfb29014330885fc9..3459794553284d61b6a79dbca693bcf4aeb52ea6 100644
--- a/qim3d/utils/__init__.py
+++ b/qim3d/utils/__init__.py
@@ -1,7 +1,6 @@
 #from .doi import get_bibtex, get_reference
 from . import doi, internal_tools
 from .augmentations import Augmentation
-from .cc import get_3d_cc
 from .data import Dataset, prepare_dataloaders, prepare_datasets
 from .img import overlay_rgb_images
 from .models import inference, model_summary, train_model
diff --git a/qim3d/viz/__init__.py b/qim3d/viz/__init__.py
index 2057c9d48476f37b0dc05e3daa098018d3a72c1e..54a1b11bfef542fcb2b0f4b5f40fc25517c2aaec 100644
--- a/qim3d/viz/__init__.py
+++ b/qim3d/viz/__init__.py
@@ -1,6 +1,8 @@
 from .visualizations import plot_metrics
-from .img import grid_pred, grid_overview, slices, slicer, orthogonal, plot_cc, local_thickness
+from .img import grid_pred, grid_overview, slices, slicer, orthogonal
 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 .detection import circles
diff --git a/qim3d/viz/cc.py b/qim3d/viz/cc.py
new file mode 100644
index 0000000000000000000000000000000000000000..0f3ac8fdf0f84240088ab8c9b5eb76da9624aaa3
--- /dev/null
+++ b/qim3d/viz/cc.py
@@ -0,0 +1,72 @@
+import numpy as np
+import matplotlib.pyplot as plt
+from qim3d.viz import slices
+from qim3d.viz.colormaps import objects as qim3dCmap
+from qim3d.processing.cc import CC
+
+def plot_cc(
+    connected_components,
+    component_indexs: list | tuple = None,
+    max_cc_to_plot=32,
+    overlay=None,
+    crop=False,
+    show=True,
+    **kwargs,
+) -> list[plt.Figure]:
+    """
+    Plot the connected components of an image.
+
+    Parameters:
+        connected_components (CC): The connected components object.
+        components (list | tuple, optional): The components to plot. If None the first max_cc_to_plot=32 components will be plotted. Defaults to None.
+        max_cc_to_plot (int, optional): The maximum number of connected components to plot. Defaults to 32.
+        overlay (optional): Overlay image. Defaults to None.
+        crop (bool, optional): Whether to crop the image to the cc. Defaults to False.
+        show (bool, optional): Whether to show the figure. Defaults to True.
+        **kwargs: Additional keyword arguments to pass to `qim3d.viz.slices`.
+
+    Returns:
+        figs (list[plt.Figure]): List of figures, if `show=False`.
+    """
+    # if no components are given, plot the first max_cc_to_plot=32 components
+    if component_indexs is None:
+        if len(connected_components) > max_cc_to_plot:
+            log.warning(
+                f"More than {max_cc_to_plot} connected components found. Only the first {max_cc_to_plot} will be plotted. Change max_cc_to_plot to plot more components."
+            )
+        component_indexs = range(
+            1, min(max_cc_to_plot + 1, len(connected_components) + 1)
+        )
+        
+    figs = []
+    for component in component_indexs:
+        if overlay is not None:
+            assert (overlay.shape == connected_components.shape), f"Overlay image must have the same shape as the connected components. overlay.shape=={overlay.shape} != connected_components.shape={connected_components.shape}."
+
+            # plots overlay masked to connected component
+            if crop:
+                # Crop the overlay image based on the bounding box of the component
+                bb = connected_components.get_bounding_box(component)[0]
+                cc = connected_components.get_cc(component, crop=True)
+                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)
+            else:
+                cc = connected_components.get_cc(component, crop=False)
+                overlay_crop = np.where(cc == 0, 0, overlay)
+                fig = 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)
+
+        figs.append(fig)
+
+    if not show:
+        return figs
+
+    return
\ No newline at end of file
diff --git a/qim3d/viz/colormaps.py b/qim3d/viz/colormaps.py
index c6ee1b5f7f2708ee376574b896758dff8d8f1939..a32b778444c2fb8d4ddf69338598ab9b19b1c687 100644
--- a/qim3d/viz/colormaps.py
+++ b/qim3d/viz/colormaps.py
@@ -25,7 +25,7 @@ def objects(
         seed (int, optional): Seed for random number generator. Defaults to 19.
 
     Returns:
-        matplotlib.colors.LinearSegmentedColormap: Colormap for matplotlib
+        cmap (matplotlib.colors.LinearSegmentedColormap): Colormap for matplotlib
     """
     # Check style
     if style not in ("bright", "soft"):
diff --git a/qim3d/viz/detection.py b/qim3d/viz/detection.py
index ca9c15fcff663b0d0c5e77dc6ac88000c11811ac..acfffa70c5e09ebc535a5823ffa207da099de779 100644
--- a/qim3d/viz/detection.py
+++ b/qim3d/viz/detection.py
@@ -16,13 +16,14 @@ 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.detection.Blob()
+            as a 4-tuple (p, r, c, radius). Usally the result of `qim3d.processing.Blob().detect()`
         vol (array-like): The 3D volume on which to plot the blobs.
-        z_slice (int, optional): The index of the slice to plot. If not provided, the middle slice is used.
+        alpha (float, optional): The transparency of the blobs. Defaults to 0.5.
+        color (str, optional): The color of the blobs. Defaults to "#ff9900".
         **kwargs: Arbitrary keyword arguments for the `slices` function.
 
     Returns:
-        matplotlib.figure.Figure: The resulting figure after adding the blobs to the slice.
+        slicer_obj (ipywidgets.interactive): An interactive widget for visualizing the blobs.
 
     """
 
diff --git a/qim3d/viz/img.py b/qim3d/viz/img.py
index cc5c417f6d5e5686415316c81064b124778ffea5..4acefda157683a86b6f59447d2e2cfb3d106c159 100644
--- a/qim3d/viz/img.py
+++ b/qim3d/viz/img.py
@@ -3,7 +3,7 @@ Provides a collection of visualization functions.
 """
 
 import math
-from typing import List, Optional, Union, Tuple
+from typing import List, Optional, Union
 
 import ipywidgets as widgets
 import matplotlib.pyplot as plt
@@ -12,10 +12,8 @@ import torch
 from matplotlib import colormaps
 from matplotlib.colors import LinearSegmentedColormap
 
-import qim3d.io
 from qim3d.io.logger import log
-from qim3d.utils.cc import CC
-from qim3d.viz.colormaps import objects
+
 
 
 def grid_overview(
@@ -270,7 +268,7 @@ def slices(
         img = qim3d.examples.shell_225x128x128
         qim3d.viz.slices(img, n_slices=15)
         ```
-
+        ![Grid of slices](assets/screenshots/viz-slices.png)
     """
 
     # Numpy array or Torch tensor input
@@ -411,8 +409,8 @@ def slicer(
         vol (np.ndarray or torch.Tensor): The 3D volume to be sliced.
         axis (int, optional): Specifies the axis, or dimension, along which to slice. Defaults to 0.
         cmap (str, optional): Specifies the color map for the image. Defaults to "viridis".
-        img_height(int, optional): Height of the figure. Defaults to 3.
-        img_width(int, optional): Width of the figure. Defaults to 3.
+        img_height (int, optional): Height of the figure. Defaults to 3.
+        img_width (int, optional): Width of the figure. Defaults to 3.
         show_position (bool, optional): If True, displays the position of the slices. Defaults to False.
         interpolation (str, optional): Specifies the interpolation method for the image. Defaults to None.
 
@@ -424,8 +422,9 @@ def slicer(
         import qim3d
 
         vol = qim3d.examples.bone_128x128x128
-        qim3d.viz.slicer(vol, cmap="magma")
+        qim3d.viz.slicer(vol)
         ```
+        ![viz slicer](assets/screenshots/viz-slicer.gif)
     """
 
     # Create the interactive widget
@@ -483,9 +482,10 @@ def orthogonal(
         ```python
         import qim3d
 
-        vol = qim3d.examples.bone_128x128x128
-        qim3d.viz.orthogonal(vol)
+        vol = qim3d.examples.fly_150x256x256
+        qim3d.viz.orthogonal(vol, cmap="magma")
         ```
+        ![viz orthogonal](assets/screenshots/viz-orthogonal.gif)
     """
 
     z_slicer = slicer(
@@ -520,194 +520,4 @@ def orthogonal(
     y_slicer.children[0].description = "Y"
     x_slicer.children[0].description = "X"
 
-    return widgets.HBox([z_slicer, y_slicer, x_slicer])
-
-
-def plot_cc(
-    connected_components: CC,
-    component_indexs: list | tuple = None,
-    max_cc_to_plot=32,
-    overlay=None,
-    crop=False,
-    show=True,
-    **kwargs,
-) -> list[plt.Figure]:
-    """
-    Plot the connected components of an image.
-
-    Parameters:
-        connected_components (CC): The connected components object.
-        components (list | tuple, optional): The components to plot. If None the first max_cc_to_plot=32 components will be plotted. Defaults to None.
-        max_cc_to_plot (int, optional): The maximum number of connected components to plot. Defaults to 32.
-        overlay (optional): Overlay image. Defaults to None.
-        crop (bool, optional): Whether to crop the image to the cc. Defaults to False.
-        show (bool, optional): Whether to show the figure. Defaults to True.
-        **kwargs: Additional keyword arguments to pass to `qim3d.viz.slices`.
-
-    Returns:
-        figs (list[plt.Figure]): List of figures, if `show=False`.
-    """
-    # if no components are given, plot the first max_cc_to_plot=32 components
-    if component_indexs is None:
-        if len(connected_components) > max_cc_to_plot:
-            log.warning(
-                f"More than {max_cc_to_plot} connected components found. Only the first {max_cc_to_plot} will be plotted. Change max_cc_to_plot to plot more components."
-            )
-        component_indexs = range(
-            1, min(max_cc_to_plot + 1, len(connected_components) + 1)
-        )
-        
-    figs = []
-    for component in component_indexs:
-        if overlay is not None:
-            assert (overlay.shape == connected_components.shape), f"Overlay image must have the same shape as the connected components. overlay.shape=={overlay.shape} != connected_components.shape={connected_components.shape}."
-
-            # plots overlay masked to connected component
-            if crop:
-                # Crop the overlay image based on the bounding box of the component
-                bb = connected_components.get_bounding_box(component)[0]
-                cc = connected_components.get_cc(component, crop=True)
-                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)
-            else:
-                cc = connected_components.get_cc(component, crop=False)
-                overlay_crop = np.where(cc == 0, 0, overlay)
-                fig = 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)
-
-        figs.append(fig)
-
-    if not show:
-        return figs
-
-    return
-
-
-def local_thickness(
-    image: np.ndarray,
-    image_lt: np.ndarray,
-    max_projection: bool = False,
-    axis: int = 0,
-    slice_idx: Optional[Union[int, float]] = None,
-    show: bool = False,
-    figsize: Tuple[int, int] = (15, 5),
-) -> Union[plt.Figure, widgets.interactive]:
-    """Visualizes the local thickness of a 2D or 3D image.
-
-    Args:
-        image (np.ndarray): 2D or 3D NumPy array representing the image/volume.
-        image_lt (np.ndarray): 2D or 3D NumPy array representing the local thickness of the input
-            image/volume.
-        max_projection (bool, optional): If True, displays the maximum projection of the local
-            thickness. Only used for 3D images. Defaults to False.
-        axis (int, optional): The axis along which to visualize the local thickness.
-            Unused for 2D images.
-            Defaults to 0.
-        slice_idx (int or float, optional): The initial slice to be visualized. The slice index
-            can afterwards be changed. If value is an integer, it will be the index of the slice
-            to be visualized. If value is a float between 0 and 1, it will be multiplied by the
-            number of slices and rounded to the nearest integer. If None, the middle slice will
-            be used for 3D images. Unused for 2D images. Defaults to None.
-        show (bool, optional): If True, displays the plot (i.e. calls plt.show()). Defaults to False.
-        figsize (Tuple[int, int], optional): The size of the figure. Defaults to (15, 5).
-
-    Raises:
-        ValueError: If the slice index is not an integer or a float between 0 and 1.
-
-    Returns:
-        If the input is 3D, returns an interactive widget. Otherwise, returns a matplotlib figure.
-
-    Example:
-        image_lt = qim3d.processing.local_thickness(image)
-        qim3d.viz.local_thickness(image, image_lt, slice_idx=10)
-    """
-
-    def _local_thickness(image, image_lt, show, figsize, axis=None, slice_idx=None):
-        if slice_idx is not None:
-            image = image.take(slice_idx, axis=axis)
-            image_lt = image_lt.take(slice_idx, axis=axis)
-
-        fig, axs = plt.subplots(1, 3, figsize=figsize, layout="constrained")
-
-        axs[0].imshow(image, cmap="gray")
-        axs[0].set_title("Original image")
-        axs[0].axis("off")
-
-        axs[1].imshow(image_lt, cmap="viridis")
-        axs[1].set_title("Local thickness")
-        axs[1].axis("off")
-
-        plt.colorbar(
-            axs[1].imshow(image_lt, cmap="viridis"), ax=axs[1], orientation="vertical"
-        )
-
-        axs[2].hist(image_lt[image_lt > 0].ravel(), bins=32, edgecolor="black")
-        axs[2].set_title("Local thickness histogram")
-        axs[2].set_xlabel("Local thickness")
-        axs[2].set_ylabel("Count")
-
-        if show:
-            plt.show()
-
-        plt.close()
-
-        return fig
-
-    # Get the middle slice if the input is 3D
-    if len(image.shape) == 3:
-        if max_projection:
-            if slice_idx is not None:
-                log.warning(
-                    "slice_idx is not used for max_projection. It will be ignored."
-                )
-            image = image.max(axis=axis)
-            image_lt = image_lt.max(axis=axis)
-            return _local_thickness(image, image_lt, show, figsize)
-        else:
-            if slice_idx is None:
-                slice_idx = image.shape[axis] // 2
-            elif isinstance(slice_idx, float):
-                if slice_idx < 0 or slice_idx > 1:
-                    raise ValueError(
-                        "Values of slice_idx of float type must be between 0 and 1."
-                    )
-                slice_idx = int(slice_idx * image.shape[0]) - 1
-            slide_idx_slider = widgets.IntSlider(
-                min=0,
-                max=image.shape[axis] - 1,
-                step=1,
-                value=slice_idx,
-                description="Slice index",
-                layout=widgets.Layout(width="450px"),
-            )
-            widget_obj = widgets.interactive(
-                _local_thickness,
-                image=widgets.fixed(image),
-                image_lt=widgets.fixed(image_lt),
-                show=widgets.fixed(True),
-                figsize=widgets.fixed(figsize),
-                axis=widgets.fixed(axis),
-                slice_idx=slide_idx_slider,
-            )
-            widget_obj.layout = widgets.Layout(align_items="center")
-            if show:
-                display(widget_obj)
-            return widget_obj
-    else:
-        if max_projection:
-            log.warning(
-                "max_projection is only used for 3D images. It will be ignored."
-            )
-        if slice_idx is not None:
-            log.warning("slice_idx is only used for 3D images. It will be ignored.")
-        return _local_thickness(image, image_lt, show, figsize)
-
-
+    return widgets.HBox([z_slicer, y_slicer, x_slicer])
\ No newline at end of file
diff --git a/qim3d/viz/k3d.py b/qim3d/viz/k3d.py
index d5f3ebbd6c9e236459df1348fa6bbe03ad1a5d56..a6e45878e7240df81f99d28b6538a49d6d45679a 100644
--- a/qim3d/viz/k3d.py
+++ b/qim3d/viz/k3d.py
@@ -17,31 +17,33 @@ def vol(img, aspectmode="data", show=True, save=False, grid_visible=False, cmap=
 
     Args:
         img (numpy.ndarray): The input 3D image data. It should be a 3D numpy array.
-        aspectmode (str, optional): Determines the proportions of the scene's axes.
-            If "data", the axes are drawn in proportion with the axes' ranges.
-            If "cube", the axes are drawn as a cube, regardless of the axes' ranges.
-            Defaults to "data".
+        aspectmode (str, optional): Determines the proportions of the scene's axes. Defaults to "data".
+
+            If `'data'`, the axes are drawn in proportion with the axes' ranges.
+            If `'cube'`, the axes are drawn as a cube, regardless of the axes' ranges.
         show (bool, optional): If True, displays the visualization inline. Defaults to True.
         save (bool or str, optional): If True, saves the visualization as an HTML file.
             If a string is provided, it's interpreted as the file path where the HTML
             file will be saved. Defaults to False.
         grid_visible (bool, optional): If True, the grid is visible in the plot. Defaults to False.
-        **kwargs: Additional keyword arguments to be passed to the k3d.plot function.
+        **kwargs: Additional keyword arguments to be passed to the `k3d.plot` function.
 
     Returns:
-        k3d.plot: If show is False, returns the K3D plot object.
+        plot (k3d.plot): If `show=False`, returns the K3D plot object.
 
     Raises:
-        ValueError: If aspectmode is not "data" or "cube".
+        ValueError: If `aspectmode` is not `'data'` or `'cube'`.
 
-    Examples:
+    Example:
         Display a volume inline:
 
         ```python
         import qim3d
+
         vol = qim3d.examples.bone_128x128x128
-        qim3d.viz.vol(vol)
+        qim3d.viz.vol(vol) 
         ```
+        <iframe src="https://platform.qim.dk/k3d/fima-bone_128x128x128-20240221113459.html" width="100%" height="500" frameborder="0"></iframe>
 
         Save a plot to an HTML file:
 
@@ -50,6 +52,7 @@ def vol(img, aspectmode="data", show=True, save=False, grid_visible=False, cmap=
         vol = qim3d.examples.bone_128x128x128
         plot = qim3d.viz.vol(vol, show=False, save="plot.html")
         ```
+        
     """
 
     if aspectmode.lower() not in ["data", "cube"]:
diff --git a/qim3d/viz/local_thickness_.py b/qim3d/viz/local_thickness_.py
new file mode 100644
index 0000000000000000000000000000000000000000..86326e8ae6bdba1273dbf0bb642d77e4102219ee
--- /dev/null
+++ b/qim3d/viz/local_thickness_.py
@@ -0,0 +1,132 @@
+from qim3d.io.logger import log
+import numpy as np
+import matplotlib.pyplot as plt
+from typing import Optional, Union, Tuple
+import ipywidgets as widgets
+
+def local_thickness(
+    image: np.ndarray,
+    image_lt: np.ndarray,
+    max_projection: bool = False,
+    axis: int = 0,
+    slice_idx: Optional[Union[int, float]] = None,
+    show: bool = False,
+    figsize: Tuple[int, int] = (15, 5),
+) -> Union[plt.Figure, widgets.interactive]:
+    """Visualizes the local thickness of a 2D or 3D image.
+
+    Args:
+        image (np.ndarray): 2D or 3D NumPy array representing the image/volume.
+        image_lt (np.ndarray): 2D or 3D NumPy array representing the local thickness of the input
+            image/volume.
+        max_projection (bool, optional): If True, displays the maximum projection of the local
+            thickness. Only used for 3D images. Defaults to False.
+        axis (int, optional): The axis along which to visualize the local thickness.
+            Unused for 2D images.
+            Defaults to 0.
+        slice_idx (int or float, optional): The initial slice to be visualized. The slice index
+            can afterwards be changed. If value is an integer, it will be the index of the slice
+            to be visualized. If value is a float between 0 and 1, it will be multiplied by the
+            number of slices and rounded to the nearest integer. If None, the middle slice will
+            be used for 3D images. Unused for 2D images. Defaults to None.
+        show (bool, optional): If True, displays the plot (i.e. calls plt.show()). Defaults to False.
+        figsize (Tuple[int, int], optional): The size of the figure. Defaults to (15, 5).
+
+    Raises:
+        ValueError: If the slice index is not an integer or a float between 0 and 1.
+
+    Returns:
+        If the input is 3D, returns an interactive widget. Otherwise, returns a matplotlib figure.
+
+    Example:
+        ```python
+        import qim3d
+
+        fly = qim3d.examples.fly_150x256x256 # 3D volume
+        lt_fly = qim3d.processing.local_thickness(fly)
+        qim3d.viz.local_thickness(fly, lt_fly, axis=0)
+        ```
+        ![local thickness 3d](assets/screenshots/local_thickness_3d.gif)
+
+        
+    """
+
+    def _local_thickness(image, image_lt, show, figsize, axis=None, slice_idx=None):
+        if slice_idx is not None:
+            image = image.take(slice_idx, axis=axis)
+            image_lt = image_lt.take(slice_idx, axis=axis)
+
+        fig, axs = plt.subplots(1, 3, figsize=figsize, layout="constrained")
+
+        axs[0].imshow(image, cmap="gray")
+        axs[0].set_title("Original image")
+        axs[0].axis("off")
+
+        axs[1].imshow(image_lt, cmap="viridis")
+        axs[1].set_title("Local thickness")
+        axs[1].axis("off")
+
+        plt.colorbar(
+            axs[1].imshow(image_lt, cmap="viridis"), ax=axs[1], orientation="vertical"
+        )
+
+        axs[2].hist(image_lt[image_lt > 0].ravel(), bins=32, edgecolor="black")
+        axs[2].set_title("Local thickness histogram")
+        axs[2].set_xlabel("Local thickness")
+        axs[2].set_ylabel("Count")
+
+        if show:
+            plt.show()
+
+        plt.close()
+
+        return fig
+
+    # Get the middle slice if the input is 3D
+    if len(image.shape) == 3:
+        if max_projection:
+            if slice_idx is not None:
+                log.warning(
+                    "slice_idx is not used for max_projection. It will be ignored."
+                )
+            image = image.max(axis=axis)
+            image_lt = image_lt.max(axis=axis)
+            return _local_thickness(image, image_lt, show, figsize)
+        else:
+            if slice_idx is None:
+                slice_idx = image.shape[axis] // 2
+            elif isinstance(slice_idx, float):
+                if slice_idx < 0 or slice_idx > 1:
+                    raise ValueError(
+                        "Values of slice_idx of float type must be between 0 and 1."
+                    )
+                slice_idx = int(slice_idx * image.shape[0]) - 1
+            slide_idx_slider = widgets.IntSlider(
+                min=0,
+                max=image.shape[axis] - 1,
+                step=1,
+                value=slice_idx,
+                description="Slice index",
+                layout=widgets.Layout(width="450px"),
+            )
+            widget_obj = widgets.interactive(
+                _local_thickness,
+                image=widgets.fixed(image),
+                image_lt=widgets.fixed(image_lt),
+                show=widgets.fixed(True),
+                figsize=widgets.fixed(figsize),
+                axis=widgets.fixed(axis),
+                slice_idx=slide_idx_slider,
+            )
+            widget_obj.layout = widgets.Layout(align_items="center")
+            if show:
+                display(widget_obj)
+            return widget_obj
+    else:
+        if max_projection:
+            log.warning(
+                "max_projection is only used for 3D images. It will be ignored."
+            )
+        if slice_idx is not None:
+            log.warning("slice_idx is only used for 3D images. It will be ignored.")
+        return _local_thickness(image, image_lt, show, figsize)
\ No newline at end of file
diff --git a/qim3d/viz/structure_tensor.py b/qim3d/viz/structure_tensor.py
index e009bfea78890063509b88371ecec81802aba40b..77265aa6713216bf8e698741e693d7a1651494f6 100644
--- a/qim3d/viz/structure_tensor.py
+++ b/qim3d/viz/structure_tensor.py
@@ -35,6 +35,21 @@ def vectors(
         ValueError: If the axis to slice along is not 0, 1, or 2.
         ValueError: If the slice index is not an integer or a float between 0 and 1.
 
+    Returns:
+        fig (Union[plt.Figure, widgets.interactive]): If `interactive` is True, returns an interactive widget. Otherwise, returns a matplotlib figure.
+
+    Example:
+        ```python
+        import qim3d
+
+        vol = qim3d.examples.NT_128x128x128
+        val, vec = qim3d.processing.structure_tensor(vol, visualize=True, axis=2)
+
+        # Visualize the structure tensor
+        qim3d.viz.vectors(vol, vec, axis=2, slice_idx=0.5, interactive=True)
+        ```
+        ![structure tensor](assets/screenshots/structure_tensor.gif)  
+
     """
 
     # Define Grid size limits