Skip to content
Draft
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
52 changes: 52 additions & 0 deletions patch-bids-tables.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
CREATE TABLE `bids_dataset` (
`ID` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`Path` VARCHAR(255) NOT NULL,
`InsertTime` DATETIME NOT NULL,
PRIMARY KEY (`ID`),
UNIQUE KEY `bids_dataset_path_unique` (`Path`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE `bids_file` (
`ID` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`DatasetID` INT(10) UNSIGNED NOT NULL,
`Path` VARCHAR(255) NOT NULL,
`SourcePath` VARCHAR(255) NULL,
`InsertTime` DATETIME NOT NULL,
`Blake2bHash` CHAR(128) NOT NULL,
`Derivative` TINYINT(1) NOT NULL,
PRIMARY KEY (`ID`),
UNIQUE KEY `bids_file_path_unique` (`Path`),
KEY `bids_file_dataset_id_fk_idx` (`DatasetID`),
CONSTRAINT `bids_file_dataset_id_fk`
FOREIGN KEY (`DatasetID`) REFERENCES `bids_dataset` (`ID`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

ALTER TABLE `files`
ADD COLUMN `BidsInfoID` INT(10) UNSIGNED NULL,
ADD KEY `files_bids_info_id_fk_idx` (`BidsInfoID`),
ADD CONSTRAINT `files_bids_info_id_fk`
FOREIGN KEY (`BidsInfoID`) REFERENCES `bids_file` (`ID`) ON DELETE SET NULL;

ALTER TABLE `physiological_file`
ADD COLUMN `BidsInfoID` INT(10) UNSIGNED NULL,
ADD KEY `physiological_file_bids_info_id_fk_idx` (`BidsInfoID`),
ADD CONSTRAINT `physiological_file_bids_info_id_fk`
FOREIGN KEY (`BidsInfoID`) REFERENCES `bids_file` (`ID`) ON DELETE SET NULL;

ALTER TABLE `physiological_event_file`
ADD COLUMN `BidsInfoID` INT(10) UNSIGNED NULL,
ADD KEY `physiological_event_file_bids_info_id_fk_idx` (`BidsInfoID`),
ADD CONSTRAINT `physiological_event_file_bids_info_id_fk`
FOREIGN KEY (`BidsInfoID`) REFERENCES `bids_file` (`ID`) ON DELETE SET NULL;

ALTER TABLE `meg_ctf_head_shape_file`
ADD COLUMN `BidsInfoID` INT(10) UNSIGNED NULL,
ADD COLUMN `InsertTime` DATETIME NOT NULL,
ADD KEY `meg_ctf_head_shape_file_bids_info_id_fk_idx` (`BidsInfoID`),
ADD CONSTRAINT `meg_ctf_head_shape_file_bids_info_id_fk`
FOREIGN KEY (`BidsInfoID`) REFERENCES `bids_file` (`ID`) ON DELETE SET NULL;

ALTER TABLE `physiological_file`
DROP FOREIGN KEY `physiological_file_head_shape_file_id_fk`,
ADD CONSTRAINT `physiological_file_head_shape_file_id_fk`
FOREIGN KEY (`HeadShapeFileID`) REFERENCES `meg_ctf_head_shape_file` (`ID`) ON DELETE SET NULL;
30 changes: 30 additions & 0 deletions python/lib/db/models/bids_dataset.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from datetime import datetime
from pathlib import Path

from sqlalchemy.orm import Mapped, mapped_column

from lib.db.base import Base
from lib.db.decorators.string_path import StringPath


class DbBidsDataset(Base):
"""
A LORIS BIDS dataset.
"""

__tablename__ = 'bids_dataset'

id: Mapped[int] = mapped_column('ID', primary_key=True, autoincrement=True)
"""
The ID of this BIDS dataset.
"""

path: Mapped[Path] = mapped_column('Path', StringPath, unique=True)
"""
The path of this BIDS dataset, relative to the LORIS data directory.
"""

insert_time: Mapped[datetime] = mapped_column('InsertTime', default=datetime.now)
"""
The time at which this BIDS dataset was created in LORIS.
"""
58 changes: 58 additions & 0 deletions python/lib/db/models/bids_file.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
from datetime import datetime
from pathlib import Path

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

import lib.db.models.bids_dataset as db_bids_dataset
from lib.db.base import Base
from lib.db.decorators.int_bool import IntBool
from lib.db.decorators.string_path import StringPath


class DbBidsFile(Base):
"""
A file within a LORIS BIDS dataset.
"""

__tablename__ = 'bids_file'

id: Mapped[int] = mapped_column('ID', primary_key=True, autoincrement=True)
"""
The ID of this BIDS file.
"""

dataset_id: Mapped[int] = mapped_column('DatasetID', ForeignKey('bids_dataset.ID', ondelete='CASCADE'))
"""
The ID of the BIDS dataset to which this file belongs.
"""

path: Mapped[Path] = mapped_column('Path', StringPath, unique=True)
"""
The path of this file relative to its LORIS BIDS dataset.
"""

source_path: Mapped[Path | None] = mapped_column('SourcePath', StringPath)
"""
The source path of this file relative to the BIDS dataset from which it was imported.
"""

insert_time: Mapped[datetime] = mapped_column('InsertTime', default=datetime.now)
"""
The time at which this BIDS dataset was created in LORIS.
"""

blake2b_hash: Mapped[str] = mapped_column('Blake2bHash')
"""
The BLAKE2b hash of this file.
"""

derivative: Mapped[bool] = mapped_column('Derivative', IntBool)
"""
Whether this file is a BIDS derivative.
"""

dataset: Mapped['db_bids_dataset.DbBidsDataset'] = relationship('DbBidsDataset')
"""
The BIDS dataset to which this file belongs.
"""
11 changes: 11 additions & 0 deletions python/lib/db/models/file.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from sqlalchemy import ForeignKey
from sqlalchemy.orm import Mapped, mapped_column, relationship

import lib.db.models.bids_file as db_bids_file
import lib.db.models.dicom_archive as db_dicom_archive
import lib.db.models.file_parameter as db_file_parameter
import lib.db.models.session as db_session
Expand Down Expand Up @@ -40,6 +41,16 @@ class DbFile(Base):
acquisition_order_per_modality : Mapped[int | None] = mapped_column('AcqOrderPerModality')
acquisition_date : Mapped[date | None] = mapped_column('AcquisitionDate')

bids_info_id: Mapped[int | None] = mapped_column('BidsInfoID', ForeignKey('bids_file.ID', ondelete='SET NULL'))
"""
The ID of the BIDS information of this file, if any.
"""

bids_info: Mapped['db_bids_file.DbBidsFile | None'] = relationship('DbBidsFile')
"""
The BIDS information of this file, if any.
"""

session: Mapped['db_session.DbSession'] = relationship('DbSession', back_populates='files')
"""
The session to which this file belongs.
Expand Down
18 changes: 18 additions & 0 deletions python/lib/db/models/meg_ctf_head_shape_file.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
from datetime import datetime
from pathlib import Path

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

import lib.db.models.bids_file as db_bids_file
import lib.db.models.meg_ctf_head_shape_point as db_meg_ctf_head_shape_point
from lib.db.base import Base
from lib.db.decorators.string_path import StringPath
Expand All @@ -20,11 +23,21 @@ class DbMegCtfHeadShapeFile(Base):
ID of the head shape file.
"""

bids_info_id: Mapped[int | None] = mapped_column('BidsInfoID', ForeignKey('bids_file.ID', ondelete='SET NULL'))
"""
The ID of the BIDS information of this head shape file, if any.
"""

path: Mapped[Path] = mapped_column('Path', StringPath)
"""
Path of the head shape file relative to the LORIS data directory.
"""

insert_time: Mapped[datetime] = mapped_column('InsertTime', default=datetime.now)
"""
The time at which this head shape file was created in LORIS.
"""

blake2b_hash: Mapped[str] = mapped_column('Blake2bHash')
"""
Blake2B hash of the head shape file, which may be used to check that the on-disk file data
Expand All @@ -35,3 +48,8 @@ class DbMegCtfHeadShapeFile(Base):
"""
3D points present in the head shape file.
"""

bids_info: Mapped['db_bids_file.DbBidsFile | None'] = relationship('DbBidsFile')
"""
The BIDS information of this head shape file, if any.
"""
11 changes: 11 additions & 0 deletions python/lib/db/models/physio_event_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from sqlalchemy import ForeignKey
from sqlalchemy.orm import Mapped, mapped_column, relationship

import lib.db.models.bids_file as db_bids_file
import lib.db.models.imaging_file_type as db_imaging_file_type
import lib.db.models.physio_event_parameter as db_physio_event_parameter
import lib.db.models.physio_file as db_physio_file
Expand All @@ -24,8 +25,18 @@ class DbPhysioEventFile(Base):
last_update : Mapped[datetime] = mapped_column('LastUpdate', default=datetime.now)
last_written : Mapped[datetime] = mapped_column('LastWritten', default=datetime.now)

bids_info_id: Mapped[int | None] = mapped_column('BidsInfoID', ForeignKey('bids_file.ID', ondelete='SET NULL'))
"""
The ID of the BIDS information of this event file, if any.
"""

physio_file : Mapped['db_physio_file.DbPhysioFile | None'] = relationship('DbPhysioFile')
project : Mapped['db_project.DbProject | None'] = relationship('DbProject')
imaging_file_type : Mapped['db_imaging_file_type.DbImagingFileType | None'] = relationship('DbImagingFileType')
task_events : Mapped[list['db_physio_task_event.DbPhysioTaskEvent']] = relationship('DbPhysioTaskEvent', back_populates='event_file')
event_parameters : Mapped[list['db_physio_event_parameter.DbPhysioEventParameter']] = relationship('DbPhysioEventParameter', back_populates='event_file')

bids_info: Mapped['db_bids_file.DbBidsFile | None'] = relationship('DbBidsFile')
"""
The BIDS information of this event file, if any.
"""
13 changes: 12 additions & 1 deletion python/lib/db/models/physio_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from sqlalchemy import ForeignKey
from sqlalchemy.orm import Mapped, mapped_column, relationship

import lib.db.models.bids_file as db_bids_file
import lib.db.models.meg_ctf_head_shape_file as db_meg_ctf_head_shape_file
import lib.db.models.physio_channel as db_physio_channel
import lib.db.models.physio_event_archive as db_physio_event_archive
Expand Down Expand Up @@ -33,7 +34,12 @@ class DbPhysioFile(Base):
index : Mapped[int | None] = mapped_column('Index')
parent_id : Mapped[int | None] = mapped_column('ParentID')

head_shape_file_id: Mapped[int | None] = mapped_column('HeadShapeFileID', ForeignKey('meg_ctf_head_shape_file.ID'))
bids_info_id: Mapped[int | None] = mapped_column('BidsInfoID', ForeignKey('bids_file.ID', ondelete='SET NULL'))
"""
The ID of the BIDS information of this file, if any.
"""

head_shape_file_id: Mapped[int | None] = mapped_column('HeadShapeFileID', ForeignKey('meg_ctf_head_shape_file.ID', ondelete='SET NULL'))
"""
ID of the head shape file associated to this file, which is only present for MEG CTF files.
"""
Expand All @@ -48,6 +54,11 @@ class DbPhysioFile(Base):
event_files : Mapped[list['db_physio_event_file.DbPhysioEventFile']] = relationship('DbPhysioEventFile', back_populates='physio_file')
task_events : Mapped[list['db_physio_task_event.DbPhysioTaskEvent']] = relationship('DbPhysioTaskEvent', back_populates='physio_file')

bids_info: Mapped['db_bids_file.DbBidsFile | None'] = relationship('DbBidsFile')
"""
The BIDS information of this file, if any.
"""

head_shape_file: Mapped['db_meg_ctf_head_shape_file.DbMegCtfHeadShapeFile | None'] = relationship('DbMegCtfHeadShapeFile')
"""
The head shape file associated to this file, which is only present for MEG CTF files.
Expand Down
16 changes: 16 additions & 0 deletions python/lib/db/queries/bids_dataset.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from pathlib import Path

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

from lib.db.models.bids_dataset import DbBidsDataset


def try_get_bids_dataset_with_path(db: Database, path: Path) -> DbBidsDataset | None:
"""
Get a BIDS dataset from the database using its path, or return `None` if no dataset is found.
"""

return db.execute(select(DbBidsDataset)
.where(DbBidsDataset.path == path)
).scalar_one_or_none()
18 changes: 18 additions & 0 deletions python/lib/db/queries/bids_file.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from pathlib import Path

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

from lib.db.models.bids_file import DbBidsFile


def try_get_bids_file_with_dataset_id_path(db: Database, dataset_id: int, path: Path) -> DbBidsFile | None:
"""
Get a BIDS file from the database using its dataset ID and path, or return `None` if no file is
found.
"""

return db.execute(select(DbBidsFile)
.where(DbBidsFile.dataset_id == dataset_id)
.where(DbBidsFile.path == path)
).scalar_one_or_none()
Original file line number Diff line number Diff line change
Expand Up @@ -676,6 +676,7 @@ def _register_into_files_and_parameter_file(self, nifti_rel_path):
Path(nifti_rel_path),
file_type,
self.session,
None,
self.scan_type,
self.mri_scanner,
self.dicom_archive,
Expand Down
3 changes: 3 additions & 0 deletions python/lib/imaging_lib/file.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from datetime import date, datetime
from pathlib import Path

from lib.db.models.bids_file import DbBidsFile
from lib.db.models.dicom_archive import DbDicomArchive
from lib.db.models.file import DbFile
from lib.db.models.imaging_file_type import DbImagingFileType
Expand All @@ -16,6 +17,7 @@ def register_mri_file(
file_path: Path,
file_type: DbImagingFileType,
session: DbSession,
bids_info: DbBidsFile | None,
scan_type: DbMriScanType | None,
scanner: DbMriScanner | None,
dicom_archive: DbDicomArchive | None,
Expand Down Expand Up @@ -46,6 +48,7 @@ def register_mri_file(
echo_number = echo_number,
phase_encoding_direction = phase_encoding_direction,
source_file_id = None,
bids_info_id = bids_info.id if bids_info is not None else None,
scan_type_id = scan_type.id if scan_type is not None else None,
scanner_id = scanner.id if scanner is not None else None,
dicom_archive_id = dicom_archive.id if dicom_archive is not None else None,
Expand Down
17 changes: 15 additions & 2 deletions python/lib/physio/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from pathlib import Path
from typing import Any

from lib.db.models.bids_file import DbBidsFile
from lib.db.models.physio_task_event_hed import DbPhysioTaskEventHed
from lib.db.models.physio_task_event_opt import DbPhysioTaskEventOpt
from lib.db.models.physio_task_event import DbPhysioTaskEvent
Expand Down Expand Up @@ -49,7 +50,12 @@ def from_file(physio_file: DbPhysioFile) -> 'EventDictFileSource':
)


def insert_event_dict_file(env: Env, source: EventDictFileSource, event_file_path: Path) -> DbPhysioEventFile:
def insert_event_dict_file(
env: Env,
bids_info: DbBidsFile,
source: EventDictFileSource,
event_file_path: Path,
) -> DbPhysioEventFile:
"""
Insert an event dictionary file into the LORIS database.
"""
Expand All @@ -59,6 +65,7 @@ def insert_event_dict_file(env: Env, source: EventDictFileSource, event_file_pat
project_id = source.project.id,
file_type = 'json',
file_path = event_file_path,
bids_info_id = bids_info.id,
)

env.db.add(event_dict_file)
Expand Down Expand Up @@ -202,7 +209,12 @@ def parse_and_insert_event_dict(
return tag_dict


def insert_events_file(env: Env, physio_file: DbPhysioFile, event_file_path: Path) -> DbPhysioEventFile:
def insert_events_file(
env: Env,
physio_file: DbPhysioFile,
bids_info: DbBidsFile,
event_file_path: Path,
) -> DbPhysioEventFile:
"""
Insert an events file into the LORIS database.
"""
Expand All @@ -212,6 +224,7 @@ def insert_events_file(env: Env, physio_file: DbPhysioFile, event_file_path: Pat
project_id = physio_file.session.project.id,
file_type = 'tsv',
file_path = event_file_path,
bids_info_id = bids_info.id,
)

env.db.add(event_dict_file)
Expand Down
Loading