# Image Processing & Rendering This reference covers accessing raw pixel data, image rendering, and creating new images in OMERO. ## Accessing Raw Pixel Data ### Get Single Plane ```python # Get image image = conn.getObject("Image", image_id) # Get dimensions size_z = image.getSizeZ() size_c = image.getSizeC() size_t = image.getSizeT() # Get pixels object pixels = image.getPrimaryPixels() # Get single plane (returns NumPy array) z, c, t = 0, 0, 0 # First Z-section, channel, and timepoint plane = pixels.getPlane(z, c, t) print(f"Shape: {plane.shape}") print(f"Data type: {plane.dtype.name}") print(f"Min: {plane.min()}, Max: {plane.max()}") ``` ### Get Multiple Planes ```python import numpy as np # Get Z-stack for specific channel and timepoint pixels = image.getPrimaryPixels() c, t = 0, 0 # First channel and timepoint # Build list of (z, c, t) coordinates zct_list = [(z, c, t) for z in range(size_z)] # Get all planes at once planes = pixels.getPlanes(zct_list) # Stack into 3D array z_stack = np.array([p for p in planes]) print(f"Z-stack shape: {z_stack.shape}") ``` ### Get Hypercube (Subset of 5D Data) ```python # Get subset of 5D data (Z, C, T) zct_list = [] for z in range(size_z // 2, size_z): # Second half of Z for c in range(size_c): # All channels for t in range(size_t): # All timepoints zct_list.append((z, c, t)) # Get planes planes = pixels.getPlanes(zct_list) # Process each plane for i, plane in enumerate(planes): z, c, t = zct_list[i] print(f"Plane Z={z}, C={c}, T={t}: Min={plane.min()}, Max={plane.max()}") ``` ### Get Tile (Region of Interest) ```python # Define tile coordinates x, y = 50, 50 # Top-left corner width, height = 100, 100 # Tile size tile = (x, y, width, height) # Get tile for specific Z, C, T z, c, t = 0, 0, 0 zct_list = [(z, c, t, tile)] tiles = pixels.getTiles(zct_list) tile_data = tiles[0] print(f"Tile shape: {tile_data.shape}") # Should be (height, width) ``` ### Get Multiple Tiles ```python # Get tiles from Z-stack c, t = 0, 0 tile = (50, 50, 100, 100) # x, y, width, height # Build list with tiles zct_list = [(z, c, t, tile) for z in range(size_z)] tiles = pixels.getTiles(zct_list) for i, tile_data in enumerate(tiles): print(f"Tile Z={i}: {tile_data.shape}, Min={tile_data.min()}") ``` ## Image Histograms ### Get Histogram ```python # Get histogram for first channel channel_index = 0 num_bins = 256 z, t = 0, 0 histogram = image.getHistogram([channel_index], num_bins, False, z, t) print(f"Histogram bins: {len(histogram)}") print(f"First 10 bins: {histogram[:10]}") ``` ### Multi-Channel Histogram ```python # Get histograms for all channels channels = list(range(image.getSizeC())) histograms = image.getHistogram(channels, 256, False, 0, 0) for c, hist in enumerate(histograms): print(f"Channel {c}: Total pixels = {sum(hist)}") ``` ## Image Rendering ### Render Image with Current Settings ```python from PIL import Image from io import BytesIO # Get image image = conn.getObject("Image", image_id) # Render at specific Z and T z = image.getSizeZ() // 2 # Middle Z-section t = 0 rendered_image = image.renderImage(z, t) # rendered_image is a PIL Image object rendered_image.save("rendered_image.jpg") ``` ### Get Thumbnail ```python from PIL import Image from io import BytesIO # Get thumbnail (uses current rendering settings) thumbnail_data = image.getThumbnail() # Convert to PIL Image thumbnail = Image.open(BytesIO(thumbnail_data)) thumbnail.save("thumbnail.jpg") # Get specific thumbnail size thumbnail_data = image.getThumbnail(size=(96, 96)) thumbnail = Image.open(BytesIO(thumbnail_data)) ``` ## Rendering Settings ### View Current Settings ```python # Display rendering settings print("Current Rendering Settings:") print(f"Grayscale mode: {image.isGreyscaleRenderingModel()}") print(f"Default Z: {image.getDefaultZ()}") print(f"Default T: {image.getDefaultT()}") print() # Channel settings print("Channel Settings:") for idx, channel in enumerate(image.getChannels()): print(f"Channel {idx + 1}:") print(f" Label: {channel.getLabel()}") print(f" Color: {channel.getColor().getHtml()}") print(f" Active: {channel.isActive()}") print(f" Window: {channel.getWindowStart()} - {channel.getWindowEnd()}") print(f" Min/Max: {channel.getWindowMin()} - {channel.getWindowMax()}") ``` ### Set Rendering Model ```python # Switch to grayscale rendering image.setGreyscaleRenderingModel() # Switch to color rendering image.setColorRenderingModel() ``` ### Set Active Channels ```python # Activate specific channels (1-indexed) image.setActiveChannels([1, 3]) # Channels 1 and 3 only # Activate all channels all_channels = list(range(1, image.getSizeC() + 1)) image.setActiveChannels(all_channels) # Activate single channel image.setActiveChannels([2]) ``` ### Set Channel Colors ```python # Set channel colors (hex format) channels = [1, 2, 3] colors = ['FF0000', '00FF00', '0000FF'] # Red, Green, Blue image.setActiveChannels(channels, colors=colors) # Use None to keep existing color colors = ['FF0000', None, '0000FF'] # Keep channel 2's color image.setActiveChannels(channels, colors=colors) ``` ### Set Channel Window (Intensity Range) ```python # Set intensity windows for channels channels = [1, 2] windows = [ [100.0, 500.0], # Channel 1: 100-500 [50.0, 300.0] # Channel 2: 50-300 ] image.setActiveChannels(channels, windows=windows) # Use None to keep existing window windows = [[100.0, 500.0], [None, None]] image.setActiveChannels(channels, windows=windows) ``` ### Set Default Z and T ```python # Set default Z-section and timepoint image.setDefaultZ(5) image.setDefaultT(0) # Render using defaults rendered_image = image.renderImage(z=None, t=None) rendered_image.save("default_rendering.jpg") ``` ## Render Individual Channels ### Render Each Channel Separately ```python # Set grayscale mode image.setGreyscaleRenderingModel() z = image.getSizeZ() // 2 t = 0 # Render each channel for c in range(1, image.getSizeC() + 1): image.setActiveChannels([c]) rendered = image.renderImage(z, t) rendered.save(f"channel_{c}.jpg") ``` ### Render Multi-Channel Composites ```python # Color composite of first 3 channels image.setColorRenderingModel() channels = [1, 2, 3] colors = ['FF0000', '00FF00', '0000FF'] # RGB image.setActiveChannels(channels, colors=colors) rendered = image.renderImage(z, t) rendered.save("rgb_composite.jpg") ``` ## Image Projections ### Maximum Intensity Projection ```python # Set projection type image.setProjection('intmax') # Render (projects across all Z) z, t = 0, 0 # Z is ignored for projections rendered = image.renderImage(z, t) rendered.save("max_projection.jpg") # Reset to normal rendering image.setProjection('normal') ``` ### Mean Intensity Projection ```python image.setProjection('intmean') rendered = image.renderImage(z, t) rendered.save("mean_projection.jpg") image.setProjection('normal') ``` ### Available Projection Types - `'normal'`: No projection (default) - `'intmax'`: Maximum intensity projection - `'intmean'`: Mean intensity projection - `'intmin'`: Minimum intensity projection (if supported) ## Save and Reset Rendering Settings ### Save Current Settings as Default ```python # Modify rendering settings image.setActiveChannels([1, 2]) image.setDefaultZ(5) # Save as new default image.saveDefaults() ``` ### Reset to Import Settings ```python # Reset to original import settings image.resetDefaults(save=True) ``` ## Create Images from NumPy Arrays ### Create Simple Image ```python import numpy as np # Create sample data size_x, size_y = 512, 512 size_z, size_c, size_t = 10, 2, 1 # Generate planes def plane_generator(): """Generator that yields planes""" for z in range(size_z): for c in range(size_c): for t in range(size_t): # Create synthetic data plane = np.random.randint(0, 255, (size_y, size_x), dtype=np.uint8) yield plane # Create image image = conn.createImageFromNumpySeq( plane_generator(), "Test Image", size_z, size_c, size_t, description="Image created from NumPy arrays", dataset=None ) print(f"Created image ID: {image.getId()}") ``` ### Create Image from Hard-Coded Arrays ```python from numpy import array, int8 # Define dimensions size_x, size_y = 5, 4 size_z, size_c, size_t = 1, 2, 1 # Create planes plane1 = array( [[0, 1, 2, 3, 4], [5, 6, 7, 8, 9], [0, 1, 2, 3, 4], [5, 6, 7, 8, 9]], dtype=int8 ) plane2 = array( [[5, 6, 7, 8, 9], [0, 1, 2, 3, 4], [5, 6, 7, 8, 9], [0, 1, 2, 3, 4]], dtype=int8 ) planes = [plane1, plane2] def plane_gen(): for p in planes: yield p # Create image desc = "Image created from hard-coded arrays" image = conn.createImageFromNumpySeq( plane_gen(), "numpy_image", size_z, size_c, size_t, description=desc, dataset=None ) print(f"Created image: {image.getName()} (ID: {image.getId()})") ``` ### Create Image in Dataset ```python # Get target dataset dataset = conn.getObject("Dataset", dataset_id) # Create image image = conn.createImageFromNumpySeq( plane_generator(), "New Analysis Result", size_z, size_c, size_t, description="Result from analysis pipeline", dataset=dataset # Add to dataset ) ``` ### Create Derived Image ```python # Get source image source = conn.getObject("Image", source_image_id) size_z = source.getSizeZ() size_c = source.getSizeC() size_t = source.getSizeT() dataset = source.getParent() pixels = source.getPrimaryPixels() new_size_c = 1 # Average channels def plane_gen(): """Average channels together""" for z in range(size_z): for c in range(new_size_c): for t in range(size_t): # Get multiple channels channel0 = pixels.getPlane(z, 0, t) channel1 = pixels.getPlane(z, 1, t) # Combine new_plane = (channel0.astype(float) + channel1.astype(float)) / 2 new_plane = new_plane.astype(channel0.dtype) yield new_plane # Create new image desc = "Averaged channels from source image" derived = conn.createImageFromNumpySeq( plane_gen(), f"{source.getName()}_averaged", size_z, new_size_c, size_t, description=desc, dataset=dataset ) print(f"Created derived image: {derived.getId()}") ``` ## Set Physical Dimensions ### Set Pixel Sizes with Units ```python from omero.model.enums import UnitsLength import omero.model # Get image image = conn.getObject("Image", image_id) # Create unit objects pixel_size_x = omero.model.LengthI(0.325, UnitsLength.MICROMETER) pixel_size_y = omero.model.LengthI(0.325, UnitsLength.MICROMETER) pixel_size_z = omero.model.LengthI(1.0, UnitsLength.MICROMETER) # Get pixels object pixels = image.getPrimaryPixels()._obj # Set physical sizes pixels.setPhysicalSizeX(pixel_size_x) pixels.setPhysicalSizeY(pixel_size_y) pixels.setPhysicalSizeZ(pixel_size_z) # Save changes conn.getUpdateService().saveObject(pixels) print("Updated pixel dimensions") ``` ### Available Length Units From `omero.model.enums.UnitsLength`: - `ANGSTROM` - `NANOMETER` - `MICROMETER` - `MILLIMETER` - `CENTIMETER` - `METER` - `PIXEL` ### Set Pixel Size on New Image ```python from omero.model.enums import UnitsLength import omero.model # Create image image = conn.createImageFromNumpySeq( plane_generator(), "New Image with Dimensions", size_z, size_c, size_t ) # Set pixel sizes pixel_size = omero.model.LengthI(0.5, UnitsLength.MICROMETER) pixels = image.getPrimaryPixels()._obj pixels.setPhysicalSizeX(pixel_size) pixels.setPhysicalSizeY(pixel_size) z_size = omero.model.LengthI(2.0, UnitsLength.MICROMETER) pixels.setPhysicalSizeZ(z_size) conn.getUpdateService().saveObject(pixels) ``` ## Complete Example: Image Processing Pipeline ```python from omero.gateway import BlitzGateway import numpy as np HOST = 'omero.example.com' PORT = 4064 USERNAME = 'user' PASSWORD = 'pass' with BlitzGateway(USERNAME, PASSWORD, host=HOST, port=PORT) as conn: # Get source image source = conn.getObject("Image", source_image_id) print(f"Processing: {source.getName()}") # Get dimensions size_x = source.getSizeX() size_y = source.getSizeY() size_z = source.getSizeZ() size_c = source.getSizeC() size_t = source.getSizeT() pixels = source.getPrimaryPixels() # Process: Maximum intensity projection over Z def plane_gen(): for c in range(size_c): for t in range(size_t): # Get all Z planes for this C, T z_stack = [] for z in range(size_z): plane = pixels.getPlane(z, c, t) z_stack.append(plane) # Maximum projection max_proj = np.max(z_stack, axis=0) yield max_proj # Create result image (single Z-section) result = conn.createImageFromNumpySeq( plane_gen(), f"{source.getName()}_MIP", 1, size_c, size_t, # Z=1 for projection description="Maximum intensity projection", dataset=source.getParent() ) print(f"Created MIP image: {result.getId()}") # Copy pixel sizes (X and Y only, no Z for projection) from omero.model.enums import UnitsLength import omero.model source_pixels = source.getPrimaryPixels()._obj result_pixels = result.getPrimaryPixels()._obj result_pixels.setPhysicalSizeX(source_pixels.getPhysicalSizeX()) result_pixels.setPhysicalSizeY(source_pixels.getPhysicalSizeY()) conn.getUpdateService().saveObject(result_pixels) print("Processing complete") ``` ## Working with Different Data Types ### Handle Various Pixel Types ```python # Get pixel type pixel_type = image.getPixelsType() print(f"Pixel type: {pixel_type}") # Common types: uint8, uint16, uint32, int8, int16, int32, float, double # Get plane with correct dtype plane = pixels.getPlane(z, c, t) print(f"NumPy dtype: {plane.dtype}") # Convert if needed for processing if plane.dtype == np.uint16: # Convert to float for processing plane_float = plane.astype(np.float32) # Process... # Convert back result = plane_float.astype(np.uint16) ``` ### Handle Large Images ```python # Process large images in tiles to save memory tile_size = 512 size_x = image.getSizeX() size_y = image.getSizeY() for y in range(0, size_y, tile_size): for x in range(0, size_x, tile_size): # Get tile dimensions w = min(tile_size, size_x - x) h = min(tile_size, size_y - y) tile = (x, y, w, h) # Get tile data zct_list = [(z, c, t, tile)] tile_data = pixels.getTiles(zct_list)[0] # Process tile # ... ``` ## Best Practices 1. **Use Generators**: For creating images, use generators to avoid loading all data in memory 2. **Specify Data Types**: Match NumPy dtypes to OMERO pixel types 3. **Set Physical Dimensions**: Always set pixel sizes for new images 4. **Tile Large Images**: Process large images in tiles to manage memory 5. **Close Connections**: Always close connections when done 6. **Rendering Efficiency**: Cache rendering settings when rendering multiple images 7. **Channel Indexing**: Remember channels are 1-indexed for rendering, 0-indexed for pixel access 8. **Save Settings**: Save rendering settings if they should be permanent 9. **Compression**: Use compression parameter in renderImage() for faster transfers 10. **Error Handling**: Check for None returns and handle exceptions