Image transformation, compression, and decompression codecs for scientific computing
—
Color space transformations and ICC profile handling using Little-CMS for accurate color reproduction and conversion between different color spaces. This enables precise color workflows for photography, printing, and scientific imaging applications.
Transform image data between different color spaces using ICC profiles or built-in color space definitions.
def cms_transform(data, profile, outprofile, *, colorspace=None, planar=None, outcolorspace=None, outplanar=None, outdtype=None, intent=None, flags=None, verbose=None, out=None):
"""
Return color-transformed array.
Parameters:
- data: NDArray - Image data to transform (2D grayscale or 3D color)
- profile: bytes | str - Input ICC profile data or color space name:
Built-in names: 'srgb', 'adobe_rgb', 'prophoto_rgb', 'lab', 'xyz', 'gray'
- outprofile: bytes | str - Output ICC profile data or color space name
- colorspace: str | None - Input color space interpretation:
'rgb', 'rgba', 'bgr', 'bgra', 'cmyk', 'gray', 'lab', 'xyz'
- planar: bool | None - Input data is planar (channels as separate arrays)
- outcolorspace: str | None - Output color space interpretation
- outplanar: bool | None - Output data as planar format
- outdtype: numpy.dtype | None - Output data type (default same as input)
- intent: str | None - Rendering intent:
'perceptual' (default), 'relative', 'saturation', 'absolute'
- flags: int | None - Transformation flags (bitwise OR of CMS constants)
- verbose: bool | None - Enable verbose output
- out: NDArray | None - Pre-allocated output buffer
Returns:
NDArray: Color-transformed image data
"""
def cms_encode(data, profile, outprofile, **kwargs):
"""
Alias for cms_transform for consistency with other codecs.
Returns:
NDArray: Color-transformed image data
"""
def cms_decode(data, profile, outprofile, **kwargs):
"""
Alias for cms_transform for consistency with other codecs.
Returns:
NDArray: Color-transformed image data
"""Create ICC profiles for standard color spaces or custom color space definitions.
def cms_profile(profile, *, whitepoint=None, primaries=None, transferfunction=None, gamma=None):
"""
Return ICC profile data.
Parameters:
- profile: str - Profile type to create:
'srgb', 'adobe_rgb', 'prophoto_rgb', 'rec2020', 'dci_p3',
'lab', 'xyz', 'gray_gamma22', 'gray_gamma18', 'gray_linear'
- whitepoint: tuple | None - White point coordinates (x, y) or temperature (K)
Common: (0.3127, 0.3290) for D65, 6504 for D65 temperature
- primaries: tuple | None - Color primaries as ((rx,ry), (gx,gy), (bx,by))
- transferfunction: str | None - Transfer function type:
'gamma', 'srgb', 'rec709', 'linear', 'lab'
- gamma: float | None - Gamma value for gamma transfer function
Returns:
bytes: ICC profile data
"""Validate ICC profiles and check for common issues.
def cms_profile_validate(profile, *, verbose=False):
"""
Validate ICC profile and raise CmsError if invalid.
Parameters:
- profile: bytes - ICC profile data to validate
- verbose: bool - Print detailed validation information
Returns:
None: Returns successfully if profile is valid
Raises:
CmsError: If profile is invalid or corrupted
"""
def cms_check(data):
"""
Check if data is an ICC profile.
Parameters:
- data: bytes | bytearray | mmap.mmap - Data to check
Returns:
bool: True if ICC profile signature detected
"""Extract information from ICC profiles.
def cms_version():
"""
Return Little-CMS version string.
Returns:
str: Version information
"""import imagecodecs
import numpy as np
# Create RGB test image
rgb_image = np.random.randint(0, 256, (256, 256, 3), dtype=np.uint8)
# Convert sRGB to Adobe RGB
adobe_rgb = imagecodecs.cms_transform(
rgb_image,
profile='srgb',
outprofile='adobe_rgb',
intent='perceptual'
)
# Convert to LAB color space for analysis
lab_image = imagecodecs.cms_transform(
rgb_image,
profile='srgb',
outprofile='lab',
colorspace='rgb',
outcolorspace='lab'
)
print(f"RGB shape: {rgb_image.shape}, dtype: {rgb_image.dtype}")
print(f"LAB shape: {lab_image.shape}, dtype: {lab_image.dtype}")
print(f"LAB L* range: {lab_image[:,:,0].min():.1f} to {lab_image[:,:,0].max():.1f}")import imagecodecs
import numpy as np
# Create custom profile
custom_profile = imagecodecs.cms_profile(
'srgb',
whitepoint=(0.3127, 0.3290), # D65 white point
gamma=2.2
)
# Validate the profile
try:
imagecodecs.cms_profile_validate(custom_profile, verbose=True)
print("Profile is valid")
except imagecodecs.CmsError as e:
print(f"Profile validation failed: {e}")
# Load image and apply custom profile
image = np.random.randint(0, 256, (100, 100, 3), dtype=np.uint8)
# Transform using custom profile
transformed = imagecodecs.cms_transform(
image,
profile=custom_profile,
outprofile='prophoto_rgb',
intent='relative'
)import imagecodecs
import numpy as np
# Simulate RAW sensor data (linear RGB)
sensor_data = np.random.random((2048, 3072, 3)).astype(np.float32)
# Create linear RGB profile
linear_profile = imagecodecs.cms_profile('srgb', transferfunction='linear')
# Create output profile for web
web_profile = imagecodecs.cms_profile('srgb')
# Transform from linear sensor RGB to sRGB for web
web_image = imagecodecs.cms_transform(
sensor_data,
profile=linear_profile,
outprofile=web_profile,
intent='perceptual',
outdtype=np.uint8
)
# Transform to ProPhoto RGB for printing
print_profile = imagecodecs.cms_profile('prophoto_rgb')
print_image = imagecodecs.cms_transform(
sensor_data,
profile=linear_profile,
outprofile=print_profile,
intent='relative',
outdtype=np.uint16
)
print(f"Web image: {web_image.shape}, {web_image.dtype}")
print(f"Print image: {print_image.shape}, {print_image.dtype}")import imagecodecs
import numpy as np
# RGB image for printing
rgb_image = np.random.randint(0, 256, (400, 600, 3), dtype=np.uint8)
# Create CMYK profile for printing
cmyk_profile = imagecodecs.cms_profile('cmyk_coated') # Assuming this profile exists
# Convert RGB to CMYK for printing
try:
cmyk_image = imagecodecs.cms_transform(
rgb_image,
profile='srgb',
outprofile=cmyk_profile,
colorspace='rgb',
outcolorspace='cmyk',
intent='perceptual' # Good for photographic content
)
print(f"RGB: {rgb_image.shape} -> CMYK: {cmyk_image.shape}")
print(f"CMYK channels - C: {cmyk_image[:,:,0].mean():.1f}, "
f"M: {cmyk_image[:,:,1].mean():.1f}, "
f"Y: {cmyk_image[:,:,2].mean():.1f}, "
f"K: {cmyk_image[:,:,3].mean():.1f}")
except imagecodecs.CmsError as e:
print(f"CMYK conversion failed: {e}")import imagecodecs
import numpy as np
# Multispectral or hyperspectral imaging data
spectral_image = np.random.random((256, 256, 16)).astype(np.float32)
# Convert first 3 bands to approximate RGB
rgb_bands = spectral_image[:, :, [4, 2, 1]] # Select appropriate bands
# Apply color correction for display
display_image = imagecodecs.cms_transform(
rgb_bands,
profile='adobe_rgb', # Wider gamut for scientific data
outprofile='srgb', # For display
intent='absolute' # Preserve absolute colorimetric values
)
# Convert to LAB for perceptual analysis
lab_image = imagecodecs.cms_transform(
display_image,
profile='srgb',
outprofile='lab',
colorspace='rgb',
outcolorspace='lab'
)
# Analyze color distribution in LAB space
L_channel = lab_image[:, :, 0] # Lightness
a_channel = lab_image[:, :, 1] # Green-Red axis
b_channel = lab_image[:, :, 2] # Blue-Yellow axis
print(f"Lightness range: {L_channel.min():.1f} to {L_channel.max():.1f}")
print(f"Green-Red range: {a_channel.min():.1f} to {a_channel.max():.1f}")
print(f"Blue-Yellow range: {b_channel.min():.1f} to {b_channel.max():.1f}")import imagecodecs
import numpy as np
# Simulate batch of images with different source profiles
images = [
(np.random.randint(0, 256, (200, 300, 3), dtype=np.uint8), 'srgb'),
(np.random.randint(0, 256, (200, 300, 3), dtype=np.uint8), 'adobe_rgb'),
(np.random.randint(0, 256, (200, 300, 3), dtype=np.uint8), 'prophoto_rgb'),
]
# Target profile for consistent output
target_profile = 'srgb'
target_intent = 'perceptual'
# Process batch with consistent output
processed_images = []
for image, source_profile in images:
try:
converted = imagecodecs.cms_transform(
image,
profile=source_profile,
outprofile=target_profile,
intent=target_intent
)
processed_images.append(converted)
print(f"Converted {source_profile} -> {target_profile}")
except Exception as e:
print(f"Failed to convert {source_profile}: {e}")
processed_images.append(image) # Use original if conversion fails
print(f"Processed {len(processed_images)} images")import imagecodecs
import numpy as np
# Create image with embedded profile
image = np.random.randint(0, 256, (300, 400, 3), dtype=np.uint8)
adobe_profile = imagecodecs.cms_profile('adobe_rgb')
# Simulate saving image with embedded profile (conceptual)
# In practice, this would be done by the image format encoder
image_with_profile = {
'data': image,
'profile': adobe_profile,
'colorspace': 'rgb'
}
# Later, when loading the image, use the embedded profile
if 'profile' in image_with_profile:
# Convert from embedded profile to working space
working_image = imagecodecs.cms_transform(
image_with_profile['data'],
profile=image_with_profile['profile'],
outprofile='srgb',
intent='perceptual'
)
print("Applied embedded color profile")
else:
# Assume sRGB if no profile
working_image = image_with_profile['data']
print("No embedded profile, assuming sRGB")RGB Color Spaces:
'srgb' - Standard RGB (IEC 61966-2-1)'adobe_rgb' - Adobe RGB (1998)'prophoto_rgb' - ProPhoto RGB (ROMM RGB)'rec2020' - ITU-R BT.2020 (Ultra HDTV)'dci_p3' - DCI-P3 (Digital Cinema)Device-Independent Color Spaces:
'lab' - CIE Lab* (perceptually uniform)'xyz' - CIE XYZ (colorimetric)'luv' - CIE Luv* (alternative uniform space)Grayscale:
'gray' - Linear grayscale'gray_gamma22' - Gamma 2.2 grayscale'gray_gamma18' - Gamma 1.8 grayscaleIntent Selection Guidelines:
'perceptual' - Best for photographic content, maintains visual relationships'relative' - Good for graphics, maintains white point mapping'saturation' - Preserves color saturation, good for business graphics'absolute' - Exact colorimetric match, for proofing and scientific applicationsclass CMS:
available: bool
class INTENT:
PERCEPTUAL = 0
RELATIVE_COLORIMETRIC = 1
SATURATION = 2
ABSOLUTE_COLORIMETRIC = 3
class FLAGS:
NOTPRECALC = 0x0100 # Disable pre-calculation
GAMUTCHECK = 0x1000 # Enable gamut checking
SOFTPROOFING = 0x4000 # Soft proofing mode
BLACKPOINTCOMPENSATION = 0x2000 # Black point compensation
NOWHITEONWHITEFIXUP = 0x0004 # Disable white on white fixup
HIGHRESPRECALC = 0x0400 # Use high resolution pre-calculation
LOWRESPRECALC = 0x0800 # Use low resolution pre-calculation
class PT:
# Pixel types for colorspace specification
GRAY = 0
RGB = 1
CMY = 2
CMYK = 3
YCbCr = 4
YUV = 5
XYZ = 6
Lab = 7
YUVK = 8
HSV = 9
HLS = 10
Yxy = 11# Standard illuminants (x, y coordinates)
D50_WHITEPOINT = (0.3457, 0.3585) # Printing standard
D65_WHITEPOINT = (0.3127, 0.3290) # Daylight, sRGB standard
A_WHITEPOINT = (0.4476, 0.4074) # Incandescent light
E_WHITEPOINT = (0.3333, 0.3333) # Equal energyclass CmsError(Exception):
"""CMS codec exception for color management errors."""
# Common error scenarios:
# - Invalid ICC profile data
# - Incompatible color space conversion
# - Unsupported pixel format
# - Profile creation failureCommon error handling patterns:
import imagecodecs
try:
transformed = imagecodecs.cms_transform(image, 'srgb', 'adobe_rgb')
except imagecodecs.CmsError as e:
print(f"Color transformation failed: {e}")
# Fallback to original image or alternative profile
transformed = image
except imagecodecs.DelayedImportError:
print("Color management not available, using original image")
transformed = imageInstall with Tessl CLI
npx tessl i tessl/pypi-imagecodecs