Skip to content

images

images

Image Processing Utilities

This module provides utilities for image processing, conversion, and manipulation. Includes support for base64 encoding/decoding, image comparison, and various image operations.

Functions:

Name Description
isBase64

Check if a string is valid base64

handle_string_to_bytes_and_decode

Convert string/bytes to decoded bytes

handle_string_to_bytes_and_encode

Convert string/bytes to base64 encoded bytes

are_same_image

Compare two images for equality

Extended PIL.Image Methods: to_bytes: Convert PIL Image to bytes crop_square: Crop image to square aspect ratio from_image_file: Create PIL Image from file path (class method) from_bytestr: Create PIL Image from bytes/string (class method)

Example

Check if string is base64

is_valid = isBase64("SGVsbG8gV29ybGQ=") # True

Load image from file and crop to square

img = ImageUtils.from_image_file("photo.jpg") square_img = img.crop_square() img_bytes = img.to_bytes()

Compare two images

img1 = ImageUtils.from_image_file("image1.jpg") img2 = ImageUtils.from_image_file("image2.jpg") same = are_same_image(img1, img2)

Note

Requires PIL (Pillow) and numpy for full functionality.

ImageUtils

Utility class providing extended functionality for PIL Images.

This class provides static methods that extend PIL Image functionality without requiring monkey-patching of the PIL Image class.

crop_square staticmethod

crop_square(image)

Crop image to square aspect ratio from center.

Parameters:

Name Type Description Default
image

PIL Image object to crop

required

Returns:

Type Description

PIL Image: Cropped square image

Raises:

Type Description
ImportError

If PIL is not available

ImageProcessingError

If cropping fails

Example

img = ImageUtils.from_image_file("rectangle.jpg") square = ImageUtils.crop_square(img) print(square.size) # (min_dimension, min_dimension)

Source code in src/crew_dcs/utils/images.py
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
@staticmethod
def crop_square(image):
    """
    Crop image to square aspect ratio from center.

    Args:
        image: PIL Image object to crop

    Returns:
        PIL Image: Cropped square image

    Raises:
        ImportError: If PIL is not available
        ImageProcessingError: If cropping fails

    Example:
        >>> img = ImageUtils.from_image_file("rectangle.jpg")
        >>> square = ImageUtils.crop_square(img)
        >>> print(square.size)  # (min_dimension, min_dimension)
    """

    try:
        width, height = image.size
        new_edge = min(width, height)

        left = (width - new_edge) / 2
        top = (height - new_edge) / 2
        right = (width + new_edge) / 2
        bottom = (height + new_edge) / 2

        return image.crop((left, top, right, bottom))

    except (OSError, ValueError, AttributeError) as e:
        raise ImageProcessingError("crop image to square", str(e)) from e

from_bytestr classmethod

from_bytestr(data: str | bytes)

Create PIL Image from bytes or base64 string.

Parameters:

Name Type Description Default
data Union[str, bytes]

Image data as bytes or base64 string

required

Returns:

Type Description

PIL Image: Loaded image object

Raises:

Type Description
ImportError

If PIL is not available

ImageProcessingError

If image loading fails

Example
From bytes

with open("image.jpg", "rb") as f: ... img_bytes = f.read() img = ImageUtils.from_bytestr(img_bytes)

From base64 string

base64_str = base64.b64encode(img_bytes).decode() img = ImageUtils.from_bytestr(base64_str)

Source code in src/crew_dcs/utils/images.py
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
@classmethod
def from_bytestr(cls, data: str | bytes):
    """
    Create PIL Image from bytes or base64 string.

    Args:
        data (Union[str, bytes]): Image data as bytes or base64 string

    Returns:
        PIL Image: Loaded image object

    Raises:
        ImportError: If PIL is not available
        ImageProcessingError: If image loading fails

    Example:
        >>> # From bytes
        >>> with open("image.jpg", "rb") as f:
        ...     img_bytes = f.read()
        >>> img = ImageUtils.from_bytestr(img_bytes)

        >>> # From base64 string
        >>> base64_str = base64.b64encode(img_bytes).decode()
        >>> img = ImageUtils.from_bytestr(base64_str)
    """
    try:
        data = handle_string_to_bytes_and_decode(data)
        return PIL.Image.open(io.BytesIO(data))

    except (OSError, ValueError, UnidentifiedImageError) as e:
        raise ImageProcessingError("load image from bytes/string", str(e)) from e

from_image_file classmethod

from_image_file(image_path: str)

