diff --git a/qim3d/utils/internal_tools.py b/qim3d/utils/internal_tools.py
index 9149b2cd01d4f28b56f64f65b0d3eaea2e6c3db2..113b66eb899aedd4b040a6f20c2fe5375ab0bf24 100644
--- a/qim3d/utils/internal_tools.py
+++ b/qim3d/utils/internal_tools.py
@@ -302,4 +302,46 @@ def get_css():
     with open(css_path,'r') as file:
         css_content = file.read()
     
-    return css_content
\ No newline at end of file
+    return css_content
+
+
+def scale_to_float16(arr):
+    """
+    Scale a NumPy array to fit within the limits of the float16 data type.
+
+    Parameters:
+    - arr (numpy.ndarray): The input array to be scaled.
+
+    Returns:
+    - numpy.ndarray: The scaled array, with values adjusted to fit within the limits of float16 data type.
+
+    This function takes a NumPy array as input and checks if its maximum and minimum values
+    exceed the limits of the float16 data type. If necessary, it scales the positive and negative
+    parts of the array independently to fit within the range of float16.
+    """
+        
+    # Determine maximum and minimum values of the array
+    arr_max = np.max(arr)
+    arr_min = np.min(arr)
+    
+    # Check if scaling is necessary for positive and negative parts separately
+    if arr_max > np.finfo(np.float16).max:
+        pos_scaled_arr = np.interp(arr[arr >= 0], (0, arr_max), (0, np.finfo(np.float16).max))
+    else:
+        pos_scaled_arr = arr[arr >= 0].astype(np.float16)
+        
+    if arr_min < -np.finfo(np.float16).max:
+        neg_scaled_arr = np.interp(arr[arr < 0], (arr_min, 0), (-np.finfo(np.float16).max, 0))
+    else:
+        neg_scaled_arr = arr[arr < 0].astype(np.float16)
+    
+    # Combine the scaled positive and negative parts
+    scaled_arr = np.concatenate((neg_scaled_arr, pos_scaled_arr))
+
+    # Reshape the scaled array to match the original shape
+    scaled_arr = scaled_arr.reshape(arr.shape)
+    
+    # Convert the scaled array to float16 data type
+    scaled_arr = scaled_arr.astype(np.float16)
+    
+    return scaled_arr
\ No newline at end of file
diff --git a/qim3d/viz/k3d.py b/qim3d/viz/k3d.py
index a6e45878e7240df81f99d28b6538a49d6d45679a..8b56df64f350c5bdcb4f3bab4cd1a1b52be5a521 100644
--- a/qim3d/viz/k3d.py
+++ b/qim3d/viz/k3d.py
@@ -9,9 +9,10 @@ Volumetric visualization using K3D
 
 import k3d
 import numpy as np
+from qim3d.utils.internal_tools import scale_to_float16
 
 
-def vol(img, aspectmode="data", show=True, save=False, grid_visible=False, cmap=None, **kwargs):
+def vol(img, aspectmode="data", show=True, save=False, grid_visible=False, cmap=None, samples="auto", **kwargs):
     """
     Visualizes a 3D volume using volumetric rendering.
 
@@ -26,6 +27,9 @@ def vol(img, aspectmode="data", show=True, save=False, grid_visible=False, cmap=
             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.
+        cmap (list, optional): The color map to be used for the volume rendering. Defaults to None.
+        samples (int, optional): The number of samples to be used for the volume rendering in k3d. Defaults to 512. 
+            Lower values will render faster but with lower quality.
         **kwargs: Additional keyword arguments to be passed to the `k3d.plot` function.
 
     Returns:
@@ -54,20 +58,35 @@ def vol(img, aspectmode="data", show=True, save=False, grid_visible=False, cmap=
         ```
         
     """
+    pixel_count = img.shape[0] * img.shape[1] * img.shape[2]
+    # target is 60fps on m1 macbook pro, using test volume: https://data.qim.dk/pages/foam.html
+    if samples == "auto":
+        y1,x1 = 256, 16777216 # 256 samples at res 256*256*256=16.777.216
+        y2,x2 = 32, 134217728 # 32 samples at res 512*512*512=134.217.728
+
+        # we fit linear function to the two points
+        a = (y1-y2)/(x1-x2)
+        b = y1 - a*x1
+
+        samples = int(min(max(a*pixel_count+b,32),512))
+    else:
+        samples = int(samples) # make sure it's an integer
+
 
     if aspectmode.lower() not in ["data", "cube"]:
         raise ValueError("aspectmode should be either 'data' or 'cube'")
 
     plt_volume = k3d.volume(
-        img.astype(np.float32),
+        scale_to_float16(img),
         bounds=(
             [0, img.shape[0], 0, img.shape[1], 0, img.shape[2]]
             if aspectmode.lower() == "data"
             else None
         ),
         color_map=cmap,
+        samples=samples,
     )
-    plot = k3d.plot(grid_visible=grid_visible, **kwargs)
+    plot = k3d.plot(grid_visible=grid_visible,**kwargs)
     plot += plt_volume
 
     if save: