**Note**: The GD extension is required to use this transformer. * * @category Machine Learning * @package Rubix/ML * @author Stylianos Tzourelis */ class ImageRotator implements Transformer { /** * The color of the area of the image filled in after rotation and cropping. * * @var int */ protected const FILL_COLOR = 0; /** * The number of degrees to rotate the image. * * @var float */ protected float $offset; /** * The amount of random jitter to add to the rotation. * * @var float */ protected float $jitter; /** * @param float $offset * @param float $jitter * @throws InvalidArgumentException */ public function __construct(float $offset, float $jitter = 0.0) { ExtensionIsLoaded::with('gd')->check(); if ($offset < -360.0 or $offset > 360.0) { throw new InvalidArgumentException('Offset must be ' . " greater than -360, and less than 360 and $offset given."); } if ($jitter < 0.0 or $jitter > 1.0) { throw new InvalidArgumentException('Jitter must be ' . " greater than 0, and less than 1 and $jitter given."); } $this->offset = $offset; $this->jitter = $jitter; } /** * Return the data types that this transformer is compatible with. * * @internal * * @return list */ public function compatibility() : array { return DataType::all(); } /** * Transform the dataset in place. * * @param array $samples */ public function transform(array &$samples) : void { array_walk($samples, [$this, 'rotateAndCrop']); } /** * Randomly rotates and crops the images in a sample to their original size. * * @internal * * @param list $sample */ protected function rotateAndCrop(array &$sample) : void { foreach ($sample as &$value) { if (DataType::detect($value)->isImage()) { $degrees = $this->rotationAngle(); $originalWidth = imagesx($value); $originalHeight = imagesy($value); $rotated = imagerotate($value, $degrees, self::FILL_COLOR); if ($rotated) { $newHeight = imagesy($rotated); $newWidth = imagesx($rotated); if ($originalHeight !== $newHeight or $originalWidth !== $newWidth) { $rotated = imagecrop($rotated, [ 'x' => $newWidth / 2 - $originalWidth / 2, 'y' => $newHeight / 2 - $originalHeight / 2, 'width' => $originalWidth, 'height' => $originalHeight ]); } $value = $rotated; } } } } /** * Return an angle with a given offset with random jitter in degrees between 0 and 360. * * @return float */ protected function rotationAngle() : float { $maxDegrees = $this->jitter * 180.0; $phi = getrandmax() / $maxDegrees; $mHat = intval($maxDegrees * $phi); $jitter = rand(-$mHat, $mHat) / $phi; $angle = $this->offset + $jitter; while ($angle < 0.0) { $angle += 360.0; } while ($angle >= 360.0) { $angle -= 360.0; } return $angle; } /** * Return the string representation of the object. * * @internal * * @return string */ public function __toString() : string { return "Image Rotator (offset: {$this->offset}, jitter: {$this->jitter})"; } }