wip
This commit is contained in:
parent
cf12ab27e4
commit
4c26b8a473
@ -3,13 +3,19 @@
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Services\Pacs\OrthancRestClient;
|
||||
use App\Services\Pacs\StudyImporter;
|
||||
use Arr;
|
||||
|
||||
class PacsController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
$studies = (new OrthancRestClient)->getStudies();
|
||||
dd($studies[0]);
|
||||
//dd($studies[0]);
|
||||
$study = array_pop($studies);
|
||||
//dd(Arr::get($study, 'MainDicomTags.StudyDate'));
|
||||
//dd(collect($study)->get('MainDicomTags.StudyDate'));
|
||||
dd($study);
|
||||
|
||||
return view('pacs.studies', compact('studies'));
|
||||
}
|
||||
@ -21,10 +27,12 @@ public function show($id)
|
||||
return view('pacs.study', compact('study'));
|
||||
}
|
||||
|
||||
public function import($id)
|
||||
public function import()
|
||||
{
|
||||
$studies = (new OrthancRestClient)->getStudies();
|
||||
|
||||
return redirect()->route('pacs.index');
|
||||
(new StudyImporter)->import($studies);
|
||||
|
||||
return redirect()->route('studies.index');
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
enum StudyLevelStatus: int
|
||||
{
|
||||
case None = 0;
|
||||
case Pending = 0;
|
||||
case StudyArrived = 1 << 1;
|
||||
case StudyLocked = 1 << 2;
|
||||
case StudyUnlocked = 1 << 3;
|
||||
|
@ -14,4 +14,6 @@ class Study extends Model
|
||||
'study_status' => StudyLevelStatus::class,
|
||||
'report_status' => ReportStatus::class,
|
||||
];
|
||||
|
||||
protected $guarded = ['id'];
|
||||
}
|
||||
|
19
app/Services/InputMatcher.php
Normal file
19
app/Services/InputMatcher.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\Enums\NameMatchModes;
|
||||
|
||||
final class InputMatcher
|
||||
{
|
||||
public static function match(string $input, string $pattern, NameMatchModes $mode): bool
|
||||
{
|
||||
return match ($mode) {
|
||||
NameMatchModes::Exact => strcasecmp($input, $pattern) === 0,
|
||||
NameMatchModes::Contains => stripos($input, $pattern) !== false,
|
||||
NameMatchModes::StartsWith => strncasecmp($input, $pattern, \strlen($pattern)) === 0,
|
||||
NameMatchModes::EndsWith => str_ends_with(strtolower($input), strtolower($pattern)),
|
||||
NameMatchModes::Regex => preg_match($pattern, $input) === 1,
|
||||
};
|
||||
}
|
||||
}
|
@ -6,10 +6,20 @@ enum DicomTags: string
|
||||
{
|
||||
case AcquisitionDate = 'AcquisitionDate';
|
||||
case AcquisitionTime = 'AcquisitionTime';
|
||||
case ContentDate = 'ContentDate';
|
||||
case ContentTime = 'ContentTime';
|
||||
case AcquisitionDeviceProcessingDescription = 'AcquisitionDeviceProcessingDescription';
|
||||
case BodyPartExamined = 'BodyPartExamined';
|
||||
case Modality = 'Modality';
|
||||
case SoftwareVersions = 'SoftwareVersions';
|
||||
case ProtocolName = 'ProtocolName';
|
||||
case StationName = 'StationName';
|
||||
case InstitutionAddress = 'InstitutionAddress';
|
||||
case StudyDescription = 'StudyDescription';
|
||||
case SeriesDescription = 'SeriesDescription';
|
||||
case Manufacturer = 'Manufacturer';
|
||||
case OperatorsName = 'OperatorsName';
|
||||
case ManufacturerModelName = 'ManufacturerModelName';
|
||||
case Private10 = '0029,0010';
|
||||
case IW_Private = '0009,0010';
|
||||
}
|
||||
|
35
app/Services/Pacs/InstituteMapper.php
Normal file
35
app/Services/Pacs/InstituteMapper.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Pacs;
|
||||
|
||||
use App\Models\Enums\NameMatchModes;
|
||||
use App\Services\InputMatcher;
|
||||
use Cache;
|
||||
use DB;
|
||||
|
||||
final class InstituteMapper
|
||||
{
|
||||
private static array $patterns = [];
|
||||
|
||||
private static int $catchAll = -1;
|
||||
|
||||
public static function map(string $input): int
|
||||
{
|
||||
if (empty(self::$patterns)) {
|
||||
self::$patterns = Cache::remember('institute_names', now()->addDay(), function () {
|
||||
return DB::table('institute_names')->orderBy('priority')->get()->toArray();
|
||||
});
|
||||
self::$catchAll = DB::table('institutes')->first('id')->id;
|
||||
}
|
||||
|
||||
$input = strtolower($input);
|
||||
|
||||
foreach (self::$patterns as $pattern) {
|
||||
if (InputMatcher::match($input, $pattern->name, NameMatchModes::from($pattern->match_mode))) {
|
||||
return $pattern->institute_id;
|
||||
}
|
||||
}
|
||||
|
||||
return self::$catchAll;
|
||||
}
|
||||
}
|
@ -13,6 +13,23 @@ public function getClient(): Client
|
||||
]);
|
||||
}
|
||||
|
||||
public function getStudyStatistics(string $study_id): array
|
||||
{
|
||||
$response = $this->getClient()->get('/studies/'.$study_id.'/statistics');
|
||||
|
||||
return json_decode($response->getBody()->getContents(), true);
|
||||
}
|
||||
|
||||
public function getStudyDetails(string $study_id): array
|
||||
{
|
||||
$query = [
|
||||
'requested-tags' => implode(';', array_column(DicomTags::cases(), 'value')),
|
||||
];
|
||||
$response = $this->getClient()->get('/studies/'.$study_id.http_build_query($query));
|
||||
|
||||
return json_decode($response->getBody()->getContents(), true);
|
||||
}
|
||||
|
||||
public function getStudies(): array
|
||||
{
|
||||
$query = [
|
||||
@ -22,8 +39,6 @@ public function getStudies(): array
|
||||
$url = '/studies?'.http_build_query($query);
|
||||
$response = $this->getClient()->get($url);
|
||||
|
||||
$studies = json_decode($response->getBody()->getContents(), true);
|
||||
|
||||
return $studies;
|
||||
return json_decode($response->getBody()->getContents(), true);
|
||||
}
|
||||
}
|
||||
|
@ -11,38 +11,67 @@ final class StudyImporter
|
||||
public function import(array $studies): void
|
||||
{
|
||||
foreach ($studies as $study) {
|
||||
$othanc_id = strtolower($study['ID']);
|
||||
$row = Study::where('othanc_id', $othanc_id)->first();
|
||||
if ($row && $row->study_status < StudyLevelStatus::StudyArrived) {
|
||||
$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 = [
|
||||
'othanc_id' => $othanc_id,
|
||||
'study_status' => StudyLevelStatus::StudyArrived,
|
||||
'study_datetime' => $study['StudyDateTime'],
|
||||
'receive_datetime' => $study['ReceiveDateTime'],
|
||||
'orthanc_uid' => $orthanc_uid,
|
||||
'is_locked' => false,
|
||||
'is_active' => true,
|
||||
|
||||
'patient_id' => $study['PatientMainDicomTags']['PatientID'],
|
||||
'patient_name' => $study['PatientMainDicomTags']['PatientName'],
|
||||
'patient_sex' => $study['PatientMainDicomTags']['PatientSex'],
|
||||
'patient_birthdate' => $study['PatientMainDicomTags']['PatientBirthDate'],
|
||||
'institution_name' => $inst_name,
|
||||
'institute_id' => $inst_id,
|
||||
|
||||
'institution_name' => $study['MainDicomTags']['InstitutionName'],
|
||||
'accession_number' => $study['MainDicomTags']['AccessionNumber'],
|
||||
'referring_physician_name' => $study['MainDicomTags']['ReferringPhysicianName'],
|
||||
'study_id' => $study['MainDicomTags']['StudyID'],
|
||||
'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' => $study['MainDicomTags']['Modality'],
|
||||
|
||||
'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']),
|
||||
];
|
||||
$row = Study::create($data);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -20,14 +20,22 @@ public function up(): void
|
||||
$table->boolean('is_locked')->default(true);
|
||||
$table->unsignedTinyInteger('study_priority')->default(0);
|
||||
$table->string('patient_id')->nullable();
|
||||
$table->string('patient_uuid')->nullable();
|
||||
$table->string('patient_name');
|
||||
$table->string('patient_sex')->nullable();
|
||||
$table->date('patient_birthdate')->nullable();
|
||||
|
||||
$table->string('study_instance_uid')->unique();
|
||||
$table->string('study_id')->nullable();
|
||||
$table->string('institution_name')->nullable();
|
||||
$table->string('accession_number')->nullable();
|
||||
$table->string('study_description')->nullable();
|
||||
$table->string('body_part_examined')->nullable();
|
||||
$table->string('station_name')->nullable();
|
||||
$table->string('operators_name')->nullable();
|
||||
$table->string('manufacturer')->nullable();
|
||||
$table->string('manufacturer_model_name')->nullable();
|
||||
|
||||
$table->string('referring_physician_name')->nullable();
|
||||
$table->string('study_modality', 4)->nullable();
|
||||
$table->dateTime('study_date');
|
||||
@ -36,8 +44,10 @@ public function up(): void
|
||||
$table->foreignIdFor(Institute::class)->constrained()->onDelete('cascade');
|
||||
$table->unsignedTinyInteger('study_status')->default(StudyLevelStatus::None->value);
|
||||
$table->unsignedTinyInteger('report_status')->default(ReportStatus::Pending->value);
|
||||
$table->unsignedSmallInteger('image_count')->default(0);
|
||||
$table->unsignedSmallInteger('series_count')->default(0);
|
||||
|
||||
$table->unsignedSmallInteger('image_count')->nullable(0);
|
||||
$table->unsignedSmallInteger('series_count')->nullable();
|
||||
$table->unsignedSmallInteger('disk_size')->nullable();
|
||||
|
||||
$table->foreignIdFor(User::class, 'assigned_physician_id')->nullable()->constrained()->onDelete('set null');
|
||||
$table->foreignIdFor(User::class, 'interpreting_physician_id')->nullable()->constrained()->onDelete('set null');
|
||||
|
@ -18,5 +18,6 @@
|
||||
|
||||
Route::get('/studies', [PacsController::class, 'index'])->name('studies.index');
|
||||
Route::get('/studies/{id}', [PacsController::class, 'show'])->name('studies.show');
|
||||
Route::get('/cron', [PacsController::class, 'import'])->name('studies.import');
|
||||
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user