Skip to content
Merged
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
4 changes: 4 additions & 0 deletions .github/labeler.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,7 @@
"Language: Python":
- changed-files:
- any-glob-to-any-file: '**/*.py'

"Package: EEG chunker":
- changed-files:
- any-glob-to-any-file: 'python/loris_eeg_chunker/**'
2 changes: 1 addition & 1 deletion install/templates/environment_template
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ source ${MINC_TOOLKIT_DIR}/minc-toolkit-config.sh
umask 0002

# export PATH, PERL5LIB, TMPDIR and LORIS_CONFIG variables
export PATH=/opt/${PROJECT}/bin/mri:/opt/${PROJECT}/bin/mri/uploadNeuroDB:/opt/${PROJECT}/bin/mri/uploadNeuroDB/bin:/opt/${PROJECT}/bin/mri/dicom-archive:/opt/${PROJECT}/bin/mri/python/scripts:/opt/${PROJECT}/bin/mri/tools:/opt/${PROJECT}/bin/mri/python/loris_eeg_chunker:${MINC_TOOLKIT_DIR}/bin:/usr/local/bin/tpcclib:$PATH
export PATH=/opt/${PROJECT}/bin/mri:/opt/${PROJECT}/bin/mri/uploadNeuroDB:/opt/${PROJECT}/bin/mri/uploadNeuroDB/bin:/opt/${PROJECT}/bin/mri/dicom-archive:/opt/${PROJECT}/bin/mri/python/scripts:/opt/${PROJECT}/bin/mri/tools:${MINC_TOOLKIT_DIR}/bin:/usr/local/bin/tpcclib:$PATH
export PERL5LIB=/opt/${PROJECT}/bin/mri/uploadNeuroDB:/opt/${PROJECT}/bin/mri/dicom-archive:$PERL5LIB
export TMPDIR=/tmp
export LORIS_CONFIG=/opt/${PROJECT}/bin/mri/config
Expand Down
9 changes: 4 additions & 5 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,24 +12,21 @@ license-files = ["LICENSE"]
requires-python = ">= 3.11"
dependencies = [
"boto3==1.35.99",
"google",
"mat73",
"matplotlib",
"mne",
"mne-bids>=0.14",
"mysqlclient",
"nibabel",
"nilearn",
"nose",
"numpy",
"protobuf>=3.0.0",
"pybids==0.17.0",
"pydicom",
"python-dateutil",
"scikit-learn",
"scipy",
"sqlalchemy>=2.0.0",
"virtualenv",
"loris-eeg-chunker @ {root:uri}/python/loris_eeg_chunker",
]

