Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ dynamic = ["version"]
dependencies = [
"qtpy",
"pydantic>=2",
"copick>=1.5.0",
"zarr>=2.10.0",
"copick[all]>=1.5.0",
"zarr<3",
"numpy>=1.21.0",
]
authors = [
Expand Down
3 changes: 0 additions & 3 deletions src/copick_shared_ui/core/image_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,14 @@ def _setup_qt(self) -> None:

self._QPixmap = QPixmap
self._qt_available = True
print("🎨 Using qtpy for image interface")
except ImportError:
try:
# Fall back to Qt (ChimeraX)
from Qt.QtGui import QPixmap

self._QPixmap = QPixmap
self._qt_available = True
print("🎨 Using Qt for image interface")
except ImportError:
print("❌ Neither qtpy nor Qt available for image interface")
self._qt_available = False

def save_image(self, image: Any, path: str, format: str = "PNG") -> bool:
Expand Down
20 changes: 0 additions & 20 deletions src/copick_shared_ui/core/thumbnail_cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,6 @@ def _setup_cache_directory(self) -> None:

# Create the cache directory if it doesn't exist
self.cache_dir.mkdir(parents=True, exist_ok=True)
print(f"📁 Thumbnail cache directory: {self.cache_dir}")

# Create metadata file if it doesn't exist
self._ensure_metadata_file()
Expand Down Expand Up @@ -186,7 +185,6 @@ def has_thumbnail(self, cache_key: str) -> bool:
"""
thumbnail_path = self.get_thumbnail_path(cache_key)
exists = thumbnail_path.exists()
# Uncomment for debugging: print(f"CACHE DEBUG: Checking cache for key '{cache_key}' at {thumbnail_path} -> {'EXISTS' if exists else 'NOT FOUND'}")
return exists

def save_thumbnail(self, cache_key: str, image: Any) -> bool:
Expand All @@ -206,10 +204,6 @@ def save_thumbnail(self, cache_key: str, image: Any) -> bool:
try:
thumbnail_path = self.get_thumbnail_path(cache_key)
success = self._image_interface.save_image(image, str(thumbnail_path), "PNG")
if success:
print(f"💾 Saved thumbnail to: {thumbnail_path}")
else:
print(f"❌ Failed to save thumbnail to: {thumbnail_path}")
return success
except Exception as e:
print(f"Error saving thumbnail to cache: {e}")
Expand All @@ -231,11 +225,8 @@ def load_thumbnail(self, cache_key: str) -> Optional[Any]:
try:
thumbnail_path = self.get_thumbnail_path(cache_key)
if thumbnail_path.exists():
print(f"📦 Loading cached thumbnail from: {thumbnail_path}")
image = self._image_interface.load_image(str(thumbnail_path))
return image if image and self._image_interface.is_valid_image(image) else None
else:
print(f"🔍 No cached thumbnail found at: {thumbnail_path}")
return None
except Exception as e:
print(f"Error loading thumbnail from cache: {e}")
Expand Down Expand Up @@ -266,18 +257,10 @@ def _cleanup_old_cache_entries(self, max_age_days: int = 14) -> None:
if age_seconds > max_age_seconds:
thumbnail_file.unlink()
removed_count += 1
print(
f"🗑️ Removed old cache entry: {thumbnail_file.name} (age: {age_seconds / (24 * 60 * 60):.1f} days)",
)

except Exception as e:
print(f"Warning: Could not process cache file {thumbnail_file}: {e}")

if removed_count > 0:
print(f"🧹 Cache cleanup: Removed {removed_count} old entries (older than {max_age_days} days)")
else:
print(f"✅ Cache cleanup: No entries older than {max_age_days} days found")

except Exception as e:
print(f"Warning: Cache cleanup failed: {e}")

Expand All @@ -290,11 +273,8 @@ def clear_cache(self) -> bool:
try:
if self.cache_dir and self.cache_dir.exists():
# Remove all PNG files (thumbnails) but keep metadata
removed_count = 0
for thumbnail_file in self.cache_dir.glob("*.png"):
thumbnail_file.unlink()
removed_count += 1
print(f"🧹 Manually cleared {removed_count} thumbnails from cache")
return True
return False
except Exception as e:
Expand Down
3 changes: 0 additions & 3 deletions src/copick_shared_ui/platform/napari_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,6 @@ def _detect_napari_theme(self) -> str:
"""Detect theme from napari viewer."""
try:
napari_theme = str(self.viewer.theme)
print(f"🎨 Detected napari theme: '{napari_theme}'")

# Map napari themes to our theme system
if napari_theme.lower() == "light":
Expand All @@ -85,7 +84,6 @@ def _detect_napari_theme(self) -> str:
return "dark"
else:
# For custom themes or unknown themes, default to dark
print(f"🎨 Unknown napari theme '{napari_theme}', defaulting to dark")
return "dark"

except Exception as e:
Expand Down Expand Up @@ -121,7 +119,6 @@ def connect_theme_changed(self, callback: Callable[[], None]) -> None:
try:
# Connect to napari's theme change events
self.viewer.events.theme.connect(lambda event: callback())
print("🔗 Connected to napari theme change events")
except Exception as e:
print(f"⚠️ Could not connect to napari theme events: {e}")
# Fallback: periodically check theme changes
Expand Down
6 changes: 0 additions & 6 deletions src/copick_shared_ui/widgets/gallery/gallery_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,8 +124,6 @@ def delete(self) -> None:

def set_copick_root(self, copick_root: Optional[Any]) -> None:
"""Set the copick root and load runs."""
print(f"🔄 Gallery: Setting copick root: {copick_root}")

# Clear workers to cancel any pending thumbnail loads from previous session
self.worker_interface.clear_workers()

Expand All @@ -139,7 +137,6 @@ def set_copick_root(self, copick_root: Optional[Any]) -> None:
if copick_root:
self.runs = list(copick_root.runs)
self.filtered_runs = self.runs.copy()
print(f"📂 Gallery: Found {len(self.runs)} runs: {[run.name for run in self.runs]}")
self._update_grid()
else:
self.runs = []
Expand Down Expand Up @@ -244,7 +241,6 @@ def _load_run_thumbnail(self, run: "CopickRun", thumbnail_id: str, force_regener
if self._is_destroyed:
return

print(f"🎨 Gallery: Starting thumbnail load for run '{thumbnail_id}' (force={force_regenerate})")
self.worker_interface.start_thumbnail_worker(run, thumbnail_id, self._on_thumbnail_loaded, force_regenerate)

def _on_thumbnail_loaded(self, thumbnail_id: str, pixmap: Optional[Any], error: Optional[str]) -> None:
Expand All @@ -258,10 +254,8 @@ def _on_thumbnail_loaded(self, thumbnail_id: str, pixmap: Optional[Any], error:
card = self.all_run_cards[thumbnail_id]

if error:
print(f"❌ Gallery: Thumbnail error for '{thumbnail_id}': {error}")
card.set_error(error)
else:
print(f"✅ Gallery: Thumbnail loaded for '{thumbnail_id}': {pixmap is not None}")
card.set_thumbnail(pixmap)
# Cache the thumbnail for future use
if pixmap:
Expand Down
10 changes: 0 additions & 10 deletions src/copick_shared_ui/widgets/info/info_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,6 @@ def set_run(self, run: Optional["CopickRun"]) -> None:
if self._is_destroyed:
return

print(f"CopickInfoWidget.set_run called with run: {run.name if run else 'None'}")
self.current_run = run
if run:
self.current_run_name = run.name
Expand All @@ -235,7 +234,6 @@ def set_run(self, run: Optional["CopickRun"]) -> None:
self._loading_states.clear()

self._update_display()
print(f"CopickInfoWidget.set_run completed for run: {run.name if run else 'None'}")

def _start_async_loading(self) -> None:
"""Start asynchronous loading of all run data."""
Expand All @@ -255,8 +253,6 @@ def _start_data_loading_worker(self, data_type: str) -> None:
if not self.current_run or self._is_destroyed:
return

print(f"🚀 InfoWidget: Starting threaded data loading for '{data_type}'")

# Use the worker interface to start threaded data loading
self.worker_interface.start_data_worker(
run=self.current_run,
Expand All @@ -266,10 +262,6 @@ def _start_data_loading_worker(self, data_type: str) -> None:

def _handle_data_loaded(self, data_type: str, data: Optional[List[Any]], error: Optional[str]) -> None:
"""Handle data loading completion."""
print(
f"📥 InfoWidget: _handle_data_loaded called for '{data_type}' - error: {error}, data count: {len(data) if data else 0}",
)

if self._is_destroyed:
print(f"⚠️ InfoWidget: Widget destroyed, ignoring data for '{data_type}'")
return
Expand All @@ -278,12 +270,10 @@ def _handle_data_loaded(self, data_type: str, data: Optional[List[Any]], error:
print(f"❌ InfoWidget: Error loading '{data_type}': {error}")
self._loading_states[data_type] = f"error: {error}"
else:
print(f"✅ InfoWidget: Successfully loaded {len(data) if data else 0} '{data_type}' items")
self._loading_states[data_type] = "loaded"
self._loaded_data[data_type] = data or []

# Update display
print(f"🔄 InfoWidget: Calling _update_display for '{data_type}'")
self._update_display()

@Slot()
Expand Down
29 changes: 1 addition & 28 deletions src/copick_shared_ui/workers/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,8 @@ def _select_best_tomogram(self, run: "CopickRun") -> Optional["CopickTomogram"]:
if cache_key in self._best_tomogram_cache:
cached_result = self._best_tomogram_cache[cache_key]
if cached_result is not None:
print(f"🎯 Using cached best tomogram selection for run '{run.name}'")
return cached_result

print(f"🔍 Selecting best tomogram for run '{run.name}'")
try:
all_tomograms = []

Expand All @@ -107,21 +105,18 @@ def _select_best_tomogram(self, run: "CopickRun") -> Optional["CopickTomogram"]:
for preferred_type in preferred_types:
for tomo in vs_tomograms:
if preferred_type.lower() in tomo.tomo_type.lower():
print(f"✅ Selected best tomogram: {tomo.tomo_type} at {vs_size}Å")
self._best_tomogram_cache[cache_key] = tomo
return tomo

# If no preferred type found, return the first tomogram at this voxel spacing
if vs_tomograms:
selected = vs_tomograms[0]
print(f"✅ Selected fallback tomogram: {selected.tomo_type} at {vs_size}Å")
self._best_tomogram_cache[cache_key] = selected
return selected

# Final fallback: return any tomogram
if all_tomograms:
selected = all_tomograms[0]
print(f"✅ Selected final fallback tomogram: {selected.tomo_type}")
self._best_tomogram_cache[cache_key] = selected
return selected

Expand All @@ -139,12 +134,8 @@ def generate_thumbnail_pixmap(self) -> tuple[Optional[Any], Optional[str]]:
if not self.force_regenerate and self._cache and self._cache_key and self._cache.has_thumbnail(self._cache_key):
cached_pixmap = self._cache.load_thumbnail(self._cache_key)
if cached_pixmap is not None:
print(f"📦 Using cached thumbnail for '{self.thumbnail_id}'")
return cached_pixmap, None

# Generate new thumbnail
print(f"🎨 Generating new thumbnail for '{self.thumbnail_id}'")

# Determine the tomogram to use
if self._is_tomogram():
tomogram = self.item
Expand Down Expand Up @@ -184,11 +175,7 @@ def generate_thumbnail_pixmap(self) -> tuple[Optional[Any], Optional[str]]:
# Cache the result
if self._cache and self._cache_key:
try:
success = self._cache.save_thumbnail(self._cache_key, pixmap)
if success:
print(f"💾 Cached thumbnail for '{self.thumbnail_id}'")
else:
print(f"⚠️ Failed to cache thumbnail for '{self.thumbnail_id}'")
_ = self._cache.save_thumbnail(self._cache_key, pixmap)
except Exception as e:
print(f"⚠️ Error caching thumbnail: {e}")

Expand All @@ -205,8 +192,6 @@ def _generate_thumbnail_array(self, tomogram: "CopickTomogram") -> Optional[Any]
import numpy as np
import zarr

print(f"🔧 Loading zarr data for tomogram: {tomogram.tomo_type}")

# Load tomogram data - handle multi-scale zarr properly
zarr_group = zarr.open(tomogram.zarr(), mode="r")

Expand All @@ -218,52 +203,40 @@ def _generate_thumbnail_array(self, tomogram: "CopickTomogram") -> Optional[Any]
# Use the highest scale level (most binned/smallest) for thumbnails
highest_scale = scale_levels[-1] # Last element is highest number = most binned
tomo_data = zarr_group[highest_scale]
print(f"🔧 Using highest binning scale level {highest_scale} from multi-scale zarr for thumbnail")
else:
# Fallback to first key
first_key = list(zarr_group.keys())[0]
tomo_data = zarr_group[first_key]
print(f"🔧 Using first key '{first_key}' from zarr group")
else:
# Direct zarr array
tomo_data = zarr_group
print("🔧 Using direct zarr array")

print(f"📐 Tomogram shape: {tomo_data.shape}")

# Calculate downsampling factor based on data size
target_size = 200
z_size, y_size, x_size = tomo_data.shape

# Use middle slice for 2D thumbnail
mid_z = z_size // 2
print(f"📍 Using middle slice z={mid_z} of {z_size}")

# Calculate downsampling for x and y dimensions
downsample_x = max(1, x_size // target_size)
downsample_y = max(1, y_size // target_size)
print(f"📉 Downsampling: x={downsample_x}, y={downsample_y}")

# Extract and downsample middle slice
print("✂️ Extracting slice...")
slice_data = tomo_data[mid_z, ::downsample_y, ::downsample_x]
print(f"📊 Slice shape: {slice_data.shape}")

# Convert to numpy array
slice_array = np.array(slice_data)
print(f"🔢 Array shape: {slice_array.shape}, dtype: {slice_array.dtype}")

# Normalize to 0-255 range
slice_array = slice_array.astype(np.float32)
data_min, data_max = slice_array.min(), slice_array.max()
print(f"📈 Data range: {data_min} to {data_max}")

if data_max > data_min:
slice_array = ((slice_array - data_min) / (data_max - data_min) * 255).astype(np.uint8)
else:
slice_array = np.zeros_like(slice_array, dtype=np.uint8)

print(f"✅ Thumbnail array generated: shape={slice_array.shape}, dtype={slice_array.dtype}")
return slice_array

except Exception as e:
Expand Down
14 changes: 0 additions & 14 deletions src/copick_shared_ui/workers/base_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ def __init__(self, max_concurrent_workers: int = 8):
self._max_concurrent = max_concurrent_workers
self._active_workers = []
self._pending_queue = []
print(f"🎛️ {self.__class__.__name__}: Initialized with max {max_concurrent_workers} concurrent workers")

def start_thumbnail_worker(
self,
Expand Down Expand Up @@ -60,9 +59,6 @@ def _queue_or_start_worker(self, worker_info: Dict[str, Any]) -> None:
self._start_worker_from_info(worker_info)
else:
self._pending_queue.append(worker_info)
print(
f"⏳ {self.__class__.__name__}: Queued {worker_info['type']} worker (queue size: {len(self._pending_queue)})",
)

@abstractmethod
def _create_thumbnail_worker(
Expand Down Expand Up @@ -106,14 +102,10 @@ def _start_worker_from_info(self, worker_info: Dict[str, Any]) -> None:
self._create_callback_wrapper(worker_info["callback"]),
)
else:
print(f"❌ {self.__class__.__name__}: Unknown worker type: {worker_info['type']}")
return

self._active_workers.append(worker)
self._start_worker(worker)
print(
f"🚀 {self.__class__.__name__}: Started {worker_info['type']} worker ({len(self._active_workers)}/{self._max_concurrent} active)",
)

def _create_callback_wrapper(self, original_callback: Callable) -> Callable:
"""Create a callback wrapper that handles worker completion and queue processing."""
Expand All @@ -135,9 +127,6 @@ def _on_worker_completed(self) -> None:
# Start next queued worker if any
if self._pending_queue and len(self._active_workers) < self._max_concurrent:
next_worker_info = self._pending_queue.pop(0)
print(
f"🔄 {self.__class__.__name__}: Starting next queued {next_worker_info['type']} worker (queue size: {len(self._pending_queue)})",
)
self._start_worker_from_info(next_worker_info)

@abstractmethod
Expand All @@ -159,7 +148,6 @@ def clear_workers(self) -> None:

# Clear pending queue
self._pending_queue.clear()
print(f"🧹 {self.__class__.__name__}: Cleared all active and pending workers")

def shutdown_workers(self, timeout_ms: int = 3000) -> None:
"""Shutdown all workers with timeout."""
Expand All @@ -178,7 +166,6 @@ def set_max_concurrent_workers(self, max_workers: int) -> None:
"""Update the maximum number of concurrent workers."""
old_max = self._max_concurrent
self._max_concurrent = max_workers
print(f"🎛️ {self.__class__.__name__}: Updated max concurrent workers from {old_max} to {max_workers}")

# If we increased the limit, try to start queued workers
if max_workers > old_max:
Expand All @@ -188,5 +175,4 @@ def _process_pending_queue(self) -> None:
"""Process pending queue to start workers if slots are available."""
while self._pending_queue and len(self._active_workers) < self._max_concurrent:
next_worker_info = self._pending_queue.pop(0)
print(f"🔄 {self.__class__.__name__}: Starting queued {next_worker_info['type']} worker")
self._start_worker_from_info(next_worker_info)
Loading