diff --git a/app/Models/Enums/StudyAccessFlags.php b/app/Models/Enums/StudyAccessFlags.php index 6daf30d..df29ea0 100644 --- a/app/Models/Enums/StudyAccessFlags.php +++ b/app/Models/Enums/StudyAccessFlags.php @@ -8,8 +8,8 @@ enum StudyAccessFlags: int { use BitmaskFunctionality; - case None = 0; - case ViewPatientInfo = 1; - case ViewDicom = 1 << 1; - case ViewReports = 1 << 2; + case Forbidden = 0; + case ViewPatientInfo = 1 << 1; + case ViewDicom = 1 << 2; + case ViewReports = 1 << 3; } diff --git a/app/Services/Pacs/OrthancRestClient.php b/app/Services/Pacs/OrthancRestClient.php index cac5616..9330995 100644 --- a/app/Services/Pacs/OrthancRestClient.php +++ b/app/Services/Pacs/OrthancRestClient.php @@ -41,4 +41,9 @@ public function getStudies(): array return json_decode($response->getBody()->getContents(), true); } + + public function getStudiesIds(): array + { + return json_decode($this->getClient()->get('/studies')->getBody()->getContents(), true); + } } diff --git a/app/Services/Pacs/StudyImporter.php b/app/Services/Pacs/StudyImporter.php index 2bc607b..e5a0a03 100644 --- a/app/Services/Pacs/StudyImporter.php +++ b/app/Services/Pacs/StudyImporter.php @@ -5,73 +5,156 @@ use App\Models\Enums\StudyLevelStatus; use App\Models\Study; use Carbon\Carbon; +use Illuminate\Support\Facades\DB; final class StudyImporter { + private array $study_ids = []; + + private array $insert_queue = []; + + private array $update_queue = []; + + private OrthancRestClient $client; + + public function __construct(?OrthancRestClient $client = null) + { + $this->client = $client ?? new OrthancRestClient; + } + public function import(array $studies): void { foreach ($studies as $study) { - $orthanc_uid = strtolower($study['ID']); - $row = Study::where(compact('orthanc_uid'))->first(); - if ($row != null) { - if ($row->study_status < StudyLevelStatus::StudyArrived) { - // todo: update study - } - - return; - } - - $inst_name = data_get($study, 'MainDicomTags.InstitutionName'); - $inst_id = InstituteMapper::map($inst_name); - - $data = [ - 'orthanc_uid' => $orthanc_uid, - 'is_locked' => false, - 'is_active' => true, - - 'institution_name' => $inst_name, - 'institute_id' => $inst_id, - - 'patient_uuid' => $study['ParentPatient'], - 'patient_id' => data_get($study, 'PatientMainDicomTags.PatientID'), - 'patient_name' => data_get($study, 'PatientMainDicomTags.PatientName'), - 'patient_sex' => data_get($study, 'PatientMainDicomTags.PatientSex'), - - 'accession_number' => data_get($study, 'MainDicomTags.AccessionNumber'), - 'referring_physician_name' => data_get($study, 'MainDicomTags.ReferringPhysicianName'), - 'study_id' => data_get($study, 'MainDicomTags.StudyID'), - 'study_instance_uid' => data_get($study, 'MainDicomTags.StudyInstanceUID'), - 'study_date' => DicomUtils::dateTimeToCarbon($study['MainDicomTags']['StudyDate'], $study['MainDicomTags']['StudyTime']), - 'receive_date' => Carbon::parse($study['LastUpdate'], 'UTC'), - - 'study_modality' => data_get($study, 'RequestedTags.Modality'), - 'body_part_examined' => data_get($study, 'RequestedTags.BodyPartExamined'), - 'software_versions' => data_get($study, 'RequestedTags.SoftwareVersions'), - 'station_name' => data_get($study, 'RequestedTags.StationName'), - 'operators_name' => data_get($study, 'RequestedTags.OperatorsName'), - 'manufacturer' => data_get($study, 'RequestedTags.Manufacturer'), - 'manufacturer_model_name' => data_get($study, 'RequestedTags.ManufacturerModelName'), - - 'series_count' => count($study['Series']), - ]; - - if ($study['IsStable']) { - $data['study_status'] = StudyLevelStatus::StudyArrived->value; - } else { - $data['study_status'] = StudyLevelStatus::Pending->value; - } - - $dob = data_get($study, 'PatientMainDicomTags.PatientBirthDate'); - if (trim($dob) != '') { - $data['patient_birthdate'] = Carbon::parse($dob); - } - - $descr = data_get($study, 'MainDicomTags.StudyDescription'); - if ($descr != null) { - $descr = data_get($study, 'RequestedTags.AcquisitionDeviceProcessingDescription'); - } - $data['study_description'] = trim($descr); - Study::create($data); + $this->importStudy($study); } } + + public function scanStudies() + { + $this->study_ids = $this->client->getStudiesIds(); + } + + private function checkUpdate(string $orthanc_uid): void + { + $row = DB::table('studies')->where('orthanc_uid', $orthanc_uid)->first(['id', 'study_status']); + + if ($row == null) { + $this->insert_queue[] = $orthanc_uid; + } + + if ($row->study_status < StudyLevelStatus::StudyArrived->value) { + $this->update_queue[$orthanc_uid] = $row->id; + } + } + + public function filterStudies() + { + $this->insert_queue = []; + $this->update_queue = []; + foreach ($this->study_ids as $study_id) { + $this->checkUpdate($study_id); + } + } + + private function fetchStudyDetails(string $orthanc_uid): ?array + { + $study = $this->client->getStudyDetails($orthanc_uid); + if ($study == null) { + return null; + } + $stats = $this->client->getStudyStatistics($orthanc_uid); + $study['Statistics'] = $stats; + + return $study; + } + + public function importStudies() + { + foreach ($this->update_queue as $orthanc_uid => $row_id) { + $study = $this->fetchStudyDetails($orthanc_uid); + if ($study == null) { + continue; + } + $this->updateStudy($row_id, $study); + } + + foreach ($this->insert_queue as $orthanc_uid) { + $study = $this->fetchStudyDetails($orthanc_uid); + if ($study == null) { + continue; + } + $this->insertStudy($study); + } + } + + private function prepareData(mixed $study): array + { + $inst_name = data_get($study, 'MainDicomTags.InstitutionName'); + $inst_id = InstituteMapper::map($inst_name); + + $data = [ + 'orthanc_uid' => strtolower($study['ID']), + 'is_locked' => false, + 'is_active' => true, + + 'institution_name' => $inst_name, + 'institute_id' => $inst_id, + + 'patient_uuid' => $study['ParentPatient'], + 'patient_id' => data_get($study, 'PatientMainDicomTags.PatientID'), + 'patient_name' => data_get($study, 'PatientMainDicomTags.PatientName'), + 'patient_sex' => data_get($study, 'PatientMainDicomTags.PatientSex'), + + 'accession_number' => data_get($study, 'MainDicomTags.AccessionNumber'), + 'referring_physician_name' => data_get($study, 'MainDicomTags.ReferringPhysicianName'), + 'study_id' => data_get($study, 'MainDicomTags.StudyID'), + 'study_instance_uid' => data_get($study, 'MainDicomTags.StudyInstanceUID'), + 'study_date' => DicomUtils::dateTimeToCarbon($study['MainDicomTags']['StudyDate'], $study['MainDicomTags']['StudyTime']), + 'receive_date' => Carbon::parse($study['LastUpdate'], 'UTC'), + + 'study_modality' => data_get($study, 'RequestedTags.Modality'), + 'body_part_examined' => data_get($study, 'RequestedTags.BodyPartExamined'), + 'software_versions' => data_get($study, 'RequestedTags.SoftwareVersions'), + 'station_name' => data_get($study, 'RequestedTags.StationName'), + 'operators_name' => data_get($study, 'RequestedTags.OperatorsName'), + 'manufacturer' => data_get($study, 'RequestedTags.Manufacturer'), + 'manufacturer_model_name' => data_get($study, 'RequestedTags.ManufacturerModelName'), + + 'image_count' => data_get($study, 'Statistics.CountInstances'), + 'series_count' => data_get($study, 'Statistics.CountSeries'), + 'disk_size' => data_get($study, 'Statistics.DiskSize'), + ]; + + if ($study['IsStable']) { + $data['study_status'] = StudyLevelStatus::StudyArrived->value; + } else { + $data['study_status'] = StudyLevelStatus::Pending->value; + } + + $dob = data_get($study, 'PatientMainDicomTags.PatientBirthDate'); + if (trim($dob) != '') { + $data['patient_birthdate'] = Carbon::parse($dob); + } + + $descr = data_get($study, 'MainDicomTags.StudyDescription'); + if ($descr != null) { + $descr = data_get($study, 'RequestedTags.AcquisitionDeviceProcessingDescription'); + } + $data['study_description'] = trim($descr); + + return $data; + } + + public function updateStudy(int $row_id, mixed $study): void + { + $data = $this->prepareData($study); + unset($data['orthanc_uid']); + Study::where('id', $row_id)->update($data); + } + + public function insertStudy(mixed $study): void + { + $data = $this->prepareData($study); + Study::create($data); + } } diff --git a/database/migrations/2024_12_27_060234_create_studies_table.php b/database/migrations/2024_12_27_060234_create_studies_table.php index e00a6e9..c14d6ba 100644 --- a/database/migrations/2024_12_27_060234_create_studies_table.php +++ b/database/migrations/2024_12_27_060234_create_studies_table.php @@ -42,10 +42,10 @@ public function up(): void $table->dateTime('receive_date'); $table->dateTime('report_date')->nullable(); $table->foreignIdFor(Institute::class)->constrained()->onDelete('cascade'); - $table->unsignedTinyInteger('study_status')->default(StudyLevelStatus::None->value); + $table->unsignedTinyInteger('study_status')->default(StudyLevelStatus::Pending->value); $table->unsignedTinyInteger('report_status')->default(ReportStatus::Pending->value); - $table->unsignedSmallInteger('image_count')->nullable(0); + $table->unsignedSmallInteger('image_count')->nullable(); $table->unsignedSmallInteger('series_count')->nullable(); $table->unsignedSmallInteger('disk_size')->nullable(); @@ -54,7 +54,7 @@ public function up(): void $table->foreignIdFor(User::class, 'referring_physician_id')->nullable()->constrained()->onDelete('set null'); $table->foreignIdFor(User::class, 'reading_physician_id')->nullable()->constrained()->onDelete('set null'); - $table->unsignedTinyInteger('access_flags')->default(StudyAccessFlags::None->value); + $table->unsignedTinyInteger('access_flags')->default(StudyAccessFlags::Forbidden->value); $table->string('access_password')->nullable(); $table->timestamps();