Implement creator studio and upload updates
This commit is contained in:
142
app/Services/Images/Detectors/VisionSubjectDetector.php
Normal file
142
app/Services/Images/Detectors/VisionSubjectDetector.php
Normal file
@@ -0,0 +1,142 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services\Images\Detectors;
|
||||
|
||||
use App\Contracts\Images\SubjectDetectorInterface;
|
||||
use App\Data\Images\CropBoxData;
|
||||
use App\Data\Images\SubjectDetectionResultData;
|
||||
use App\Models\Artwork;
|
||||
|
||||
final class VisionSubjectDetector implements SubjectDetectorInterface
|
||||
{
|
||||
public function detect(string $sourcePath, int $sourceWidth, int $sourceHeight, array $context = []): ?SubjectDetectionResultData
|
||||
{
|
||||
$boxes = $this->extractCandidateBoxes($context, $sourceWidth, $sourceHeight);
|
||||
if ($boxes === []) {
|
||||
return null;
|
||||
}
|
||||
|
||||
usort($boxes, static function (array $left, array $right): int {
|
||||
return $right['score'] <=> $left['score'];
|
||||
});
|
||||
|
||||
$best = $boxes[0];
|
||||
|
||||
return new SubjectDetectionResultData(
|
||||
cropBox: $best['box'],
|
||||
strategy: 'subject',
|
||||
reason: 'vision_subject_box',
|
||||
confidence: (float) $best['confidence'],
|
||||
meta: [
|
||||
'label' => $best['label'],
|
||||
'score' => $best['score'],
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, array{box: CropBoxData, label: string, confidence: float, score: float}>
|
||||
*/
|
||||
private function extractCandidateBoxes(array $context, int $sourceWidth, int $sourceHeight): array
|
||||
{
|
||||
$boxes = [];
|
||||
$preferredLabels = collect((array) config('uploads.square_thumbnails.subject_detector.preferred_labels', []))
|
||||
->map(static fn ($label): string => mb_strtolower((string) $label))
|
||||
->filter()
|
||||
->values()
|
||||
->all();
|
||||
|
||||
$candidates = $context['subject_boxes'] ?? $context['vision_boxes'] ?? null;
|
||||
|
||||
if ($candidates === null && ($context['artwork'] ?? null) instanceof Artwork) {
|
||||
$candidates = $this->boxesFromArtwork($context['artwork']);
|
||||
}
|
||||
|
||||
foreach ((array) $candidates as $row) {
|
||||
if (! is_array($row)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$box = $this->normalizeBox($row, $sourceWidth, $sourceHeight);
|
||||
if ($box === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$label = mb_strtolower((string) ($row['label'] ?? $row['tag'] ?? $row['name'] ?? 'subject'));
|
||||
$confidence = max(0.0, min(1.0, (float) ($row['confidence'] ?? $row['score'] ?? 0.75)));
|
||||
$areaWeight = ($box->width * $box->height) / max(1, $sourceWidth * $sourceHeight);
|
||||
$preferredBoost = in_array($label, $preferredLabels, true) ? 1.25 : 1.0;
|
||||
|
||||
$boxes[] = [
|
||||
'box' => $box,
|
||||
'label' => $label,
|
||||
'confidence' => $confidence,
|
||||
'score' => ($confidence * 0.8 + $areaWeight * 0.2) * $preferredBoost,
|
||||
];
|
||||
}
|
||||
|
||||
return $boxes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, array<string, mixed>>
|
||||
*/
|
||||
private function boxesFromArtwork(Artwork $artwork): array
|
||||
{
|
||||
return collect((array) ($artwork->yolo_objects_json ?? []))
|
||||
->filter(static fn ($row): bool => is_array($row))
|
||||
->values()
|
||||
->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $row
|
||||
*/
|
||||
private function normalizeBox(array $row, int $sourceWidth, int $sourceHeight): ?CropBoxData
|
||||
{
|
||||
$payload = is_array($row['box'] ?? null) ? $row['box'] : $row;
|
||||
|
||||
$left = $payload['x'] ?? $payload['left'] ?? $payload['x1'] ?? null;
|
||||
$top = $payload['y'] ?? $payload['top'] ?? $payload['y1'] ?? null;
|
||||
$width = $payload['width'] ?? null;
|
||||
$height = $payload['height'] ?? null;
|
||||
|
||||
if ($width === null && isset($payload['x2'], $payload['x1'])) {
|
||||
$width = (float) $payload['x2'] - (float) $payload['x1'];
|
||||
}
|
||||
|
||||
if ($height === null && isset($payload['y2'], $payload['y1'])) {
|
||||
$height = (float) $payload['y2'] - (float) $payload['y1'];
|
||||
}
|
||||
|
||||
if (! is_numeric($left) || ! is_numeric($top) || ! is_numeric($width) || ! is_numeric($height)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$left = (float) $left;
|
||||
$top = (float) $top;
|
||||
$width = (float) $width;
|
||||
$height = (float) $height;
|
||||
|
||||
$normalized = max(abs($left), abs($top), abs($width), abs($height)) <= 1.0;
|
||||
if ($normalized) {
|
||||
$left *= $sourceWidth;
|
||||
$top *= $sourceHeight;
|
||||
$width *= $sourceWidth;
|
||||
$height *= $sourceHeight;
|
||||
}
|
||||
|
||||
if ($width <= 1 || $height <= 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (new CropBoxData(
|
||||
x: (int) floor($left),
|
||||
y: (int) floor($top),
|
||||
width: (int) round($width),
|
||||
height: (int) round($height),
|
||||
))->clampToImage($sourceWidth, $sourceHeight);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user