wip
This commit is contained in:
parent
cf12ab27e4
commit
4c26b8a473
@ -3,13 +3,19 @@
|
|||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
use App\Services\Pacs\OrthancRestClient;
|
use App\Services\Pacs\OrthancRestClient;
|
||||||
|
use App\Services\Pacs\StudyImporter;
|
||||||
|
use Arr;
|
||||||
|
|
||||||
class PacsController extends Controller
|
class PacsController extends Controller
|
||||||
{
|
{
|
||||||
public function index()
|
public function index()
|
||||||
{
|
{
|
||||||
$studies = (new OrthancRestClient)->getStudies();
|
$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'));
|
return view('pacs.studies', compact('studies'));
|
||||||
}
|
}
|
||||||
@ -21,10 +27,12 @@ public function show($id)
|
|||||||
return view('pacs.study', compact('study'));
|
return view('pacs.study', compact('study'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function import($id)
|
public function import()
|
||||||
{
|
{
|
||||||
$studies = (new OrthancRestClient)->getStudies();
|
$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
|
enum StudyLevelStatus: int
|
||||||
{
|
{
|
||||||
case None = 0;
|
case Pending = 0;
|
||||||
case StudyArrived = 1 << 1;
|
case StudyArrived = 1 << 1;
|
||||||
case StudyLocked = 1 << 2;
|
case StudyLocked = 1 << 2;
|
||||||
case StudyUnlocked = 1 << 3;
|
case StudyUnlocked = 1 << 3;
|
||||||
|
@ -14,4 +14,6 @@ class Study extends Model
|
|||||||
'study_status' => StudyLevelStatus::class,
|
'study_status' => StudyLevelStatus::class,
|
||||||
'report_status' => ReportStatus::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 AcquisitionDate = 'AcquisitionDate';
|
||||||
case AcquisitionTime = 'AcquisitionTime';
|
case AcquisitionTime = 'AcquisitionTime';
|
||||||
|
case ContentDate = 'ContentDate';
|
||||||
|
case ContentTime = 'ContentTime';
|
||||||
case AcquisitionDeviceProcessingDescription = 'AcquisitionDeviceProcessingDescription';
|
case AcquisitionDeviceProcessingDescription = 'AcquisitionDeviceProcessingDescription';
|
||||||
case BodyPartExamined = 'BodyPartExamined';
|
case BodyPartExamined = 'BodyPartExamined';
|
||||||
case Modality = 'Modality';
|
case Modality = 'Modality';
|
||||||
case SoftwareVersions = 'SoftwareVersions';
|
case SoftwareVersions = 'SoftwareVersions';
|
||||||
|
case ProtocolName = 'ProtocolName';
|
||||||
case StationName = 'StationName';
|
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 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
|
public function getStudies(): array
|
||||||
{
|
{
|
||||||
$query = [
|
$query = [
|
||||||
@ -22,8 +39,6 @@ public function getStudies(): array
|
|||||||
$url = '/studies?'.http_build_query($query);
|
$url = '/studies?'.http_build_query($query);
|
||||||
$response = $this->getClient()->get($url);
|
$response = $this->getClient()->get($url);
|
||||||
|
|
||||||
$studies = json_decode($response->getBody()->getContents(), true);
|
return json_decode($response->getBody()->getContents(), true);
|
||||||
|
|
||||||
return $studies;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,38 +11,67 @@ final class StudyImporter
|
|||||||
public function import(array $studies): void
|
public function import(array $studies): void
|
||||||
{
|
{
|
||||||
foreach ($studies as $study) {
|
foreach ($studies as $study) {
|
||||||
$othanc_id = strtolower($study['ID']);
|
$orthanc_uid = strtolower($study['ID']);
|
||||||
$row = Study::where('othanc_id', $othanc_id)->first();
|
$row = Study::where(compact('orthanc_uid'))->first();
|
||||||
if ($row && $row->study_status < StudyLevelStatus::StudyArrived) {
|
if ($row != null) {
|
||||||
// todo: update study
|
if ($row->study_status < StudyLevelStatus::StudyArrived) {
|
||||||
|
// todo: update study
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$inst_name = data_get($study, 'MainDicomTags.InstitutionName');
|
||||||
|
$inst_id = InstituteMapper::map($inst_name);
|
||||||
|
|
||||||
$data = [
|
$data = [
|
||||||
'othanc_id' => $othanc_id,
|
'orthanc_uid' => $orthanc_uid,
|
||||||
'study_status' => StudyLevelStatus::StudyArrived,
|
|
||||||
'study_datetime' => $study['StudyDateTime'],
|
|
||||||
'receive_datetime' => $study['ReceiveDateTime'],
|
|
||||||
'is_locked' => false,
|
'is_locked' => false,
|
||||||
'is_active' => true,
|
'is_active' => true,
|
||||||
|
|
||||||
'patient_id' => $study['PatientMainDicomTags']['PatientID'],
|
'institution_name' => $inst_name,
|
||||||
'patient_name' => $study['PatientMainDicomTags']['PatientName'],
|
'institute_id' => $inst_id,
|
||||||
'patient_sex' => $study['PatientMainDicomTags']['PatientSex'],
|
|
||||||
'patient_birthdate' => $study['PatientMainDicomTags']['PatientBirthDate'],
|
|
||||||
|
|
||||||
'institution_name' => $study['MainDicomTags']['InstitutionName'],
|
'patient_uuid' => $study['ParentPatient'],
|
||||||
'accession_number' => $study['MainDicomTags']['AccessionNumber'],
|
'patient_id' => data_get($study, 'PatientMainDicomTags.PatientID'),
|
||||||
'referring_physician_name' => $study['MainDicomTags']['ReferringPhysicianName'],
|
'patient_name' => data_get($study, 'PatientMainDicomTags.PatientName'),
|
||||||
'study_id' => $study['MainDicomTags']['StudyID'],
|
'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']),
|
'study_date' => DicomUtils::dateTimeToCarbon($study['MainDicomTags']['StudyDate'], $study['MainDicomTags']['StudyTime']),
|
||||||
'receive_date' => Carbon::parse($study['LastUpdate'], 'UTC'),
|
'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']),
|
'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->boolean('is_locked')->default(true);
|
||||||
$table->unsignedTinyInteger('study_priority')->default(0);
|
$table->unsignedTinyInteger('study_priority')->default(0);
|
||||||
$table->string('patient_id')->nullable();
|
$table->string('patient_id')->nullable();
|
||||||
|
$table->string('patient_uuid')->nullable();
|
||||||
$table->string('patient_name');
|
$table->string('patient_name');
|
||||||
$table->string('patient_sex')->nullable();
|
$table->string('patient_sex')->nullable();
|
||||||
$table->date('patient_birthdate')->nullable();
|
$table->date('patient_birthdate')->nullable();
|
||||||
|
|
||||||
$table->string('study_instance_uid')->unique();
|
$table->string('study_instance_uid')->unique();
|
||||||
$table->string('study_id')->nullable();
|
$table->string('study_id')->nullable();
|
||||||
$table->string('institution_name')->nullable();
|
$table->string('institution_name')->nullable();
|
||||||
$table->string('accession_number')->nullable();
|
$table->string('accession_number')->nullable();
|
||||||
$table->string('study_description')->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('referring_physician_name')->nullable();
|
||||||
$table->string('study_modality', 4)->nullable();
|
$table->string('study_modality', 4)->nullable();
|
||||||
$table->dateTime('study_date');
|
$table->dateTime('study_date');
|
||||||
@ -36,8 +44,10 @@ public function up(): void
|
|||||||
$table->foreignIdFor(Institute::class)->constrained()->onDelete('cascade');
|
$table->foreignIdFor(Institute::class)->constrained()->onDelete('cascade');
|
||||||
$table->unsignedTinyInteger('study_status')->default(StudyLevelStatus::None->value);
|
$table->unsignedTinyInteger('study_status')->default(StudyLevelStatus::None->value);
|
||||||
$table->unsignedTinyInteger('report_status')->default(ReportStatus::Pending->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, 'assigned_physician_id')->nullable()->constrained()->onDelete('set null');
|
||||||
$table->foreignIdFor(User::class, 'interpreting_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', [PacsController::class, 'index'])->name('studies.index');
|
||||||
Route::get('/studies/{id}', [PacsController::class, 'show'])->name('studies.show');
|
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