diff --git a/qim3d/filters/_common_filter_methods.py b/qim3d/filters/_common_filter_methods.py
index 59b0be1291ff2f3480b44dc3df524c9794a2c6ed..026d292691b0d00946c78e832fb1e8571226fd80 100644
--- a/qim3d/filters/_common_filter_methods.py
+++ b/qim3d/filters/_common_filter_methods.py
@@ -8,9 +8,10 @@ from skimage import morphology
 import dask.array as da
 import dask_image.ndfilters as dask_ndfilters
 
-from qim3d.utils._logger import log
+from qim3d.utils import log
 
 __all__ = [
+    "FilterBase",
     "Gaussian",
     "Median",
     "Maximum",
diff --git a/qim3d/gui/annotation_tool.py b/qim3d/gui/annotation_tool.py
index 5ebae5f76cefe4d983f9dd1ac1dd24e5127feb62..79910e763048887c0f8f0fe1bd24d7fe7deaf6d3 100644
--- a/qim3d/gui/annotation_tool.py
+++ b/qim3d/gui/annotation_tool.py
@@ -27,10 +27,7 @@ import tempfile
 import gradio as gr
 import numpy as np
 from PIL import Image
-
-from qim3d.io import load, save
-from qim3d.operations._common_operations_methods import overlay_rgb_images
-from qim3d.gui.interface import BaseInterface
+import qim3d
 
 # TODO: img in launch should be self.img
 
@@ -68,7 +65,7 @@ class Interface(BaseInterface):
         for temp_file in temp_path_list:
             mask_file = os.path.basename(temp_file)
             mask_name = os.path.splitext(mask_file)[0]
-            masks[mask_name] = load(temp_file)
+            masks[mask_name] = qim3d.io.load(temp_file)
 
         return masks
 
@@ -98,7 +95,7 @@ class Interface(BaseInterface):
     def create_preview(self, img_editor: gr.ImageEditor) -> np.ndarray:
         background = img_editor["background"]
         masks = img_editor["layers"][0]
-        overlay_image = overlay_rgb_images(background, masks)
+        overlay_image = qim3d.operations.overlay_rgb_images(background, masks)
         return overlay_image
 
     def cerate_download_list(self, img_editor: gr.ImageEditor) -> list[str]:
@@ -122,7 +119,7 @@ class Interface(BaseInterface):
                 filepath = os.path.join(self.temp_dir, filename)
                 files_list.append(filepath)
 
-                save(filepath, mask, replace=True)
+                qim3d.io.save(filepath, mask, replace=True)
                 self.temp_files.append(filepath)
 
         return files_list
diff --git a/qim3d/gui/iso3d.py b/qim3d/gui/iso3d.py
index c7d4620ff1ebdf0df84255cf9a733de047b54d9e..2725403f094dd58e89030f23dba06982551491f4 100644
--- a/qim3d/gui/iso3d.py
+++ b/qim3d/gui/iso3d.py
@@ -22,9 +22,8 @@ import numpy as np
 import plotly.graph_objects as go
 from scipy import ndimage
 
-from qim3d.io import load
+import qim3d
 from qim3d.utils._logger import log
-
 from qim3d.gui.interface import InterfaceWithExamples
 
 
@@ -46,7 +45,7 @@ class Interface(InterfaceWithExamples):
 
     def load_data(self, gradiofile: gr.File):
         try:
-            self.vol = load(gradiofile.name)
+            self.vol = qim3d.io.load(gradiofile.name)
             assert self.vol.ndim == 3
         except AttributeError:
             raise gr.Error("You have to select a file")
diff --git a/qim3d/gui/layers2d.py b/qim3d/gui/layers2d.py
index febd16a506bcba477eed1d0f42ba6e20b763d610..8b7ae4419f2fc388c8e020d2b39556c83d585ff4 100644
--- a/qim3d/gui/layers2d.py
+++ b/qim3d/gui/layers2d.py
@@ -21,7 +21,6 @@ import os
 
 import gradio as gr
 import numpy as np
-
 from .interface import BaseInterface
 
 # from qim3d.processing import layers2d as l2d
@@ -358,7 +357,7 @@ class Interface(BaseInterface):
             raise gr.Error("Invalid file path")
 
         try:
-            self.data = load(
+            self.data = qim3d.io.load(
                 file_path,
                 progress_bar=False
             )
@@ -403,7 +402,7 @@ class Interface(BaseInterface):
                 
                 if self.is_transposed(slicing_axis, segmenting_axis):
                     slice = np.rot90(slice)
-                self.__dict__[seg_key] = segment_layers(slice, inverted = inverted, n_layers = n_layers, delta = delta, min_margin = min_margin, wrap = wrap)
+                self.__dict__[seg_key] = qim3d.processing.segment_layers(slice, inverted = inverted, n_layers = n_layers, delta = delta, min_margin = min_margin, wrap = wrap)
         
         return process
 
@@ -456,13 +455,13 @@ class Interface(BaseInterface):
                     seg = np.rot90(seg, k = 3)
                 # slice = 255 * (slice/np.max(slice))
                 # return image_with_overlay(np.repeat(slice[..., None], 3, -1), seg, alpha) 
-                return overlay_rgb_images(slice, seg, alpha)
+                return qim3d.operations.overlay_rgb_images(slice, seg, alpha)
             else:
-                lines = get_lines(seg)
+                lines = qim3d.processing.get_lines(seg)
                 if self.is_transposed(slicing_axis, segmenting_axis):
-                    return image_with_lines(np.rot90(slice), lines, line_thickness).rotate(270, expand = True)
+                    return qim3d.viz.image_with_lines(np.rot90(slice), lines, line_thickness).rotate(270, expand = True)
                 else:
-                    return image_with_lines(slice, lines, line_thickness)
+                    return qim3d.viz.image_with_lines(slice, lines, line_thickness)
             
         return plot_output_img
     
diff --git a/qim3d/gui/local_thickness.py b/qim3d/gui/local_thickness.py
index 461f3e1774fedb3ac61e0f916b3c72bdafff6f92..4a4cbf474ae5283a73ed9305b9969e3dfd0b812b 100644
--- a/qim3d/gui/local_thickness.py
+++ b/qim3d/gui/local_thickness.py
@@ -40,12 +40,11 @@ import gradio as gr
 import numpy as np
 import tifffile
 import localthickness as lt
+import qim3d
 
-from qim3d.io import load
-from qim3d.gui.interface import InterfaceWithExamples
 
 
-class Interface(InterfaceWithExamples):
+class Interface(qim3d.gui.interface.InterfaceWithExamples):
     def __init__(self,
                  img: np.ndarray = None,
                  verbose:bool = False,
@@ -79,7 +78,7 @@ class Interface(InterfaceWithExamples):
         file_idx = np.argmax(creation_time_list)
 
         # Load the temporary file
-        vol_lt = load(temp_path_list[file_idx])
+        vol_lt = qim3d.io.load(temp_path_list[file_idx])
 
         return vol_lt
 
@@ -251,7 +250,7 @@ class Interface(InterfaceWithExamples):
     def process_input(self, data: np.ndarray, dark_objects: bool):
         # Load volume
         try:
-            self.vol = load(data.name)
+            self.vol = qim3d.io.load(data.name)
             assert self.vol.ndim == 3
         except AttributeError:
             self.vol = data
diff --git a/qim3d/io/_convert.py b/qim3d/io/_convert.py
index 0d776822689430b733dfa1467ecdff0b48d87bb1..55ab80cb14218ccae8184b2a95488a0de8801431 100644
--- a/qim3d/io/_convert.py
+++ b/qim3d/io/_convert.py
@@ -10,7 +10,7 @@ from tqdm import tqdm
 import zarr.core
 
 from qim3d.utils._misc import stringify_path
-from qim3d.io._saving import save
+from qim3d.io import save
 
 
 class Convert:
diff --git a/qim3d/io/_downloader.py b/qim3d/io/_downloader.py
index 18e36ca591ad2bf0859cc2b05f18d0033de3f8ee..c77d61ccb016e3745c7a15736a179699a2c6cf9a 100644
--- a/qim3d/io/_downloader.py
+++ b/qim3d/io/_downloader.py
@@ -8,7 +8,7 @@ from tqdm import tqdm
 from pathlib import Path
 
 from qim3d.io import load
-from qim3d.utils._logger import log
+from qim3d.utils import log
 import outputformat as ouf
 
 
diff --git a/qim3d/io/_loading.py b/qim3d/io/_loading.py
index e3d7d45bc220a04c0e96e4efd5fbb2f94cc3c99a..b34ac1521daa4c3dee1fbd6f8efba0402ea62c38 100644
--- a/qim3d/io/_loading.py
+++ b/qim3d/io/_loading.py
@@ -23,9 +23,9 @@ from dask import delayed
 from PIL import Image, UnidentifiedImageError
 
 import qim3d
-from qim3d.utils._logger import log
+from qim3d.utils import log
 from qim3d.utils._misc import get_file_size, sizeof, stringify_path
-from qim3d.utils._system import Memory
+from qim3d.utils import Memory
 from qim3d.utils._progress_bar import FileLoadingProgressBar
 import trimesh
 
@@ -718,7 +718,7 @@ class DataLoader:
         # Fails
         else:
             # Find the closest matching path to warn the user
-            similar_paths = qim3d.utils.misc.find_similar_paths(path)
+            similar_paths = qim3d.utils._misc.find_similar_paths(path)
 
             if similar_paths:
                 suggestion = similar_paths[0]  # Get the closest match
diff --git a/qim3d/io/_ome_zarr.py b/qim3d/io/_ome_zarr.py
index 36976316b8811380d68cbe1b7f5f43e5c001c4f9..6573831a1e29bbb02607fab777145f2838c8b6d0 100644
--- a/qim3d/io/_ome_zarr.py
+++ b/qim3d/io/_ome_zarr.py
@@ -31,7 +31,7 @@ from skimage.transform import (
     resize,
 )
 
-from qim3d.utils._logger import log
+from qim3d.utils import log
 from qim3d.utils._progress_bar import OmeZarrExportProgressBar
 from qim3d.utils._ome_zarr import get_n_chunks
 
diff --git a/qim3d/io/_saving.py b/qim3d/io/_saving.py
index a7053c00fde4b7d4541b1a01286f18ad669e811d..3ef6457f9ae26554cd810547b8a1911072492f7a 100644
--- a/qim3d/io/_saving.py
+++ b/qim3d/io/_saving.py
@@ -37,7 +37,7 @@ from pydicom.dataset import FileDataset, FileMetaDataset
 from pydicom.uid import UID
 import trimesh
 
-from qim3d.utils._logger import log
+from qim3d.utils import log
 from qim3d.utils._misc import sizeof, stringify_path
 
 
diff --git a/qim3d/io/_sync.py b/qim3d/io/_sync.py
index 953bf15b2e5ad6690bd6a077cd29b7b430b22c40..48c17a8cb8d8fba2c3ededc93f252196087b6fc2 100644
--- a/qim3d/io/_sync.py
+++ b/qim3d/io/_sync.py
@@ -2,7 +2,7 @@
 import os
 import subprocess
 import outputformat as ouf
-from qim3d.utils._logger import log
+from qim3d.utils import log
 from pathlib import Path
 
 
diff --git a/qim3d/ml/_data.py b/qim3d/ml/_data.py
index d54f1bf6d7a457457b8fea638fd8057410d2a853..6050001eef37a7491ab7f2e6d88dec660bf38102 100644
--- a/qim3d/ml/_data.py
+++ b/qim3d/ml/_data.py
@@ -1,7 +1,7 @@
 """Provides a custom Dataset class for building a PyTorch dataset."""
 from pathlib import Path
 from PIL import Image
-from qim3d.utils._logger import log
+from qim3d.utils import log
 import torch
 import numpy as np
 from typing import Optional, Callable
diff --git a/qim3d/ml/models/_unet.py b/qim3d/ml/models/_unet.py
index 27ee78a4009b235253662c8af50c76baca4870f4..a396d88d38a5ff9809f6e2a0bd1139df1f48eb40 100644
--- a/qim3d/ml/models/_unet.py
+++ b/qim3d/ml/models/_unet.py
@@ -2,7 +2,7 @@
 
 import torch.nn as nn
 
-from qim3d.utils._logger import log
+from qim3d.utils import log
 
 
 class UNet(nn.Module):
diff --git a/qim3d/operations/_common_operations_methods.py b/qim3d/operations/_common_operations_methods.py
index 5975c30ea3c43f95995559597c8a61f02790809a..26959254dfd8783f28ad560de6103e3c9f70ce7d 100644
--- a/qim3d/operations/_common_operations_methods.py
+++ b/qim3d/operations/_common_operations_methods.py
@@ -1,6 +1,6 @@
 import numpy as np
 import qim3d.filters as filters
-from qim3d.utils._logger import log
+from qim3d.utils import log
 
 __all__ = ["remove_background", "fade_mask", "overlay_rgb_images"]
 
diff --git a/qim3d/processing/_local_thickness.py b/qim3d/processing/_local_thickness.py
index 947f8865cfdbdbed4438d0f1e416498502de0e4a..eb02bb3e04dee57c63a1dabb4a4a8dda4634e991 100644
--- a/qim3d/processing/_local_thickness.py
+++ b/qim3d/processing/_local_thickness.py
@@ -2,7 +2,7 @@
 
 import numpy as np
 from typing import Optional
-from qim3d.utils._logger import log
+from qim3d.utils import log
 import qim3d
 
 
diff --git a/qim3d/processing/_structure_tensor.py b/qim3d/processing/_structure_tensor.py
index f6dc98cd253f05bd89c4a8a8c5cdf9a8fd228c2d..08a6f20ec1cf96c3432831e2914017c58b6cd777 100644
--- a/qim3d/processing/_structure_tensor.py
+++ b/qim3d/processing/_structure_tensor.py
@@ -3,7 +3,7 @@
 from typing import Tuple
 import logging
 import numpy as np
-from qim3d.utils._logger import log
+from qim3d.utils import log
 
 
 def structure_tensor(
@@ -108,7 +108,7 @@ def structure_tensor(
     val, vec = st.eig_special_3d(s_vol, full=full)
 
     if visualize:
-        from qim3d.viz._structure_tensor import vectors
+        from qim3d.viz import vectors
 
         display(vectors(vol, vec, **viz_kwargs))
 
diff --git a/qim3d/tests/processing/test_filters.py b/qim3d/tests/filters/test_filters.py
similarity index 70%
rename from qim3d/tests/processing/test_filters.py
rename to qim3d/tests/filters/test_filters.py
index 5a6d0993fb4ac6cecaf9c26d06b450eeb2a9f893..adbfd0130713b469c9a624a2be5ef72596d209dd 100644
--- a/qim3d/tests/processing/test_filters.py
+++ b/qim3d/tests/filters/test_filters.py
@@ -1,11 +1,10 @@
 import qim3d
-from qim3d.filters import *
 import numpy as np
 import pytest
 import re
 
 def test_filter_base_initialization():
-    filter_base = qim3d.processing.filters.FilterBase(3,size=2)
+    filter_base = qim3d.filters.FilterBase(3,size=2)
     assert filter_base.args == (3,)
     assert filter_base.kwargs == {'size': 2}
 
@@ -13,10 +12,10 @@ def test_gaussian_filter():
     input_image = np.random.rand(50, 50)
 
     # Testing the function
-    filtered_image_fn = gaussian(input_image,sigma=1.5)
+    filtered_image_fn = qim3d.filters.gaussian(input_image,sigma=1.5)
 
     # Testing the class method
-    gaussian_filter_cls = Gaussian(sigma=1.5)
+    gaussian_filter_cls = qim3d.filters.Gaussian(sigma=1.5)
     filtered_image_cls = gaussian_filter_cls(input_image)
     
     # Assertions
@@ -28,10 +27,10 @@ def test_median_filter():
     input_image = np.random.rand(50, 50)
 
     # Testing the function
-    filtered_image_fn = median(input_image, size=3)
+    filtered_image_fn = qim3d.filters.median(input_image, size=3)
 
     # Testing the class method
-    median_filter_cls = Median(size=3)
+    median_filter_cls = qim3d.filters.Median(size=3)
     filtered_image_cls = median_filter_cls(input_image)
 
     # Assertions
@@ -43,10 +42,10 @@ def test_maximum_filter():
     input_image = np.random.rand(50, 50)
 
     # Testing the function
-    filtered_image_fn = maximum(input_image, size=3)
+    filtered_image_fn = qim3d.filters.maximum(input_image, size=3)
 
     # Testing the class method
-    maximum_filter_cls = Maximum(size=3)
+    maximum_filter_cls = qim3d.filters.Maximum(size=3)
     filtered_image_cls = maximum_filter_cls(input_image)
 
     # Assertions
@@ -58,10 +57,10 @@ def test_minimum_filter():
     input_image = np.random.rand(50, 50)
 
     # Testing the function
-    filtered_image_fn = minimum(input_image, size=3)
+    filtered_image_fn = qim3d.filters.minimum(input_image, size=3)
 
     # Testing the class method
-    minimum_filter_cls = Minimum(size=3)
+    minimum_filter_cls = qim3d.filters.Minimum(size=3)
     filtered_image_cls = minimum_filter_cls(input_image)
 
     # Assertions
@@ -73,16 +72,16 @@ def test_sequential_filter_pipeline():
     input_image = np.random.rand(50, 50)
 
     # Individual filters
-    gaussian_filter = Gaussian(sigma=1.5)
-    median_filter = Median(size=3)
-    maximum_filter = Maximum(size=3)
+    gaussian_filter = qim3d.filters.Gaussian(sigma=1.5)
+    median_filter = qim3d.filters.Median(size=3)
+    maximum_filter = qim3d.filters.Maximum(size=3)
 
     # Testing the sequential pipeline
-    sequential_pipeline = Pipeline(gaussian_filter, median_filter, maximum_filter)
+    sequential_pipeline = qim3d.filters.Pipeline(gaussian_filter, median_filter, maximum_filter)
     filtered_image_pipeline = sequential_pipeline(input_image)
 
     # Testing the equivalence to maximum(median(gaussian(input,**kwargs),**kwargs),**kwargs)
-    expected_output = maximum(median(gaussian(input_image, sigma=1.5), size=3), size=3)
+    expected_output = qim3d.filters.maximum(qim3d.filters.median(qim3d.filters.gaussian(input_image, sigma=1.5), size=3), size=3)
 
     # Assertions
     assert filtered_image_pipeline.shape == expected_output.shape == input_image.shape
@@ -93,16 +92,16 @@ def test_sequential_filter_appending():
     input_image = np.random.rand(50, 50)
 
     # Individual filters
-    gaussian_filter = Gaussian(sigma=1.5)
-    median_filter = Median(size=3)
-    maximum_filter = Maximum(size=3)
+    gaussian_filter = qim3d.filters.Gaussian(sigma=1.5)
+    median_filter = qim3d.filters.Median(size=3)
+    maximum_filter = qim3d.filters.Maximum(size=3)
 
     # Sequential pipeline with filter initialized at the beginning
-    sequential_pipeline_initial = Pipeline(gaussian_filter, median_filter, maximum_filter)
+    sequential_pipeline_initial = qim3d.filters.Pipeline(gaussian_filter, median_filter, maximum_filter)
     filtered_image_initial = sequential_pipeline_initial(input_image)
 
     # Sequential pipeline with filter appended
-    sequential_pipeline_appended = Pipeline(gaussian_filter, median_filter)
+    sequential_pipeline_appended = qim3d.filters.Pipeline(gaussian_filter, median_filter)
     sequential_pipeline_appended.append(maximum_filter)
     filtered_image_appended = sequential_pipeline_appended(input_image)
 
@@ -113,7 +112,7 @@ def test_sequential_filter_appending():
 
 def test_assertion_error_not_filterbase_subclass():
     # Get valid filter classes
-    valid_filters = [subclass.__name__ for subclass in qim3d.processing.filters.FilterBase.__subclasses__()]
+    valid_filters = [subclass.__name__ for subclass in qim3d.filters.FilterBase.__subclasses__()]
 
     # Create invalid object
     invalid_filter = object()  # An object that is not an instance of FilterBase
@@ -124,4 +123,4 @@ def test_assertion_error_not_filterbase_subclass():
 
     # Use pytest.raises to catch the AssertionError
     with pytest.raises(AssertionError, match=re.escape(message)):
-        sequential_pipeline = Pipeline(invalid_filter)
\ No newline at end of file
+        sequential_pipeline = qim3d.filters.Pipeline(invalid_filter)
\ No newline at end of file
diff --git a/qim3d/tests/io/test_downloader.py b/qim3d/tests/io/test_downloader.py
index 6fc0846405462a20bd19eb90160889ea573e1fbb..a6791aa0a60b0133824e62e68146f469a48f9c9c 100644
--- a/qim3d/tests/io/test_downloader.py
+++ b/qim3d/tests/io/test_downloader.py
@@ -1,40 +1,61 @@
 import qim3d
 import os
+import pytest
+from pathlib import Path
+import shutil
 
-def test_download():
-    folder = 'Cowry_Shell'
-    file = 'Cowry_DOWNSAMPLED.tif'
-    path = os.path.join(folder,file)
+@pytest.fixture
+def setup_temp_folder():
+    """Fixture to create and clean up a temporary folder for tests."""
+    folder = "Cowry_Shell"
+    file = "Cowry_DOWNSAMPLED.tif"
+    path = Path(folder) / file
 
-    dl = qim3d.io.Downloader()
+    # Ensure clean environment before running tests
+    if Path(folder).exists():
+        shutil.rmtree(folder)
+    yield folder, path
 
-    dl.Cowry_Shell.Cowry_DOWNSAMPLED()
+    # Cleanup after tests
+    if path.exists():
+        path.unlink()
+    if Path(folder).exists():
+        shutil.rmtree(folder)
 
 
-    img = qim3d.io.load(path)
+def test_download(setup_temp_folder):
+    folder, path = setup_temp_folder
 
-    # Remove temp file
-    os.remove(path)
-    os.rmdir(folder)
+    dl = qim3d.io.Downloader()
+    dl.Cowry_Shell.Cowry_DOWNSAMPLED()
 
-    assert img.shape == (500,350,350)
+    # Verify the file was downloaded correctly
+    assert path.exists(), f"{path} does not exist after download."
 
-def test_get_file_size_right():
-    coal_file = 'https://archive.compute.dtu.dk/download/public/projects/viscomp_data_repository/Coal/CoalBrikett.tif'
-    size = qim3d.io.downloader._get_file_size(coal_file)
+    img = qim3d.io.load(str(path))
+    assert img.shape == (500, 350, 350)
 
-    assert size == 2_400_082_900
+    # Cleanup is handled by the fixture
 
 
-def test_get_file_size_wrong():
-    file_to_folder = 'https://archive.compute.dtu.dk/files/public/projects/viscomp_data_repository/'
-    size = qim3d.io.downloader._get_file_size(file_to_folder)
+def test_get_file_size():
+    """Tests for correct and incorrect file size retrieval."""
+    coal_file = "https://archive.compute.dtu.dk/download/public/projects/viscomp_data_repository/Coal/CoalBrikett.tif"
+    folder_url = "https://archive.compute.dtu.dk/files/public/projects/viscomp_data_repository/"
 
-    assert size == -1
+    # Correct file size
+    size = qim3d.io._downloader._get_file_size(coal_file)
+    assert size == 2_400_082_900, f"Expected size mismatch for {coal_file}."
+
+    # Wrong URL (not a file)
+    size = qim3d.io._downloader._get_file_size(folder_url)
+    assert size == -1, "Expected size -1 for non-file URL."
 
 
 def test_extract_html():
-    url = 'https://archive.compute.dtu.dk/files/public/projects/viscomp_data_repository'
-    html = qim3d.io.downloader._extract_html(url)
-    
-    assert 'data-path="/files/public/projects/viscomp_data_repository"' in html
+    url = "https://archive.compute.dtu.dk/files/public/projects/viscomp_data_repository"
+    html = qim3d.io._downloader._extract_html(url)
+
+    assert 'data-path="/files/public/projects/viscomp_data_repository"' in html, \
+        "Expected HTML content not found in extracted HTML."
+
diff --git a/qim3d/tests/models/test_unet.py b/qim3d/tests/ml/models/test_unet.py
similarity index 64%
rename from qim3d/tests/models/test_unet.py
rename to qim3d/tests/ml/models/test_unet.py
index 80b17605329af78f3b04cdcedd35de60608577f6..9f32bfce12be3a21238f2d2d388e59f873a8ef1c 100644
--- a/qim3d/tests/models/test_unet.py
+++ b/qim3d/tests/ml/models/test_unet.py
@@ -3,13 +3,13 @@ import torch
 
 # unit tests for UNet()
 def test_starting_unet():
-    unet = qim3d.models.UNet()
+    unet = qim3d.ml.models.UNet()
 
     assert unet.size == 'medium'
 
 
 def test_forward_pass():
-    unet = qim3d.models.UNet()
+    unet = qim3d.ml.models.UNet()
 
     # Size: B x C x H x W
     x = torch.ones([1,1,256,256])
@@ -19,14 +19,14 @@ def test_forward_pass():
 
 # unit tests for Hyperparameters()
 def test_hyper():
-    unet = qim3d.models.UNet()
-    hyperparams = qim3d.models.Hyperparameters(unet)
+    unet = qim3d.ml.models.UNet()
+    hyperparams = qim3d.ml.models.Hyperparameters(unet)
 
     assert hyperparams.n_epochs == 10
 
 def test_hyper_dict():
-    unet = qim3d.models.UNet()
-    hyperparams = qim3d.models.Hyperparameters(unet)
+    unet = qim3d.ml.models.UNet()
+    hyperparams = qim3d.ml.models.Hyperparameters(unet)
 
     hyper_dict = hyperparams()
 
diff --git a/qim3d/tests/models/test_models.py b/qim3d/tests/ml/test_models.py
similarity index 62%
rename from qim3d/tests/models/test_models.py
rename to qim3d/tests/ml/test_models.py
index 83543b76299d400ac6c848fd6e6213a428e1bfd0..547310f303e5b78ca0036dd2be464673e0975384 100644
--- a/qim3d/tests/models/test_models.py
+++ b/qim3d/tests/ml/test_models.py
@@ -12,16 +12,16 @@ def test_model_summary():
     folder = "folder_data"
     temp_data(folder, img_shape=img_shape, n=n)
 
-    unet = qim3d.models.UNet(size="small")
-    augment = qim3d.models.Augmentation(transform_train=None)
-    train_set, val_set, test_set = qim3d.models.prepare_datasets(
+    unet = qim3d.ml.models.UNet(size="small")
+    augment = qim3d.ml.Augmentation(transform_train=None)
+    train_set, val_set, test_set = qim3d.ml.prepare_datasets(
         folder, 1 / 3, unet, augment
     )
 
-    _, val_loader, _ = qim3d.models.prepare_dataloaders(
+    _, val_loader, _ = qim3d.ml.prepare_dataloaders(
         train_set, val_set, test_set, batch_size=1, num_workers=1, pin_memory=False
     )
-    summary = qim3d.models.model_summary(val_loader, unet)
+    summary = qim3d.ml.model_summary(val_loader, unet)
 
     assert summary.input_size[0] == (1, 1) + img_shape
 
@@ -33,11 +33,11 @@ def test_inference():
     folder = "folder_data"
     temp_data(folder)
 
-    unet = qim3d.models.UNet(size="small")
-    augment = qim3d.models.Augmentation(transform_train=None)
-    train_set, _, _ = qim3d.models.prepare_datasets(folder, 1 / 3, unet, augment)
+    unet = qim3d.ml.models.UNet(size="small")
+    augment = qim3d.ml.Augmentation(transform_train=None)
+    train_set, _, _ = qim3d.ml.prepare_datasets(folder, 1 / 3, unet, augment)
 
-    _, targ, _ = qim3d.models.inference(train_set, unet)
+    _, targ, _ = qim3d.ml.inference(train_set, unet)
 
     assert tuple(targ[0].unique()) == (0, 1)
 
@@ -49,11 +49,11 @@ def test_inference_tuple():
     folder = "folder_data"
     temp_data(folder)
 
-    unet = qim3d.models.UNet(size="small")
+    unet = qim3d.ml.models.UNet(size="small")
 
     data = [1, 2, 3]
     with pytest.raises(ValueError, match="Data items must be tuples"):
-        qim3d.models.inference(data, unet)
+        qim3d.ml.inference(data, unet)
 
     temp_data(folder, remove=True)
 
@@ -63,11 +63,11 @@ def test_inference_tensor():
     folder = "folder_data"
     temp_data(folder)
 
-    unet = qim3d.models.UNet(size="small")
+    unet = qim3d.ml.models.UNet(size="small")
 
     data = [(1, 2)]
     with pytest.raises(ValueError, match="Data items must consist of tensors"):
-        qim3d.models.inference(data, unet)
+        qim3d.ml.inference(data, unet)
 
     temp_data(folder, remove=True)
 
@@ -77,12 +77,12 @@ def test_inference_dim():
     folder = "folder_data"
     temp_data(folder)
 
-    unet = qim3d.models.UNet(size="small")
+    unet = qim3d.ml.models.UNet(size="small")
 
     data = [(ones(1), ones(1))]
     # need the r"" for special characters
     with pytest.raises(ValueError, match=r"Input image must be \(C,H,W\) format"):
-        qim3d.models.inference(data, unet)
+        qim3d.ml.inference(data, unet)
 
     temp_data(folder, remove=True)
 
@@ -94,17 +94,17 @@ def test_train_model():
 
     n_epochs = 1
 
-    unet = qim3d.models.UNet(size="small")
-    augment = qim3d.models.Augmentation(transform_train=None)
-    hyperparams = qim3d.models.Hyperparameters(unet, n_epochs=n_epochs)
-    train_set, val_set, test_set = qim3d.models.prepare_datasets(
+    unet = qim3d.ml.models.UNet(size="small")
+    augment = qim3d.ml.Augmentation(transform_train=None)
+    hyperparams = qim3d.ml.Hyperparameters(unet, n_epochs=n_epochs)
+    train_set, val_set, test_set = qim3d.ml.prepare_datasets(
         folder, 1 / 3, unet, augment
     )
-    train_loader, val_loader, _ = qim3d.models.prepare_dataloaders(
+    train_loader, val_loader, _ = qim3d.ml.prepare_dataloaders(
         train_set, val_set, test_set, batch_size=1, num_workers=1, pin_memory=False
     )
 
-    train_loss, _ = qim3d.models.train_model(
+    train_loss, _ = qim3d.ml.train_model(
         unet, hyperparams, train_loader, val_loader, plot=False, return_loss=True
     )
 
diff --git a/qim3d/tests/processing/test_connected_components.py b/qim3d/tests/segmentation/test_connected_components.py
similarity index 100%
rename from qim3d/tests/processing/test_connected_components.py
rename to qim3d/tests/segmentation/test_connected_components.py
diff --git a/qim3d/tests/utils/test_augmentations.py b/qim3d/tests/utils/test_augmentations.py
index 289ebcb4a723c6ab9432ca375d51e4a4cd01479a..809e7ddbe2771519a5c71d4032edd7ebbd029d7f 100644
--- a/qim3d/tests/utils/test_augmentations.py
+++ b/qim3d/tests/utils/test_augmentations.py
@@ -5,13 +5,13 @@ import pytest
 
 # unit tests for Augmentation()
 def test_augmentation():
-    augment_class = qim3d.models.Augmentation()
+    augment_class = qim3d.ml.Augmentation()
 
     assert augment_class.resize == "crop"
 
 
 def test_augment():
-    augment_class = qim3d.models.Augmentation()
+    augment_class = qim3d.ml.Augmentation()
 
     album_augment = augment_class.augment(256, 256)
 
@@ -26,11 +26,11 @@ def test_resize():
         ValueError,
         match=f"Invalid resize type: {resize_str}. Use either 'crop', 'resize' or 'padding'.",
     ):
-        augment_class = qim3d.models.Augmentation(resize=resize_str)
+        augment_class = qim3d.ml.Augmentation(resize=resize_str)
 
 
 def test_levels():
-    augment_class = qim3d.models.Augmentation()
+    augment_class = qim3d.ml.Augmentation()
 
     level = "Not a valid level"
 
diff --git a/qim3d/tests/utils/test_data.py b/qim3d/tests/utils/test_data.py
index 32b290a86b55a0c23a7d75d551559437b3df7b7e..81beef5334ddf4bbdd20de34b6d52f55a13f6307 100644
--- a/qim3d/tests/utils/test_data.py
+++ b/qim3d/tests/utils/test_data.py
@@ -10,7 +10,7 @@ def test_dataset():
     folder = 'folder_data'
     temp_data(folder, img_shape = img_shape)
     
-    images = qim3d.models.Dataset(folder)
+    images = qim3d.ml.Dataset(folder)
 
     assert images[0][0].shape == img_shape
 
@@ -19,19 +19,19 @@ def test_dataset():
 
 # unit tests for check_resize()
 def test_check_resize():
-    h_adjust,w_adjust = qim3d.models.data.check_resize(240,240,resize = 'crop',n_channels = 6)
+    h_adjust,w_adjust = qim3d.ml._data.check_resize(240,240,resize = 'crop',n_channels = 6)
 
     assert (h_adjust,w_adjust) == (192,192)
 
 def test_check_resize_pad():
-    h_adjust,w_adjust = qim3d.models.data.check_resize(16,16,resize = 'padding',n_channels = 6)
+    h_adjust,w_adjust = qim3d.ml._data.check_resize(16,16,resize = 'padding',n_channels = 6)
 
     assert (h_adjust,w_adjust) == (64,64)
 
 def test_check_resize_fail():
 
     with pytest.raises(ValueError,match="The size of the image is too small compared to the depth of the UNet. Choose a different 'resize' and/or a smaller model."):
-        h_adjust,w_adjust = qim3d.models.data.check_resize(16,16,resize = 'crop',n_channels = 6)
+        h_adjust,w_adjust = qim3d.ml._data.check_resize(16,16,resize = 'crop',n_channels = 6)
 
 
 # unit tests for prepare_datasets()
@@ -42,9 +42,9 @@ def test_prepare_datasets():
     folder = 'folder_data'
     img = temp_data(folder,n = n)
 
-    my_model = qim3d.models.UNet()
-    my_augmentation = qim3d.models.Augmentation(transform_test='light')
-    train_set, val_set, test_set = qim3d.models.prepare_datasets(folder,validation,my_model,my_augmentation)
+    my_model = qim3d.ml.models.UNet()
+    my_augmentation = qim3d.ml.Augmentation(transform_test='light')
+    train_set, val_set, test_set = qim3d.ml.prepare_datasets(folder,validation,my_model,my_augmentation)
 
     assert (len(train_set),len(val_set),len(test_set)) == (int((1-validation)*n), int(n*validation), n)
 
@@ -56,7 +56,7 @@ def test_validation():
     validation = 10
     
     with pytest.raises(ValueError,match = "The validation fraction must be a float between 0 and 1."):
-        augment_class = qim3d.models.prepare_datasets('folder',validation,'my_model','my_augmentation')
+        augment_class = qim3d.ml.prepare_datasets('folder',validation,'my_model','my_augmentation')
 
 
 # unit test for prepare_dataloaders()
@@ -65,11 +65,11 @@ def test_prepare_dataloaders():
     temp_data(folder)
 
     batch_size = 1
-    my_model = qim3d.models.UNet()
-    my_augmentation = qim3d.models.Augmentation()
-    train_set, val_set, test_set = qim3d.models.prepare_datasets(folder,1/3,my_model,my_augmentation)
+    my_model = qim3d.ml.models.UNet()
+    my_augmentation = qim3d.ml.Augmentation()
+    train_set, val_set, test_set = qim3d.ml.prepare_datasets(folder,1/3,my_model,my_augmentation)
 
-    _,val_loader,_ = qim3d.models.prepare_dataloaders(train_set,val_set,test_set,
+    _,val_loader,_ = qim3d.ml.prepare_dataloaders(train_set,val_set,test_set,
                                                                            batch_size,num_workers = 1,
                                                                            pin_memory = False)
     
diff --git a/qim3d/tests/utils/test_doi.py b/qim3d/tests/utils/test_doi.py
index 0db54357db9366f416a1e72cbef0ef10c640dc0a..859ebdb92baefca4eba721b3ac71fb00e1d5dec6 100644
--- a/qim3d/tests/utils/test_doi.py
+++ b/qim3d/tests/utils/test_doi.py
@@ -4,12 +4,12 @@ doi = "https://doi.org/10.1007/s10851-021-01041-3"
 
 
 def test_get_bibtex():
-    bibtext = qim3d.utils.doi.get_bibtex(doi)
+    bibtext = qim3d.utils._doi.get_bibtex(doi)
 
     assert "Measuring Shape Relations Using r-Parallel Sets" in bibtext
 
 
 def test_get_reference():
-    reference = qim3d.utils.doi.get_reference(doi)
+    reference = qim3d.utils._doi.get_reference(doi)
 
     assert "Stephensen" in reference
diff --git a/qim3d/tests/utils/test_helpers.py b/qim3d/tests/utils/test_helpers.py
index 04ae8e63db39d1b47a9fe1f6ae24aecc29c29ac4..cdbbedc4589f9801077444605874916a8558f0c1 100644
--- a/qim3d/tests/utils/test_helpers.py
+++ b/qim3d/tests/utils/test_helpers.py
@@ -33,7 +33,7 @@ def test_get_local_ip():
         else:
             return False
 
-    local_ip = qim3d.utils.misc.get_local_ip()
+    local_ip = qim3d.utils._misc.get_local_ip()
 
     assert validate_ip(local_ip) == True
 
@@ -42,7 +42,7 @@ def test_stringify_path1():
     """Test that the function converts os.PathLike objects to strings"""
     blobs_path = Path(qim3d.__file__).parents[0] / "img_examples" / "blobs_256x256.tif"
 
-    assert str(blobs_path) == qim3d.utils.misc.stringify_path(blobs_path)
+    assert str(blobs_path) == qim3d.utils._misc.stringify_path(blobs_path)
 
 
 def test_stringify_path2():
@@ -50,4 +50,4 @@ def test_stringify_path2():
     # Create test_path
     test_path = os.path.join("this", "path", "doesnt", "exist.tif")
 
-    assert test_path == qim3d.utils.misc.stringify_path(test_path)
+    assert test_path == qim3d.utils._misc.stringify_path(test_path)
diff --git a/qim3d/tests/viz/test_img.py b/qim3d/tests/viz/test_img.py
index ec9599105a9cdccb3d44fdfa048f0a452cd47c8a..d33d00cc54e0d989cf870e474343e2787e069cfd 100644
--- a/qim3d/tests/viz/test_img.py
+++ b/qim3d/tests/viz/test_img.py
@@ -36,11 +36,11 @@ def test_grid_pred():
     n = 4
     temp_data(folder, n=n)
 
-    model = qim3d.models.UNet()
-    augmentation = qim3d.models.Augmentation()
-    train_set, _, _ = qim3d.models.prepare_datasets(folder, 0.1, model, augmentation)
+    model = qim3d.ml.models.UNet()
+    augmentation = qim3d.ml.Augmentation()
+    train_set, _, _ = qim3d.ml.prepare_datasets(folder, 0.1, model, augmentation)
 
-    in_targ_pred = qim3d.models.inference(train_set, model)
+    in_targ_pred = qim3d.ml.inference(train_set, model)
 
     fig = qim3d.viz.grid_pred(in_targ_pred)
 
@@ -52,7 +52,7 @@ def test_grid_pred():
 # unit tests for slices function
 def test_slices_numpy_array_input():
     example_volume = np.ones((10, 10, 10))
-    fig = qim3d.viz.slices_grid(example_volume, n_slices=1)
+    fig = qim3d.viz.slices_grid(example_volume, num_slices=1)
     assert isinstance(fig, plt.Figure)
 
 
@@ -77,7 +77,7 @@ def test_slices_wrong_position_format1():
         ValueError,
         match='Position not recognized. Choose an integer, list of integers or one of the following strings: "start", "mid" or "end".',
     ):
-        qim3d.viz.slices_grid(example_volume, position="invalid_slice")
+        qim3d.viz.slices_grid(example_volume, slice_positions="invalid_slice")
 
 
 def test_slices_wrong_position_format2():
@@ -86,7 +86,7 @@ def test_slices_wrong_position_format2():
         ValueError,
         match='Position not recognized. Choose an integer, list of integers or one of the following strings: "start", "mid" or "end".',
     ):
-        qim3d.viz.slices_grid(example_volume, position=1.5)
+        qim3d.viz.slices_grid(example_volume, slice_positions=1.5)
 
 
 def test_slices_wrong_position_format3():
@@ -95,16 +95,16 @@ def test_slices_wrong_position_format3():
         ValueError,
         match='Position not recognized. Choose an integer, list of integers or one of the following strings: "start", "mid" or "end".',
     ):
-        qim3d.viz.slices_grid(example_volume, position=[1, 2, 3.5])
+        qim3d.viz.slices_grid(example_volume, slice_positions=[1, 2, 3.5])
 
 
 def test_slices_invalid_axis_value():
     example_volume = np.ones((10, 10, 10))
     with pytest.raises(
         ValueError,
-        match="Invalid value for 'axis'. It should be an integer between 0 and 2",
+        match=f"Invalid value for 'slice_axis'. It should be an integer between 0 and {example_volume.ndim - 1}.",
     ):
-        qim3d.viz.slices_grid(example_volume, axis=3)
+        qim3d.viz.slices_grid(example_volume, slice_axis=3)
 
 
 def test_slices_interpolation_option():
@@ -113,8 +113,8 @@ def test_slices_interpolation_option():
     interpolation_method = "bilinear"
     fig = qim3d.viz.slices_grid(
         example_volume,
-        n_slices=1,
-        img_width=img_width,
+        num_slices=1,
+        image_width=img_width,
         interpolation=interpolation_method,
     )
 
@@ -128,27 +128,27 @@ def test_slices_interpolation_option():
 
 def test_slices_multiple_slices():
     example_volume = np.ones((10, 10, 10))
-    img_width = 3
-    n_slices = 3
-    fig = qim3d.viz.slices_grid(example_volume, n_slices=n_slices, img_width=img_width)
+    image_width = 3
+    num_slices = 3
+    fig = qim3d.viz.slices_grid(example_volume, num_slices=num_slices, image_width=image_width)
     # Add assertions for the expected number of subplots in the figure
-    assert len(fig.get_axes()) == n_slices
+    assert len(fig.get_axes()) == num_slices
 
 
 def test_slices_axis_argument():
     # Non-symmetric input
     example_volume = np.arange(1000).reshape((10, 10, 10))
-    img_width = 3
+    image_width = 3
 
     # Call the function with different values of the axis
     fig_axis_0 = qim3d.viz.slices_grid(
-        example_volume, n_slices=1, img_width=img_width, axis=0
+        example_volume, num_slices=1, image_width=image_width, slice_axis=0
     )
     fig_axis_1 = qim3d.viz.slices_grid(
-        example_volume, n_slices=1, img_width=img_width, axis=1
+        example_volume, num_slices=1, image_width=image_width, slice_axis=1
     )
     fig_axis_2 = qim3d.viz.slices_grid(
-        example_volume, n_slices=1, img_width=img_width, axis=2
+        example_volume, num_slices=1, image_width=image_width, slice_axis=2
     )
 
     # Ensure that different axes result in different plots
@@ -216,7 +216,7 @@ def test_orthogonal_with_numpy_array():
     # Create a sample NumPy array
     vol = np.random.rand(10, 10, 10)
     # Call the orthogonal function with the NumPy array
-    orthogonal_obj = qim3d.viz.orthogonal(vol)
+    orthogonal_obj = qim3d.viz.slicer_orthogonal(vol)
     # Assert that the orthogonal object is created successfully
     assert isinstance(orthogonal_obj, widgets.HBox)
 
@@ -225,28 +225,28 @@ def test_orthogonal_with_torch_tensor():
     # Create a sample PyTorch tensor
     vol = torch.rand(10, 10, 10)
     # Call the orthogonal function with the PyTorch tensor
-    orthogonal_obj = qim3d.viz.orthogonal(vol)
+    orthogonal_obj = qim3d.viz.slicer_orthogonal(vol)
     # Assert that the orthogonal object is created successfully
     assert isinstance(orthogonal_obj, widgets.HBox)
 
 
 def test_orthogonal_with_different_parameters():
     # Test with different colormaps
-    for cmap in ["viridis", "gray", "plasma"]:
-        orthogonal_obj = qim3d.viz.orthogonal(np.random.rand(10, 10, 10), cmap=cmap)
+    for color_map in ["viridis", "gray", "plasma"]:
+        orthogonal_obj = qim3d.viz.slicer_orthogonal(np.random.rand(10, 10, 10), color_map=color_map)
         assert isinstance(orthogonal_obj, widgets.HBox)
 
     # Test with different image sizes
-    for img_height, img_width in [(2, 2), (4, 4)]:
-        orthogonal_obj = qim3d.viz.orthogonal(
-            np.random.rand(10, 10, 10), img_height=img_height, img_width=img_width
+    for image_height, image_width in [(2, 2), (4, 4)]:
+        orthogonal_obj = qim3d.viz.slicer_orthogonal(
+            np.random.rand(10, 10, 10), image_height=image_height, image_width=image_width
         )
         assert isinstance(orthogonal_obj, widgets.HBox)
 
     # Test with show_position set to True and False
-    for show_position in [True, False]:
-        orthogonal_obj = qim3d.viz.orthogonal(
-            np.random.rand(10, 10, 10), show_position=show_position
+    for display_positions in [True, False]:
+        orthogonal_obj = qim3d.viz.slicer_orthogonal(
+            np.random.rand(10, 10, 10), display_positions=display_positions
         )
         assert isinstance(orthogonal_obj, widgets.HBox)
 
@@ -255,7 +255,7 @@ def test_orthogonal_initial_slider_value():
     # Create a sample NumPy array
     vol = np.random.rand(10, 7, 19)
     # Call the orthogonal function with the NumPy array
-    orthogonal_obj = qim3d.viz.orthogonal(vol)
+    orthogonal_obj = qim3d.viz.slicer_orthogonal(vol)
     for idx, slicer in enumerate(orthogonal_obj.children):
         assert slicer.children[0].value == vol.shape[idx] // 2
 
@@ -264,7 +264,7 @@ def test_orthogonal_slider_description():
     # Create a sample NumPy array
     vol = np.random.rand(10, 10, 10)
     # Call the orthogonal function with the NumPy array
-    orthogonal_obj = qim3d.viz.orthogonal(vol)
+    orthogonal_obj = qim3d.viz.slicer_orthogonal(vol)
     for idx, slicer in enumerate(orthogonal_obj.children):
         assert slicer.children[0].description == ["Z", "Y", "X"][idx]
 
diff --git a/qim3d/utils/__init__.py b/qim3d/utils/__init__.py
index 0c7d5b30a7b3daf15b80d5995e6d0e4a1f18e5cc..fab0c339cfa63a4fd86f5420cc0f90e4a2738597 100644
--- a/qim3d/utils/__init__.py
+++ b/qim3d/utils/__init__.py
@@ -1,6 +1,8 @@
 from . import _doi
 from ._system import Memory
 
+from ._logger import log
+
 from ._misc import (
     get_local_ip,
     port_from_str,
@@ -13,4 +15,4 @@ from ._misc import (
     scale_to_float16,
 )
 
-from ._server import start_http_server
\ No newline at end of file
+from ._server import start_http_server
diff --git a/qim3d/utils/_misc.py b/qim3d/utils/_misc.py
index 3d8660b445a65577ef3b4c7d16af613b44785aac..090672d2fc5370d16bd56c28c384b9efb4c528e7 100644
--- a/qim3d/utils/_misc.py
+++ b/qim3d/utils/_misc.py
@@ -153,7 +153,7 @@ def get_file_size(file_path: str) -> int:
     try:
         file_size = os.path.getsize(file_path)
     except FileNotFoundError:
-        similar_paths = qim3d.utils.misc.find_similar_paths(file_path)
+        similar_paths = qim3d.utils._misc.find_similar_paths(file_path)
 
         if similar_paths:
             suggestion = similar_paths[0]  # Get the closest match