diff --git a/python/lib/db/base.py b/python/lib/db/base.py new file mode 100644 index 000000000..7d23cab17 --- /dev/null +++ b/python/lib/db/base.py @@ -0,0 +1,9 @@ +from sqlalchemy.orm import DeclarativeBase + + +class Base(DeclarativeBase): + """ + Base SQLAlchemy class that must be inherited by all the ORM model classes. + """ + + pass diff --git a/python/lib/db/connect.py b/python/lib/db/connect.py new file mode 100644 index 000000000..a58912f0f --- /dev/null +++ b/python/lib/db/connect.py @@ -0,0 +1,17 @@ +from typing import Any +from sqlalchemy import create_engine +from sqlalchemy.orm import Session + + +default_port = 3306 + + +def connect_to_db(credentials: dict[str, Any]): + host = credentials['host'] + port = credentials['port'] + username = credentials['username'] + password = credentials['passwd'] + database = credentials['database'] + port = int(port) if port else default_port + engine = create_engine(f'mariadb+mysqlconnector://{username}:{password}@{host}:{port}/{database}') + return Session(engine) diff --git a/python/lib/db/orm/dicom_archive.py b/python/lib/db/orm/dicom_archive.py new file mode 100644 index 000000000..a2d6907f1 --- /dev/null +++ b/python/lib/db/orm/dicom_archive.py @@ -0,0 +1,51 @@ +from datetime import date, datetime +from typing import List, Optional +from sqlalchemy import String +from sqlalchemy.orm import Mapped, mapped_column, relationship +from lib.db.base import Base +import lib.db.orm.dicom_archive_file as db_dicom_archive_file +import lib.db.orm.dicom_archive_series as db_dicom_archive_series +import lib.db.orm.mri_upload as db_mri_upload + + +class DbDicomArchive(Base): + __tablename__ = 'tarchive' + + id : Mapped[int] = mapped_column('TarchiveID', primary_key=True) + series : Mapped[List['db_dicom_archive_series.DbDicomArchiveSeries']] \ + = relationship('DbDicomArchiveSeries', back_populates='archive') + files : Mapped[List['db_dicom_archive_file.DbDicomArchiveFile']] \ + = relationship('DbDicomArchiveFile', back_populates='archive') + upload : Mapped[Optional['db_mri_upload.DbMriUpload']] \ + = relationship('DbMriUpload', back_populates='dicom_archive') + study_uid : Mapped[str] = mapped_column('DicomArchiveID', type_ = String()) + patient_id : Mapped[str] = mapped_column('PatientID') + patient_name : Mapped[str] = mapped_column('PatientName') + patient_birthdate : Mapped[Optional[date]] = mapped_column('PatientDoB') + patient_sex : Mapped[Optional[str]] = mapped_column('PatientSex') + neuro_db_center_name : Mapped[Optional[str]] = mapped_column('neurodbCenterName') + center_name : Mapped[str] = mapped_column('CenterName') + last_update : Mapped[Optional[date]] = mapped_column('LastUpdate') + date_acquired : Mapped[Optional[date]] = mapped_column('DateAcquired') + date_first_archived : Mapped[Optional[datetime]] = mapped_column('DateFirstArchived') + date_last_archived : Mapped[Optional[datetime]] = mapped_column('DateLastArchived') + acquisition_count : Mapped[int] = mapped_column('AcquisitionCount') + dicom_file_count : Mapped[int] = mapped_column('DicomFileCount') + non_dicom_file_count : Mapped[int] = mapped_column('NonDicomFileCount') + md5_sum_dicom_only : Mapped[Optional[str]] = mapped_column('md5sumDicomOnly') + md5_sum_archive : Mapped[Optional[str]] = mapped_column('md5sumArchive') + creating_user : Mapped[str] = mapped_column('CreatingUser') + sum_type_version : Mapped[int] = mapped_column('sumTypeVersion') + tar_type_version : Mapped[Optional[int]] = mapped_column('tarTypeVersion') + source_location : Mapped[str] = mapped_column('SourceLocation') + archive_location : Mapped[Optional[str]] = mapped_column('ArchiveLocation') + scanner_manufacturer : Mapped[str] = mapped_column('ScannerManufacturer') + scanner_model : Mapped[str] = mapped_column('ScannerModel') + scanner_serial_number : Mapped[str] = mapped_column('ScannerSerialNumber') + scanner_software_version : Mapped[str] = mapped_column('ScannerSoftwareVersion') + session_id : Mapped[Optional[int]] = mapped_column('SessionID') + upload_attempt : Mapped[int] = mapped_column('uploadAttempt') + create_info : Mapped[Optional[str]] = mapped_column('CreateInfo') + acquisition_metadata : Mapped[str] = mapped_column('AcquisitionMetadata') + date_sent : Mapped[Optional[datetime]] = mapped_column('DateSent') + pending_transfer : Mapped[int] = mapped_column('PendingTransfer') diff --git a/python/lib/db/orm/dicom_archive_file.py b/python/lib/db/orm/dicom_archive_file.py new file mode 100644 index 000000000..4e47eaddc --- /dev/null +++ b/python/lib/db/orm/dicom_archive_file.py @@ -0,0 +1,25 @@ +from typing import Optional +from sqlalchemy.orm import Mapped, mapped_column, relationship +from sqlalchemy import ForeignKey +from lib.db.base import Base +import lib.db.orm.dicom_archive as db_dicom_archive +import lib.db.orm.dicom_archive_series as db_dicom_archive_series + + +class DbDicomArchiveFile(Base): + __tablename__ = 'tarchive_files' + + id : Mapped[int] = mapped_column('TarchiveFileID', primary_key=True) + archive_id : Mapped[int] = mapped_column('TarchiveID', ForeignKey('tarchive.TarchiveID')) + archive : Mapped['db_dicom_archive.DbDicomArchive'] \ + = relationship('DbDicomArchive', back_populates='files') + series_id : Mapped[Optional[int]] \ + = mapped_column('TarchiveSeriesID', ForeignKey('tarchive_series.TarchiveSeriesID')) + series : Mapped[Optional['db_dicom_archive_series.DbDicomArchiveSeries']] \ + = relationship('DbDicomArchiveSeries', back_populates="files") + series_number : Mapped[Optional[int]] = mapped_column('SeriesNumber') + series_description : Mapped[Optional[str]] = mapped_column('SeriesDescription') + file_number : Mapped[Optional[int]] = mapped_column('FileNumber') + echo_number : Mapped[Optional[int]] = mapped_column('EchoNumber') + md5_sum : Mapped[str] = mapped_column('Md5Sum') + file_name : Mapped[str] = mapped_column('FileName') diff --git a/python/lib/db/orm/dicom_archive_series.py b/python/lib/db/orm/dicom_archive_series.py new file mode 100644 index 000000000..4956d8fe5 --- /dev/null +++ b/python/lib/db/orm/dicom_archive_series.py @@ -0,0 +1,28 @@ +from typing import List, Optional +from sqlalchemy.orm import Mapped, mapped_column, relationship +from sqlalchemy import ForeignKey +from lib.db.base import Base +import lib.db.orm.dicom_archive as db_dicom_archive +import lib.db.orm.dicom_archive_file as db_dicom_archive_file + + +class DbDicomArchiveSeries(Base): + __tablename__ = 'tarchive_series' + + id : Mapped[int] = mapped_column('TarchiveSeriesID', primary_key=True) + archive_id : Mapped[int] = mapped_column('TarchiveID', ForeignKey("tarchive.TarchiveID")) + archive : Mapped['db_dicom_archive.DbDicomArchive'] \ + = relationship('DbDicomArchive', back_populates="series") + files : Mapped[List['db_dicom_archive_file.DbDicomArchiveFile']] \ + = relationship('DbDicomArchiveFile', back_populates="series") + series_number : Mapped[int] = mapped_column('SeriesNumber') + series_description : Mapped[Optional[str]] = mapped_column('SeriesDescription') + sequence_name : Mapped[Optional[str]] = mapped_column('SequenceName') + echo_time : Mapped[Optional[float]] = mapped_column('EchoTime') + repetition_time : Mapped[Optional[float]] = mapped_column('RepetitionTime') + inversion_time : Mapped[Optional[float]] = mapped_column('InversionTime') + slice_thickness : Mapped[Optional[float]] = mapped_column('SliceThickness') + phase_encoding : Mapped[Optional[str]] = mapped_column('PhaseEncoding') + number_of_files : Mapped[int] = mapped_column('NumberOfFiles') + series_uid : Mapped[Optional[str]] = mapped_column('SeriesUID') + modality : Mapped[Optional[str]] = mapped_column('Modality') diff --git a/python/lib/db/orm/mri_upload.py b/python/lib/db/orm/mri_upload.py new file mode 100644 index 000000000..f81316a9d --- /dev/null +++ b/python/lib/db/orm/mri_upload.py @@ -0,0 +1,29 @@ +from datetime import datetime +from typing import Optional +from sqlalchemy import ForeignKey +from sqlalchemy.orm import Mapped, mapped_column, relationship +from lib.db.base import Base +import lib.db.orm.dicom_archive as db_dicom_archive + + +class DbMriUpload(Base): + __tablename__ = 'mri_upload' + + id : Mapped[int] = mapped_column('UploadID', primary_key=True) + uploaded_by : Mapped[str] = mapped_column('UploadedBy') + upload_date : Mapped[Optional[datetime]] = mapped_column('UploadDate') + upload_location : Mapped[str] = mapped_column('UploadLocation') + decompressed_location : Mapped[str] = mapped_column('DecompressedLocation') + insertion_complete : Mapped[bool] = mapped_column('InsertionComplete') + inserting : Mapped[Optional[bool]] = mapped_column('Inserting') + patient_name : Mapped[str] = mapped_column('PatientName') + number_of_minc_inserted : Mapped[Optional[int]] = mapped_column('number_of_mincInserted') + number_of_minc_created : Mapped[Optional[int]] = mapped_column('number_of_mincCreated') + dicom_archive_id : Mapped[Optional[int]] \ + = mapped_column('TarchiveID', ForeignKey('tarchive.TarchiveID')) + dicom_archive : Mapped[Optional['db_dicom_archive.DbDicomArchive']] \ + = relationship('DicomArchive', back_populates='upload') + session_id : Mapped[Optional[int]] = mapped_column('SessionID') + is_candidate_info_validated : Mapped[Optional[bool]] = mapped_column('IsCandidateInfoValidated') + is_dicom_archive_validated : Mapped[bool] = mapped_column('IsTarchiveValidated') + is_phantom : Mapped[str] = mapped_column('IsPhantom') diff --git a/python/lib/db/orm/session.py b/python/lib/db/orm/session.py new file mode 100644 index 000000000..9fe1a7596 --- /dev/null +++ b/python/lib/db/orm/session.py @@ -0,0 +1,44 @@ +from datetime import date, datetime +from typing import Optional +from sqlalchemy.orm import Mapped, mapped_column +from lib.db.base import Base + + +class DbSession(Base): + __tablename__ = 'session' + + id : Mapped[int] = mapped_column('ID', primary_key=True) + cand_id : Mapped[int] = mapped_column('CandID') + center_id : Mapped[int] = mapped_column('CenterID') + project_id : Mapped[int] = mapped_column('ProjectID') + visit_no : Mapped[Optional[int]] = mapped_column('VisitNo') + visit_label : Mapped[str] = mapped_column('Visit_label') + cohort_id : Mapped[int] = mapped_column('CohortID') + submitted : Mapped[str] = mapped_column('Submitted') + current_stage : Mapped[str] = mapped_column('Current_stage') + date_stage_change : Mapped[Optional[date]] = mapped_column('Date_stage_change') + screening : Mapped[Optional[str]] = mapped_column('Screening') + date_screening : Mapped[date] = mapped_column('Date_screening') + visit : Mapped[Optional[str]] = mapped_column('Visit') + date_visit : Mapped[Optional[date]] = mapped_column('Date_visit') + date_status_change : Mapped[Optional[date]] = mapped_column('Date_status_change') + approval : Mapped[Optional[str]] = mapped_column('Approval') + date_approval : Mapped[Optional[date]] = mapped_column('Date_approval') + active : Mapped[str] = mapped_column('Active') + date_active : Mapped[Optional[date]] = mapped_column('Date_active') + registered_by : Mapped[Optional[str]] = mapped_column('RegisteredBy') + user_id : Mapped[str] = mapped_column('UserID') + date_registered : Mapped[Optional[date]] = mapped_column('Date_registered') + test_date : Mapped[int] = mapped_column('Testdate') + hardcopy_request : Mapped[str] = mapped_column('Hardcopy_request') + bvl_qc_status : Mapped[Optional[str]] = mapped_column('BVLQCStaus') + bvl_qc_type : Mapped[Optional[str]] = mapped_column('BVLQCType') + bvl_qc_exclusion : Mapped[Optional[str]] = mapped_column('BVLQCExclusion') + qcd : Mapped[Optional[str]] = mapped_column('QCd') + scan_done : Mapped[Optional[str]] = mapped_column('Scan_done') + mri_qc_status : Mapped[str] = mapped_column('MRIQCStatus') + mri_qc_pending : Mapped[str] = mapped_column('MRIQCPending') + mri_qc_first_change_time : Mapped[Optional[datetime]] = mapped_column('MRIQCFirstChange') + mri_qc_last_change_time : Mapped[Optional[datetime]] = mapped_column('MRIQCLastChange') + mri_caveat : Mapped[str] = mapped_column('MRICaveat') + language_id : Mapped[Optional[int]] = mapped_column('languageID') diff --git a/python/requirements.txt b/python/requirements.txt index b98e97a39..0d1abe193 100644 --- a/python/requirements.txt +++ b/python/requirements.txt @@ -1,19 +1,20 @@ +boto3 +flake8 +google +mat73 +matplotlib mne mne-bids>=0.6 -protobuf>=3.0.0 -pybids==0.14.0 -mysqlclient mysql-connector -google -matplotlib -nose +mysqlclient nilearn -scikit-learn -virtualenv -python-dateutil nibabel +nose numpy +protobuf>=3.0.0 +pybids==0.17.0 +python-dateutil +scikit-learn scipy -flake8 -boto3 -mat73 +sqlalchemy>=2.0.0 +virtualenv