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: