diff --git a/qim3d/__init__.py b/qim3d/__init__.py
index 02a844ed7d57e7fd42c0eb9ae7b48fd728e7e8c1..eca387a06b555c30c94a7ea7d7ee8a3cfbdff604 100644
--- a/qim3d/__init__.py
+++ b/qim3d/__init__.py
@@ -1,9 +1,10 @@
-import qim3d.io as io
-import qim3d.gui as gui
-import qim3d.viz as viz
-import qim3d.utils as utils
-import qim3d.models as models
-import qim3d.processing as processing
+# import qim3d.io as io
+# import qim3d.gui as gui
+# import qim3d.viz as viz
+# import qim3d.utils as utils
+# import qim3d.models as models
+# import qim3d.processing as processing
+from . import io, gui, viz, utils, models, processing
 import logging
 
 __version__ = '0.3.2'
diff --git a/qim3d/processing/__init__.py b/qim3d/processing/__init__.py
index 771b7222bed1aa8e05ce4481c86758123ab26de0..be9d29a22ec4c3795eea5d080c6a59482529522a 100644
--- a/qim3d/processing/__init__.py
+++ b/qim3d/processing/__init__.py
@@ -1,2 +1,3 @@
 from .filters import *
-from .local_thickness import local_thickness
\ No newline at end of file
+from .local_thickness import local_thickness
+from .detection import *
diff --git a/qim3d/processing/detection.py b/qim3d/processing/detection.py
new file mode 100644
index 0000000000000000000000000000000000000000..b74cf36e9e0eacb957fb66975c9a5809dc06abe3
--- /dev/null
+++ b/qim3d/processing/detection.py
@@ -0,0 +1,99 @@
+import numpy as np
+from qim3d.io.logger import log
+from skimage.feature import blob_dog
+
+__all__ = ["Blob"]
+
+
+class Blob:
+    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)
+        """
+        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
+        '''
+        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
+
+        return binary_volume
+
+
diff --git a/qim3d/viz/__init__.py b/qim3d/viz/__init__.py
index eedcc00b20c7b8f447e47db5540ab169502cb5a1..a641174d1fba75ebc66847d328e92e956b3df8c0 100644
--- a/qim3d/viz/__init__.py
+++ b/qim3d/viz/__init__.py
@@ -1,3 +1,4 @@
 from .visualizations import plot_metrics
 from .img import grid_pred, grid_overview, slices, slicer, orthogonal, plot_cc, local_thickness
 from .k3d import vol
+from .detection import circles
\ No newline at end of file
diff --git a/qim3d/viz/detection.py b/qim3d/viz/detection.py
new file mode 100644
index 0000000000000000000000000000000000000000..ca9c15fcff663b0d0c5e77dc6ac88000c11811ac
--- /dev/null
+++ b/qim3d/viz/detection.py
@@ -0,0 +1,76 @@
+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
+
+
+def circles(blobs, vol, alpha=0.5, color="#ff9900", **kwargs):
+    """
+    Plots the blobs found on a slice of the volume.
+
+    This function takes in a 3D volume and a list of blobs (detected features)
+    and plots the blobs on a specified slice of the volume. If no slice is specified,
+    it defaults to the middle slice of the volume.
+
+    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()
+        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.
+        **kwargs: Arbitrary keyword arguments for the `slices` function.
+
+    Returns:
+        matplotlib.figure.Figure: The resulting figure after adding the blobs to the slice.
+
+    """
+
+    def _slicer(z_slice):
+        clear_output(wait=True)
+        fig = slices(
+            vol,
+            n_slices=1,
+            position=z_slice,
+            img_height=3,
+            img_width=3,
+            cmap="gray",
+            show_position=False,
+        )
+        # Add circles from deteced blobs
+        for detected in blobs:
+            z, y, x, s = detected
+            if abs(z - z_slice) < s:  # The blob is in the slice
+
+                # Adjust the radius based on the distance from the center of the sphere
+                distance_from_center = abs(z - z_slice)
+                angle = (
+                    np.pi / 2 * (distance_from_center / s)
+                )  # Angle varies from 0 at the center to pi/2 at the edge
+                adjusted_radius = s * np.cos(angle)  # Radius follows a cosine curve
+
+                if adjusted_radius > 0.5:
+                    c = plt.Circle(
+                        (x, y),
+                        adjusted_radius,
+                        color=color,
+                        linewidth=0,
+                        fill=True,
+                        alpha=alpha,
+                    )
+                    fig.get_axes()[0].add_patch(c)
+
+        display(fig)
+        return fig
+
+    position_slider = widgets.IntSlider(
+        value=vol.shape[0] // 2,
+        min=0,
+        max=vol.shape[0] - 1,
+        description="Slice",
+        continuous_update=True,
+    )
+    slicer_obj = widgets.interactive(_slicer, z_slice=position_slider)
+    slicer_obj.layout = widgets.Layout(align_items="flex-start")
+
+    return slicer_obj
diff --git a/qim3d/viz/img.py b/qim3d/viz/img.py
index cfa7022391e8cbc27cdd6627bab171881d3b79b8..812b2028621ff508a638b78b009d4fc69a7fd139 100644
--- a/qim3d/viz/img.py
+++ b/qim3d/viz/img.py
@@ -399,7 +399,7 @@ def slicer(
     img_height: int = 3,
     img_width: int = 3,
     show_position: bool = False,
-    interpolation: Optional[str] = None,
+    interpolation: Optional[str] = "none",
 ) -> widgets.interactive:
     """Interactive widget for visualizing slices of a 3D volume.