Create PIL Image from file path.

Parameters:

Name Type Description Default
image_path str

Path to image file

required

Returns:

Type Description

PIL Image: Loaded image object

Raises:

Type Description
ImportError

If PIL is not available

FileNotFoundError

If image file doesn't exist

ImageProcessingError

If image loading fails

Example

img = ImageUtils.from_image_file("/path/to/image.jpg") print(img.size) # (width, height)

Source code in src/crew_dcs/utils/images.py
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
@classmethod
def from_image_file(cls, image_path: str):
    """
    Create PIL Image from file path.

    Args:
        image_path (str): Path to image file

    Returns:
        PIL Image: Loaded image object

    Raises:
        ImportError: If PIL is not available
        FileNotFoundError: If image file doesn't exist
        ImageProcessingError: If image loading fails

    Example:
        >>> img = ImageUtils.from_image_file("/path/to/image.jpg")
        >>> print(img.size)  # (width, height)
    """

    try:
        with open(image_path, "rb") as file:
            data = file.read()

        data = handle_string_to_bytes_and_decode(data)
        return PIL.Image.open(io.BytesIO(data))

    except (OSError, ValueError, UnidentifiedImageError) as e:
        raise ImageProcessingError(
            f"load image from file {image_path}", str(e)
        ) from e

to_bytes staticmethod

to_bytes(image, format_type: str | None = None) -> bytes

Convert PIL Image to bytes.

Parameters:

Name Type Description Default
image

PIL Image object

required
format_type str

Image format (PNG, JPEG, etc.). If None, uses image's original format.

None

Returns:

Name Type Description
bytes bytes

Image data as bytes

Raises:

Type Description
ImportError

If PIL is not available

ImageProcessingError

If conversion fails

Example

img = ImageUtils.from_image_file("photo.jpg") img_bytes = ImageUtils.to_bytes(img) png_bytes = ImageUtils.to_bytes(img, "PNG")

Source code in src/crew_dcs/utils/images.py
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
@staticmethod
def to_bytes(image, format_type: str | None = None) -> bytes:
    """
    Convert PIL Image to bytes.

    Args:
        image: PIL Image object
        format_type (str, optional): Image format (PNG, JPEG, etc.).
                                   If None, uses image's original format.

    Returns:
        bytes: Image data as bytes

    Raises:
        ImportError: If PIL is not available
        ImageProcessingError: If conversion fails

    Example:
        >>> img = ImageUtils.from_image_file("photo.jpg")
        >>> img_bytes = ImageUtils.to_bytes(img)
        >>> png_bytes = ImageUtils.to_bytes(img, "PNG")
    """

    try:
        byte_arr = io.BytesIO()
        save_format = format_type or getattr(image, "format", "PNG")
        image.save(byte_arr, format=save_format)
        return byte_arr.getvalue()

    except (OSError, ValueError, AttributeError) as e:
        raise ImageProcessingError("convert image to bytes", str(e)) from e

are_same_image

are_same_image(image1, image2) -> bool

Compare two PIL Images to determine if they are identical.

Uses PIL.ImageChops.difference to compare images pixel by pixel.

Parameters:

Name Type Description Default
image1

First PIL Image to compare

required
image2

Second PIL Image to compare

required

Returns:

Name Type Description
bool bool

True if images are identical, False otherwise

Raises:

Type Description
ImportError

If PIL or numpy are not available

ImageProcessingError

If image comparison fails

Example

img1 = ImageUtils.from_image_file("image1.jpg") img2 = ImageUtils.from_image_file("image2.jpg") same = are_same_image(img1, img2) print(same) # True or False

Source code in src/crew_dcs/utils/images.py
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
def are_same_image(image1, image2) -> bool:
    """
    Compare two PIL Images to determine if they are identical.

    Uses PIL.ImageChops.difference to compare images pixel by pixel.

    Args:
        image1: First PIL Image to compare
        image2: Second PIL Image to compare

    Returns:
        bool: True if images are identical, False otherwise

    Raises:
        ImportError: If PIL or numpy are not available
        ImageProcessingError: If image comparison fails

    Example:
        >>> img1 = ImageUtils.from_image_file("image1.jpg")
        >>> img2 = ImageUtils.from_image_file("image2.jpg")
        >>> same = are_same_image(img1, img2)
        >>> print(same)  # True or False
    """

    try:
        img_chop = PIL.ImageChops.difference(image1, image2)
        pixel_sum = np.sum(np.array(img_chop.getdata()))
        print(f"Pixel difference sum: {pixel_sum}")
        return pixel_sum == 0

    except ValueError as e:
        print(f"Image comparison error: {e}")
        return False
    except (OSError, UnidentifiedImageError, RuntimeError) as e:
        raise ImageProcessingError("compare images", str(e)) from e

