diff --git a/docs/assets/screenshots/synthetic_blob_cylinder_slice.png b/docs/assets/screenshots/synthetic_blob_cylinder_slice.png
new file mode 100644
index 0000000000000000000000000000000000000000..facb7337077171227921723f33ae2bb6254b5a47
Binary files /dev/null and b/docs/assets/screenshots/synthetic_blob_cylinder_slice.png differ
diff --git a/docs/assets/screenshots/synthetic_blob_tube_slice.png b/docs/assets/screenshots/synthetic_blob_tube_slice.png
new file mode 100644
index 0000000000000000000000000000000000000000..feecc5de7c84820112a9b705f55489e1577eba71
Binary files /dev/null and b/docs/assets/screenshots/synthetic_blob_tube_slice.png differ
diff --git a/docs/assets/screenshots/synthetic_collection_cylinder_slices.png b/docs/assets/screenshots/synthetic_collection_cylinder_slices.png
new file mode 100644
index 0000000000000000000000000000000000000000..8e64ab92626ca6c5e67f78f07e108e3c4ad237c7
Binary files /dev/null and b/docs/assets/screenshots/synthetic_collection_cylinder_slices.png differ
diff --git a/docs/assets/screenshots/synthetic_collection_tube_slices.png b/docs/assets/screenshots/synthetic_collection_tube_slices.png
new file mode 100644
index 0000000000000000000000000000000000000000..1e298daf17075ae3e29e5ab2554c3f583abcfcfe
Binary files /dev/null and b/docs/assets/screenshots/synthetic_collection_tube_slices.png differ
diff --git a/qim3d/generate/blob_.py b/qim3d/generate/blob_.py
index ed7f6f6c25b4d8be64853bcba674d5d8b761c046..bb7561b425a50b3def1b3684026d6d29d5f66f06 100644
--- a/qim3d/generate/blob_.py
+++ b/qim3d/generate/blob_.py
@@ -2,6 +2,8 @@ import numpy as np
 import scipy.ndimage
 from noise import pnoise3
 
