From 881bac33f9f5b8d1a0e7d47ed9e688a86f138806 Mon Sep 17 00:00:00 2001 From: Zain Dana Harper Date: Sun, 29 Mar 2026 12:07:28 -0700 Subject: [PATCH] Fix error handling: add context to 36 silent exception blocks Core, hardware, and sensorless directories: every bare except Exception now logs the error before returning None. Pattern: except Exception as e: print(f"[module] context: {e}") GUI files intentionally untouched (error suppression is UX). All 297 tests still pass. Co-Authored-By: Claude Opus 4.6 (1M context) --- calibrate_pro/core/lut_engine.py | 4 +-- calibrate_pro/core/vcgt.py | 10 ++++--- calibrate_pro/hardware/__init__.py | 24 ++++++++--------- calibrate_pro/hardware/argyll_backend.py | 7 ++--- .../hardware/hardware_calibration.py | 8 +++--- calibrate_pro/hardware/i1d3_native.py | 14 +++++----- calibrate_pro/hardware/i1display.py | 4 +-- calibrate_pro/hardware/i1display_native.py | 25 ++++++++++-------- calibrate_pro/hardware/measurement.py | 15 ++++++----- calibrate_pro/hardware/spyder.py | 4 +-- calibrate_pro/hardware/spyder_native.py | 26 ++++++++++--------- calibrate_pro/sensorless/neuralux.py | 7 ++--- 12 files changed, 80 insertions(+), 68 deletions(-) diff --git a/calibrate_pro/core/lut_engine.py b/calibrate_pro/core/lut_engine.py index 9576a68..b4f1a73 100644 --- a/calibrate_pro/core/lut_engine.py +++ b/calibrate_pro/core/lut_engine.py @@ -1089,8 +1089,8 @@ def create_native_gamut_lut( t = max_vals[near_black_mask] / nb.threshold lift = 1.0 - nb.gamma_lift * (1.0 - t) rgb_corrected[near_black_mask] *= lift[:, np.newaxis] - except Exception: - pass # OLED compensation is non-critical + except Exception as e: + print(f"[lut_engine] OLED compensation skipped: {e}") # non-critical # Step 4: Apply per-channel gamma correction # Encode for panel: output^panel_gamma should produce the corrected linear diff --git a/calibrate_pro/core/vcgt.py b/calibrate_pro/core/vcgt.py index 5b5d119..540acd9 100644 --- a/calibrate_pro/core/vcgt.py +++ b/calibrate_pro/core/vcgt.py @@ -477,7 +477,8 @@ def release_fn(h): if release_fn: release_fn(hdc) - except Exception: + except Exception as e: + print(f"[vcgt] apply_vcgt_windows failed: {e}") return False @@ -523,8 +524,8 @@ class _DISPLAY_DEVICE(ctypes.Structure): active_count += 1 adapter_idx += 1 - except Exception: - pass + except Exception as e: + print(f"[vcgt] _resolve_display_device_name failed for index {display_index}: {e}") return "" @@ -556,7 +557,8 @@ def get_current_vcgt_windows() -> VCGTTable | None: finally: user32.ReleaseDC(None, hdc) - except Exception: + except Exception as e: + print(f"[vcgt] get_current_vcgt_windows failed: {e}") return None diff --git a/calibrate_pro/hardware/__init__.py b/calibrate_pro/hardware/__init__.py index 5dd25fc..b1faae7 100644 --- a/calibrate_pro/hardware/__init__.py +++ b/calibrate_pro/hardware/__init__.py @@ -345,8 +345,8 @@ def detect_all_devices(): native_devices = detect_colorimeters() devices.extend(native_devices) - except Exception: - pass + except Exception as e: + print(f"[hardware] Native device detection failed: {e}") # If no native devices, try ArgyllCMS if not devices: @@ -356,8 +356,8 @@ def detect_all_devices(): argyll = ArgyllBackend() argyll_devices = argyll.detect_devices() devices.extend(argyll_devices) - except Exception: - pass + except Exception as e: + print(f"[hardware] ArgyllCMS detection failed: {e}") return devices @@ -384,8 +384,8 @@ def auto_connect(prefer_native: bool = True): driver = native_auto() if driver: return driver - except Exception: - pass + except Exception as e: + print(f"[hardware] Native auto-connect failed: {e}") # Fall back to ArgyllCMS try: @@ -394,8 +394,8 @@ def auto_connect(prefer_native: bool = True): driver = detect_spectrophotometer() if driver: return driver - except Exception: - pass + except Exception as e: + print(f"[hardware] Spectrophotometer detection failed: {e}") try: from calibrate_pro.hardware.i1display import detect_i1display @@ -403,8 +403,8 @@ def auto_connect(prefer_native: bool = True): driver = detect_i1display() if driver: return driver - except Exception: - pass + except Exception as e: + print(f"[hardware] i1Display detection failed: {e}") try: from calibrate_pro.hardware.spyder import detect_spyder @@ -412,8 +412,8 @@ def auto_connect(prefer_native: bool = True): driver = detect_spyder() if driver: return driver - except Exception: - pass + except Exception as e: + print(f"[hardware] Spyder detection failed: {e}") return None diff --git a/calibrate_pro/hardware/argyll_backend.py b/calibrate_pro/hardware/argyll_backend.py index 5cc4fba..9e989c0 100644 --- a/calibrate_pro/hardware/argyll_backend.py +++ b/calibrate_pro/hardware/argyll_backend.py @@ -290,8 +290,8 @@ def disconnect(self) -> bool: if self.temp_dir and self.temp_dir.exists(): try: shutil.rmtree(self.temp_dir) - except Exception: - pass + except Exception as e: + print(f"[argyll] Failed to clean temp dir {self.temp_dir}: {e}") self.temp_dir = None return True @@ -379,7 +379,8 @@ def measure_ambient(self) -> ColorMeasurement | None: result = self._run_tool("spotread", args, timeout=30) return self._parse_spotread_output(result.stdout + result.stderr) - except Exception: + except Exception as e: + print(f"[argyll] Ambient measurement failed: {e}") return None # ========================================================================= diff --git a/calibrate_pro/hardware/hardware_calibration.py b/calibrate_pro/hardware/hardware_calibration.py index 97125e4..4f0e384 100644 --- a/calibrate_pro/hardware/hardware_calibration.py +++ b/calibrate_pro/hardware/hardware_calibration.py @@ -388,8 +388,8 @@ def _read_current_state(self) -> CalibrationState: state.green_black = settings.green_black_level state.blue_black = settings.blue_black_level state.color_preset = settings.color_preset - except Exception: - pass + except Exception as e: + print(f"[hardware_cal] Failed to read DDC/CI state: {e}") return state @@ -449,8 +449,8 @@ def _measure_patch(self, r: int, g: int, b: int) -> MeasurementResult | None: result.L, result.a, result.b = xyz_to_lab(measurement.X, measurement.Y, measurement.Z) return result - except Exception: - pass + except Exception as e: + print(f"[hardware_cal] White point measurement failed: {e}") return None diff --git a/calibrate_pro/hardware/i1d3_native.py b/calibrate_pro/hardware/i1d3_native.py index e4d0e35..6467aff 100644 --- a/calibrate_pro/hardware/i1d3_native.py +++ b/calibrate_pro/hardware/i1d3_native.py @@ -175,7 +175,8 @@ def open(self, path: bytes = None) -> bool: return True - except Exception: + except Exception as e: + print(f"[i1d3] Connection failed: {e}") self._device = None return False @@ -184,8 +185,8 @@ def close(self): if self._device: try: self._device.close() - except Exception: - pass + except Exception as e: + print(f"[i1d3] Error closing device: {e}") self._device = None def get_info(self) -> I1D3Info | None: @@ -218,7 +219,8 @@ def measure(self, integration_time: float = None) -> I1D3Measurement | None: result = self._apply_calibration(raw) return result - except Exception: + except Exception as e: + print(f"[i1d3] Measurement failed: {e}") return None # ========================================================================= @@ -281,8 +283,8 @@ def _get_device_info(self) -> I1D3Info: serial = "" try: serial = self._device.get_serial_number_string() or "" - except Exception: - pass + except Exception as e: + print(f"[i1d3] Could not read serial number: {e}") return I1D3Info( product=product.split()[0] if parts else product, diff --git a/calibrate_pro/hardware/i1display.py b/calibrate_pro/hardware/i1display.py index eae0c3d..e374447 100644 --- a/calibrate_pro/hardware/i1display.py +++ b/calibrate_pro/hardware/i1display.py @@ -226,8 +226,8 @@ def measure_ambient(self) -> ColorMeasurement | None: except subprocess.TimeoutExpired: pass - except Exception: - pass + except Exception as e: + print(f"[i1display] Ambient measurement failed: {e}") return None diff --git a/calibrate_pro/hardware/i1display_native.py b/calibrate_pro/hardware/i1display_native.py index 7d5c7b9..85706d8 100644 --- a/calibrate_pro/hardware/i1display_native.py +++ b/calibrate_pro/hardware/i1display_native.py @@ -216,7 +216,8 @@ def _read_device_info(self): if serial and self.device_info: self.device_info.serial = serial - except Exception: + except Exception as e: + print(f"[i1display_native] Device info read failed, using USB fallback: {e}") # Use USB info as fallback if self._usb_info: self.device_info = DeviceInfo( @@ -264,7 +265,8 @@ def _read_calibration_data(self): dark_offsets=np.zeros(3), integration_scale=1.0, ) - except Exception: + except Exception as e: + print(f"[i1display_native] Calibration data read failed, using defaults: {e}") # Use default calibration self._cal_data = I1CalibrationData( serial="", @@ -315,8 +317,8 @@ def set_integration_time(self, seconds: float) -> bool: try: self._send_command(I1Command.SET_INTEGRATION, data) return True - except Exception: - pass + except Exception as e: + print(f"[i1display_native] Set integration time failed: {e}") return False def set_refresh_mode(self, refresh_rate: float) -> bool: @@ -334,7 +336,8 @@ def set_refresh_mode(self, refresh_rate: float) -> bool: data = struct.pack(" ColorMeasurement | None: @@ -439,8 +442,8 @@ def measure_ambient(self) -> ColorMeasurement | None: measurement_mode="ambient", ) - except Exception: - pass + except Exception as e: + print(f"[i1display_native] Ambient measurement failed: {e}") return None @@ -455,8 +458,8 @@ def detect_refresh_rate(self) -> float | None: # Parse refresh rate (Hz) rate = struct.unpack(" I1DisplayNative | None: diff --git a/calibrate_pro/hardware/measurement.py b/calibrate_pro/hardware/measurement.py index 5cba321..0741877 100644 --- a/calibrate_pro/hardware/measurement.py +++ b/calibrate_pro/hardware/measurement.py @@ -157,7 +157,8 @@ def _create_display_window(self): self._tk_canvas.pack(fill=tk.BOTH, expand=True) self._tk_root.update() - except Exception: + except Exception as e: + print(f"[measurement] Display window creation failed: {e}") self._tk_root = None self._tk_canvas = None @@ -170,8 +171,8 @@ def _get_display_geometry(self) -> tuple[int, int, int, int] | None: if self.config.display_index < len(displays): d = displays[self.config.display_index] return (d.position_x, d.position_y, d.width, d.height) - except Exception: - pass + except Exception as e: + print(f"[measurement] Display geometry detection failed: {e}") return None def _measure_argyll(self) -> tuple[float, float, float]: @@ -222,16 +223,16 @@ def close(self): if self._tk_root is not None: try: self._tk_root.destroy() - except Exception: - pass + except Exception as e: + print(f"[measurement] Error destroying display window: {e}") self._tk_root = None self._tk_canvas = None if self._argyll_backend is not None: try: self._argyll_backend.disconnect() - except Exception: - pass + except Exception as e: + print(f"[measurement] Error disconnecting argyll backend: {e}") def __enter__(self): self.initialize() diff --git a/calibrate_pro/hardware/spyder.py b/calibrate_pro/hardware/spyder.py index 6760ab1..ffc413c 100644 --- a/calibrate_pro/hardware/spyder.py +++ b/calibrate_pro/hardware/spyder.py @@ -240,8 +240,8 @@ def measure_ambient(self) -> ColorMeasurement | None: if result.returncode == 0: return self._parse_ambient_output(result.stdout) - except Exception: - pass + except Exception as e: + print(f"[spyder] Ambient measurement failed: {e}") return None diff --git a/calibrate_pro/hardware/spyder_native.py b/calibrate_pro/hardware/spyder_native.py index f4eb21f..6bb8b35 100644 --- a/calibrate_pro/hardware/spyder_native.py +++ b/calibrate_pro/hardware/spyder_native.py @@ -173,8 +173,8 @@ def disconnect(self) -> bool: # Turn off LED try: self._set_led(0) - except Exception: - pass + except Exception as e: + print(f"[spyder_native] LED off failed during disconnect: {e}") self._transport.close() self._transport = None self.is_connected = False @@ -201,8 +201,8 @@ def _init_device(self): try: self._send_command(SpyderCommand.RESET) time.sleep(0.2) - except Exception: - pass + except Exception as e: + print(f"[spyder_native] Device reset failed: {e}") # Read device info self._read_device_info() @@ -240,7 +240,8 @@ def _read_device_info(self): capabilities=["spot", "ambient", "emission"], ) - except Exception: + except Exception as e: + print(f"[spyder_native] Device info read failed, using USB fallback: {e}") if self._usb_info: self.device_info = DeviceInfo( name=self._usb_info.product, @@ -283,7 +284,8 @@ def _read_calibration_data(self): else: self._use_default_calibration() - except Exception: + except Exception as e: + print(f"[spyder_native] Calibration data read failed, using defaults: {e}") self._use_default_calibration() def _use_default_calibration(self): @@ -301,8 +303,8 @@ def _set_led(self, state: int): """Set LED state (0=off, 1=green, 2=red, 3=blue).""" try: self._send_command(SpyderCommand.SET_LED, bytes([state])) - except Exception: - pass + except Exception as e: + print(f"[spyder_native] LED control failed: {e}") def calibrate_device(self) -> bool: """Perform dark calibration.""" @@ -346,8 +348,8 @@ def set_integration_time(self, seconds: float) -> bool: try: self._send_command(SpyderCommand.SET_INTEGRATION, data) return True - except Exception: - pass + except Exception as e: + print(f"[spyder_native] Set integration time failed: {e}") return False def measure_spot(self) -> ColorMeasurement | None: @@ -438,8 +440,8 @@ def measure_ambient(self) -> ColorMeasurement | None: if len(resp) >= 8: lux = struct.unpack("