handle_string_to_bytes_and_decode

handle_string_to_bytes_and_decode(
    data: str | bytes,
) -> bytes

Convert string or bytes to decoded bytes, handling base64 if present.

Parameters:

Name Type Description Default
data Union[str, bytes]

Data to convert and decode

required

Returns:

Name Type Description
bytes bytes

Decoded bytes data

Example

Base64 encoded data gets decoded

decoded = handle_string_to_bytes_and_decode("SGVsbG8=") print(decoded) # b'Hello'

Regular bytes pass through

decoded = handle_string_to_bytes_and_decode(b"Hello") print(decoded) # b'Hello'

Source code in src/crew_dcs/utils/images.py
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
def handle_string_to_bytes_and_decode(data: str | bytes) -> bytes:
    """
    Convert string or bytes to decoded bytes, handling base64 if present.

    Args:
        data (Union[str, bytes]): Data to convert and decode

    Returns:
        bytes: Decoded bytes data

    Example:
        >>> # Base64 encoded data gets decoded
        >>> decoded = handle_string_to_bytes_and_decode("SGVsbG8=")
        >>> print(decoded)  # b'Hello'

        >>> # Regular bytes pass through
        >>> decoded = handle_string_to_bytes_and_decode(b"Hello")
        >>> print(decoded)  # b'Hello'
    """
    if isinstance(data, str):
        data = data.encode("utf-8")

    if isBase64(data):
        data = base64.b64decode(data)

    return data

handle_string_to_bytes_and_encode

handle_string_to_bytes_and_encode(
    data: str | bytes,
) -> bytes

Convert string or bytes to base64 encoded bytes.

Parameters:

Name Type Description Default
data Union[str, bytes]

Data to convert and encode

required

Returns:

Name Type Description
bytes bytes

Base64 encoded bytes

Example

Raw bytes get base64 encoded

encoded = handle_string_to_bytes_and_encode(b"Hello") print(encoded) # b'SGVsbG8='

Already base64 data passes through

encoded = handle_string_to_bytes_and_encode("SGVsbG8=") print(encoded) # b'SGVsbG8='

Source code in src/crew_dcs/utils/images.py
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
def handle_string_to_bytes_and_encode(data: str | bytes) -> bytes:
    """
    Convert string or bytes to base64 encoded bytes.

    Args:
        data (Union[str, bytes]): Data to convert and encode

    Returns:
        bytes: Base64 encoded bytes

    Example:
        >>> # Raw bytes get base64 encoded
        >>> encoded = handle_string_to_bytes_and_encode(b"Hello")
        >>> print(encoded)  # b'SGVsbG8='

        >>> # Already base64 data passes through
        >>> encoded = handle_string_to_bytes_and_encode("SGVsbG8=")
        >>> print(encoded)  # b'SGVsbG8='
    """
    if isinstance(data, str):
        data = data.encode("utf-8")

    if not isBase64(data):
        data = base64.b64encode(data)

    return data

isBase64

isBase64(s: str | bytes) -> bool

Check if a string or bytes object is valid base64.

Parameters:

Name Type Description Default
s Union[str, bytes]

String or bytes to check

required

Returns:

Name Type Description
bool bool

True if input is valid base64, False otherwise

Example

isBase64("SGVsbG8gV29ybGQ=") # "Hello World" in base64 True isBase64("not-base64!") False isBase64(b"SGVsbG8gV29ybGQ=") True

Source code in src/crew_dcs/utils/images.py
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
def isBase64(s: str | bytes) -> bool:
    """
    Check if a string or bytes object is valid base64.

    Args:
        s (Union[str, bytes]): String or bytes to check

    Returns:
        bool: True if input is valid base64, False otherwise

    Example:
        >>> isBase64("SGVsbG8gV29ybGQ=")  # "Hello World" in base64
        True
        >>> isBase64("not-base64!")
        False
        >>> isBase64(b"SGVsbG8gV29ybGQ=")
        True
    """
    try:
        if isinstance(s, str):
            s = s.encode("ascii")
        return base64.b64encode(base64.b64decode(s)) == s
    except (ValueError, binascii.Error):
        return False