+import qim3d.processing
+
 def blob(
     base_shape: tuple = (128, 128, 128),
     final_shape: tuple = (128, 128, 128),
@@ -11,6 +13,7 @@ def blob(
     max_value: int = 255,
     threshold: float = 0.5,
     smooth_borders: bool = False,
+    object_shape: str = None,
     dtype: str = "uint8",
     ) -> np.ndarray:
     """
@@ -25,6 +28,7 @@ def blob(
         max_value (int, optional): Maximum value for the volume intensity. Defaults to 255.
         threshold (float, optional): Threshold value for clipping low intensity values. Defaults to 0.5.
         smooth_borders (bool, optional): Flag for automatic computation of the threshold value to ensure a blob with no straight edges. If True, the `threshold` parameter is ignored. Defaults to False.
+        object_shape (str, optional): Shape of the object to generate, either "cylinder", or "tube". Defaults to None.
         dtype (str, optional): Desired data type of the output volume. Defaults to "uint8".
 
     Returns:
@@ -41,17 +45,63 @@ def blob(
         # Generate synthetic blob
         synthetic_blob = qim3d.generate.blob(noise_scale = 0.015)
 
+        # Visualize 3D volume
+        qim3d.viz.vol(synthetic_blob)
+        ```
+        <iframe src="https://platform.qim.dk/k3d/synthetic_blob.html" width="100%" height="500" frameborder="0"></iframe>
+
+        ```python
         # Visualize slices
         qim3d.viz.slices(synthetic_blob, vmin = 0, vmax = 255, n_slices = 15)
         ```
         ![synthetic_blob](assets/screenshots/synthetic_blob_slices.png)
 
+    Example:
         ```python
-        # Visualize 3D volume
-        qim3d.viz.vol(synthetic_blob)
+        import qim3d
+
+        # Generate tubular synthetic blob
+        vol = qim3d.generate.blob(base_shape = (10, 300, 300),
+                                final_shape = (100, 100, 100),
+                                noise_scale = 0.3,
+                                gamma = 2,
+                                threshold = 0.0,
+                                object_shape = "cylinder"
+                                )
+
+        # Visualize synthetic blob
+        qim3d.viz.vol(vol)
         ```
-        <iframe src="https://platform.qim.dk/k3d/synthetic_blob.html" width="100%" height="500" frameborder="0"></iframe>
+        <iframe src="https://platform.qim.dk/k3d/synthetic_blob_cylinder.html" width="100%" height="500" frameborder="0"></iframe>
+
+        ```python
+        # Visualize slices
+        qim3d.viz.slices(vol, n_slices=15, axis=1)        ```
+        ![synthetic_blob_cylinder_slice](assets/screenshots/synthetic_blob_cylinder_slice.png)
+
+    Example:
+        ```python
+        import qim3d
 
+        # Generate tubular synthetic blob
+        vol = qim3d.generate.blob(base_shape = (200, 100, 100),
+                                final_shape = (400, 100, 100),
+                                noise_scale = 0.03,
+                                gamma = 0.12,
+                                threshold = 0.85,
+                                object_shape = "tube"
+                                )
+
+        # Visualize synthetic blob
+        qim3d.viz.vol(vol)
+        ```
+        <iframe src="https://platform.qim.dk/k3d/synthetic_blob_tube.html" width="100%" height="500" frameborder="0"></iframe>
+        
+        ```python
+        # Visualize
+        qim3d.viz.slices(vol, n_slices=15)
+        ```
+        ![synthetic_blob_tube_slice](assets/screenshots/synthetic_blob_tube_slice.png)    
     """
 
     if not isinstance(final_shape, tuple) or len(final_shape) != 3:
@@ -93,6 +143,10 @@ def blob(
     # Scale the volume to the maximum value
     volume = volume * max_value
 
+    # If object shape is specified, smooth borders are disabled
+    if object_shape:
+        smooth_borders = False
+
     if smooth_borders: 
         # Maximum value among the six sides of the 3D volume
         max_border_value = np.max([
@@ -115,4 +169,46 @@ def blob(
         volume, np.array(final_shape) / np.array(base_shape), order=order
     )
 
-    return volume.astype(dtype)
+    # Fade into a shape if specified
+    if object_shape == "cylinder":
+
+        # Arguments for the fade_mask function
+        geometry = "cylindrical"        # Fade in cylindrical geometry
+        axis = np.argmax(volume.shape)  # Fade along the dimension where the object is the largest
+        target_max_normalized_distance = 1.4   # This value ensures that the object will become cylindrical
+
+        volume = qim3d.processing.operations.fade_mask(volume, 
+                                                       geometry = geometry, 
+                                                       axis = axis, 
+                                                       target_max_normalized_distance = target_max_normalized_distance
+                                                       )
+
+    elif object_shape == "tube":
+
+        # Arguments for the fade_mask function
+        geometry = "cylindrical"        # Fade in cylindrical geometry
+        axis = np.argmax(volume.shape)  # Fade along the dimension where the object is the largest
+        decay_rate = 5                  # Decay rate for the fade operation
+        target_max_normalized_distance = 1.4   # This value ensures that the object will become cylindrical
+
+        # Fade once for making the object cylindrical
+        volume = qim3d.processing.operations.fade_mask(volume, 
+                                                       geometry = geometry, 
+                                                       axis = axis,
+                                                       decay_rate = decay_rate,
+                                                       target_max_normalized_distance = target_max_normalized_distance,
+                                                       invert = False
+                                                       )
+
+        # Fade again with invert = True for making the object a tube (i.e. with a hole in the middle)
+        volume = qim3d.processing.operations.fade_mask(volume, 
+                                                       geometry = geometry, 
+                                                       axis = axis, 
+                                                       decay_rate = decay_rate,
+                                                       invert = True
+                                                       )
+        
+    # Convert to desired data type
+    volume = volume.astype(dtype)
+
+    return volume
\ No newline at end of file
diff --git a/qim3d/generate/collection_.py b/qim3d/generate/collection_.py
index f07e500c0d0e7bf51827e9b020528b7dfc3bcb0d..dc54da7e5efa65ee5dea5643fa7eed26fe90e475 100644
--- a/qim3d/generate/collection_.py
+++ b/qim3d/generate/collection_.py
@@ -139,6 +139,7 @@ def collection(
     min_threshold: float = 0.5,
     max_threshold: float = 0.6,
     smooth_borders: bool = False,
+    object_shape: str = None,
     seed: int = 0,
     verbose: bool = False,
 ) -> tuple[np.ndarray, object]:
@@ -163,30 +164,30 @@ def collection(
         max_high_value (int, optional): Maximum maximum value for the volume intensity. Defaults to 255.
         min_threshold (float, optional): Minimum threshold value for clipping low intensity values. Defaults to 0.5.
         max_threshold (float, optional): Maximum threshold value for clipping low intensity values. Defaults to 0.6.
-        smooth_borders (bool, optional): Flag for smoothing blob borders to avoid straight edges in the objects. If True, the `min_threshold` and `max_threshold` parameters are ignored. Defaults to False.
+        smooth_borders (bool, optional): Flag for smoothing object borders to avoid straight edges in the objects. If True, the `min_threshold` and `max_threshold` parameters are ignored. Defaults to False.
+        object_shape (str, optional): Shape of the object to generate, either "cylinder", or "tube". Defaults to None.
         seed (int, optional): Seed for reproducibility. Defaults to 0.
         verbose (bool, optional): Flag to enable verbose logging. Defaults to False.
 
-
     Returns:
         synthetic_collection (numpy.ndarray): 3D volume of the generated collection of synthetic objects with specified parameters.
         labels (numpy.ndarray): Array with labels for each voxel, same shape as synthetic_collection.
 
     Raises:
         TypeError: If `collection_shape` is not 3D.
-        ValueError: If blob parameters are invalid.
+        ValueError: If object parameters are invalid.
 
     Note:
         - The function places objects without overlap.
         - The function can either place objects at random positions in the collection (if `positions = None`) or at specific positions provided in the `positions` argument. If specific positions are provided, the number of blobs must match the number of positions (e.g. `num_objects = 2` with `positions = [(12, 8, 10), (24, 20, 18)]`).
-        - If not all `num_objects` can be placed, the function returns the `synthetic_collection` volume with as many blobs as possible in it, and logs an error.
-        - Labels for all objects are returned, even if they are not a sigle connected component.
+        - If not all `num_objects` can be placed, the function returns the `synthetic_collection` volume with as many objects as possible in it, and logs an error.
+        - Labels for all objects are returned, even if they are not a single connected component.
 
     Example:
         ```python
         import qim3d
 
-        # Generate synthetic collection of blobs
+        # Generate synthetic collection of objects
         num_objects = 15
         synthetic_collection, labels = qim3d.generate.collection(num_objects = num_objects)
 
@@ -207,12 +208,11 @@ def collection(
         ```
         ![synthetic_collection](assets/screenshots/synthetic_collection_default_labels.gif)
 
-
     Example:
         ```python
         import qim3d
 
-        # Generate synthetic collection of dense blobs
+        # Generate synthetic collection of dense objects
         synthetic_collection, labels = qim3d.generate.collection(
                                     min_high_value = 255,
                                     max_high_value = 255,
@@ -228,34 +228,66 @@ def collection(
         ```
         <iframe src="https://platform.qim.dk/k3d/synthetic_collection_dense.html" width="100%" height="500" frameborder="0"></iframe>
 
-
-
     Example:
         ```python
         import qim3d
 
-        # Generate synthetic collection of tubular structures
-        synthetic_collection, labels = qim3d.generate.collection(
-                                    num_objects=10,
-                                    collection_shape=(200,100,100),
-                                    min_shape = (190, 50, 50),
-                                    max_shape = (200, 60, 60),
-                                    object_shape_zoom = (1, 0.2, 0.2),
-                                    min_object_noise = 0.01,
-                                    max_object_noise = 0.02,
-                                    max_rotation_degrees=10,
-                                    min_threshold = 0.95,
-                                    max_threshold = 0.98,
-                                    min_gamma = 0.02,
-                                    max_gamma = 0.03
-                                    )
+        # Generate synthetic collection of cylindrical structures
+        vol, labels = qim3d.generate.collection(num_objects = 40,
+                                                collection_shape = (300, 150, 150),
+                                                min_shape = (280, 10, 10),
+                                                max_shape = (290, 15, 15),
+                                                min_object_noise = 0.08,
+                                                max_object_noise = 0.09,
+                                                max_rotation_degrees = 5,
+                                                min_threshold = 0.7,
+                                                max_threshold = 0.9,
+                                                min_gamma = 0.10,
+                                                max_gamma = 0.11,
+                                                object_shape = "cylinder"
+                                                )
 
         # Visualize synthetic collection
-        qim3d.viz.vol(synthetic_collection)
+        qim3d.viz.vol(vol)
+
+        ```
+        <iframe src="https://platform.qim.dk/k3d/synthetic_collection_cylinder.html" width="100%" height="500" frameborder="0"></iframe>
+        
+        ```python
+        # Visualize slices
+        qim3d.viz.slices(vol, n_slices=15)
         ```
-        <iframe src="https://platform.qim.dk/k3d/synthetic_collection_tubular.html" width="100%" height="500" frameborder="0"></iframe>
+        ![synthetic_collection_cylinder](assets/screenshots/synthetic_collection_cylinder_slices.png)    
+        
+    Example:
+        ```python
+        import qim3d
 
+        # Generate synthetic collection of tubular (hollow) structures
+        vol, labels = qim3d.generate.collection(num_objects = 10,
+                                                collection_shape = (200, 200, 200),
+                                                min_shape = (180, 25, 25),
+                                                max_shape = (190, 35, 35),
+                                                min_object_noise = 0.02,
+                                                max_object_noise = 0.03,
+                                                max_rotation_degrees = 5,
+                                                min_threshold = 0.7,
+                                                max_threshold = 0.9,
+                                                min_gamma = 0.10,
+                                                max_gamma = 0.11,
+                                                object_shape = "tube"
+                                                )
 
+        # Visualize synthetic collection
+        qim3d.viz.vol(vol)
+        ```
+        <iframe src="https://platform.qim.dk/k3d/synthetic_collection_tube.html" width="100%" height="500" frameborder="0"></iframe>
+        
+        ```python
+        # Visualize slices
+        qim3d.viz.slices(vol, n_slices=15, axis=1)
+        ```
+        ![synthetic_collection_tube](assets/screenshots/synthetic_collection_tube_slices.png)
     """
     if verbose:
         original_log_level = log.getEffectiveLevel()
@@ -270,10 +302,6 @@ def collection(
     if len(min_shape) != len(max_shape):
         raise ValueError("Object shapes must be tuples of the same length")
 
-    # if not isinstance(blob_shapes, list) or \
-    #     len(blob_shapes) != 2 or len(blob_shapes[0]) != 3 or len(blob_shapes[1]) != 3:
-    #     raise TypeError("Blob shapes must be a list of two tuples with three dimensions (z, y, x)")
-
     if (positions is not None) and (len(positions) != num_objects):
         raise ValueError(
             "Number of objects must match number of positions, otherwise set positions = None"
@@ -301,6 +329,10 @@ def collection(
             )
         log.debug(f"- Blob shape: {blob_shape}")
 
+        # Scale object shape
+        final_shape = tuple(l * r for l, r in zip(blob_shape, object_shape_zoom))
+        final_shape = tuple(int(x) for x in final_shape) # NOTE: Added this 
+
         # Sample noise scale
         noise_scale = rng.uniform(low=min_object_noise, high=max_object_noise)
         log.debug(f"- Object noise scale: {noise_scale:.4f}")
@@ -317,15 +349,16 @@ def collection(
         threshold = rng.uniform(low=min_threshold, high=max_threshold)
         log.debug(f"- Threshold: {threshold:.3f}")
 
-        # Generate synthetic blob
+        # Generate synthetic object
         blob = qim3d.generate.blob(
             base_shape=blob_shape,
-            final_shape=tuple(l * r for l, r in zip(blob_shape, object_shape_zoom)),
+            final_shape=final_shape,
             noise_scale=noise_scale,
             gamma=gamma,
             max_value=max_value,
             threshold=threshold,
             smooth_borders=smooth_borders,
+            object_shape=object_shape,
         )
 
         # Rotate object
@@ -336,21 +369,21 @@ def collection(
             axes = rng.choice(rotation_axes)  # Sample the two axes to rotate around
             log.debug(f"- Rotation angle: {angle:.2f} at axes: {axes}")
 
-            blob = scipy.ndimage.rotate(blob, angle, axes, order=0)
+            blob = scipy.ndimage.rotate(blob, angle, axes, order=1)
 
         # Place synthetic object into the collection
-        # If positions are specified, place blob at one of the specified positions
+        # If positions are specified, place object at one of the specified positions
         collection_before = collection_array.copy()
         if positions:
             collection_array, placed, positions = specific_placement(
                 collection_array, blob, positions
             )
 
-        # Otherwise, place blob at a random available position
+        # Otherwise, place object at a random available position
         else:
             collection_array, placed = random_placement(collection_array, blob, rng)
 
-        # Break if blob could not be placed
+        # Break if object could not be placed
         if not placed:
             break
 
diff --git a/qim3d/processing/operations.py b/qim3d/processing/operations.py
index 8d2d0f83995ccb3507672a354fcb9d0b7cfd185e..79fb8fdc743fe6d30a50a6d3ddf51cf0febc199a 100644
--- a/qim3d/processing/operations.py
+++ b/qim3d/processing/operations.py
@@ -125,6 +125,7 @@ def fade_mask(
     geometry: str = "spherical",
     invert: bool = False,
     axis: int = 0,
+    **kwargs,
 ) -> np.ndarray:
     """
     Apply edge fading to a volume.
@@ -133,8 +134,10 @@ def fade_mask(
         vol (np.ndarray): The volume to apply edge fading to.
         decay_rate (float, optional): The decay rate of the fading. Defaults to 10.
         ratio (float, optional): The ratio of the volume to fade. Defaults to 0.5.
-        geometric (str, optional): The geometric shape of the fading. Can be 'spherical' or 'cylindrical'. Defaults to 'spherical'.
+        geometry (str, optional): The geometric shape of the fading. Can be 'spherical' or 'cylindrical'. Defaults to 'spherical'.
+        invert (bool, optional): Flag for inverting the fading. Defaults to False.
         axis (int, optional): The axis along which to apply the fading. Defaults to 0.
+        **kwargs: Additional keyword arguments for the edge fading.
 
     Returns:
         vol_faded (np.ndarray): The volume with edge fading applied.
@@ -165,6 +168,9 @@ def fade_mask(
     shape = vol.shape
     z, y, x = np.indices(shape)
 
+    # Store the original maximum value of the volume
+    original_max_value = np.max(vol)
+
     # Calculate the center of the array
     center = np.array([(s - 1) / 2 for s in shape])
 
@@ -177,10 +183,18 @@ def fade_mask(
         distance_list = np.delete(distance_list, axis, axis=0)
         distance = np.linalg.norm(distance_list, axis=0)
     else:
-        raise ValueError("geometric must be 'spherical' or 'cylindrical'")
+        raise ValueError("Geometry must be 'spherical' or 'cylindrical'")
+    
+    # Compute the maximum distance from the center
+    max_distance = np.linalg.norm(center)
+    
+    # Compute ratio to make synthetic blobs exactly cylindrical
+    # target_max_normalized_distance = 1.4 works well to make the blobs cylindrical
+    if "target_max_normalized_distance" in kwargs:
+        target_max_normalized_distance = kwargs["target_max_normalized_distance"]
+        ratio = np.max(distance) / (target_max_normalized_distance * max_distance)
 
     # Normalize the distances so that they go from 0 at the center to 1 at the farthest point
-    max_distance = np.linalg.norm(center)
     normalized_distance = distance / (max_distance * ratio)
 
     # Apply the decay rate
@@ -196,7 +210,10 @@ def fade_mask(
     # Apply the fading to the volume
     vol_faded = vol * fade_array
 
-    return vol_faded
+    # Normalize the volume to retain the original maximum value
+    vol_normalized = vol_faded * (original_max_value / np.max(vol_faded))
+
+    return vol_normalized
 
 
 def overlay_rgb_images(