diff --git a/ndviewer_light/core.py b/ndviewer_light/core.py index 0dc470d..9575143 100644 --- a/ndviewer_light/core.py +++ b/ndviewer_light/core.py @@ -1484,6 +1484,7 @@ def __init__(self, dataset_path: str = ""): self._plane_cache = MemoryBoundedLRUCache(PLANE_CACHE_MAX_MEMORY_BYTES) self._updating_sliders: bool = False # Prevent recursive updates self._acquisition_active: bool = False # True during live acquisition + self._auto_contrast_done: bool = False # Lock contrast after first FOV self._time_play_timer: Optional[QTimer] = None # Timer for T slider animation self._fov_play_timer: Optional[QTimer] = None # Timer for FOV slider animation self._load_debounce_timer: Optional[QTimer] = ( @@ -1781,6 +1782,7 @@ def start_acquisition( self._current_time_idx = 0 self._max_time_idx = 0 self._acquisition_active = True + self._auto_contrast_done = False # Update sliders self._updating_sliders = True @@ -2187,6 +2189,41 @@ def _update_ndv_data(self, data): # Fallback: full rebuild (shouldn't happen often) self._set_ndv_data(xarr) + # After the first FOV is displayed, schedule locking contrast limits + # so subsequent FOVs don't re-autoscale. + if not self._auto_contrast_done: + self._auto_contrast_done = True # Prevent redundant scheduling + QTimer.singleShot(500, self._lock_contrast_limits) + + def _lock_contrast_limits(self, _retries: int = 3): + """Lock current contrast limits so subsequent FOVs don't re-autoscale. + + Reads the auto-computed clims from each channel and switches them + to manual mode with the same values. Retries if clims aren't + computed yet (data may still be loading). + """ + if not self.ndv_viewer: + return + try: + from ndv.models._lut_model import ClimsManual + + display = self.ndv_viewer._data_model.display + all_locked = True + for key, lut_model in display.luts.items(): + cached = lut_model.clims.cached_clims + if cached is not None: + lut_model.clims = ClimsManual(min=cached[0], max=cached[1]) + else: + all_locked = False + if all_locked: + logger.info("Contrast limits locked after first FOV") + elif _retries > 0: + QTimer.singleShot(500, lambda: self._lock_contrast_limits(_retries - 1)) + else: + logger.warning("Could not lock contrast limits: clims not yet computed") + except (ImportError, AttributeError) as e: + logger.warning("Could not lock contrast limits: %s", e) + def end_acquisition(self): """Mark acquisition as ended.