Skip to content

Commit 6e938ec

Browse files
feat: Make Image utility immutable
1 parent 3d8257a commit 6e938ec

3 files changed

Lines changed: 173 additions & 120 deletions

File tree

docs/utils/image.md

Lines changed: 31 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,9 @@ Image::setDriver(ImageDriver::GD);
4747
## Image Processing Operations
4848

4949
The `Image` utility class provides a range of image processing operations that can be performed on images. These
50-
operations include:
50+
operations are immutable, meaning that all operations essentially return a new instance of `Image` with the operation
51+
applied without affecting the current instance. This allows for method chaining and simplifies the process of applying
52+
multiple operations to an image.
5153

5254
- ### `read(string $input, array $options = [])`
5355
Reads an image from a file path, URL, or resource and returns an image object.
@@ -94,8 +96,7 @@ operations include:
9496
```
9597

9698
- ### `resize(int $width, int $height, int|Resample $resample = 2)`
97-
Resizes an image to the specified width and height, using the specified resampling method. This operation affects the
98-
instance it's called on, but still returns that instance for method chaining.
99+
Resizes an image to the specified width and height, using the specified resampling method.
99100

100101
Parameters:
101102
- `$width` (int) The target width of the resized image.
@@ -111,8 +112,7 @@ operations include:
111112
```
112113

113114
- ### `thumbnail(int $width, int $height, int|Resample $resample = 2)`
114-
Creates a thumbnail of the image with the specified width and height, using the specified resampling method. This
115-
operation affects the instance it's called on, but still returns that instance for method chaining.
115+
Creates a thumbnail of the image with the specified width and height, using the specified resampling method.
116116

117117
Parameters:
118118
- `$width` (int) The target width of the thumbnail.
@@ -128,8 +128,7 @@ operations include:
128128
```
129129

130130
- ### `crop(int $xMin, int $yMin, int $xMax, int $yMax)`
131-
Crops the image to the specified bounding box defined by the top-left and bottom-right coordinates. This operation
132-
affects the instance it's called on, but still returns that instance for method chaining.
131+
Crops the image to the specified bounding box defined by the top-left and bottom-right coordinates.
133132

134133
Parameters:
135134
- `$xMin` (int) The x-coordinate of the top-left corner of the bounding box.
@@ -146,8 +145,7 @@ operations include:
146145
```
147146

148147
- ### `centerCrop(int $width, int $height)`
149-
Crops the image to the specified width and height by centering the crop around the image's center. This operation
150-
affects the instance it's called on, but still returns that instance for method chaining.
148+
Crops the image to the specified width and height by centering the crop around the image's center.
151149

152150
Parameters:
153151
- `$width` (int) The target width of the cropped image.
@@ -162,8 +160,7 @@ operations include:
162160
```
163161

164162
- ### `pad(int $left, int $right, int $top, int $bottom)`
165-
Pads the image with the specified number of pixels on each side. This operation affects the instance it's called on,
166-
but still returns that instance for method chaining.
163+
Pads the image with the specified number of pixels on each side.
167164

168165
Parameters:
169166
- `$left` (int) The number of pixels to add to the left side.
@@ -180,8 +177,7 @@ operations include:
180177
```
181178

182179
- ### `grayscale()`
183-
Converts the image to grayscale. This operation affects the instance it's called on, but still returns that instance
184-
for method chaining.
180+
Converts the image to grayscale.
185181

186182
Returns:
187183
- An image object representing the grayscale image.
@@ -192,8 +188,7 @@ operations include:
192188
```
193189

194190
- ### `rgb()`
195-
Converts the image to RGB color space. This operation affects the instance it's called on, but still returns that
196-
instance for method chaining.
191+
Converts the image to RGB color space.
197192

198193
Returns:
199194
- An image object representing the RGB image.
@@ -204,8 +199,7 @@ operations include:
204199
```
205200

206201
- ### `rgba()`
207-
Converts the image to RGBA color space. This operation affects the instance it's called on, but still returns that
208-
instance for method chaining.
202+
Converts the image to RGBA color space.
209203

210204
Returns:
211205
- An image object representing the RGBA image.
@@ -214,10 +208,27 @@ operations include:
214208
```php
215209
$rgbaImage = $image->rgba();
216210
```
211+
212+
- ### `applyMask(Image $mask)`
213+
Applies a mask to the current image.
214+
215+
Parameters:
216+
- `$mask` (Image) The mask to apply.
217+
218+
Returns:
219+
- An image object representing the image with the mask applied.
220+
221+
Throws:
222+
- `InvalidArgumentException` if the given mask doesn't match the current image's size or if the image driver is unsupported.
223+
- `RuntimeException` if the apply mask operation fails.
224+
225+
Example:
226+
```php
227+
$maskedImage = $image->applyMask($mask);
228+
```
217229

218230
- ### `drawRectangle(int $xMin, int $yMin, int $xMax, int $yMax, string $color = 'FFF', $fill = false, float $thickness = 1)`
219-
Draws a rectangle on the image with the specified coordinates, color, and thickness. This operation affects the
220-
instance it's called on, but still returns that instance for method chaining.
231+
Draws a rectangle on the image with the specified coordinates, color, and thickness.
221232

