Skip to content
Open
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
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ dependencies = [
"scipy",
"sqlalchemy>=2.0.0",
"virtualenv",
"loris-bids-reader @ {root:uri}/python/loris_bids_reader",
"loris-eeg-chunker @ {root:uri}/python/loris_eeg_chunker",
]

Expand Down Expand Up @@ -80,6 +81,7 @@ include = [
"python/lib/make_env.py",
"python/scripts/import_dicom_study.py",
"python/scripts/summarize_dicom_study.py",
"python/loris_bids_reader",
]
typeCheckingMode = "strict"
reportMissingTypeStubs = "none"
Expand Down
16 changes: 7 additions & 9 deletions python/lib/bidsreader.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
"""Reads a BIDS structure into a data dictionary using bids.grabbids."""

import json
import re
import sys
from pathlib import Path

from bids import BIDSLayout
from loris_bids_reader.files.dataset_description import BidsDatasetDescriptionJsonFile

import lib.exitcode
import lib.utilities as utilities
Expand Down Expand Up @@ -53,14 +54,11 @@ def __init__(self, bids_dir, verbose, validate = True):
self.dataset_name = None
self.bids_version = None
try:
dataset_json = bids_dir + "/dataset_description.json"
dataset_description = {}
with open(dataset_json) as json_file:
dataset_description = json.load(json_file)
self.dataset_name = dataset_description['Name']
self.bids_version = dataset_description['BIDSVersion']
except Exception:
print("WARNING: Cannot read dataset_description.json")
dataset_description = BidsDatasetDescriptionJsonFile(Path(bids_dir) / "dataset_description.json")
self.dataset_name = dataset_description.data['Name']
self.bids_version = dataset_description.data['BIDSVersion']
except Exception as error:
print(f"WARNING: Error while reading dataset_description.json: {error}")

# load BIDS candidates information
self.participants_info = self.load_candidates_from_bids()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import sys
from pathlib import Path

from loris_bids_reader.mri.sidecar import BidsMriSidecarJsonFile

import lib.exitcode
from lib.bids import get_bids_json_session_info
from lib.db.queries.dicom_archive import try_get_dicom_archive_series_with_series_uid_echo_time
Expand Down Expand Up @@ -47,9 +49,13 @@ def __init__(self, loris_getopt_obj, script_name):
if 's3_url' in self.options_dict["nifti_path"].keys() else None
self.nifti_blake2 = compute_file_blake2b_hash(self.nifti_path)
self.nifti_md5 = compute_file_md5_hash(self.nifti_path)
self.json_path = self.options_dict["json_path"]["value"]
self.json_blake2 = compute_file_blake2b_hash(self.json_path) if self.json_path else None
self.json_md5 = compute_file_md5_hash(self.json_path) if self.json_path else None
self.sidecar_json = self._load_json_sidecar_file()
if self.sidecar_json is not None:
self.json_blake2 = compute_file_blake2b_hash(self.sidecar_json.path)
self.json_md5 = compute_file_md5_hash(self.sidecar_json.path)
else:
self.json_blake2 = None
self.json_md5 = None
self.bval_path = self.options_dict["bval_path"]["value"]
self.bval_blake2 = compute_file_blake2b_hash(self.bval_path) if self.bval_path else None
self.bvec_path = self.options_dict["bvec_path"]["value"]
Expand Down Expand Up @@ -77,7 +83,7 @@ def __init__(self, loris_getopt_obj, script_name):
# ---------------------------------------------------------------------------------------------
# Load the JSON file object with scan parameters if a JSON file was provided
# ---------------------------------------------------------------------------------------------
self.json_file_dict = self._load_json_sidecar_file()
self.json_file_dict = self.sidecar_json.data if self.sidecar_json is not None else dict()
add_nifti_spatial_file_parameters(self.nifti_path, self.json_file_dict)

