Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion requirements.txt

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

torchvision 0.27.1 depends on torch 2.12.1, the current requirements.txt can't be installed like this.

Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
torch==2.13.0
torch==2.12.1
torchvision==0.27.1
pycocotools
albumentations
Expand Down
28 changes: 0 additions & 28 deletions src/Database/Factories/MaiaJobStateFactory.php

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,6 @@ public function up()
->onDelete('cascade');

$table->integer('shape_id')->unsigned();
$table->foreign('shape_id')
->references('id')
->on('shapes')
->onDelete('restrict');

$table->integer('job_id')->unsigned();
$table->foreign('job_id')
Expand All @@ -114,10 +110,6 @@ public function up()
->onDelete('cascade');

$table->integer('shape_id')->unsigned();
$table->foreign('shape_id')
->references('id')
->on('shapes')
->onDelete('restrict');

$table->integer('job_id')->unsigned();
$table->foreign('job_id')
Expand Down
59 changes: 59 additions & 0 deletions src/Database/migrations/2026_08_31_185108_remove_invalid_fks.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
$this->dropForeignKeyIfExists(
'maia_training_proposals',
'maia_training_proposals_shape_id_foreign',
);

$this->dropForeignKeyIfExists(
'maia_annotation_candidates',
'maia_annotation_candidates_shape_id_foreign',
);
}

private function dropForeignKeyIfExists($table, $constraint)
{
$driverName = DB::getDriverName();
if ($driverName !== 'pgsql') // TODO do we support others?
{
throw new RuntimeException("Unsupported DB driver '$driverName'. Only psql is supported");
}

DB::statement(sprintf(
'alter table %s drop constraint if exists %s',
$table, $constraint
));
}

/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('maia_training_proposals', function (Blueprint $t) {
$t->foreign('shape_id')
->references('id')
->on('shapes')
->onDelete('restrict');
});

Schema::table('maia_annotation_candidates', function (Blueprint $t) {
$t->foreign('shape_id')
->references('id')
->on('shapes')
->onDelete('restrict');
});
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('maia_jobs', fn (Blueprint $t) => $t->dropForeign(['state_id']));
Schema::dropIfExists('maia_job_states');
}

/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::create('maia_job_states', function (Blueprint $table) {
$table->increments('id');
$table->string('name', 64);
});

DB::table('maia_job_states')->insert([
['name' => 'novelty-detection'],
['name' => 'failed-novelty-detection'],
['name' => 'training-proposals'],
['name' => 'annotation-candidates'],
['name' => 'instance-segmentation'],
['name' => 'failed-instance-segmentation'],
]);

Schema::table('maia_jobs', function (Blueprint $t) {
$t->foreign('state_id')
->references('id')
->on('maia_job_states')
->onDelete('restrict');
});
}
};
3 changes: 2 additions & 1 deletion src/Http/Controllers/Views/MaiaJobController.php
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,8 @@ public function show(Request $request, $id)
$job = MaiaJob::findOrFail($id);
$this->authorize('access', $job);
$volume = $job->volume;
$states = State::pluck('id', 'name');
$states = collect(State::cases())
->mapWithKeys(fn (State $state) => [$state->label() => $state->value]);

$user = $request->user();

