Skip to content

updated last version : serWarning: can't convert cuda:0 device type tensor to numpy. Use Tensor.cpu() to copy the tensor to host memory first. #208

Description

@anthomontan

Hi,

I updated to the last Valis repo yesterday, was code was working well before BUT now it doesn't :/

Here is my code and the error : (Also for whatever reason at the error measurement step the dimensions are swapped ! so I added some code to verify and swap it back (no idea why this occurs , BUT if you can have a look that will be great !)

Could you please have a look and fix it , I really need Valis to align images :D

THanks
antho

Code :

VALIS last update :

RUNNING 1:44 8/18/2025

import os
import pathlib
import numpy as np
from pathlib import Path
from tqdm import tqdm
import pyvips
import cv2
from skimage import exposure
from valis import valtils, slide_io, registration
from valis import feature_detectors, feature_matcher # Import the new feature modules
from valis.micro_rigid_registrar import MicroRigidRegistrar
import shutil
import re

Add missing imports

try:
from tqdm.notebook import tqdm
except ImportError:
from tqdm import tqdm
print("tqdm.notebook not found. Using tqdm instead.")

def organize_channels_automatically(base_dir, target_dapi_dir=None, target_all_channels_dir=None):
"""
Automatically organize channel files from a base directory

Args:
    base_dir: Path to the folder containing all channel files (e.g., ".../lb06/R2/")
    target_dapi_dir: Where to move ch00 files (if None, creates dapi subfolder)
    target_all_channels_dir: Where to move ch01/ch02 files (if None, creates all_channels subfolder)
"""
base_path = Path(base_dir)

# Create target directories if not specified
if target_dapi_dir is None:
    target_dapi_dir = base_path / "dapi"
else:
    target_dapi_dir = Path(target_dapi_dir)
    
if target_all_channels_dir is None:
    target_all_channels_dir = base_path / "all_channels"
else:
    target_all_channels_dir = Path(target_all_channels_dir)

# Create directories if they don't exist
target_dapi_dir.mkdir(parents=True, exist_ok=True)
target_all_channels_dir.mkdir(parents=True, exist_ok=True)

print(f"Organizing files from: {base_path}")
print(f"DAPI (ch00) files will go to: {target_dapi_dir}")
print(f"Other channels will go to: {target_all_channels_dir}")
print("-" * 60)

# Get all files in the base directory
all_files = [f for f in base_path.iterdir() if f.is_file()]
moved_files = {"dapi": [], "other_channels": [], "unrecognized": []}

for file_path in all_files:
    filename = file_path.name.lower()
    
    # Check for channel patterns
    if "_ch00" in filename or "_merged_ch00" in filename:
        # This is a DAPI file
        target_path = target_dapi_dir / file_path.name
        shutil.move(str(file_path), str(target_path))
        moved_files["dapi"].append(file_path.name)
        print(f"📁 DAPI: {file_path.name}")
        
    elif "_ch01" in filename or "_ch02" in filename or "_ch03" in filename or "_merged_ch01" in filename or "_merged_ch02" in filename or "_merged_ch03" in filename:
        # This is another channel
        target_path = target_all_channels_dir / file_path.name
        shutil.move(str(file_path), str(target_path))
        moved_files["other_channels"].append(file_path.name)
        print(f"🔬 Channel: {file_path.name}")
        
    else:
        # Unrecognized file pattern
        moved_files["unrecognized"].append(file_path.name)
        print(f"❓ Unrecognized: {file_path.name}")

# Summary
print("\n" + "="*60)
print("ORGANIZATION COMPLETE!")
print("="*60)
print(f"✅ DAPI files moved: {len(moved_files['dapi'])}")
print(f"✅ Other channel files moved: {len(moved_files['other_channels'])}")
print(f"⚠️  Unrecognized files: {len(moved_files['unrecognized'])}")

if moved_files["unrecognized"]:
    print("\nUnrecognized files (not moved):")
    for filename in moved_files["unrecognized"]:
        print(f"  - {filename}")

return moved_files

def get_round_name(src_f):
"""Extract the round name from the filename"""
img_name = valtils.get_name(src_f)
round_name = img_name.lower().split("_merged")[0]
return round_name

def get_channel_number(src_f):
"""Extract the channel number from the filename"""
img_name = os.path.basename(src_f).lower()
if "_ch00" in img_name:
return 0
elif "_ch01" in img_name:
return 1
elif "_ch02" in img_name:
return 2
else:
return None

def pad_image(img, target_width, target_height):
"""Pad image to target dimensions, centering the original content"""
pad_width = max(0, target_width - img.width)
pad_height = max(0, target_height - img.height)

if pad_width > 0 or pad_height > 0:
    pad_left = pad_width // 2
    pad_top = pad_height // 2
    padded = img.embed(pad_left, pad_top, target_width, target_height, extend="black")
    return padded
return img

def apply_clahe_normalization(img_array, clip_limit=2.0, tile_grid_size=(16,16)):
"""Apply CLAHE normalization to improve contrast for registration"""
if img_array.dtype != np.uint8:
# Convert to uint8 for CLAHE
img_normalized = exposure.rescale_intensity(img_array, out_range=(0, 255)).astype(np.uint8)
else:
img_normalized = img_array.copy()

# Apply CLAHE
clahe = cv2.createCLAHE(clipLimit=clip_limit, tileGridSize=tile_grid_size)
clahe_img = clahe.apply(img_normalized)

return clahe_img

def create_clahe_padded_image(img_path, target_width, target_height, output_path, clip_limit=2.0, tile_grid_size=(16, 16)):
"""Pad image first, then apply CLAHE to the padded result"""
try:
# Load original image
img = pyvips.Image.new_from_file(img_path)

    # Print image format for debugging
    print(f"Image format for {os.path.basename(img_path)}: {img.format}, bands: {img.bands}")

    # Step 1: Pad the image
    padded_img = pad_image(img, target_width, target_height)
    
    # Step 2: Convert padded image to numpy for CLAHE processing
    img_array = np.ndarray(buffer=padded_img.write_to_memory(),
                          dtype=np.uint8 if padded_img.format == 'uchar' else np.uint16,
                          shape=[padded_img.height, padded_img.width, padded_img.bands])
    
    # Step 3: Apply CLAHE to first channel (DAPI)
    if padded_img.bands == 1:
        clahe_array = apply_clahe_normalization(img_array[:, :, 0], clip_limit, tile_grid_size)
        clahe_array = clahe_array[:, :, np.newaxis]
    else:
        # For multi-channel, apply CLAHE to first channel only
        clahe_array = img_array.copy()
        clahe_array[:, :, 0] = apply_clahe_normalization(img_array[:, :, 0], clip_limit, tile_grid_size)
    
    # Step 4: Convert back to pyvips - always use 'uchar' for CLAHE output
    # CLAHE always outputs uint8 data
    clahe_padded_img = pyvips.Image.new_from_memory(clahe_array.data,
                                                   clahe_array.shape[1],
                                                   clahe_array.shape[0],
                                                   clahe_array.shape[2],
                                                   'uchar')
    
    # Step 5: Save CLAHE-processed padded image
    clahe_padded_img.write_to_file(output_path)
    return output_path
    
except Exception as e:
    print(f"Error creating CLAHE-padded image for {img_path}: {str(e)}")
    return None

def resize_warped_images(warped_dir, padded_reference_path):
"""
Resize warped images to match padded reference dimensions.

Args:
    warped_dir: Directory containing warped images
    padded_reference_path: Path to padded reference image to get correct dimensions
"""
print(f"Resizing warped images in {warped_dir}...")

# Load padded reference image to get correct dimensions
ref_img = pyvips.Image.new_from_file(padded_reference_path)
ref_width = ref_img.width
ref_height = ref_img.height
print(f"Padded reference dimensions: {ref_width} x {ref_height}")

# Create backup directory
backup_dir = os.path.join(os.path.dirname(warped_dir), "resize_backup")
os.makedirs(backup_dir, exist_ok=True)

# Process all warped images
resized_count = 0
for root, dirs, files in os.walk(warped_dir):
    for file in files:
        if file.startswith("warped_") and file.endswith(".tif"):
            img_path = os.path.join(root, file)
            
            try:
                # Load warped image
                warped_img = pyvips.Image.new_from_file(img_path)
                
                # Check if dimensions match reference
                if warped_img.width != ref_width or warped_img.height != ref_height:
                    print(f"Resizing image: {file}")
                    print(f"  Current dimensions: {warped_img.width} x {warped_img.height}")
                    
                    # Create backup
                    backup_path = os.path.join(backup_dir, file)
                    shutil.copy2(img_path, backup_path)
                    
                    # Resize to match reference dimensions
                    # Use bilinear interpolation for better quality
                    resized_img = warped_img.resize(ref_width / warped_img.width, 
                                                   vscale=ref_height / warped_img.height,
                                                   kernel="bilinear")
                    
                    # Save with proper format preservation
                    if warped_img.format == 'ushort':
                        resized_img.write_to_file(img_path, compression="none", tile=False)
                    else:
                        resized_img.write_to_file(img_path)
                        
                    print(f"  Resized dimensions: {resized_img.width} x {resized_img.height}")
                    resized_count += 1
                else:
                    print(f"Image dimensions already match reference: {file}")
                    
            except Exception as e:
                print(f"Error processing {file}: {str(e)}")

print(f"\nResized {resized_count} images")
return resized_count

def fix_warped_images(individual_channel_dir, reference_slide_path):
"""
Fix warped images that appear squished by ensuring they maintain the correct aspect ratio
relative to the reference image.

Args:
    individual_channel_dir: Directory containing warped images
    reference_slide_path: Path to reference image to get correct dimensions
"""
print("\nFixing warped images to maintain correct aspect ratio...")

# Load reference image to get correct dimensions
ref_img = pyvips.Image.new_from_file(reference_slide_path)
ref_width = ref_img.width
ref_height = ref_img.height
ref_format = ref_img.format
ref_bands = ref_img.bands

print(f"Reference image: {ref_width}x{ref_height}, format={ref_format}, bands={ref_bands}")

# Create backup directory
backup_dir = os.path.join(os.path.dirname(individual_channel_dir), "warped_backup")
os.makedirs(backup_dir, exist_ok=True)

# Process all warped images in the directory and subdirectories
for root, dirs, files in os.walk(individual_channel_dir):
    for file in files:
        if file.startswith("warped_") and file.endswith(".tif"):
            img_path = os.path.join(root, file)
            
            try:
                # Load warped image
                warped_img = pyvips.Image.new_from_file(img_path)
                
                # Print image details for debugging
                print(f"Processing {file}: {warped_img.width}x{warped_img.height}, format={warped_img.format}, bands={warped_img.bands}")
                
                # Check if dimensions match reference
                if warped_img.width != ref_width or warped_img.height != ref_height:
                    print(f"Fixing dimensions for {file}")
                    
                    # Create backup
                    backup_path = os.path.join(backup_dir, file)
                    shutil.copy2(img_path, backup_path)
                    
                    # For 16-bit images, ensure we preserve the format
                    # Use affine transformation instead of resize to maintain aspect ratio
                    
                    # Calculate the scale factors
                    scale_x = ref_width / warped_img.width
                    scale_y = ref_height / warped_img.height
                    
                    # Use the minimum scale to avoid stretching
                    scale = min(scale_x, scale_y)
                    
                    # Calculate dimensions after scaling
                    new_width = int(warped_img.width * scale)
                    new_height = int(warped_img.height * scale)
                    
                    # Calculate padding to center the image
                    left_pad = (ref_width - new_width) // 2
                    top_pad = (ref_height - new_height) // 2
                    
                    # Create transformation matrix for affine transform
                    # [scale, 0, left_pad, 0, scale, top_pad]
                    transform = [scale, 0, left_pad, 0, scale, top_pad]
                    
                    # Apply affine transformation with proper interpolation
                    interp = pyvips.Interpolate.new('bicubic')
                    fixed_img = warped_img.affine(transform, interpolate=interp)
                    
                    # If the image is smaller than reference after transform, pad it
                    if fixed_img.width < ref_width or fixed_img.height < ref_height:
                        fixed_img = fixed_img.embed(
                            0, 0, 
                            ref_width, ref_height, 
                            extend="black"
                        )
                    
                    # Ensure we crop to exact dimensions if somehow larger
                    if fixed_img.width > ref_width or fixed_img.height > ref_height:
                        fixed_img = fixed_img.crop(0, 0, ref_width, ref_height)
                    
                    # Save with proper format preservation
                    # For 16-bit TIFFs, we need to ensure we save with the right options
                    if warped_img.format == 'ushort':
                        fixed_img.write_to_file(img_path, compression="none", tile=False)
                    else:
                        fixed_img.write_to_file(img_path)
                    
                    print(f"Fixed image saved: {img_path}")
                
            except Exception as e:
                print(f"Error fixing {file}: {str(e)}")

print("✅ Fixed warped images to match reference dimensions")
return backup_dir

def main():
# Set up base directory
base_dir = "####your directory /##"

# Organize files automatically
print("Organizing channel files...")
organize_channels_automatically(base_dir)

# Set up directories using the organized structure
slide_src_dir = Path(base_dir) / "dapi"  # Now points to organized DAPI folder
path_to_all_channels = Path(base_dir) / "all_channels"  # Now points to organized channels folder

# Results directory
results_dst_dir = Path(base_dir) / "registration"
results_dst_dir.mkdir(parents=True, exist_ok=True)
registered_slide_dst_dir = results_dst_dir / "registered_slides"
registered_slide_dst_dir.mkdir(parents=True, exist_ok=True)

# Reference slide - now from the organized DAPI folder
reference_slide_name = "###.tif"  # Update this to your actual reference
reference_slide = str(slide_src_dir / reference_slide_name)

print(f"✅ DAPI images directory: {slide_src_dir}")
print(f"✅ All channels directory: {path_to_all_channels}")
print(f"✅ Results directory: {results_dst_dir}")
print(f"✅ Reference slide: {reference_slide}")

# Process images: PAD FIRST, then CLAHE on DAPI only
print("Starting image processing: PADDING FIRST, then CLAHE on DAPI only...")

# Create output directories
src_dir = str(slide_src_dir)
dst_dir = str(results_dst_dir)
all_channels_dir = str(path_to_all_channels)

# Directory for padded images (original intensities)
padded_dir = os.path.join(dst_dir, "padded_images")
os.makedirs(padded_dir, exist_ok=True)
padded_dapi_dir = os.path.join(padded_dir, "dapi")
os.makedirs(padded_dapi_dir, exist_ok=True)
padded_all_channels_dir = os.path.join(padded_dir, "all_channels")
os.makedirs(padded_all_channels_dir, exist_ok=True)
padded_ref_dir = os.path.join(padded_dir, "reference")
os.makedirs(padded_ref_dir, exist_ok=True)

# Directory for CLAHE-padded DAPI images (for registration only)
clahe_padded_dir = os.path.join(dst_dir, "clahe_padded_dapi")
os.makedirs(clahe_padded_dir, exist_ok=True)

# Directory for individual warped channels
individual_channel_dir = os.path.join(dst_dir, "warped_channels")
os.makedirs(individual_channel_dir, exist_ok=True)

# Get all images
dapi_imgs = [os.path.join(src_dir, f) for f in os.listdir(src_dir)]
all_channel_imgs = [os.path.join(all_channels_dir, f) for f in os.listdir(all_channels_dir)]

print(f"Found {len(dapi_imgs)} DAPI images for registration")
print(f"Found {len(all_channel_imgs)} other channel images")

# STEP 1: Find maximum dimensions across all images
max_width = 0
max_height = 0

print("Finding maximum dimensions across all images...")
for img_path in dapi_imgs + all_channel_imgs:
    try:
        img = pyvips.Image.new_from_file(img_path)
        max_width = max(max_width, img.width)
        max_height = max(max_height, img.height)
        # Print format for the first few images to debug
        if max_width == img.width or max_height == img.height:
            print(f"New max from {os.path.basename(img_path)}: {img.width}x{img.height}, format={img.format}")
    except Exception as e:
        print(f"Error reading {img_path}: {str(e)}")

print(f"Maximum dimensions: {max_width} x {max_height}")

# STEP 2: Create padded images (original intensities) AND CLAHE-padded DAPI images
print("Creating padded images and CLAHE-padded DAPI images...")

# Process reference image
ref_img = pyvips.Image.new_from_file(reference_slide)
print(f"Reference image format: {ref_img.format}, bands: {ref_img.bands}")
padded_ref_img = pad_image(ref_img, max_width, max_height)
ref_basename = os.path.basename(reference_slide)
padded_reference_slide = os.path.join(padded_ref_dir, f"padded_{ref_basename}")

# Save with proper format preservation for 16-bit images
if ref_img.format == 'ushort':
    padded_ref_img.write_to_file(padded_reference_slide, compression="none", tile=False)
else:
    padded_ref_img.write_to_file(padded_reference_slide)

print(f"Padded reference: {padded_reference_slide}")

# Create CLAHE-padded version of reference for registration
clahe_padded_reference = os.path.join(clahe_padded_dir, f"clahe_padded_{ref_basename}")
create_clahe_padded_image(reference_slide, max_width, max_height, clahe_padded_reference)
print(f"CLAHE-padded reference: {clahe_padded_reference}")

# Process DAPI images with existence check
padded_dapi_paths = []
clahe_padded_dapi_paths = []

for img_path in dapi_imgs:
    try:
        basename = os.path.basename(img_path)
        padded_path = os.path.join(padded_dapi_dir, f"padded_{basename}")
        
        # Check if padded image already exists
        if os.path.exists(padded_path):
            print(f"✅ Reusing existing padded DAPI: {basename}")
            padded_dapi_paths.append(padded_path)
            continue  # Skip processing if exists
        
        # Process new image
        img = pyvips.Image.new_from_file(img_path)
        padded_img = pad_image(img, max_width, max_height)
        if img.format == 'ushort':
            padded_img.write_to_file(padded_path, compression="none", tile=False)
        else:
            padded_img.write_to_file(padded_path)
        padded_dapi_paths.append(padded_path)
        
        # Create CLAHE-padded version (for registration)
        clahe_padded_path = os.path.join(clahe_padded_dir, f"clahe_padded_{basename}")
        if create_clahe_padded_image(img_path, max_width, max_height, clahe_padded_path):
            clahe_padded_dapi_paths.append(clahe_padded_path)
        
        print(f"Processed DAPI: {basename}")
        
    except Exception as e:
        print(f"Error processing {img_path}: {str(e)}")

# Process all channel images (only padded, no CLAHE) with existence check
padded_all_channel_paths = []
for img_path in all_channel_imgs:
    try:
        basename = os.path.basename(img_path)
        padded_path = os.path.join(padded_all_channels_dir, f"padded_{basename}")
        
        # Check if padded image already exists
        if os.path.exists(padded_path):
            print(f"✅ Reusing existing padded channel: {basename}")
            padded_all_channel_paths.append(padded_path)
            continue  # Skip processing if exists
        
        # Process new image
        img = pyvips.Image.new_from_file(img_path)
        padded_img = pad_image(img, max_width, max_height)
        if img.format == 'ushort':
            padded_img.write_to_file(padded_path, compression="none", tile=False)
        else:
            padded_img.write_to_file(padded_path)
        padded_all_channel_paths.append(padded_path)
        print(f"Padded channel: {basename}")
    except Exception as e:
        print(f"Error padding {img_path}: {str(e)}")

# STEP 3: Perform registration using CLAHE-PADDED DAPI images
print("Performing registration with CLAHE-padded DAPI images...")

# NEW: Configure feature detector and matcher for the latest Valis version
# Using DISK feature detector with grayscale images (more stable for DAPI)
fd = feature_detectors.DiskFD(rgb=False, num_features=2000, device='cpu')
matcher = feature_matcher.LightGlueMatcher(fd)

registrar = registration.Valis(
    clahe_padded_dir,  # Directory with CLAHE-padded DAPI images
    dst_dir,
    reference_img_f=clahe_padded_reference,
    align_to_reference=True,
    micro_rigid_registrar_cls=MicroRigidRegistrar,
    max_processed_image_dim_px=2000,
    max_non_rigid_registration_dim_px=1500,
    norm_method='img_stats',
    matcher=matcher  # Device is already configured in the matcher
)

rigid_registrar, non_rigid_registrar, error_df = registrar.register()

# Perform micro-registration
print("Performing micro-registration...")
micro_reg, micro_error = registrar.register_micro(
    max_non_rigid_registration_dim_px=2000,
    align_to_reference=True
)

# STEP 4: Apply registration transforms to original padded images
print("Applying registration transforms to original intensity padded images...")

# Create mapping between CLAHE-padded slides and original padded slides
clahe_to_original_mapping = {}
for slide_name, slide_obj in registrar.slide_dict.items():
    # Find corresponding original padded image
    clahe_basename = os.path.basename(slide_obj.src_f).replace("clahe_padded_", "padded_")
    original_path = os.path.join(padded_dapi_dir, clahe_basename)
    clahe_to_original_mapping[slide_obj] = original_path

# Warp original padded DAPI images
dapi_warped_dir = os.path.join(individual_channel_dir, "dapi_warped")
os.makedirs(dapi_warped_dir, exist_ok=True)

for slide_obj, original_dapi_path in clahe_to_original_mapping.items():
    try:
        # Load original padded DAPI (original intensities)
        original_dapi_img = pyvips.Image.new_from_file(original_dapi_path)
        orig_width, orig_height = original_dapi_img.width, original_dapi_img.height
        print(f"Warping {os.path.basename(original_dapi_path)}, format={original_dapi_img.format}, dimensions={orig_width}x{orig_height}")

        # Apply transformation found using CLAHE-padded DAPI
        warped_dapi = slide_obj.warp_img(img=original_dapi_img, non_rigid=True, crop=False)
        
        # CRITICAL CHECK: Verify dimensions have correct orientation
        current_width, current_height = warped_dapi.width, warped_dapi.height
        print(f"  Raw warped dimensions: {current_width}x{current_height}")
        
        # Check if width/height are transposed (width should be larger than height for landscape images)
        orig_orientation = "landscape" if orig_width > orig_height else "portrait"
        warped_orientation = "landscape" if current_width > current_height else "portrait"
        
        # Calculate aspect ratio match
        orig_aspect = orig_width / orig_height
        warped_aspect = current_width / current_height
        aspect_ratio_diff = abs(warped_aspect - orig_aspect)
        aspect_ratio_diff_transposed = abs(warped_aspect - (1/orig_aspect))
        
        # Determine if transposition is needed
        needs_transposition = False
        if orig_orientation == warped_orientation:
            # Same orientation, but check if aspect ratios match better when transposed
            if aspect_ratio_diff_transposed < aspect_ratio_diff * 0.5:
                needs_transposition = True
                print(f"  ⚠️ Aspect ratio mismatch suggests transposition needed")
        else:
            # Different orientation - likely needs transposition
            needs_transposition = True
            print(f"  ⚠️ Orientation mismatch: original={orig_orientation}, warped={warped_orientation}")
        
        # Apply transposition if needed
        if needs_transposition:
            print(f"  💡 Transposing image to fix orientation...")
            warped_dapi = warped_dapi.transpose(pyvips.enums.Orientation.ROT90)
            current_width, current_height = warped_dapi.width, warped_dapi.height
            print(f"  ✅ Fixed dimensions: {current_width}x{current_height}")
        
        basename = os.path.basename(original_dapi_path)
        warped_path = os.path.join(dapi_warped_dir, f"warped_{basename}")
        
        # Save with proper format preservation for 16-bit images
        if original_dapi_img.format == 'ushort':
            warped_dapi.write_to_file(warped_path, compression="none", tile=False)
        else:
            warped_dapi.write_to_file(warped_path)
            
        print(f"Warped original DAPI: {warped_path}, dimensions={current_width}x{current_height}")
        
    except Exception as e:
        print(f"Error warping original DAPI {slide_obj.name}: {str(e)}")

# Warp all other channels (ch01, ch02) with original intensities
print("Warping all other channels with original intensities...")
for slide_obj, round_slides in round_dict.items():
    print(f"\nWarping channels for round: {slide_obj.name}")
    valtils.sort_nicely(round_slides)

    for src_f in round_slides:
        img_name = os.path.basename(src_f)
        print(f"Warping channel: {img_name}")
        
        try:
            # Load original intensity padded channel
            channel_img = pyvips.Image.new_from_file(src_f)
            orig_width, orig_height = channel_img.width, channel_img.height
            print(f"  Original channel dimensions: {orig_width}x{orig_height}, format={channel_img.format}, bands={channel_img.bands}")
            
            # Apply transformation found using CLAHE-padded DAPI
            warped_channel = slide_obj.warp_img(img=channel_img, non_rigid=True, crop=False)
            
            # CRITICAL CHECK: Verify dimensions have correct orientation
            current_width, current_height = warped_channel.width, warped_channel.height
            print(f"  Raw warped dimensions: {current_width}x{current_height}")
            
            # Check if width/height are transposed
            orig_orientation = "landscape" if orig_width > orig_height else "portrait"
            warped_orientation = "landscape" if current_width > current_height else "portrait"
            
            # Calculate aspect ratio match
            orig_aspect = orig_width / orig_height
            warped_aspect = current_width / current_height
            aspect_ratio_diff = abs(warped_aspect - orig_aspect)
            aspect_ratio_diff_transposed = abs(warped_aspect - (1/orig_aspect))
            
            # Determine if transposition is needed
            needs_transposition = False
            if orig_orientation == warped_orientation:
                if aspect_ratio_diff_transposed < aspect_ratio_diff * 0.5:
                    needs_transposition = True
                    print(f"  ⚠️ Aspect ratio mismatch suggests transposition needed")
            else:
                needs_transposition = True
                print(f"  ⚠️ Orientation mismatch: original={orig_orientation}, warped={warped_orientation}")
            
            # Apply transposition if needed
            if needs_transposition:
                print(f"  💡 Transposing image to fix orientation...")
                warped_channel = warped_channel.transpose(pyvips.enums.Orientation.ROT90)
                current_width, current_height = warped_channel.width, warped_channel.height
                print(f"  ✅ Fixed dimensions: {current_width}x{current_height}")
            
            channel_basename = os.path.basename(src_f).replace("padded_", "")
            individual_channel_path = os.path.join(individual_channel_dir, f"warped_{channel_basename}")
            
            # Save with proper format preservation for 16-bit images
            if channel_img.format == 'ushort':
                warped_channel.write_to_file(individual_channel_path, compression="none", tile=False)
            else:
                warped_channel.write_to_file(individual_channel_path)
                
            print(f"Saved warped channel: {individual_channel_path}, dimensions={current_width}x{current_height}")
            
        except Exception as e:
            print(f"Error processing {src_f}: {str(e)}")

# # STEP 5: Resize warped images to match padded reference dimensions
# print("Resizing warped images to match padded reference dimensions...")
# resize_warped_images(individual_channel_dir, padded_reference_slide)

# # STEP 6: Fix any remaining aspect ratio issues
# fix_warped_images(individual_channel_dir, padded_reference_slide)

print("\n" + "="*60)
print("REGISTRATION COMPLETE!")
print("="*60)
print(f"✅ CLAHE applied ONLY to padded DAPI (ch00) for registration")
print(f"✅ All channels warped with ORIGINAL intensities preserved")
print(f"✅ 16-bit image format preserved throughout processing")
print(f"✅ Warped images resized to match padded reference dimensions ({max_width} x {max_height})")
print(f"✅ Warped images fixed to maintain correct aspect ratio")
print(f"📁 Original padded images: {padded_dir}")
print(f"📁 Final warped images: {individual_channel_dir}")
print(f"📁 Backup of original warped images: {os.path.join(dst_dir, 'resize_backup')}")

# Clean up CLAHE-padded directory
print("\nCleaning up temporary CLAHE-padded files...")
shutil.rmtree(clahe_padded_dir)
print("✅ Cleaned up CLAHE-padded temporary files")

# Kill JVM
registration.kill_jvm()

if name == "main":
main()

ERROR TRACE :

Performing registration with CLAHE-padded DAPI images...
Loaded LightGlue model
/home/m197816/anaconda3/envs/VALIS2/lib/python3.10/site-packages/valis/valtils.py:29: UserWarning: Can't find slide file associated with masks
warnings.warn(warning_msg, warning_type)
/home/m197816/anaconda3/envs/VALIS2/lib/python3.10/site-packages/valis/valtils.py:29: UserWarning: Can't find slide file associated with processed
warnings.warn(warning_msg, warning_type)
/home/m197816/anaconda3/envs/VALIS2/lib/python3.10/site-packages/valis/valtils.py:29: UserWarning: max_image_dim_px is 1024 but needs to be less or equal to 2000. Setting max_image_dim_px to 2000
warnings.warn(warning_msg, warning_type)

==== Converting images

Converting images: 100%|██████████| 11/11 [04:22<00:00, 23.87s/image]

==== Processing images

Processing images : 100%|██████████| 11/11 [04:26<00:00, 24.26s/image]
Normalizing images: 100%|██████████| 11/11 [00:04<00:00, 2.24image/s]

==== Rigid registration

Detecting features : 0%| | 0/11 [00:00<?, ?image/s]
detecting features in level 0 with image shape (1019, 2000)
Detecting features : 9%|▉ | 1/11 [00:03<00:38, 3.82s/image]
detecting features in level 0 with image shape (1081, 2000)
Detecting features : 18%|█▊ | 2/11 [00:08<00:38, 4.31s/image]
detecting features in level 0 with image shape (1081, 2000)
Detecting features : 27%|██▋ | 3/11 [00:11<00:30, 3.84s/image]
detecting features in level 0 with image shape (1019, 2000)
Detecting features : 36%|███▋ | 4/11 [00:15<00:27, 3.90s/image]
detecting features in level 0 with image shape (1081, 2000)
Detecting features : 45%|████▌ | 5/11 [00:19<00:22, 3.75s/image]
detecting features in level 0 with image shape (1018, 2000)
Detecting features : 55%|█████▍ | 6/11 [00:23<00:19, 3.88s/image]
detecting features in level 0 with image shape (1081, 2000)
Detecting features : 64%|██████▎ | 7/11 [00:27<00:15, 3.82s/image]
detecting features in level 0 with image shape (878, 2000)
Detecting features : 73%|███████▎ | 8/11 [00:30<00:10, 3.64s/image]
detecting features in level 0 with image shape (1153, 2000)
Detecting features : 82%|████████▏ | 9/11 [00:34<00:07, 3.94s/image]
detecting features in level 0 with image shape (877, 1999)
Detecting features : 91%|█████████ | 10/11 [00:39<00:04, 4.00s/image]
detecting features in level 0 with image shape (1018, 1999)
Detecting features : 100%|██████████| 11/11 [00:41<00:00, 3.80s/image]
QUEUEING TASKS | Matching images : 100%|██████████| 11/11 [00:00<00:00, 211.23image/s]
PROCESSING TASKS | Matching images : 100%|██████████| 11/11 [00:51<00:00, 4.64s/image]
COLLECTING RESULTS | Matching images : 100%|██████████| 11/11 [00:00<00:00, 37755.60image/s]
Images sorted using VggFD features. Will now use LightGlueMatcher to match images using DiskFD features
detecting features in level 0 with image shape (1153, 2000)
Re-matching images: 0%| | 0/10 [00:00<?, ?image/s]
detecting features in level 0 with image shape (1256, 2146)
Re-matching images: 0%| | 0/10 [00:03<?, ?image/s]
/home/m197816/anaconda3/envs/VALIS2/lib/python3.10/site-packages/valis/valtils.py:29: UserWarning: can't convert cuda:0 device type tensor to numpy. Use Tensor.cpu() to copy the tensor to host memory first.
warnings.warn(warning_msg, warning_type)
Traceback (most recent call last):
File "/home/m197816/anaconda3/envs/VALIS2/lib/python3.10/site-packages/valis/registration.py", line 4736, in register
rigid_registrar = self.rigid_register()
File "/home/m197816/anaconda3/envs/VALIS2/lib/python3.10/site-packages/valis/registration.py", line 3401, in rigid_register
rigid_registrar = serial_rigid.register_images(img_dir=self.processed_dir,
File "/home/m197816/anaconda3/envs/VALIS2/lib/python3.10/site-packages/valis/serial_rigid.py", line 1825, in register_images
registrar.rematch(matcher_obj=matcher, valis_obj=valis_obj, keep_unfiltered=False)
File "/home/m197816/anaconda3/envs/VALIS2/lib/python3.10/site-packages/valis/serial_rigid.py", line 901, in rematch
matcher_obj.match_images(img1=prev_img, img2=rotated_img,
File "/home/m197816/anaconda3/envs/VALIS2/lib/python3.10/site-packages/valis/feature_matcher.py", line 1458, in match_images
match_distances = match_distances.detach().numpy()
TypeError: can't convert cuda:0 device type tensor to numpy. Use Tensor.cpu() to copy the tensor to host memory first.

Performing micro-registration...

TypeError Traceback (most recent call last)
Cell In[7], line 698
695 registration.kill_jvm()
697 if name == "main":
--> 698 main()

Cell In[7], line 529
527 # Perform micro-registration
528 print("Performing micro-registration...")
--> 529 micro_reg, micro_error = registrar.register_micro(
530 max_non_rigid_registration_dim_px=2000,
531 align_to_reference=True
532 )
534 # STEP 4: Apply registration transforms to original padded images
535 print("Applying registration transforms to original intensity padded images...")

File ~/anaconda3/envs/VALIS2/lib/python3.10/site-packages/valis/valtils.py:40, in deprecated_args..deco..wrapper(*args, **kwargs)
37 @functools.wraps(f)
38 def wrapper(*args, **kwargs):
39 rename_kwargs(f.name, kwargs, aliases)
---> 40 return f(*args, **kwargs)

File ~/anaconda3/envs/VALIS2/lib/python3.10/site-packages/valis/registration.py:4901, in Valis.register_micro(self, brightfield_processing_cls, brightfield_processing_kwargs, if_processing_cls, if_processing_kwargs, processor_dict, max_non_rigid_registration_dim_px, non_rigid_registrar_cls, non_rigid_reg_params, reference_img_f, align_to_reference, mask, tile_wh)
4897 non_rigid_registrar_obj = non_rigid_registrar_cls
4899 reg_in_rgb = non_rigid_registrar_obj.rgb
4900 nr_reg_src, max_img_dim, non_rigid_reg_mask, full_out_shape_rc, mask_bbox_xywh, using_tiler =
-> 4901 self.prep_images_for_large_non_rigid_registration(max_img_dim=max_non_rigid_registration_dim_px,
4902 processor_dict=slide_processors,
4903 updating_non_rigid=True,
4904 mask=mask,
4905 rgb=reg_in_rgb)
4907 img_specific_args = None
4908 write_dxdy = isinstance(ref_slide.bk_dxdy, pyvips.Image)

File ~/anaconda3/envs/VALIS2/lib/python3.10/site-packages/valis/registration.py:3892, in Valis.prep_images_for_large_non_rigid_registration(self, max_img_dim, processor_dict, updating_non_rigid, mask, rgb)
3889 s = np.min(max_img_dim/np.array(to_reg_mask_wh))
3891 if s < max_s:
-> 3892 full_out_shape = self.get_aligned_slide_shape(s)
3893 else:
3894 full_out_shape = self.get_aligned_slide_shape(0)

File ~/anaconda3/envs/VALIS2/lib/python3.10/site-packages/valis/registration.py:5095, in Valis.get_aligned_slide_shape(self, level)
5092 else:
5093 s_rc = level
-> 5095 aligned_out_shape_rc = np.ceil(np.array(ref_slide.reg_img_shape_rc)*s_rc).astype(int)
5097 return aligned_out_shape_rc

TypeError: unsupported operand type(s) for *: 'NoneType' and 'float'

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions