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
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ include = [
"python/lib/get_session_info.py",
"python/lib/logging.py",
"python/lib/make_env.py",
"python/scripts/import_bids_dataset.py",
"python/scripts/import_dicom_study.py",
"python/scripts/summarize_dicom_study.py",
"python/loris_bids_reader",
Expand Down
123 changes: 42 additions & 81 deletions python/lib/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,22 +34,7 @@ def get_data_dir_path_config(env: Env) -> Path:
"""

data_dir_path = Path(_get_config_value(env, 'dataDirBasepath'))

if not data_dir_path.is_dir():
log_error_exit(
env,
(
f"The LORIS base data directory path configuration value '{data_dir_path}' does not refer to an"
" existing directory."
)
)

if not os.access(data_dir_path, os.R_OK) or not os.access(data_dir_path, os.W_OK):
log_error_exit(
env,
f"Missing read or write permission on the LORIS base data directory '{data_dir_path}'.",
)

check_loris_directory(env, data_dir_path, "data")
return data_dir_path


Expand All @@ -60,22 +45,7 @@ def get_dicom_archive_dir_path_config(env: Env) -> Path:
"""

dicom_archive_dir_path = Path(_get_config_value(env, 'tarchiveLibraryDir'))

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 directory."
),
)

if not os.access(dicom_archive_dir_path, os.R_OK) or not os.access(dicom_archive_dir_path, os.W_OK):
log_error_exit(
env,
f"Missing read or write permission on the LORIS DICOM archive directory '{dicom_archive_dir_path}'.",
)

check_loris_directory(env, dicom_archive_dir_path, "DICOM archive")
return dicom_archive_dir_path


Expand All @@ -87,74 +57,43 @@ def get_default_bids_visit_label_config(env: Env) -> str | None:
return _try_get_config_value(env, 'default_bids_vl')


def get_eeg_viz_enabled_config(env: Env) -> bool:
def get_ephys_visualization_enabled_config(env: Env) -> bool:
"""
Get whether the EEG visualization is enabled from the in-database configuration.
Get whether the electrophysiology visualization is enabled from the in-database configuration.
"""

eeg_viz_enabled = _try_get_config_value(env, 'useEEGBrowserVisualizationComponents')
return eeg_viz_enabled == 'true' or eeg_viz_enabled == '1'


def get_eeg_chunks_dir_path_config(env: Env) -> Path | None:
def get_ephys_chunks_dir_path_config(env: Env) -> Path | None:
"""
Get the EEG chunks directory path configuration value from the in-database configuration.
Get the electrophysiology chunks directory path configuration value from the in-database
configuration.
"""

eeg_chunks_path = _try_get_config_value(env, 'EEGChunksPath')
if eeg_chunks_path is None:
ephys_chunks_path = _try_get_config_value(env, 'EEGChunksPath')
if ephys_chunks_path is None:
return None

eeg_chunks_path = Path(eeg_chunks_path)

if not eeg_chunks_path.is_dir():
log_error_exit(
env,
(
f"The configuration value for the LORIS EEG chunks directory path '{eeg_chunks_path}' does not refer to"
" an existing directory."
),
)

if not os.access(eeg_chunks_path, os.R_OK) or not os.access(eeg_chunks_path, os.W_OK):
log_error_exit(
env,
f"Missing read or write permission on the LORIS EEG chunks directory '{eeg_chunks_path}'.",
)

return eeg_chunks_path
ephys_chunks_path = Path(ephys_chunks_path)
check_loris_directory(env, ephys_chunks_path, "electrophysiology chunks")
return ephys_chunks_path


def get_eeg_pre_package_download_dir_path_config(env: Env) -> Path | None:
def get_ephys_archive_dir_path_config(env: Env) -> Path | None:
"""
Get the EEG pre-packaged download path configuration value from the in-database configuration.
Get the electrophysiology archive directory path configuration value from the in-database
configuration.
"""

eeg_pre_package_path = _try_get_config_value(env, 'prePackagedDownloadPath')
if eeg_pre_package_path is None:
ephys_archive_dir_path = _try_get_config_value(env, 'prePackagedDownloadPath')
if ephys_archive_dir_path is None:
return None

eeg_pre_package_path = Path(eeg_pre_package_path)

if not eeg_pre_package_path.is_dir():
log_error_exit(
env,
(
"The configuration value for the LORIS EEG pre-packaged download directory path"
f" '{eeg_pre_package_path}' does not refer to an existing directory."
),
)

if not os.access(eeg_pre_package_path, os.R_OK) or not os.access(eeg_pre_package_path, os.W_OK):
log_error_exit(
env,
(
"Missing read or write permission on the LORIS EEG pre-packaged download directory"
f" '{eeg_pre_package_path}'."
),
)

return eeg_pre_package_path
ephys_archive_dir_path = Path(ephys_archive_dir_path)
check_loris_directory(env, ephys_archive_dir_path, "electrophysiology archive")
return ephys_archive_dir_path


def _get_config_value(env: Env, setting_name: str) -> str:
Expand All @@ -179,6 +118,28 @@ def _get_config_value(env: Env, setting_name: str) -> str:
return config.value


def check_loris_directory(env: Env, dir_path: Path, display_name: str):
"""
Check that a LORIS directory exists and is readable and writable, or exit the program with an
error otherwise.
"""

if not dir_path.is_dir():
log_error_exit(
env,
(
f"The LORIS {display_name} directory path configuration value '{dir_path}' does not refer to an"
" existing directory."
),
)

if not os.access(dir_path, os.R_OK) or not os.access(dir_path, os.W_OK):
log_error_exit(
env,
f"Missing read or write permission on the {display_name} directory '{dir_path}'.",
)


def _try_get_config_value(env: Env, setting_name: str) -> str | None:
"""
Get a configuration value from the database using a configuration setting name, or return
Expand Down
5 changes: 5 additions & 0 deletions python/lib/database_lib/physiological_event_archive.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
"""This class performs database queries for the physiological_event_archive table"""

from typing_extensions import deprecated


@deprecated('Use `lib.db.physio_event_archive.DbPhysioEventArchive` instead')
class PhysiologicalEventArchive:

def __init__(self, db, verbose):
Expand All @@ -17,6 +20,7 @@ def __init__(self, db, verbose):
self.table = 'physiological_event_archive'
self.verbose = verbose

@deprecated('Use `lib.db.physio_event_archive.DbPhysioEventArchive.physio_file_id` instead')
def grep_from_physiological_file_id(self, physiological_file_id):
"""
Gets rows given a physiological_file_id
Expand All @@ -33,6 +37,7 @@ def grep_from_physiological_file_id(self, physiological_file_id):
args=(physiological_file_id,)
)

@deprecated('Use `lib.db.physio_event_archive.DbPhysioEventArchive` instead')
def insert(self, physiological_file_id, blake2, archive_path):
"""
Inserts a new entry in the physiological_event_archive table.
Expand Down
37 changes: 37 additions & 0 deletions python/lib/db/models/meg_ctf_head_shape_file.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
from pathlib import Path

from sqlalchemy.orm import Mapped, mapped_column, relationship

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


class DbMegCtfHeadShapeFile(Base):
"""
A MEG CTF `headshape.pos` file. This file contains 3D points positioned on the subject head and
is shared by all the CTF files of an MEG acquisition.
"""

__tablename__ = 'meg_ctf_head_shape_file'

id: Mapped[int] = mapped_column('ID', primary_key=True)
"""
ID of the head shape file.
"""

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

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
matches the file registered in the LORIS database.
"""

points: Mapped[list['db_meg_ctf_head_shape_point.DbMegCtfHeadShapePoint']] = relationship('DbMegCtfHeadShapePoint', back_populates='file')
"""
3D points present in the head shape file.
"""
52 changes: 52 additions & 0 deletions python/lib/db/models/meg_ctf_head_shape_point.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
from decimal import Decimal

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

import lib.db.models.meg_ctf_head_shape_file as db_meg_ctf_head_shape_file
from lib.db.base import Base


# TODO: It might be possible that headshape files contain points in other units than centimeters,
# in which case the database should be extended to handle it.
class DbMegCtfHeadShapePoint(Base):
"""
A 3D point present in a MEG CTF `headshape.pos` file.
"""

__tablename__ = 'meg_ctf_head_shape_point'

id: Mapped[int] = mapped_column('ID', primary_key=True)
"""
ID of the head shape point.
"""

file_id: Mapped[int] = mapped_column('FileID', ForeignKey('meg_ctf_head_shape_file.ID'))
"""
ID of the head shape file to which this point belongs.
"""

name: Mapped[str] = mapped_column('Name')
"""
Name of the point, which may either be an integer or an anatomical landmark label.
"""

x: Mapped[Decimal] = mapped_column('X')
"""
X coordinate of the point in the head shape file, in centimeters.
"""

y: Mapped[Decimal] = mapped_column('Y')
"""
Y coordinate of the point in the head shape file, in centimeters.
"""

z: Mapped[Decimal] = mapped_column('Z')
"""
Z coordinate of the point in the head shape file, in centimeters.
"""

file: Mapped['db_meg_ctf_head_shape_file.DbMegCtfHeadShapeFile'] = relationship('DbMegCtfHeadShapeFile', back_populates='points')
"""
The head shape file to which this point belongs.
"""
14 changes: 14 additions & 0 deletions python/lib/db/models/physio_coord_system_electrode.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from datetime import datetime

from sqlalchemy.orm import Mapped, mapped_column

from lib.db.base import Base


class DbPhysioCoordSystemElectrode(Base):
__tablename__ = 'physiological_coord_system_electrode_rel'

coord_system_id : Mapped[int] = mapped_column('PhysiologicalCoordSystemID', primary_key=True)
electrode_id : Mapped[int] = mapped_column('PhysiologicalElectrodeID', primary_key=True)
physio_file_id : Mapped[int] = mapped_column('PhysiologicalFileID')
insert_time : Mapped[datetime] = mapped_column('InsertTime', default=datetime.now)
11 changes: 11 additions & 0 deletions python/lib/db/models/physio_coord_system_point_3d.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from sqlalchemy.orm import Mapped, mapped_column

from lib.db.base import Base


class DbPhysioCoordSystemPoint3d(Base):
__tablename__ = 'physiological_coord_system_point_3d_rel'

coord_system_id : Mapped[int] = mapped_column('PhysiologicalCoordSystemID', primary_key=True)
point_3d_id : Mapped[int] = mapped_column('Point3DID', primary_key=True)
name : Mapped[str] = mapped_column('Name')
26 changes: 26 additions & 0 deletions python/lib/db/models/physio_electrode.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from pathlib import Path

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

import lib.db.models.physio_electrode_material as db_physio_electrode_material
import lib.db.models.physio_electrode_type as db_physio_electrode_type
import lib.db.models.point_3d as db_point_3d
from lib.db.base import Base
from lib.db.decorators.string_path import StringPath


class DbPhysioElectrode(Base):
__tablename__ = 'physiological_electrode'

id : Mapped[int] = mapped_column('PhysiologicalElectrodeID', primary_key=True)
type_id : Mapped[int | None] = mapped_column('PhysiologicalElectrodeTypeID', ForeignKey('physiological_electrode_type.PhysiologicalElectrodeTypeID'))
material_id : Mapped[int | None] = mapped_column('PhysiologicalElectrodeMaterialID', ForeignKey('physiological_electrode_material.PhysiologicalElectrodeMaterialID'))
name : Mapped[str] = mapped_column('Name')
point_3d_id : Mapped[int] = mapped_column('Point3DID', ForeignKey('point_3d.Point3DID'))
impedance : Mapped[int | None] = mapped_column('Impedance')
file_path : Mapped[Path | None] = mapped_column('FilePath', StringPath)

type : Mapped['db_physio_electrode_type.DbPhysioElectrodeType | None'] = relationship('DbPhysioElectrodeType')
material : Mapped['db_physio_electrode_material.DbPhysioElectrodeMaterial | None'] = relationship('DbPhysioElectrodeMaterial')
point_3d : Mapped['db_point_3d.DbPoint3D'] = relationship('DbPoint3D')
10 changes: 10 additions & 0 deletions python/lib/db/models/physio_electrode_material.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from sqlalchemy.orm import Mapped, mapped_column

from lib.db.base import Base


class DbPhysioElectrodeMaterial(Base):
__tablename__ = 'physiological_electrode_material'

id : Mapped[int] = mapped_column('PhysiologicalElectrodeMaterialID', primary_key=True)
name : Mapped[str] = mapped_column('ElectrodeMaterial')
10 changes: 10 additions & 0 deletions python/lib/db/models/physio_electrode_type.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from sqlalchemy.orm import Mapped, mapped_column

from lib.db.base import Base


class DbPhysioElectrodeType(Base):
__tablename__ = 'physiological_electrode_type'

id : Mapped[int] = mapped_column('PhysiologicalElectrodeTypeID', primary_key=True)
name : Mapped[str] = mapped_column('ElectrodeType')
2 changes: 1 addition & 1 deletion python/lib/db/models/physio_event_archive.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,6 @@ class DbPhysioEventArchive(Base):
id : Mapped[int] = mapped_column('EventArchiveID', primary_key=True)
physio_file_id : Mapped[int] = mapped_column('PhysiologicalFileID', ForeignKey('physiological_file.PhysiologicalFileID'))
blake2b_hash : Mapped[str] = mapped_column('Blake2bHash')
file_path : Mapped[Path] = mapped_column('FilePath', StringPath)
path : Mapped[Path] = mapped_column('FilePath', StringPath)

physio_file: Mapped['db_physio_file.DbPhysioFile'] = relationship('DbPhysioFile')
Loading