[project.optional-dependencies]
Expand All @@ -42,10 +39,12 @@ dev = [
[project.urls]
Homepage = "https://github.com/aces/loris-mri"

[tool.hatch.metadata]
allow-direct-references = true

[tool.hatch.build.targets.wheel]
packages = [
"python/lib",
"python/loris_eeg_chunker",
"python/tests",
]

Expand Down
15 changes: 8 additions & 7 deletions python/lib/config.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import os
from pathlib import Path
from typing import Literal

from lib.db.queries.config import try_get_config_with_setting_name
Expand Down Expand Up @@ -26,15 +27,15 @@ def get_patient_id_dicom_header_config(env: Env) -> Literal['PatientID', 'Patien
return patient_id_dicom_header


def get_data_dir_path_config(env: Env) -> str:
def get_data_dir_path_config(env: Env) -> Path:
"""
Get the LORIS base data directory path from the in-database configuration, or exit the program
with an error if that configuration value does not exist or is incorrect.
"""

data_dir_path = os.path.normpath(_get_config_value(env, 'dataDirBasepath'))
data_dir_path = Path(_get_config_value(env, 'dataDirBasepath'))

if not os.path.isdir(data_dir_path):
if not data_dir_path.is_dir():
log_error_exit(
env,
(
Expand All @@ -52,20 +53,20 @@ def get_data_dir_path_config(env: Env) -> str:
return data_dir_path


def get_dicom_archive_dir_path_config(env: Env) -> str:
def get_dicom_archive_dir_path_config(env: Env) -> Path:
"""
Get the LORIS DICOM archive directory path from the in-database configuration, or exit the
program with an error if that configuration value does not exist or is incorrect.
"""

dicom_archive_dir_path = os.path.normpath(_get_config_value(env, 'tarchiveLibraryDir'))
dicom_archive_dir_path = Path(_get_config_value(env, 'tarchiveLibraryDir'))

if not os.path.isdir(dicom_archive_dir_path):
if not dicom_archive_dir_path.is_dir():
log_error_exit(
env,
(
f"The LORIS DICOM archive directory path configuration value '{dicom_archive_dir_path}' does not refer"
" to an existing diretory."
" to an existing directory."
),
)

Expand Down
28 changes: 28 additions & 0 deletions python/lib/db/decorators/string_path.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from pathlib import Path

from sqlalchemy import String
from sqlalchemy.engine import Dialect
from sqlalchemy.types import TypeDecorator


class StringPath(TypeDecorator[Path]):
"""
Decorator for a database path type.
In SQL, the type will appear as a string.
In Python, the type will appear as a path object.
"""

impl = String
cache_ok = True

def process_bind_param(self, value: Path | None, dialect: Dialect) -> str | None:
if value is None:
return None

return str(value)

def process_result_value(self, value: str | None, dialect: Dialect) -> Path | None:
if value is None:
return None

return Path(value)
6 changes: 4 additions & 2 deletions python/lib/db/models/dicom_archive.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from datetime import date, datetime
from pathlib import Path
from typing import Optional

from sqlalchemy import ForeignKey
Expand All @@ -12,6 +13,7 @@
import lib.db.models.session as db_session
from lib.db.base import Base
from lib.db.decorators.int_bool import IntBool
from lib.db.decorators.string_path import StringPath


class DbDicomArchive(Base):
Expand All @@ -37,8 +39,8 @@ class DbDicomArchive(Base):
creating_user : Mapped[str] = mapped_column('CreatingUser')
sum_type_version : Mapped[int] = mapped_column('sumTypeVersion')
tar_type_version : Mapped[int | None] = mapped_column('tarTypeVersion')
source_location : Mapped[str] = mapped_column('SourceLocation')
archive_location : Mapped[str | None] = mapped_column('ArchiveLocation')
source_path : Mapped[Path] = mapped_column('SourceLocation', StringPath)
archive_path : Mapped[Path | None] = mapped_column('ArchiveLocation', StringPath)
scanner_manufacturer : Mapped[str] = mapped_column('ScannerManufacturer')
scanner_model : Mapped[str] = mapped_column('ScannerModel')
scanner_serial_number : Mapped[str] = mapped_column('ScannerSerialNumber')
Expand Down
4 changes: 3 additions & 1 deletion python/lib/db/models/file.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from datetime import date, datetime
from pathlib import Path

from sqlalchemy import ForeignKey
from sqlalchemy.orm import Mapped, mapped_column, relationship
Expand All @@ -8,14 +9,15 @@
from lib.db.base import Base
from lib.db.decorators.int_bool import IntBool
from lib.db.decorators.int_datetime import IntDatetime
from lib.db.decorators.string_path import StringPath


class DbFile(Base):
__tablename__ = 'files'

id : Mapped[int] = mapped_column('FileID', primary_key=True)
session_id : Mapped[int] = mapped_column('SessionID', ForeignKey('session.ID'))
rel_path : Mapped[str] = mapped_column('File')
path : Mapped[Path] = mapped_column('File', StringPath)
series_uid : Mapped[str | None] = mapped_column('SeriesUID')
echo_time : Mapped[float | None] = mapped_column('EchoTime')
phase_encoding_direction : Mapped[str | None] = mapped_column('PhaseEncodingDirection')
Expand Down
4 changes: 3 additions & 1 deletion python/lib/db/models/mri_protocol_violated_scan.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from datetime import datetime
from pathlib import Path
from typing import Optional

from sqlalchemy import ForeignKey
Expand All @@ -8,6 +9,7 @@
import lib.db.models.dicom_archive as db_dicom_archive
import lib.db.models.mri_protocol_group as db_mri_protocol_group
from lib.db.base import Base
from lib.db.decorators.string_path import StringPath


class DbMriProtocolViolatedScan(Base):
Expand All @@ -19,7 +21,7 @@ class DbMriProtocolViolatedScan(Base):
dicom_archive_id : Mapped[int | None] = mapped_column('TarchiveID', ForeignKey('tarchive.TarchiveID'))
time_run : Mapped[datetime | None] = mapped_column('time_run')
series_description : Mapped[str | None] = mapped_column('series_description')
file_rel_path : Mapped[str | None] = mapped_column('minc_location')
file_path : Mapped[Path | None] = mapped_column('minc_location', StringPath)
patient_name : Mapped[str | None] = mapped_column('PatientName')
tr_range : Mapped[str | None] = mapped_column('TR_range')
te_range : Mapped[str | None] = mapped_column('TE_range')
Expand Down
6 changes: 4 additions & 2 deletions python/lib/db/models/mri_upload.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from datetime import datetime
from pathlib import Path
from typing import Optional

from sqlalchemy import ForeignKey
Expand All @@ -8,6 +9,7 @@
import lib.db.models.session as db_session
from lib.db.base import Base
from lib.db.decorators.int_bool import IntBool
from lib.db.decorators.string_path import StringPath
from lib.db.decorators.y_n_bool import YNBool


Expand All @@ -17,8 +19,8 @@ class DbMriUpload(Base):
id : Mapped[int] = mapped_column('UploadID', primary_key=True)
uploaded_by : Mapped[str] = mapped_column('UploadedBy')
upload_date : Mapped[datetime | None] = mapped_column('UploadDate')
upload_location : Mapped[str] = mapped_column('UploadLocation')
decompressed_location : Mapped[str] = mapped_column('DecompressedLocation')
upload_path : Mapped[Path] = mapped_column('UploadLocation', StringPath)
decompressed_path : Mapped[Path] = mapped_column('DecompressedLocation', StringPath)
insertion_complete : Mapped[bool] = mapped_column('InsertionComplete', IntBool)
inserting : Mapped[bool | None] = mapped_column('Inserting', IntBool)
patient_name : Mapped[str] = mapped_column('PatientName')
Expand Down
4 changes: 3 additions & 1 deletion python/lib/db/models/mri_violation_log.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from datetime import datetime
from pathlib import Path
from typing import Optional

from sqlalchemy import ForeignKey
Expand All @@ -9,6 +10,7 @@
import lib.db.models.mri_protocol_check_group as db_mri_protocol_check_group
import lib.db.models.mri_scan_type as db_mri_scan_type
from lib.db.base import Base
from lib.db.decorators.string_path import StringPath


class DbMriViolationLog(Base):
Expand All @@ -19,7 +21,7 @@ class DbMriViolationLog(Base):
series_uid : Mapped[str | None] = mapped_column('SeriesUID')
dicom_archive_id : Mapped[int | None] \
= mapped_column('TarchiveID', ForeignKey('tarchive.TarchiveID'))
file_rel_path : Mapped[str | None] = mapped_column('MincFile')
file_path : Mapped[Path | None] = mapped_column('MincFile', StringPath)
patient_name : Mapped[str | None] = mapped_column('PatientName')
candidate_id : Mapped[int | None] = mapped_column('CandidateID', ForeignKey('candidate.ID'))
visit_label : Mapped[str | None] = mapped_column('Visit_label')
Expand Down
4 changes: 3 additions & 1 deletion python/lib/db/models/physio_channel.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from datetime import datetime
from decimal import Decimal
from pathlib import Path

from sqlalchemy import ForeignKey
from sqlalchemy.orm import Mapped, mapped_column, relationship
Expand All @@ -8,6 +9,7 @@
import lib.db.models.physio_file as db_physio_file
import lib.db.models.physio_status_type as db_physio_status_type
from lib.db.base import Base
from lib.db.decorators.string_path import StringPath


class DbPhysioChannel(Base):
Expand All @@ -28,7 +30,7 @@ class DbPhysioChannel(Base):
reference : Mapped[str | None] = mapped_column('Reference')
status_description : Mapped[str | None] = mapped_column('StatusDescription')
unit : Mapped[str | None] = mapped_column('Unit')
file_path : Mapped[str | None] = mapped_column('FilePath')
file_path : Mapped[Path | None] = mapped_column('FilePath', StringPath)

physio_file : Mapped['db_physio_file.DbPhysioFile'] = relationship('DbPhysioFile', back_populates='channels')
channel_type : Mapped['db_physio_channel_type.DbPhysioChannelType'] = relationship('DbPhysioChannelType')
Expand Down
15 changes: 9 additions & 6 deletions python/lib/db/models/physio_coord_system.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from pathlib import Path

from sqlalchemy import ForeignKey
from sqlalchemy.orm import Mapped, mapped_column, relationship

Expand All @@ -6,17 +8,18 @@
import lib.db.models.physio_coord_system_unit as db_physio_coord_system_unit
import lib.db.models.physio_modality as db_physio_modality
from lib.db.base import Base
from lib.db.decorators.string_path import StringPath


class DbPhysioCoordSystem(Base):
__tablename__ = 'physiological_coord_system'

id : Mapped[int] = mapped_column('PhysiologicalCoordSystemID', primary_key=True)
name_id : Mapped[int] = mapped_column('NameID', ForeignKey('physiological_coord_system_name.PhysiologicalCoordSystemNameID'))
type_id : Mapped[int] = mapped_column('TypeID', ForeignKey('physiological_coord_system_type.PhysiologicalCoordSystemTypeID'))
unit_id : Mapped[int] = mapped_column('UnitID', ForeignKey('physiological_coord_system_unit.PhysiologicalCoordSystemUnitID'))
modality_id : Mapped[int] = mapped_column('ModalityID', ForeignKey('physiological_modality.PhysiologicalModalityID'))
file_path : Mapped[str | None] = mapped_column('FilePath')
id : Mapped[int] = mapped_column('PhysiologicalCoordSystemID', primary_key=True)
name_id : Mapped[int] = mapped_column('NameID', ForeignKey('physiological_coord_system_name.PhysiologicalCoordSystemNameID'))
type_id : Mapped[int] = mapped_column('TypeID', ForeignKey('physiological_coord_system_type.PhysiologicalCoordSystemTypeID'))
unit_id : Mapped[int] = mapped_column('UnitID', ForeignKey('physiological_coord_system_unit.PhysiologicalCoordSystemUnitID'))
modality_id : Mapped[int] = mapped_column('ModalityID', ForeignKey('physiological_modality.PhysiologicalModalityID'))
file_path : Mapped[Path | None] = mapped_column('FilePath', StringPath)

name : Mapped['db_physio_coord_system_name.DbPhysioCoordSystemName'] = relationship('DbPhysioCoordSystemName')
type : Mapped['db_physio_coord_system_type.DbPhysioCoordSystemType'] = relationship('DbPhysioCoordSystemType')
Expand Down
16 changes: 9 additions & 7 deletions python/lib/db/models/physio_event_file.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from datetime import datetime
from pathlib import Path

from sqlalchemy import ForeignKey
from sqlalchemy.orm import Mapped, mapped_column, relationship
Expand All @@ -9,18 +10,19 @@
import lib.db.models.physio_task_event as db_physio_task_event
import lib.db.models.project as db_project
from lib.db.base import Base
from lib.db.decorators.string_path import StringPath


class DbPhysioEventFile(Base):
__tablename__ = 'physiological_event_file'

id : Mapped[int] = mapped_column('EventFileID', primary_key=True)
physio_file_id : Mapped[int | None] = mapped_column('PhysiologicalFileID', ForeignKey('physiological_file.PhysiologicalFileID'))
project_id : Mapped[int | None] = mapped_column('ProjectID', ForeignKey('Project.ProjectID'))
file_type : Mapped[str] = mapped_column('FileType', ForeignKey('ImagingFileTypes.type'))
file_path : Mapped[str | None] = mapped_column('FilePath')
last_update : Mapped[datetime] = mapped_column('LastUpdate')
last_written : Mapped[datetime] = mapped_column('LastWritten')
id : Mapped[int] = mapped_column('EventFileID', primary_key=True)
physio_file_id : Mapped[int | None] = mapped_column('PhysiologicalFileID', ForeignKey('physiological_file.PhysiologicalFileID'))
project_id : Mapped[int | None] = mapped_column('ProjectID', ForeignKey('Project.ProjectID'))
file_type : Mapped[str] = mapped_column('FileType', ForeignKey('ImagingFileTypes.type'))
file_path : Mapped[Path | None] = mapped_column('FilePath', StringPath)
last_update : Mapped[datetime] = mapped_column('LastUpdate')
last_written : Mapped[datetime] = mapped_column('LastWritten')

physio_file : Mapped['db_physio_file.DbPhysioFile | None'] = relationship('PhysiologicalFile')
project : Mapped['db_project.DbProject | None'] = relationship('Project')
Expand Down
4 changes: 3 additions & 1 deletion python/lib/db/models/physio_file.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from datetime import datetime
from pathlib import Path

from sqlalchemy import ForeignKey
from sqlalchemy.orm import Mapped, mapped_column, relationship
Expand All @@ -7,6 +8,7 @@
import lib.db.models.physio_modality as db_physio_modality
import lib.db.models.physio_output_type as db_physio_output_type
from lib.db.base import Base
from lib.db.decorators.string_path import StringPath


class DbPhysioFile(Base):
Expand All @@ -20,7 +22,7 @@ class DbPhysioFile(Base):
file_type : Mapped[str | None] = mapped_column('FileType')
acquisition_time : Mapped[datetime | None] = mapped_column('AcquisitionTime')
inserted_by_user : Mapped[str] = mapped_column('InsertedByUser')
path : Mapped[str] = mapped_column('FilePath')
path : Mapped[Path] = mapped_column('FilePath', StringPath)
index : Mapped[int | None] = mapped_column('Index')
parent_id : Mapped[int | None] = mapped_column('ParentID')

Expand Down
8 changes: 5 additions & 3 deletions python/lib/db/queries/dicom_archive.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@

from pathlib import Path

from sqlalchemy import delete, select
from sqlalchemy.orm import Session as Database

Expand Down Expand Up @@ -29,14 +31,14 @@ def try_get_dicom_archive_with_patient_name(db: Database, patient_name: str) ->
).scalar_one_or_none()


def try_get_dicom_archive_with_archive_location(db: Database, archive_location: str) -> DbDicomArchive | None:
def try_get_dicom_archive_with_archive_path(db: Database, archive_path: Path) -> DbDicomArchive | None:
"""
Get a DICOM archive from the database using its archive location, or return `None` if no DICOM
Get a DICOM archive from the database using its archive path, or return `None` if no DICOM
archive is found.
"""

return db.execute(select(DbDicomArchive)
.where(DbDicomArchive.archive_location.like(f'%{archive_location}%'))
.where(DbDicomArchive.archive_path.like(f'%{archive_path}%'))
).scalar_one_or_none()


Expand Down
Loading
Loading