# ---------------------------------------------------------------------------------
Expand Down Expand Up @@ -251,15 +257,12 @@ def _load_json_sidecar_file(self):
:return: dictionary with the information present in the JSON file
:rtype: dict
"""
json_path = self.options_dict["json_path"]["value"]

if not json_path:
return dict()
sidecar_json_path = Path(self.options_dict["json_path"]["value"])

with open(json_path) as json_file:
json_data_dict = json.load(json_file)
if not sidecar_json_path:
return None

return json_data_dict
return BidsMriSidecarJsonFile(sidecar_json_path)

def _validate_nifti_patient_name_with_dicom_patient_name(self):
"""
Expand Down Expand Up @@ -385,12 +388,11 @@ def _move_to_assembly_and_insert_file_info(self):

# add TaskName to the JSON file if the file's BIDS scan type subcategory contains task-*
bids_subcategories = self.bids_categories_dict['BIDSScanTypeSubCategory']
if self.json_path and bids_subcategories and 'task-' in bids_subcategories:
with open(self.json_path) as json_file:
json_data = json.load(json_file)
json_data['TaskName'] = re.search(r'task-([a-zA-Z0-9]*)', bids_subcategories).group(1)
with open(self.json_path, 'w') as json_file:
json_file.write(json.dumps(json_data, indent=4))
if self.sidecar_json is not None and bids_subcategories and 'task-' in bids_subcategories:
# FIXME: This code writes data in the input files, this should be avoided.
self.sidecar_json.data['TaskName'] = re.search(r'task-([a-zA-Z0-9]*)', bids_subcategories).group(1)
with open(self.sidecar_json.path, 'w') as json_file:
json_file.write(json.dumps(self.sidecar_json.data, indent=4))

