Source code for mesoSPIM.src.utils.utility_functions

'''
Contains a variety of mesoSPIM utility functions
'''
import ctypes
import logging
from PyQt5 import QtWidgets
logger = logging.getLogger(__name__)

# Windows API binding
GetCurrentProcessorNumber = ctypes.windll.kernel32.GetCurrentProcessorNumber


[docs] def fit_window_to_screen(window, margin=60): '''Shrink a window so it fits within the available screen geometry. The mesoSPIM .ui files specify fixed window sizes that can exceed the resolution of smaller monitors. The windows are built from resizable Qt layouts, so shrinking them down still leaves a usable, scaled-down GUI. Windows that already fit are left untouched. Args: window (QWidget): The window to resize. margin (int): Pixels to leave free between the window and the edges of the available screen area (taskbars etc.). ''' available = QtWidgets.QApplication.primaryScreen().availableGeometry() target_width = min(window.width(), available.width() - margin) target_height = min(window.height(), available.height() - margin) if target_width < window.width() or target_height < window.height(): window.resize(max(target_width, 1), max(target_height, 1))
[docs] def move_window_into_screen(window, x, y): '''Move a window to (x, y), clamped so it stays fully within the available screen. Useful when a saved/configured window position (or a tiled layout based on it) would otherwise place a window partially or fully off-screen, e.g. after switching to a smaller monitor. Args: window (QWidget): The window to move. x (int): Desired x position (left edge). y (int): Desired y position (top edge). ''' available = QtWidgets.QApplication.primaryScreen().availableGeometry() x = min(max(x, available.left()), max(available.left(), available.right() - window.width())) y = min(max(y, available.top()), max(available.top(), available.bottom() - window.height())) window.move(x, y)
[docs] def convert_seconds_to_string(delta_t): ''' Converts an input value in seconds into a string in the format hh:mm:ss Interestingly, a variant using np.divmod is around 4-5x slower in initial tests. ''' if delta_t <= 0: return '--:--:--' else: hours, remainder = divmod(delta_t, 3600) minutes, seconds = divmod(remainder, 60) return f"{int(hours):02}:{int(minutes):02}:{int(seconds):02}"
[docs] def format_data_size(bytes): ''' Converts bytes into human-readable format (kb, MB, GB) ''' try: bytes = float(bytes) kb = bytes / 1024 except Exception as e: print(f"{e}") return None if kb >= 1024: M = kb / 1024 if M >= 1024: G = M / 1024 return "%.1f GB" % (G) else: return "%.1f MB" % (M) else: return "%.1f kb" % (kb)
[docs] def write_line(file, key='', value=''): ''' Little helper method to write a single line with a key and value for metadata Adds a line break at the end. ''' if key != '': file.write('['+str(key)+'] '+str(value) + '\n') else: file.write('\n')
[docs] def gb_size_of_array_shape(shape): '''Given a tuple of array shape, return the size in GB of a uint16 array. Args: shape (tuple[int]): Array dimensions, e.g. ``(100, 2048, 2048)``. Returns: float: Size in gibibytes (GiB). ''' for idx,ii in enumerate(shape): if idx == 0: total = ii else: total *= ii total = total * 16 / 8 return total / 1024**3
[docs] def replace_with_underscores(string): '''Replace spaces, slashes and percent signs with underscores or ASCII equivalents. Used for sanitising file and folder names produced from user inputs. Args: string (str): Raw string, e.g. a filter name like ``"488 nm / 50%"``. Returns: str: Sanitised string safe for use in file paths. ''' s = string.replace(' ', '_').replace('/', '_').replace('%', 'pct') return s
[docs] def log_cpu_core(func): '''Decorator to log (at DEBUG level) which logical CPU core the calling thread is currently running on. Useful for verifying thread affinity in the Core / Camera / Writer thread model——each Qt thread should remain pinned to a consistent CPU core. ''' import functools @functools.wraps(func) def wrapper(*args, **kwargs): core = GetCurrentProcessorNumber() # Windows only. logger.debug(f"{func.__name__}() running on logical CPU core: {core}") return func(*args, **kwargs) return wrapper
[docs] def timed(func): '''Decorator to time functions and log the elapsed time''' import time def wrapper(*args, **kwargs): start = time.perf_counter() result = func(*args, **kwargs) elapsed_ms = (time.perf_counter() - start) * 1000 logger.info(f"{func.__name__} took {elapsed_ms:.1f} ms") return result return wrapper