A package for combining dithered astronomical images into a single image using the Drizzle algorithm
Functions for analyzing and decoding context images to determine which input images contributed to specific output pixels. The context system uses bit-field encoding to efficiently track image contributions across large datasets.
from typing import Union, List
import numpy as npDecode context bit fields to determine which input images contributed to specific output pixels.
def decode_context(
context: np.ndarray,
x: Union[int, List[int], np.ndarray],
y: Union[int, List[int], np.ndarray]
) -> List[np.ndarray]:
"""
Get 0-based indices of input images that contributed to output pixels.
The context array uses bit-field encoding where each bit represents
whether a specific input image contributed to an output pixel.
For >32 input images, multiple "planes" are used in the 3rd dimension.
Parameters:
- context: 3D context array of shape (num_planes, height, width)
Each plane encodes 32 input images using int32 bit fields
- x: X-coordinates of pixels to decode (can be scalar, list, or array)
- y: Y-coordinates of pixels to decode (can be scalar, list, or array)
Must have same length as x
Returns:
List of numpy arrays, one per coordinate pair.
Each array contains 0-based indices of input images that contributed
to the corresponding output pixel.
Raises:
- ValueError: If context is not 3D array
- ValueError: If x and y have different lengths
- ValueError: If coordinates are not integer values
- ValueError: If x/y are not scalars or 1D arrays
Notes:
- Context array uses 32-bit integers with bit-field encoding
- Plane 0 encodes images 0-31, plane 1 encodes images 32-63, etc.
- Bit k in plane p represents input image (32 * p + k)
- Empty arrays returned for pixels with no contributions
"""import numpy as np
from drizzle.utils import decode_context
# Create example context array for 5x6 output image
# This example shows results from processing 80 input images
context = np.array([
# Plane 0: images 0-31
[[0, 0, 0, 0, 0, 0],
[0, 0, 0, 36196864, 0, 0],
[0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0],
[0, 0, 537920000, 0, 0, 0]],
# Plane 1: images 32-63
[[0, 0, 0, 0, 0, 0],
[0, 0, 0, 67125536, 0, 0],
[0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0],
[0, 0, 163856, 0, 0, 0]],
# Plane 2: images 64-95
[[0, 0, 0, 0, 0, 0],
[0, 0, 0, 8203, 0, 0],
[0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0],
[0, 0, 32865, 0, 0, 0]]
], dtype=np.int32)
# Decode specific pixels
contributing_images = decode_context(context, [3, 2], [1, 4])
print("Pixel (3,1) has contributions from images:", contributing_images[0])
print("Pixel (2,4) has contributions from images:", contributing_images[1])import numpy as np
from drizzle.utils import decode_context
# Decode a single pixel
x_coord = 3
y_coord = 1
contributors = decode_context(context, x_coord, y_coord)
print(f"Pixel ({x_coord},{y_coord}) contributors: {contributors[0]}")
# Check if specific images contributed
target_images = [9, 25, 40]
pixel_contributors = contributors[0]
for img_id in target_images:
if img_id in pixel_contributors:
print(f"Image {img_id} contributed to this pixel")
else:
print(f"Image {img_id} did NOT contribute to this pixel")import numpy as np
from drizzle.utils import decode_context
# Analyze multiple pixels at once
x_coords = np.array([0, 1, 2, 3, 4])
y_coords = np.array([0, 1, 2, 3, 4]) # Diagonal pixels
contributors_list = decode_context(context, x_coords, y_coords)
for i, (x, y) in enumerate(zip(x_coords, y_coords)):
contributors = contributors_list[i]
if len(contributors) > 0:
print(f"Pixel ({x},{y}): {len(contributors)} contributing images")
print(f" Image IDs: {contributors}")
else:
print(f"Pixel ({x},{y}): No contributing images")import numpy as np
from drizzle.resample import Drizzle
from drizzle.utils import decode_context
# Create drizzle object and process some images
drizzler = Drizzle(out_shape=(100, 100))
# Simulate adding multiple images
for i in range(5):
data = np.random.random((80, 80)).astype(np.float32)
y, x = np.indices((80, 80), dtype=np.float64)
x += i * 2 # Shift each image slightly
y += i * 1.5
pixmap = np.dstack([x, y])
drizzler.add_image(data, exptime=10.0, pixmap=pixmap)
# Get the context image
context = drizzler.out_ctx
print(f"Context shape: {context.shape}")
print(f"Processed {drizzler.ctx_id} images")
# Analyze specific output pixels
sample_x = [25, 50, 75]
sample_y = [25, 50, 75]
contributors = decode_context(context, sample_x, sample_y)
for i, (x, y) in enumerate(zip(sample_x, sample_y)):
imgs = contributors[i]
print(f"Pixel ({x},{y}) has {len(imgs)} contributors: {list(imgs)}")import numpy as np
from drizzle.utils import decode_context
# Example with many input images requiring multiple context planes
# Simulate context for 100 input images (requires 4 planes: 0-31, 32-63, 64-95, 96-99)
# Create context array for demonstration
height, width = 50, 50
num_planes = 4 # For 100 input images
context_large = np.random.randint(0, 2**20, (num_planes, height, width), dtype=np.int32)
# Analyze center pixel
center_x, center_y = width // 2, height // 2
center_contributors = decode_context(context_large, center_x, center_y)[0]
print(f"Center pixel ({center_x},{center_y}) contributors:")
print(f"Total contributing images: {len(center_contributors)}")
print(f"Contributing image IDs: {center_contributors[:10]}...") # Show first 10
# Count contributions per plane
plane_counts = []
for plane in range(num_planes):
start_img = plane * 32
end_img = min(start_img + 32, 100)
plane_contributors = center_contributors[
(center_contributors >= start_img) & (center_contributors < end_img)
]
plane_counts.append(len(plane_contributors))
print(f"Plane {plane} (images {start_img}-{end_img-1}): {len(plane_contributors)} contributors")import numpy as np
from drizzle.utils import decode_context
def analyze_context_statistics(context):
"""Analyze context image to get contribution statistics."""
height, width = context.shape[1], context.shape[2]
# Sample grid of pixels for analysis
x_sample = np.arange(0, width, width // 10)
y_sample = np.arange(0, height, height // 10)
# Create meshgrid for all combinations
x_grid, y_grid = np.meshgrid(x_sample, y_sample)
x_flat = x_grid.flatten()
y_flat = y_grid.flatten()
# Decode all sample pixels
contributors_list = decode_context(context, x_flat, y_flat)
# Calculate statistics
contribution_counts = [len(contributors) for contributors in contributors_list]
return {
'mean_contributors': np.mean(contribution_counts),
'max_contributors': np.max(contribution_counts),
'min_contributors': np.min(contribution_counts),
'std_contributors': np.std(contribution_counts),
'pixels_with_no_contributions': sum(1 for c in contribution_counts if c == 0),
'total_sampled_pixels': len(contribution_counts)
}
# Use with drizzle results
stats = analyze_context_statistics(context)
print("Context Statistics:")
for key, value in stats.items():
print(f" {key}: {value:.2f}")import numpy as np
def manual_bit_decode(context_value, plane_index):
"""
Manually decode a context value to understand bit field encoding.
Parameters:
- context_value: int32 value from context array
- plane_index: which plane this value came from (0, 1, 2, ...)
Returns:
List of contributing image indices
"""
contributors = []
base_image_id = plane_index * 32
# Check each bit
for bit_pos in range(32):
if context_value & (1 << bit_pos):
contributors.append(base_image_id + bit_pos)
return contributors
# Example: decode the bit pattern manually
example_context_value = 36196864 # From our example above
plane = 0
manual_result = manual_bit_decode(example_context_value, plane)
print(f"Manual decode of value {example_context_value} (binary: {bin(example_context_value)}):")
print(f"Contributing images: {manual_result}")
# Compare with decode_context function
# (This would match the result from decode_context for the same pixel)import numpy as np
from drizzle.utils import decode_context
# Handle invalid context array dimensions
try:
bad_context_2d = np.ones((50, 50), dtype=np.int32) # 2D instead of 3D
decode_context(bad_context_2d, [10], [10])
except ValueError as e:
print(f"Dimension error: {e}")
# Handle mismatched coordinate arrays
try:
context = np.ones((1, 50, 50), dtype=np.int32)
decode_context(context, [10, 20], [10]) # Different lengths
except ValueError as e:
print(f"Coordinate length error: {e}")
# Handle non-integer coordinates
try:
decode_context(context, [10.5], [10.7]) # Float coordinates
except ValueError as e:
print(f"Coordinate type error: {e}")
# Handle invalid coordinate dimensions
try:
coords_2d = np.array([[10, 20], [15, 25]]) # 2D coordinate array
decode_context(context, coords_2d, [10, 20])
except ValueError as e:
print(f"Coordinate dimension error: {e}")disable_ctx=TrueInstall with Tessl CLI
npx tessl i tessl/pypi-drizzle