222233
Parameters:
223234
- `$xMin` (int) The x-coordinate of the top-left corner of the rectangle.
@@ -238,8 +249,7 @@ operations include:
238249

239250
- ### `drawText(string $text, int $xPos, int $yPos, string $fontFile, int $fontSize = 16, string $color = 'FFF')`
240251

241-
Draws text on the image at the specified position with the specified font, size, and color. This operation affects the
242-
instance it's called on, but still returns that instance for method chaining.
252+
Draws text on the image at the specified position with the specified font, size, and color.
243253

244254
Parameters:
245255
- `$text` (string) The text to draw on the image.

src/FeatureExtractors/ImageFeatureExtractor.php

Lines changed: 33 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -7,57 +7,36 @@
77

88
use Codewithkyrian\Transformers\Tensor\Tensor;
99
use Codewithkyrian\Transformers\Utils\Image;
10+
use Exception;
1011
use Imagine\Image\Point;
1112

1213
class ImageFeatureExtractor extends FeatureExtractor
1314
{
14-
/**
15-
* The mean values for image normalization.
16-
* @var int|int[]
17-
*/
15+
/** The mean values for image normalization. */
1816
protected int|array|null $imageMean;
1917

20-
/**
21-
* The standard deviation values for image normalization.
22-
* @var int|int[]
23-
*/
18+
/** The standard deviation values for image normalization. */
2419
protected int|array|null $imageStd;
2520

26-
/*
27-
* What method to use for resampling.
28-
*/
21+
/* What method to use for resampling. */
2922
protected int $resample;
3023

31-
/**
32-
* Whether to rescale the image pixel values to the [0,1] range.
33-
* @var bool
34-
*/
24+
/** Whether to rescale the image pixel values to the [0,1] range. */
3525
protected bool $doRescale;
3626

37-
/**
38-
* The factor to use for rescaling the image pixel values.
39-
* @var float
40-
*/
27+
/** The factor to use for rescaling the image pixel values. */
4128
protected float $rescaleFactor;
4229

43-
/**
44-
* Whether to normalize the image pixel values.
45-
* @var ?bool
46-
*/
30+
/** Whether to normalize the image pixel values. */
4731
protected ?bool $doNormalize;
4832

49-
/**
50-
* Whether to resize the image.
51-
* @var ?bool
52-
*/
33+
/** Whether to resize the image. */
5334
protected ?bool $doResize;
5435

36+
/** The size to resize the image to. */
5537
protected ?bool $doThumbnail;
5638

57-
/**
58-
* The size to resize the image to.
59-
* @var ?array
60-
*/
39+
/** The size to resize the image to. */
6140
protected ?array $size;
6241
protected mixed $sizeDivisibility;
6342
protected ?bool $doCenterCrop;
@@ -102,18 +81,20 @@ public function __construct(public array $config)
10281

10382
/**
10483
* Pad the image by a certain amount.
84+
*
10585
* @param Tensor $imageTensor The pixel data to pad.
10686
* @param int[]|int $padSize The dimensions of the padded image.
10787
* @param string $mode The type of padding to add.
10888
* @param bool $center Whether to center the image.
10989
* @param int $constantValues The constant value to use for padding.
90+
*
11091
* @return Tensor The padded pixel data and image dimensions.
111-
* @throws \Exception
92+
* @throws Exception
11293
*/
11394
public function padImage(
11495
Tensor $imageTensor,
11596
int|array $padSize,
116-
string $tensorFormat = 'CHW', // 'HWC' or 'CHW
97+
string $tensorFormat = 'CHW', // 'HWC' or 'CHW
11798
string $mode = 'constant',
11899
bool $center = false,
119100
int $constantValues = 0
@@ -170,7 +151,7 @@ public function padImage(
170151

171152
if ($mode === 'symmetric') {
172153
if ($center) {
173-
throw new \Exception('`center` padding is not supported when `mode` is set to `symmetric`.');
154+
throw new Exception('`center` padding is not supported when `mode` is set to `symmetric`.');
174155
// TODO: Implement this
175156
}
176157
$h1 = $imageHeight - 1;
@@ -210,9 +191,12 @@ private function calculateReflectOffset(int $val, int $max): int
210191
/**
211192
* Find the target (width, height) dimension of the output image after
212193
* resizing given the input image and the desired size.
194+
*
213195
* @param Image $image The image to be resized.
214196
* @param int|array|null $size The size to use for resizing the image.
197+
*
215198
* @return array The target (width, height) dimension of the output image after resizing.
199+
* @throws Exception
216200
*/
217201
public function getResizeOutputImageSize(Image $image, int|array|null $size): array
218202
{
@@ -286,7 +270,7 @@ public function getResizeOutputImageSize(Image $image, int|array|null $size): ar
286270
} elseif ($this->sizeDivisibility != null) {
287271
return $this->enforceSizeDivisibility([$srcWidth, $srcHeight], $this->sizeDivisibility);
288272
} else {
289-
throw new \Exception("Could not resize image due to unsupported 'size' parameter passed: " . json_encode($size));
273+
throw new Exception("Could not resize image due to unsupported 'size' parameter passed: ".json_encode($size));
290274
}
291275
}
292276

@@ -295,12 +279,13 @@ public function getResizeOutputImageSize(Image $image, int|array|null $size): ar
295279
* Preprocesses the given image.
296280
*
297281
* @param Image $image The image to preprocess.
298-
* @param ?bool $doNormalize
299-
* @param ?bool $doPad
300-
* @param ?bool $doConvertRGB
301-
* @param ?bool $doConvertGrayscale
282+
* @param ?bool $doNormalize Whether to normalize the image.
283+
* @param ?bool $doPad Whether to pad the image.
284+
* @param ?bool $doConvertRGB Whether to convert the image to RGB.
285+
* @param ?bool $doConvertGrayscale Whether to convert the image to grayscale.
286+
*
302287
* @return array The preprocessed image.
303-
* @throws \Exception
288+
* @throws Exception
304289
*/
305290
public function preprocess(
306291
Image $image,
@@ -316,7 +301,6 @@ public function preprocess(
316301
$image = $image->cropMargin();
317302
}
318303

319-
320304
$originalInputSize = $image->size(); // original image size
321305

322306
// Convert image to RGB if specified in config.
@@ -348,7 +332,7 @@ public function preprocess(
348332
$cropHeight = $this->cropSize['height'];
349333
}
350334

351-
$image = $image->centerCrop($cropWidth, $cropHeight);
335+
$image = $image->centerCrop($cropWidth, $cropHeight);
352336
}
353337

354338
$reshapedInputSize = $image->size();
@@ -362,7 +346,7 @@ public function preprocess(
362346
if ($doNormalize ?? $this->doNormalize) {
363347
if (is_array($this->imageMean)) {
364348
// Negate the mean values to add instead of subtract
365-
$negatedMean = array_map(fn($mean) => -$mean, $this->imageMean);
349+
$negatedMean = array_map(fn ($mean) => -$mean, $this->imageMean);
366350
$imageMean = Tensor::repeat($negatedMean, $image->height() * $image->width(), 1);
367351
} else {
368352
$imageMean = Tensor::fill([$image->channels * $image->height() * $image->width()], -$this->imageMean);
@@ -371,7 +355,7 @@ public function preprocess(
371355

372356
if (is_array($this->imageStd)) {
373357
// Inverse the standard deviation values to multiple instead of divide
374-
$inversedStd = array_map(fn($std) => 1 / $std, $this->imageStd);
358+
$inversedStd = array_map(fn ($std) => 1 / $std, $this->imageStd);
375359
$imageStd = Tensor::repeat($inversedStd, $image->height() * $image->width(), 1);
376360
} else {
377361
$imageStd = Tensor::fill([$image->channels * $image->height() * $image->width()], 1 / $this->imageStd);
@@ -383,7 +367,7 @@ public function preprocess(
383367
$imageStd = $imageStd->reshape($imageTensor->shape());
384368

385369
if (count($imageMean) !== $image->channels || count($imageStd) !== $image->channels) {
386-
throw new \Exception("When set to arrays, the length of `imageMean` (" . count($imageMean) . ") and `imageStd` (" . count($imageStd) . ") must match the number of channels in the image ({$image->channels}).");
370+
throw new Exception("When set to arrays, the length of `imageMean` (".count($imageMean).") and `imageStd` (".count($imageStd).") must match the number of channels in the image ({$image->channels}).");
387371
}
388372

389373
// Normalize pixel data
@@ -411,31 +395,26 @@ public function preprocess(
411395
* Calls the feature extraction process on an array of images,
412396
* preprocesses each image, and concatenates the resulting
413397
* features into a single Tensor.
398+
*
414399
* @param Image|Image[] $images The image(s) to extract features from.
415400
* @param mixed ...$args Additional arguments.
401+
*
416402
* @return array An object containing the concatenated pixel values (and other metadata) of the preprocessed images.
417403
*/
418404
public function __invoke(Image|array $images, ...$args): array
419405
{
420-
// Ensure $images is an array
421406
if (!is_array($images)) {
422407
$images = [$images];
423408
}
424409

425-
// Preprocess each image
426-
$imageData = [];
427-
foreach ($images as $image) {
428-
$imageData[] = $this->preprocess($image);
429-
}
410+
$imageData = array_map([$this, 'preprocess'], $images);
430411

431412
$pixelValues = array_column($imageData, 'pixel_values');
432413
$originalSizes = array_column($imageData, 'original_size');
433414
$reshapedInputSizes = array_column($imageData, 'reshaped_input_size');
434415

435-
$stackedPixelValues = Tensor::stack($pixelValues, 0);
436-
437416
return [
438-
'pixel_values' => $stackedPixelValues,
417+
'pixel_values' => Tensor::stack($pixelValues),
439418
'original_sizes' => $originalSizes,
440419
'reshaped_input_sizes' => $reshapedInputSizes
441420
];

0 commit comments

Comments
 (0)