Expand Down
4 changes: 2 additions & 2 deletions src/Jobs/ProcessNoveltyDetectedImage.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ public function __construct(
/**
* {@inheritdoc}
*/
protected function getAnnotationQuery(VolumeFile $file): Builder
protected function getAnnotationQuery(): Builder
{
return TrainingProposal::where('image_id', $file->id)
return TrainingProposal::where('image_id', $this->file->id)
->where('job_id', $this->maiaJob->id)
->when(!empty($this->only), fn ($q) => $q->whereIn('id', $this->only));
}
Expand Down
4 changes: 2 additions & 2 deletions src/Jobs/ProcessObjectDetectedImage.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ public function __construct(
/**
* {@inheritdoc}
*/
protected function getAnnotationQuery(VolumeFile $file): Builder
protected function getAnnotationQuery(): Builder
{
return AnnotationCandidate::where('image_id', $file->id)
return AnnotationCandidate::where('image_id', $this->file->id)
->where('job_id', $this->maiaJob->id)
->when(!empty($this->only), fn ($q) => $q->whereIn('id', $this->only));
}
Expand Down
6 changes: 3 additions & 3 deletions src/MaiaAnnotation.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,11 @@ public function image()
/**
* The shape of this MAIA annotation.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
* @return Shape
*/
public function shape()
public function getShapeAttribute()
{
return $this->belongsTo(Shape::class);
return Shape::from($this->shape_id);
}

/**
Expand Down
6 changes: 3 additions & 3 deletions src/MaiaJob.php
Original file line number Diff line number Diff line change
Expand Up @@ -77,11 +77,11 @@ public function user()
/**
* The state of this MAIA job.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
* @return MaiaJobState
*/
public function state()
public function getStateAttribute()
{
return $this->belongsTo(MaiaJobState::class);
return MaiaJobState::from($this->state_id);
}

/**
Expand Down
152 changes: 106 additions & 46 deletions src/MaiaJobState.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,53 +2,113 @@

namespace Biigle\Modules\Maia;

use Biigle\Modules\Maia\Database\Factories\MaiaJobStateFactory;
use Biigle\Traits\HasConstantInstances;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Override;
use ValueError;

class MaiaJobState extends Model
/**
* Represents the different stages of a Maia job.
*/
enum MaiaJobState: int implements \JsonSerializable
{
use HasConstantInstances, HasFactory;

/**
* The constant instances of this model.
*
* @var array
*/
const INSTANCES = [
// The novelty detection stage.
'noveltyDetection' => 'novelty-detection',
// A failure during novelty detection.
'failedNoveltyDetection' => 'failed-novelty-detection',
// The manual selection and refinement of training proposals stage.
'trainingProposals' => 'training-proposals',
// The object detection stage.
// NOTE: This was incorrectly called instance segmentation before and the
// entry in the DB still uses this term.
'objectDetection' => 'instance-segmentation',
// A failure during object detection.
// NOTE: This was incorrectly called instance segmentation before and the
// entry in the DB still uses this term.
'failedObjectDetection' => 'failed-instance-segmentation',
// The manual review of annotation candidates stage.
'annotationCandidates' => 'annotation-candidates',
];

/**
* Don't maintain timestamps for this model.
*
* @var bool
*/
public $timestamps = false;

/**
* Create a new factory instance for the model.
*
* @return \Illuminate\Database\Eloquent\Factories\Factory
*/
protected static function newFactory()
{
return MaiaJobStateFactory::new();
// The novelty detection stage.
case NOVELTY_DETECTION = 1;
// A failure during novelty detection.
case FAILED_NOVELTY_DETECTION = 2;
// The manual selection and refinement of training proposals stage.
case TRAINING_PROPOSALS = 3;
// The manual review of annotation candidates stage.
case ANNOTATION_CANDIDATES = 4;
// The object detection stage.
// NOTE: This was incorrectly called instance segmentation before
case OBJECT_DETECTION = 5;
// A failure during object detection.
// NOTE: This was incorrectly called instance segmentation before
case FAILED_OBJECT_DETECTION = 6;

public static function noveltyDetection(): self
{
return self::NOVELTY_DETECTION;
}

public static function failedNoveltyDetection(): self
{
return self::FAILED_NOVELTY_DETECTION;
}

public static function trainingProposals(): self
{
return self::TRAINING_PROPOSALS;
}

public static function annotationCandidates(): self
{
return self::ANNOTATION_CANDIDATES;
}

public static function objectDetection(): self
{
return self::OBJECT_DETECTION;
}

public static function failedObjectDetection(): self
{
return self::FAILED_OBJECT_DETECTION;
}

public static function noveltyDetectionId(): int
{
return self::NOVELTY_DETECTION->value;
}

public static function failedNoveltyDetectionId(): int
{
return self::FAILED_NOVELTY_DETECTION->value;
}

public static function trainingProposalsId(): int
{
return self::TRAINING_PROPOSALS->value;
}

public static function annotationCandidatesId(): int
{
return self::ANNOTATION_CANDIDATES->value;
}

public static function objectDetectionId(): int
{
return self::OBJECT_DETECTION->value;
}

public static function failedObjectDetectionId(): int
{
return self::FAILED_OBJECT_DETECTION->value;
}

public function label(): string
{
return match ($this) {
self::NOVELTY_DETECTION => 'novelty-detection',
self::FAILED_NOVELTY_DETECTION => 'failed-novelty-detection',
self::TRAINING_PROPOSALS => 'training-proposals',
self::ANNOTATION_CANDIDATES => 'annotation-candidates',
self::OBJECT_DETECTION => 'instance-segmentation',
self::FAILED_OBJECT_DETECTION => 'failed-instance-segmentation',
};
}

public function toArray(): array
{
return [
'id' => $this->value,
'name' => $this->label(),
];
}

#[Override]
public function jsonSerialize(): mixed
{
return $this->toArray();
}
}

Loading
Loading