# determine the new file paths and move the files in assembly_bids
self.assembly_nifti_rel_path = self._determine_new_nifti_assembly_rel_path()
Expand Down Expand Up @@ -519,7 +521,7 @@ def _create_destination_dir_and_move_image_files(self, destination):
:type destination: str
"""
nii_rel_path = self.assembly_nifti_rel_path if destination == 'assembly_bids' else self.trashbin_nifti_rel_path
json_rel_path = re.sub(r"\.nii(\.gz)?$", '.json', nii_rel_path) if self.json_path else None
json_rel_path = re.sub(r"\.nii(\.gz)?$", '.json', nii_rel_path) if self.sidecar_json is not None else None
bval_rel_path = re.sub(r"\.nii(\.gz)?$", '.bval', nii_rel_path) if self.bval_path else None
bvec_rel_path = re.sub(r"\.nii(\.gz)?$", '.bvec', nii_rel_path) if self.bvec_path else None

Expand All @@ -532,10 +534,10 @@ def _create_destination_dir_and_move_image_files(self, destination):
'new_file_path': os.path.join(self.data_dir, nii_rel_path)
}
]
if self.json_path:
if self.sidecar_json is not None:
file_type_to_move_list.append(
{
'original_file_path': self.json_path,
'original_file_path': self.sidecar_json.path,
'new_file_path': os.path.join(self.data_dir, json_rel_path)
}
)
Expand Down Expand Up @@ -564,7 +566,7 @@ def _create_destination_dir_and_move_image_files(self, destination):

if destination == 'assembly_bids':
self.json_file_dict['file_blake2b_hash'] = self.nifti_blake2
if self.json_path:
if self.sidecar_json is not None:
self.json_file_dict['bids_json_file'] = json_rel_path
self.json_file_dict['bids_json_file_blake2b_hash'] = self.json_blake2
if self.bval_path:
Expand Down
18 changes: 9 additions & 9 deletions python/lib/mri.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
"""Deals with MRI BIDS datasets and register them into the database."""

import getpass
import json
import os
import re
import sys
from pathlib import Path

from loris_bids_reader.mri.sidecar import BidsMriSidecarJsonFile

import lib.exitcode
import lib.utilities as utilities
Expand Down Expand Up @@ -275,12 +277,12 @@ def fetch_and_insert_nifti_file(self, nifti_file, derivatives=None):
scan_type = entities['suffix']

# loop through the associated files to grep JSON, bval, bvec...
json_file = None
sidecar_json = None
other_assoc_files = {}
for assoc_file in associated_files:
file_info = assoc_file.get_entities()
if re.search(r'json$', file_info['extension']):
json_file = assoc_file.path
sidecar_json = BidsMriSidecarJsonFile(Path(assoc_file.path))
elif re.search(r'bvec$', file_info['extension']):
other_assoc_files['bvec_file'] = assoc_file.path
elif re.search(r'bval$', file_info['extension']):
Expand All @@ -292,14 +294,12 @@ def fetch_and_insert_nifti_file(self, nifti_file, derivatives=None):

# read the json file if it exists
file_parameters = {}
if json_file:
with open(json_file) as data_file:
file_parameters = json.load(data_file)
file_parameters = imaging.map_bids_param_to_loris_param(file_parameters)
if sidecar_json is not None:
file_parameters = imaging.map_bids_param_to_loris_param(sidecar_json.data)
# copy the JSON file to the LORIS BIDS import directory
json_path = self.copy_file_to_loris_bids_dir(json_file)
json_path = self.copy_file_to_loris_bids_dir(sidecar_json.path)
file_parameters['bids_json_file'] = json_path
json_blake2 = compute_file_blake2b_hash(json_file)
json_blake2 = compute_file_blake2b_hash(sidecar_json.path)
file_parameters['bids_json_file_blake2b_hash'] = json_blake2

# grep the file type from the ImagingFileTypes table
Expand Down
3 changes: 3 additions & 0 deletions python/loris_bids_reader/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# LORIS BIDS reader

This package contains a LORIS-agnostic BIDS reader library used to read BIDS datasets and files in order to use these datasets in various LORIS pipelines.
18 changes: 18 additions & 0 deletions python/loris_bids_reader/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[project]
name = "loris-bids-reader"
version = "27.0.0"
description = "The LORIS BIDS reader library"
readme = "README.md"
license = "GPL-3.0-or-later"
requires-python = ">= 3.11"

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[tool.hatch.build.targets.wheel]
packages = ["src/loris_bids_reader"]

[tool.ruff]
extend = "../../pyproject.toml"
src = ["src"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from typing import Any

from loris_bids_reader.json import BidsJsonFile


class BidsDatasetDescriptionJsonFile(BidsJsonFile):
"""
Class representing a BIDS dataset_description.json file.

Documentation: https://bids-specification.readthedocs.io/en/stable/modality-agnostic-files/dataset-description.html#dataset_descriptionjson
"""

def validate_data(self, data: dict[str, Any]):
if 'Name' not in data:
raise Exception("Missing required field 'Name' in dataset_description.json.")

if 'BIDSVersion' not in data:
raise Exception("Missing required field 'BIDSVersion' in dataset_description.json.")
26 changes: 26 additions & 0 deletions python/loris_bids_reader/src/loris_bids_reader/json.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import json
from pathlib import Path
from typing import Any


class BidsJsonFile:
"""
Class representing a BIDS JSON file.
"""

path: Path
data: dict[str, Any]

def __init__(self, path: Path):
self.path = path
with open(path) as file:
data = json.load(file)
self.validate_data(data)
self.data = data

def validate_data(self, data: dict[str, Any]):
"""
Validate the JSON data for this file.
"""

pass
11 changes: 11 additions & 0 deletions python/loris_bids_reader/src/loris_bids_reader/mri/sidecar.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from loris_bids_reader.json import BidsJsonFile


class BidsMriSidecarJsonFile(BidsJsonFile):
"""
Class representing a BIDS MRI sidecar JSON file.

Documentation: https://bids-specification.readthedocs.io/en/stable/modality-specific-files/magnetic-resonance-imaging-data.html
"""

pass
Loading