diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 00000000..13566b81 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/DigLabTools.iml b/.idea/DigLabTools.iml new file mode 100644 index 00000000..57c2c965 --- /dev/null +++ b/.idea/DigLabTools.iml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 00000000..eff0e8d7 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,19 @@ + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 00000000..105ce2da --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 00000000..0ab07188 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 00000000..770a7849 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/shelf/Uncommitted_changes_before_Checkout_at_23_04_2024_10_24_[Changes]/shelved.patch b/.idea/shelf/Uncommitted_changes_before_Checkout_at_23_04_2024_10_24_[Changes]/shelved.patch new file mode 100644 index 00000000..83892eb4 --- /dev/null +++ b/.idea/shelf/Uncommitted_changes_before_Checkout_at_23_04_2024_10_24_[Changes]/shelved.patch @@ -0,0 +1,17 @@ +Index: bep32v01/BidsDatatype.py +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP +<+>import yaml\n\n\ndef _load_data_types(yaml_path=\"ressources/schema/objects/datatypes.yaml\"):\n \"\"\"\n Load data types from a YAML file.\n\n Args:\n yaml_path (str): The path to the YAML file containing data type data.\n\n Returns:\n dict: A dictionary containing data type data.\n \"\"\"\n with open(yaml_path, 'r') as file:\n data_types_data = yaml.safe_load(file)\n return data_types_data\n\n\nclass DataTypes:\n def __init__(self):\n \"\"\"\n Initialize a DataTypes object and load data types from a YAML file.\n \"\"\"\n self.data_types = _load_data_types()\n\n def get_data_type_value(self, data_type_name):\n \"\"\"\n Get the value of a specific data type.\n\n Args:\n data_type_name (str): The name of the data type to retrieve.\n\n Returns:\n str: The value of the data type, or None if the data type does not exist.\n \"\"\"\n return self.data_types.get(data_type_name, {}).get(\"value\")\n\n\ndef main():\n \"\"\"\n Main function to demonstrate the usage of the DataTypes class.\n \"\"\"\n data_types = DataTypes()\n data_type_name = \"anat\"\n data_type = data_types.get_data_type_value(data_type_name)\n if data_type:\n print(f\"Données de type '{data_type_name}':\")\n print(data_type)\n else:\n print(f\"Le type de données '{data_type_name}' n'existe pas.\")\n\n\nif __name__ == \"__main__\":\n main()\n +=================================================================== +diff --git a/bep32v01/BidsDatatype.py b/bep32v01/BidsDatatype.py +--- a/bep32v01/BidsDatatype.py ++++ b/bep32v01/BidsDatatype.py +@@ -41,7 +41,7 @@ + Main function to demonstrate the usage of the DataTypes class. + """ + data_types = DataTypes() +- data_type_name = "anat" ++ data_type_name = "mri" + data_type = data_types.get_data_type_value(data_type_name) + if data_type: + print(f"Données de type '{data_type_name}':") diff --git a/.idea/shelf/Uncommitted_changes_before_Checkout_at_23_04_2024_10_24_[Changes]1/shelved.patch b/.idea/shelf/Uncommitted_changes_before_Checkout_at_23_04_2024_10_24_[Changes]1/shelved.patch new file mode 100644 index 00000000..e69de29b diff --git a/.idea/shelf/Uncommitted_changes_before_Checkout_at_23_04_2024_10_24__Changes_.xml b/.idea/shelf/Uncommitted_changes_before_Checkout_at_23_04_2024_10_24__Changes_.xml new file mode 100644 index 00000000..35eddc03 --- /dev/null +++ b/.idea/shelf/Uncommitted_changes_before_Checkout_at_23_04_2024_10_24__Changes_.xml @@ -0,0 +1,4 @@ + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 00000000..35eb1ddf --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml new file mode 100644 index 00000000..33616f73 --- /dev/null +++ b/.idea/workspace.xml @@ -0,0 +1,247 @@ + + + + + + + + + + + + + + + + + + { + "associatedIndex": 0 +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1712147092346 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/=2.0.0 b/=2.0.0 new file mode 100644 index 00000000..0fd27fec --- /dev/null +++ b/=2.0.0 @@ -0,0 +1,6 @@ +Channels: + - conda-forge + - defaults +Platform: linux-64 +Collecting package metadata (repodata.json): ...working... done +Solving environment: ...working... failed diff --git a/bep32v01/BidsDatatype.py b/bep32v01/BidsDatatype.py new file mode 100644 index 00000000..900cea5a --- /dev/null +++ b/bep32v01/BidsDatatype.py @@ -0,0 +1,54 @@ +import yaml + + +def _load_data_types(yaml_path="ressources/schema/objects/datatypes.yaml"): + """ + Load data types from a YAML file. + + Args: + yaml_path (str): The path to the YAML file containing data type data. + + Returns: + dict: A dictionary containing data type data. + """ + with open(yaml_path, 'r') as file: + data_types_data = yaml.safe_load(file) + return data_types_data + + +class DataTypes: + def __init__(self): + """ + Initialize a DataTypes object and load data types from a YAML file. + """ + self.data_types = _load_data_types() + + def get_data_type_value(self, data_type_name): + """ + Get the value of a specific data type. + + Args: + data_type_name (str): The name of the data type to retrieve. + + Returns: + str: The value of the data type, or None if the data type does not exist. + """ + return self.data_types.get(data_type_name, {}).get("value") + + +def main(): + """ + Main function to demonstrate the usage of the DataTypes class. + """ + data_types = DataTypes() + data_type_name = "anat" + data_type = data_types.get_data_type_value(data_type_name) + if data_type: + print(f"Données de type '{data_type_name}':") + print(data_type) + else: + print(f"Le type de données '{data_type_name}' n'existe pas.") + + +if __name__ == "__main__": + main() diff --git a/bep32v01/BidsDirectoryStructure.py b/bep32v01/BidsDirectoryStructure.py new file mode 100644 index 00000000..f61f77cd --- /dev/null +++ b/bep32v01/BidsDirectoryStructure.py @@ -0,0 +1,144 @@ +from pathlib import Path +import yaml +import helper + + +class DirectoryStructure: + def __init__(self): + """ + Initialize a DirectoryStructure object with default parameters. + """ + self.relative_path = "ressources/schema/rules/directories.yaml" + self.entity_directory = [] + self.all_directory = None + self.value_directory = None + self.required_directory = None + self.optional_directory = None + self.recommended_directory = None + self.top_level_directory = None + self.sub_directory = None + self.get_detail() + + def load_all_directories(self, relative_path): + """ + Load all directories from a YAML file. + + Args: + relative_path (str): The relative path to the YAML file. + + Returns: + list: A list of all directories. + """ + # Retrieve absolute path + absolute_path = Path(relative_path).resolve() + + # Check if the file exists + if absolute_path.exists(): + with open(absolute_path, 'r') as file: + directory_rules = yaml.safe_load(file) + + if directory_rules: + self.all_directory = list(set(helper.find_keys_in_dict(directory_rules, 'level'))) + else: + print("Le fichier de règles des répertoires est vide.") + else: + print("Le fichier YAML spécifié n'existe pas :", absolute_path) + return self.all_directory + + def load_all_directoires_all_details(self, relative_path): + """ + Load all directory details from a YAML file. + + Args: + relative_path (str): The relative path to the YAML file. + """ + self.entity_directory, self.value_directory, self.required_directory, self.optional_directory, self.recommended_directory, self.top_level_directory = helper.get_directories_with_details( + relative_path) + + def get_detail(self): + """ + Get details of all directories. + """ + self.load_all_directories(self.relative_path) + self.load_all_directoires_all_details(self.relative_path) + return self + + # Getters for attributes + + def get_all_directory(self): + """ + Get all directories. + + Returns: + list: A list of all directories. + """ + return self.all_directory + + def get_entity_directory(self): + """ + Get entity directories. + + Returns: + list: A list of entity directories. + """ + return self.entity_directory + + def get_value_directory(self): + """ + Get value directories. + + Returns: + list: A list of value directories. + """ + return self.value_directory + + def get_required_directory(self): + """ + Get required directories. + + Returns: + list: A list of required directories. + """ + return self.required_directory + + def get_optional_directory(self): + """ + Get optional directories. + + Returns: + list: A list of optional directories. + """ + return self.optional_directory + + def get_recommended_directory(self): + """ + Get recommended directories. + + Returns: + list: A list of recommended directories. + """ + return self.recommended_directory + + def get_top_level_directory(self): + """ + Get top-level directories. + + Returns: + list: A list of top-level directories. + """ + return self.top_level_directory + + +if __name__ == "__main__": + relative_path = "ressources/schema/rules/directories.yaml" + + common_structure = DirectoryStructure() + common_structure.get_detail() + + print("All:", common_structure.get_all_directory()) + print("Entity:", common_structure.get_entity_directory()) + print("par Valeur :", common_structure.get_value_directory()) + print("REQUIRED :", common_structure.get_required_directory()) + print("optional :", common_structure.get_optional_directory()) + print("top level:", common_structure.get_top_level_directory()) + print("recomende:", common_structure.get_recommended_directory()) diff --git a/bep32v01/BidsEmptyRepositoryGenerator.py b/bep32v01/BidsEmptyRepositoryGenerator.py new file mode 100644 index 00000000..447dff9d --- /dev/null +++ b/bep32v01/BidsEmptyRepositoryGenerator.py @@ -0,0 +1,42 @@ +import sys +from Createfile import CreatFile +from Createdirectory import Createdirectory + + +class Generator: + def __init__(self, output, sub_id=1, session_id=1, modality=None): + """ + Initialize a Generator object. + + Args: + output (str): The output folder path. + sub_id (int): Subject ID. + session_id (int): Session ID. + modality (str, optional): The modality name. + """ + self.output = output + self.modality = modality.strip() if modality else None + if self.modality: + self.directory_builder = Createdirectory(output, sub_id, session_id, self.modality) + self.file_builder = CreatFile(output) + self.generate() + else: + print("No modality provided. Please specify a modality.") + + def generate(self): + """Generate files and directories.""" + self.directory_builder.build() + self.file_builder.build() + + +if __name__ == "__main__": + output = input("Enter the output folder path: ").strip() + if output: + sub_id = input("Enter the subject ID (default is 1): ").strip() + session_id = input("Enter the session ID (default is 1): ").strip() + modality = input("Enter the modality (optional): ").strip() + sub_id = int(sub_id) if sub_id.isdigit() else 1 + session_id = int(session_id) if session_id.isdigit() else 1 + generator = Generator(output, sub_id, session_id, modality) + else: + print("Output folder path is required.") diff --git a/bep32v01/BidsEntity.py b/bep32v01/BidsEntity.py new file mode 100644 index 00000000..bce8329d --- /dev/null +++ b/bep32v01/BidsEntity.py @@ -0,0 +1,55 @@ +import yaml + + +class Entity: + def __init__(self): + """ + Initialize an Entity object and load entities from a YAML file. + """ + self.entities = self._load_entities() + + def _load_entities(self, yaml_path="ressources/schema/objects/entities.yaml"): + """ + Load entities from a YAML file. + + Args: + yaml_path (str): The path to the YAML file containing entity data. + + Returns: + dict: A dictionary containing entity data. + """ + with open(yaml_path, 'r') as file: + entities_data = yaml.safe_load(file) + return entities_data + + def get_entity_name(self, entity_name): + """ + Get the name of a specific entity. + + Args: + entity_name (str): The name of the entity to retrieve. + + Returns: + str: The name of the entity, or None if the entity does not exist. + """ + if entity_name in self.entities: + return self.entities[entity_name].get("name") + else: + return None + + +def main(): + """ + Main function to demonstrate the usage of the Entity class. + """ + entities = Entity() + entity_name = "acquisition" # Example entity name + entity_name_output = entities.get_entity_name(entity_name) + if entity_name_output: + print(f"Nom de l'entité '{entity_name}': {entity_name_output}") + else: + print(f"L'entité '{entity_name}' n'existe pas.") + + +if __name__ == "__main__": + main() diff --git a/bep32v01/BidsFilestructure.py b/bep32v01/BidsFilestructure.py new file mode 100644 index 00000000..4a071415 --- /dev/null +++ b/bep32v01/BidsFilestructure.py @@ -0,0 +1,148 @@ +import yaml + +class FileStructure: + def __init__(self, relative_path="ressources/schema/rules/files/common/core.yaml"): + """ + Initialize a FileStructure object with a relative path to a YAML file. + + Args: + relative_path (str): The relative path to the YAML file containing file structure rules. + """ + self.relative_path = relative_path + self.all_files = [] + self.top_level_files = [] + self.top_level_directory = [] + self.top_level_file_details = {} + self.top_level_directory_detail = {} + self.get_detail() + + def get_all_files(self): + """ + Retrieve all file names from the YAML file containing file structure rules. + """ + with open("ressources/schema/objects/files.yaml", 'r') as file: + file_rules = yaml.safe_load(file) + if file_rules: + for key in file_rules: + self.all_files.append(key) + if file_rules.get(key).get("file_type") == "regular": + self.top_level_files.append(key) + else: + self.top_level_directory.append(key) + + def get_all_files_detail(self, relative_path): + """ + Retrieve details for all files and directories from a specified YAML file. + + Args: + relative_path (str): The relative path to the YAML file containing file structure details. + """ + with open(relative_path, 'r') as file: + file_rules = yaml.safe_load(file) + if file_rules: + for key, value in file_rules.items(): + if key in self.top_level_files: + self.top_level_file_details[key] = value + else: + self.top_level_directory_detail[key] = value + + def get_detail(self): + """ + Retrieve file structure details and store them in class attributes. + """ + self.get_all_files() + self.get_all_files_detail(self.relative_path) + self.get_all_files_detail("ressources/schema/rules/files/common/tables.yaml") + return self + + def get_detail_for_file(self, file_name): + """ + Retrieve details for a specific file. + + Args: + file_name (str): The name of the file. + + Returns: + dict: Details of the file. + """ + return self.top_level_file_details.get(file_name) + + def get_detail_for_directory(self, directory_name): + """ + Retrieve details for a specific directory. + + Args: + directory_name (str): The name of the directory. + + Returns: + dict: Details of the directory. + """ + return self.top_level_directory_detail.get(directory_name) + + # Attributes Getters + def get_relative_path(self): + """ + Get the relative path to the YAML file containing file structure rules. + + Returns: + str: The relative path. + """ + return self.relative_path + + def get_all_files_list(self): + """ + Get the list of all files. + + Returns: + list: List of all file names. + """ + return self.all_files + + def get_top_level_files_list(self): + """ + Get the list of top-level files. + + Returns: + list: List of top-level file names. + """ + return self.top_level_files + + def get_top_level_directory_list(self): + """ + Get the list of top-level directories. + + Returns: + list: List of top-level directory names. + """ + return self.top_level_directory + + def get_top_level_file_details(self): + """ + Get details of top-level files. + + Returns: + dict: Dictionary containing details of top-level files. + """ + return self.top_level_file_details + + def get_top_level_directory_details(self): + """ + Get details of top-level directories. + + Returns: + dict: Dictionary containing details of top-level directories. + """ + return self.top_level_directory_detail + + +def main(): + """ + Main function to demonstrate the usage of the FileStructure class. + """ + file_structure = FileStructure() + file_structure.get_detail() + print(file_structure.get_all_files_list()) + + +if __name__ == "__main__": + main() diff --git a/bep32v01/BidsModality.py b/bep32v01/BidsModality.py new file mode 100644 index 00000000..6a2019ea --- /dev/null +++ b/bep32v01/BidsModality.py @@ -0,0 +1,31 @@ +import yaml + + +class Modality: + def __init__(self, relative_path="ressources/schema/objects/modalities.yaml"): + """ + Initialize a Modality object with a relative path to a YAML file containing modalities. + + Args: + relative_path (str): The relative path to the YAML file containing modalities. + """ + self.relative_path = relative_path + self.modalities = [] + + with open(relative_path, "r") as file: + modalities_yaml = yaml.safe_load(file) + if modalities_yaml: + self.modalities = list(modalities_yaml.keys()) + + +def main(): + """ + Main function to demonstrate the usage of the Modality class. + """ + modality = Modality() + print("Modalités :") + print(modality.modalities) + + +if __name__ == "__main__": + main() diff --git a/bep32v01/Createdirectory.py b/bep32v01/Createdirectory.py new file mode 100644 index 00000000..4a2f1044 --- /dev/null +++ b/bep32v01/Createdirectory.py @@ -0,0 +1,90 @@ +import json +import os +from BidsFilestructure import FileStructure +from BidsDirectoryStructure import DirectoryStructure +from BidsEntity import Entity +from BidsDatatype import DataTypes +from pathlib import Path + +class Createdirectory: + def __init__(self, output_path, sub_id=1, session_id=1, modality="micr"): + """ + Initialize a Createdirectory object with output path, subject ID, session ID, and modality. + + Args: + output_path (str): The path where directories will be created. + sub_id (int): Subject ID. + session_id (int): Session ID. + modality (str): Modality name. + """ + self.session_path = None + self.output_path = output_path + self.dir_name = [] + self.filestructure = FileStructure() + self.filestructure.get_detail() + self.directorystructure = DirectoryStructure() + self.entity = Entity() + self.dataType = DataTypes() + self.sub_id = sub_id + self.session_id = session_id + self.modality = modality + sub_directory = [] + + def layout_folder(self): + """ + Create directory layout based on BIDS directory structure. + """ + top_level_dir = self.directorystructure.get_top_level_directory() + entity_dir = self.directorystructure.get_entity_directory() + value_dir = self.directorystructure.get_value_directory() + all_dir = self.directorystructure.get_all_directory() + for dir in all_dir: + + path = "" + if dir in top_level_dir: + if dir in entity_dir: + path = self.entity.get_entity_name(dir) + f'-{str(self.sub_id)}' + + elif dir in value_dir: + path = self.dataType.get_data_type_value(dir) + + else: + path = dir + + self.dir_name.append(path) + + def build(self): + """ + Build the directory structure. + """ + for dir in self.dir_name: + first_level_dir = os.path.join(self.output_path, dir) + print("path: ", first_level_dir) + if not os.path.exists(first_level_dir): + os.makedirs(first_level_dir) + + # subdirectory session + subject_dir = os.path.join(self.output_path, f'sub-{self.sub_id}') + session_dir = os.path.join(subject_dir, f'ses-{self.session_id}') + print("subject_dir: ", session_dir) + if not os.path.exists(session_dir): + os.makedirs(session_dir) + # subdirectory_modality + modality_dir = os.path.join(session_dir, self.modality) + if not os.path.exists(modality_dir): + os.makedirs(modality_dir) + + +def main(): + """ + Main function to create directory layout. + """ + output_path = "Essaie" # Change this to your desired output path + creator = Createdirectory(output_path) + creator.layout_folder() + creator.build() + print("Directory layout created successfully.") + + +if __name__ == "__main__": + main() diff --git a/bep32v01/Createfile.py b/bep32v01/Createfile.py new file mode 100644 index 00000000..a8f10d60 --- /dev/null +++ b/bep32v01/Createfile.py @@ -0,0 +1,137 @@ +import json +import os +from BidsFilestructure import FileStructure + + +class CreatFile: + def __init__(self, output_path): + """ + Initialize a CreatFile object with the output path and initialize filestructure. + + Args: + output_path (str): The path where files will be created. + """ + self.output_path = output_path + self.file_name = [] + self.filestructure = FileStructure() + + def create_empty_file(self, filename): + """ + Create an empty file. + + Args: + filename (str): The name of the file to create. + """ + file_path = os.path.join(self.output_path, filename) + with open(file_path, 'w'): + pass + + def write_json_to_file(self, filename, data): + """ + Write JSON data to a file. + + Args: + filename (str): The name of the file to write. + data (dict): The JSON data to write to the file. + """ + file_path = os.path.join(self.output_path, filename) + with open(file_path, 'w') as file: + json.dump(data, file) + + def dataset_structure(self, input_data): + """ + Write dataset description JSON data to a file. + + Args: + input_data (dict): The dataset description data. + """ + self.write_json_to_file('dataset_description.json', input_data) + + def readme_change_licence(self): + """ + Create empty README, CHANGES, and LICENSES files. + """ + for filename in ['README', 'CHANGES', 'LICENSES']: + self.create_empty_file(filename) + + def create_file(self, filename): + """ + Create an empty file. + + Args: + filename (str): The name of the file to create. + """ + self.create_empty_file(filename) + + def citation_file(self): + """ + Create a CITATION.cff file. + """ + self.create_file('CITATION.cff') + + def participant_file(self): + """ + Create participant.tsv and participant.json files. + """ + self.create_file('participants.tsv') + self.create_file('participants.json') + + def sample_file(self): + """ + Create sample.tsv and sample.json files. + """ + self.create_file('sample.tsv') + self.create_file('sample.json') + + def dataset_description(self): + """ + Create a dataset_description.json file. + """ + self.create_file('dataset_description.json') + + def build(self): + """ + Build files based on file structure. + """ + self.layout_file() + for filename in self.file_name: + self.create_empty_file(filename) + + def get_file_structure(self): + """ + Get the file structure. + + Returns: + FileStructure: The file structure. + """ + return self.filestructure + + def layout_file(self): + """ + Layout files based on file structure. + """ + all_file = self.filestructure.get_top_level_files_list() + + for filename in all_file: + + info = self.filestructure.get_detail_for_file(filename) + + if 'path' in info: + self.file_name.append(info['path']) + + elif 'stem' in info: + + path = " " + path = info['stem'] + + for extension in info['extensions']: + path = path + extension + self.file_name.append(path) + if extension != '': + path = path[:-len(extension)] + + return self.file_name + + +if __name__ == "__main__": + pass diff --git a/bep32v01/Essaie/CHANGES b/bep32v01/Essaie/CHANGES new file mode 100644 index 00000000..e69de29b diff --git a/bep32v01/Essaie/CITATION.cff b/bep32v01/Essaie/CITATION.cff new file mode 100644 index 00000000..e69de29b diff --git a/bep32v01/Essaie/LICENSE b/bep32v01/Essaie/LICENSE new file mode 100644 index 00000000..e69de29b diff --git a/bep32v01/Essaie/README b/bep32v01/Essaie/README new file mode 100644 index 00000000..e69de29b diff --git a/bep32v01/Essaie/README.md b/bep32v01/Essaie/README.md new file mode 100644 index 00000000..e69de29b diff --git a/bep32v01/Essaie/README.rst b/bep32v01/Essaie/README.rst new file mode 100644 index 00000000..e69de29b diff --git a/bep32v01/Essaie/README.txt b/bep32v01/Essaie/README.txt new file mode 100644 index 00000000..e69de29b diff --git a/bep32v01/Essaie/dataset_description.json b/bep32v01/Essaie/dataset_description.json new file mode 100644 index 00000000..e69de29b diff --git a/bep32v01/Essaie/fichier.csv b/bep32v01/Essaie/fichier.csv new file mode 100644 index 00000000..25de6e16 --- /dev/null +++ b/bep32v01/Essaie/fichier.csv @@ -0,0 +1,2 @@ +id,age,sex,group,EEG cap,TaskName,acq_time,EEGGround,EEG system,TaskName 2,TaskName 3,TaskName 4,TaskName 5,TaskName 6,handedness,session_id,Run comment,EEGReference,Run comment 2,Run comment 3,Run comment 4,Run comment 5,Run comment 6,Experimenter 1,Experimenter 2,Experimenter 3,Experiment name,SoftwareFilters,TaskDescription,EEG cap position,Hardware comment,HeadCircumference,SamplingFrequency,TaskDescription 2,TaskDescription 3,TaskDescription 4,TaskDescription 5,TaskDescription 6 +01,6,F,control under 2 years,,facesnback,2024-02-17T17:00,IDK,,Rest,,,,,right,002,Pretty well,Single electrode,good,,,,,Toto,,,EEG_TEST_SUB2001,N/A,more task,CZ at 20cm from nasion,,53.5,3000,just to see,,,, diff --git a/bep32v01/Essaie/genetic_info.json b/bep32v01/Essaie/genetic_info.json new file mode 100644 index 00000000..e69de29b diff --git a/bep32v01/Essaie/participants.json b/bep32v01/Essaie/participants.json new file mode 100644 index 00000000..e69de29b diff --git a/bep32v01/Essaie/participants.tsv b/bep32v01/Essaie/participants.tsv new file mode 100644 index 00000000..e69de29b diff --git a/bep32v01/Essaie/samples.json b/bep32v01/Essaie/samples.json new file mode 100644 index 00000000..e69de29b diff --git a/bep32v01/Essaie/samples.tsv b/bep32v01/Essaie/samples.tsv new file mode 100644 index 00000000..e69de29b diff --git a/bep32v01/__init__.py b/bep32v01/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/bep32v01/helper.py b/bep32v01/helper.py new file mode 100644 index 00000000..16d0f850 --- /dev/null +++ b/bep32v01/helper.py @@ -0,0 +1,110 @@ +import os +import yaml + + +def find_keys_in_dict(dictionary, target_value): + """ + Search for keys corresponding to the given value in a dictionary. + + Args: + dictionary (dict): The dictionary to search in. + target_value: The value to search for. + + Returns: + list: A list of keys corresponding to the value, or an empty list if the value is not found. + """ + keys = [] + + for key, value in dictionary.items(): + if target_value in value: + keys.append(key) + elif isinstance(value, dict): + nested_keys = find_keys_in_dict(value, target_value) + keys.extend(nested_keys) + + return keys + + +def find_value_in_dict(dictionary, target_key): + """ + Search for a value corresponding to the given key in a dictionary. + + Args: + dictionary (dict): The dictionary to search in. + target_key: The key to search for. + + Returns: + The value corresponding to the key, or None if the key is not found. + """ + for key, value in dictionary.items(): + if key == target_key: + return value + elif isinstance(value, dict): + result = find_value_in_dict(value, target_key) + if result is not None: + return result + return None + + +def find_keys_with_value(dictionary, target_value): + """ + Find keys containing the given value in a dictionary. + + Args: + dictionary (dict): The dictionary to search in. + target_value: The value to search for. + + Returns: + list: A list of keys containing the value, or an empty list if the value is not found. + """ + keys = [] + + for key, value in dictionary.items(): + if isinstance(value, list): + if target_value in value: + keys.append(key) + elif value == target_value: + keys.append(key) + elif isinstance(value, dict): + nested_keys = find_keys_with_value(value, target_value) + keys.extend(nested_keys) + + return keys + + +def get_directories_with_details(yaml_file): + """ + Get directories with the 'entity' attribute from a YAML file. + + Args: + yaml_file (str): Path to the YAML file containing directory information. + + Returns: + tuple: A tuple containing lists of directory names categorized based on different criteria. + """ + directories_entities = [] + directories_values = [] + directory_required = [] + directory_optional = [] + directory_recommended = [] + top_level_directory = [] + + with open(yaml_file, 'r') as file: + data = yaml.safe_load(file) + + for directory, info in data.get('raw', {}).items(): + if 'entity' in info: + directories_entities.append(directory) + if 'value' in info: + directories_values.append(directory) + if 'level' in info and info.get('level') == 'required': + directory_required.append(directory) + if 'level' in info and info.get('level') == 'optional': + directory_optional.append(directory) + if 'recommended' in info: + directory_recommended.append(directory) + for directory in data.get('raw', {}).get('root', {}).get('subdirs', {}): + top_level_directory.append(directory) + + return (directories_entities, directories_values, directory_required, + directory_optional, directory_recommended, top_level_directory) diff --git a/bep32v01/launch.py b/bep32v01/launch.py new file mode 100644 index 00000000..83630806 --- /dev/null +++ b/bep32v01/launch.py @@ -0,0 +1,34 @@ +from pathlib import Path + +from pandas import read_csv + +import elab_bridge +import json +from elab_bridge import server_interface +from BidsEmptyRepositoryGenerator import Generator +import os + +from diglab_utils.test_utils import (test_directory, initialize_test_dir) + +project_dir = test_directory / 'test files_elab' / 'TestProject' +SERVER_CONFIG_YAML = ('/home/pourtoi/PycharmProjects/DigLabTools/elab_bridge/tests/' + 'testfiles_elab/TestProject/project' + '.json') + + +def main(): + output = input( + "Enter the output folder path: :" + " ex : /home/pourtoi/PycharmProjects/DigLabTools/bep32v01/Essaie") + + csv_file = os.path.join(output, 'fichier.csv') + + jsonformat = elab_bridge.server_interface.download_experiment(csv_file, + SERVER_CONFIG_YAML, 247, format='csv') + df = read_csv(csv_file) + + generator = Generator(output, df['id'][0], df['session_id'][0], "micr") + + +if __name__ == "__main__": + main() diff --git a/bep32v01/ressources/.idea/inspectionProfiles/Project_Default.xml b/bep32v01/ressources/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 00000000..e94858f7 --- /dev/null +++ b/bep32v01/ressources/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,19 @@ + + + + \ No newline at end of file diff --git a/bep32v01/ressources/.idea/inspectionProfiles/profiles_settings.xml b/bep32v01/ressources/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 00000000..105ce2da --- /dev/null +++ b/bep32v01/ressources/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/bep32v01/ressources/.idea/misc.xml b/bep32v01/ressources/.idea/misc.xml new file mode 100644 index 00000000..6eeaaf25 --- /dev/null +++ b/bep32v01/ressources/.idea/misc.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/bep32v01/ressources/.idea/modules.xml b/bep32v01/ressources/.idea/modules.xml new file mode 100644 index 00000000..8efbbeb2 --- /dev/null +++ b/bep32v01/ressources/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/bep32v01/ressources/.idea/ressources.iml b/bep32v01/ressources/.idea/ressources.iml new file mode 100644 index 00000000..d0876a78 --- /dev/null +++ b/bep32v01/ressources/.idea/ressources.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/bep32v01/ressources/.idea/vcs.xml b/bep32v01/ressources/.idea/vcs.xml new file mode 100644 index 00000000..b2bdec2d --- /dev/null +++ b/bep32v01/ressources/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/bep32v01/ressources/.idea/workspace.xml b/bep32v01/ressources/.idea/workspace.xml new file mode 100644 index 00000000..258be40d --- /dev/null +++ b/bep32v01/ressources/.idea/workspace.xml @@ -0,0 +1,194 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + { + "associatedIndex": 8 +} + + + + { + "keyToString": { + "RunOnceActivity.OpenProjectViewOnStart": "true", + "RunOnceActivity.ShowReadmeOnStart": "true", + "SHARE_PROJECT_CONFIGURATION_FILES": "true", + "git-widget-placeholder": "elab/testexportbids", + "last_opened_file_path": "/home/pourtoi/PycharmProjects/DigLabTools/bep32v01/ressources", + "node.js.detected.package.eslint": "true", + "node.js.detected.package.tslint": "true", + "node.js.selected.package.eslint": "(autodetect)", + "node.js.selected.package.tslint": "(autodetect)", + "nodejs_package_manager_path": "npm", + "vue.rearranger.settings.migration": "true" + } +} + + + + + + + + + + + 1712044823700 + + + + + + \ No newline at end of file diff --git a/bep32v01/ressources/schema/BIDS_VERSION b/bep32v01/ressources/schema/BIDS_VERSION new file mode 100644 index 00000000..a01185b4 --- /dev/null +++ b/bep32v01/ressources/schema/BIDS_VERSION @@ -0,0 +1 @@ +1.10.0-dev diff --git a/bep32v01/ressources/schema/README.md b/bep32v01/ressources/schema/README.md new file mode 100644 index 00000000..d8013145 --- /dev/null +++ b/bep32v01/ressources/schema/README.md @@ -0,0 +1,1025 @@ +# BIDS schema description + +Portions of the BIDS specification are defined using YAML files in order to +make the specification machine-readable. + +Currently the portions of the specification that rely on this schema are: +- the entity tables, +- entity definitions, +- filename templates, +- metadata tables. + +Any changes to the specification should be mirrored in the schema. + +## Organization and syntax + +At the time of this writing, the schema has the following file layout: + +```plain +├── meta +│   ├── ... +│   └── versions.yaml +├── objects +│   ├── ... +│   └── suffixes.yaml +├── rules +│   ├── checks +│   │   ├── ... +│   │   └── references.yaml +│   ├── files +│   │   ├── common +│   │   │   ├── core.yaml +│   │   │   └── tables.yaml +│   │   ├── deriv +│   │   │   ├── imaging.yaml +│   │   │   └── preprocessed_data.yaml +│   │   └── raw +│   │   ├── ... +│   │   └── task.yaml +│   ├── sidecars +│   │   ├── derivatives +│   │   │   └── common_derivatives.yaml +│   │   ├── ... +│   │   └── pet.yaml +│   ├── tabular_data +│   │   ├── derivatives +│   │   │   └── common_derivatives.yaml +│   │   ├── ... +│   │   └── task.yaml +│   ├── ... +│   └── modalities.yaml +├── BIDS_VERSION +└── SCHEMA_VERSION +``` + +The top-level organization includes `objects`, where terms are defined; +`rules`, where constraints (such as valid filenames or required metadata fields) +are defined; +and `meta`, where definitions useful for interpreting the schema are defined. + +Each file is made up of YAML data, most often an *object*. +For example, the file `rules/checks/mri.yaml` contains the contents: + +```YAML +PhasePartUnits: + issue: + code: PHASE_UNITS + message: | + Phase images (with the `part-phase` entity) must have units + "rad" or "arbitrary". + level: error + selectors: + - modality == "mri" + - entities.part == "phase" + - '"Units" in sidecar' + checks: + - intersects([sidecar.Units], ["rad", "arbitrary"]) +``` + +When we wish to refer to a file we might write `rules/checks/mri.yaml`. +Alternately, we can use `rules.checks.mri` to refer to the object contained by the +file. +Using this notation, the *qualified name*, the contents of an entire directory or a +portion of a file can be referred to unambiguously. +For example, the entire `rules/checks/` directory is referred to as `rules.checks`, +and `rules.checks.mri.PhasePartUnits.issue` refers to the object: + +```JSON +{ + "code": "PHASE_UNITS", + "message": "Phase images (with the `part-phase` [...]\n\"rad\" or \"arbitrary\".\n", + "level": "error" +} +``` + +These qualified names may be used in this README, as well as in *references* and +*expressions*. + +### Description formatting + +Many objects throughout the schema have a `description` field, +which will typically be rendered somewhere in the specification. +Because the specification is written in [Markdown](https://en.wikipedia.org/wiki/Markdown), +these `description` fields may also contain Markdown, +including links to other locations in the specification. + +Because the same description may be used in multiple locations, +a mechanism is needed to ensure that the correct path is discovered +to render the description in each location. +To do this, the path should follow the form `SPEC_ROOT/path/within/source.md#anchor`. +For example, to link to the +[Definitions](https://bids-specification.readthedocs.io/en/stable/common-principles.html#definitions) +section of +[Common principles](https://bids-specification.readthedocs.io/en/stable/common-principles.html), +use the path `SPEC_ROOT/common-principles.md#definitions`: + +```Markdown +[Common principles - Definitions](SPEC_ROOT/common-principles.md#definitions) +``` + +Note that the Markdown extension `.md` MUST be used for this to render correctly. + +For more information please see the following pull request and linked discussions: +[#1096](https://github.com/bids-standard/bids-specification/pull/1096) + +### References + +Some schema entries take the form: + +```YAML +ObjectName: + $ref: objects.metadata.OtherObjectName +``` + +This object may be *dereferenced* by replacing the `$ref` entry +with the object being referenced. +The following two prototypical examples are presented to clarify the semantics of +references (the cases in which they are used will be presented later): + +1. In `objects.enums`: + ```YAML + _GeneticLevelEnum: + type: string + enum: + - $ref: objects.enums.Genetic.value + - $ref: objects.enums.Genomic.value + - $ref: objects.enums.Epigenomic.value + - $ref: objects.enums.Transcriptomic.value + - $ref: objects.enums.Metabolomic.value + - $ref: objects.enums.Proteomic.value + ``` + and in `objects.metadata`: + ```YAML + GeneticLevel: + name: GeneticLevel + display_name: Genetic Level + description: | + Describes the level of analysis. + Values MUST be one of `"Genetic"`, `"Genomic"`, `"Epigenomic"`, + `"Transcriptomic"`, `"Metabolomic"`, or `"Proteomic"`. + anyOf: + - $ref: objects.enums._GeneticLevelEnum + - type: array + items: + $ref: objects.enums._GeneticLevelEnum + ``` + Here `_GeneticLevelEnum` is used to describe the valid values of `GeneticLevel`, + (which are in turn references to individual values), and the references inside `GeneticLevel.anyOf` indicate that there may be a single + such value or a list of values. + +1. In [`rules.files.deriv.preprocessed_data`](./rules/files/deriv/preprocessed_data.yaml): + ```YAML + anat_nonparametric_common: + $ref: rules.files.raw.anat.nonparametric + entities: + $ref: rules.files.raw.anat.nonparametric.entities + space: optional + description: optional + ``` + Here, the derivative datatype rule starts by copying the raw datatype rule + `rules.files.raw.anat.nonparametric`. + It then *overrides* the `entities` portion of that rule with a new object. + To *extend* the original `entities`, it again begins + by referencing `rules.files.raw.anat.nonparametric.entities`, + and adding the new entities `space` and `description`. + +### Expressions + +Rules definitions make use of a limited language of expressions that always evaluate to `true` or `false`. +These expressions may be used as `selectors`, determining whether a rule applies, +or `checks`, determining whether a rule is satisfied. + +Re-examining `rules.checks.mri.PhasePartUnits` from above: + +```YAML +PhasePartUnits: + issue: + code: PHASE_UNITS + message: | + Phase images (with the `part-phase` entity) must have units + "rad" or "arbitrary". + level: error + selectors: + - modality == "mri" + - entities.part == "phase" + - '"Units" in sidecar' + checks: + - intersects([sidecar.Units], ["rad", "arbitrary"]) +``` + +We see expressions may contain: + +- fields such as `modality`, `entities` (which has a `.part` subfield), `sidecar` +- String literals such as `"mri"`, `"Units"` or `"rad"` +- Lists containing fields or strings +- Comparison operators such as `==` (equality) or `in` (subfield exists in field) +- Functions such as `intersects()` + +In fact, the full list of fields is defined in the `meta.context.context` object, +which (currently) contains at the top level: + +- `schema`: access to the schema itself +- `dataset`: attributes of the whole dataset +- `subject`: attributes of the current subject +- `path`: the full path of the current file (relative to dataset root) +- `entities`: an object of entities parsed from the path +- `datatype`: the datatype, parsed from the path +- `suffix`: the suffix, parsed from the path +- `extension`: the file extension +- `modality`: the file modality, determined by datatype +- `sidecar`: the metadata values, accumulated by the inheritance principle +- `associations`: associated files, discovered by the inheritance principle +- `columns`: the columns in the current TSV file +- `json`: the contents of the current JSON file +- `gzip`: the contents of the current file GZIP header +- `nifti_header`: selected contents of the current NIfTI file's header +- `ome`: the contents of the current OME-XML metadata +- `tiff`: the contents of the current TIFF file's header + +Some of these are strings, while others are nested objects. +These are to be populated by an *interpreter* of the schema, +and provide the *namespace* in which expressions are evaluated. + +The following operators should be defined by an interpreter: + +| Operator | Definition | Example | +| ----------- | ------------------------------------------------------------- | --------------------------------------------- | +| `==` | equality | `suffix == "T1w"` | +| `!=` | inequality | `entities.task != "rest"` | +| `<`/`>` | less-than / greater-than | `sidecar.EchoTime < 0.5` | +| `<=`/`>=` | less-than-or-equal / greater-than-or-equal | `0 <= 4` | +| `in` | object lookup, true if RHS is a subfield of LHS | `"Units" in sidecar` | +| `!` | negation, true if the following value is false, or vice versa | `!true == false` | +| `&&` | conjunction, true if both RHS and LHS are true | `"Units" in sidecar && sidecar.Units == "mm"` | +| `\|\|` | disjunction, true if either RHS or LHS is true | `a < mn \|\| a > mx` | +| `.` | object query, returns value of subfield | `sidecar.Units` | +| `[]` | array/string index, returns value of Nth element (0-indexed) | `columns.participant_label[0]` | +| `+` | numeric addition / string concatenation | `x + 1`, `stem + "suffix"` | +| `-`/`*`/`/` | numeric operators (division coerces to float) | `length(array) - 2`, `x * 2`, `1 / 2 == 0.5` | + +The following functions should be defined by an interpreter: + +| Function | Definition | Example | Note | +| ----------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------ | ------------------------------------------------------------------------------ | +| `count(arg: array, val: any)` | Number of elements in an array equal to `val` | `count(columns.type, "EEG")` | The number of times "EEG" appears in the column "type" of the current TSV file | +| `exists(arg: str \| array, rule: str) -> int` | Count of files in an array that exist in the dataset. String is array with length 1. Rules include `"bids-uri"`, `"dataset"`, `"subject"` and `"stimuli"`. | `exists(sidecar.IntendedFor, "subject")` | True if all files in `IntendedFor` exist, relative to the subject directory. | +| `index(arg: array, val: any)` | Index of first element in an array equal to `val`, `null` if not found | `index(["i", "j", "k"], axis)` | The number, from 0-2 corresponding to the string `axis` | +| `intersects(a: array, b: array) -> bool` | `true` if arguments contain any shared elements | `intersects(dataset.modalities, ["pet", "mri"])` | True if either PET or MRI data is found in dataset | +| `length(arg: array) -> int` | Number of elements in an array | `length(columns.onset) > 0` | True if there is at least one value in the onset column | +| `match(arg: str, pattern: str) -> bool` | `true` if `arg` matches the regular expression `pattern` (anywhere in string) | `match(extension, ".gz$")` | True if the file extension ends with `.gz` | +| `max(arg: array) -> number` | The largest non-`n/a` value in an array | `max(columns.onset)` | The time of the last onset in an events.tsv file | +| `min(arg: array) -> number` | The smallest non-`n/a` value in an array | `min(sidecar.SliceTiming) == 0` | A check that the onset of the first slice is 0s | +| `sorted(arg: array) -> array` | The sorted values of the input array | `sorted(sidecar.VolumeTiming) == sidecar.VolumeTiming` | True if `sidecar.VolumeTiming` is sorted | +| `substr(arg: str, start: int, end: int) -> str` | The portion of the input string spanning from start position to end position | `substr(path, 0, length(path) - 3)` | `path` with the last three characters dropped | +| `type(arg: Any) -> str` | The name of the type, including `"array"`, `"object"`, `"null"` | `type(datatypes)` | Returns `"array"` | + +#### The special value `null` + +Missing values in the context object have the special value `null`. +This value propagates through all of the above operations in a fully-defined, +hopefully intuitive way. +Most operations involving `null` simply resolve to `null`: + +| Operation | Result | +| -------------------------- | ------ | +| `sidecar.MissingValue` | `null` | +| `null.anything` | `null` | +| `null[0]` | `null` | +| `null && true` | `null` | +| `null \|\| true` | `null` | +| `!null` | `null` | +| `null + 1` | `null` | +| `null - 1` | `null` | +| `null * 1` | `null` | +| `null / 1` | `null` | +| `match(null, pattern)` | `null` | +| `intersects(list, null)` | `null` | +| `substr(null, 0, 1)` | `null` | +| `substr(str, null, 1)` | `null` | +| `substr(str, 0, null)` | `null` | +| `length(null)` | `null` | +| `count(null, val)` | `null` | +| `count(list, null)` | `null` | +| `index(null, val)` | `null` | +| `index([0], null)` | `null` | +| `index([], val)` | `null` | +| `min(null)` | `null` | +| `max(null)` | `null` | +| `exists(null, "bids-uri")` | `null` | +| `exists("/path", null)` | `null` | + +The following operators have boolean results: + +| Operation | Result | Comment | +| ------------------------ | ------- | ---------------------------- | +| `null == false` | `false` | | +| `null == true` | `false` | | +| `null != false` | `true` | | +| `null != true` | `true` | | +| `null == null` | `true` | | +| `null == 1` | `false` | Also `<`, `>`, `<=` and `>=` | +| `"VolumeTiming" in null` | `false` | | + +The `type()` function returns a string: + +| Operation | Result | +| ------------ | -------- | +| `type(null)` | `"null"` | + +Finally, if an expression (selector or check) evaluates to `null`, +the `null` will be interpreted equivalent to `false`. +That is, a `null` selector will not apply the current rule, and a `null` +check will fail. + +## Object files + +Object files define "objects" or "terms", which are semantic descriptions of +concepts used in BIDS. These reside under the `object.*` namespace in the schema. +These files **do not** describe how objects of different types +(for example file suffixes and file entities) interact with one another, +or whether objects are required in a given dataset or file. + +### Overview + +There are currently 12 sub-namespaces, which fall into five rough categories. + +The namespaces are: + +| Namespace | Description | Group | +| --------------------------- | ----------------------------------------------------------------------------------- | ---------------- | +| `objects.common_principles` | Terms that are used throughout BIDS | General terms | +| `objects.modalities` | Broad categories of data represented in BIDS, roughly matching recording instrument | General terms | +| `objects.entities` | Name-value pairs appearing in filenames | Name/value terms | +| `objects.metadata` | Name-value pairs appearing in JSON files | Name/value terms | +| `objects.columns` | Column headings and values appearing in TSV files | Name/value terms | +| `objects.datatypes` | Subdirectories that organize files by type (such as `anat`, `eeg`) | Value terms | +| `objects.suffixes` | Filename suffixes that describe the contents of the file | Value terms | +| `objects.extensions` | Filename component that describe the format of the file | Value terms | +| `objects.formats` | Terms that define the forms values (for example, in metadata) might take | Formats | +| `objects.files` | Files and directories that may appear at the root of a dataset | Files | +| `objects.enums` | Full descriptions of enumerated values used in other sub-namespaces | Value terms | + +Because these objects vary, the contents of each namespace can vary. + +Common fields to all objects: + +| Field | Description | +| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | +| `description` | A description of the term that can be understood that should not depend on particular surrounding text; may contain markdown for rendering | +| `display_name` | A human-friendly name, for tools to display; may include spaces | + +The name/value terms groups (`entities`, `metadata` and `columns`) define terms where +a name, when present, has a given meaning, and its value may be restricted. + +These objects additionally have the field: + +| Field | Description | +| -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `name` | For terms that can take on multiple values (such as entities, metadata fields), the name of the term as it appears in the specification and in a dataset; must be alphanumeric; mutually exclusive with `value` | +| `type` | The type (such as `string`, `integer`, `object`) of values the term describes | +| `format` | The format of the term (defined in `objects.formats`) | + +Value terms groups (`datatypes`, `suffixes`, `extensions`) define terms where a field +can take on multiple values. +For example, a file has one datatype, as compared to a collection of entities. + +These objects may have the fields: + +| Field | Description | +| ------- | ---------------------------------------------------------------------------------------------------------------- | +| `value` | For terms that cannot take on multiple values (for example suffixes or extensions), the string value of the term | + +The `formats` terms provide one additional field: + +| Field | Description | +| --------- | ----------------------------------------------------------- | +| `pattern` | Regular expression validating a string rendering of a value | + +#### Value constraints + +For name/value terms, the `type` and `format` fields allow constraints to be placed on +the values described by the names. + +Additional fields may apply to further constrain the type: + +| Field | Description | +| -------------------------------------- | --------------------------------------------- | +| `maximum`/`minimum`/`exclusiveMinimum` | Value ranges for `integer` and `number` types | +| `maxValue`/`minValue` | Value ranges for `integer` and `number` types | +| `maxItems`/`minItems` | Size ranges for `array` types | +| `enum` | List of accepted values for `string` types | + +Some values may be more flexible, allowing multiple possible values, or may be +arrays or objects: + +| Field | Description | +| ---------------------- | --------------------------------------------------------------------------------------------- | +| `anyOf` | A list of constraints, any of which could apply | +| `items` | The array described contains values whose types are constrained | +| `properties` | The object described has a given set of fields; the values of these fields may be constrained | +| `additionalProperties` | The object described has constraints on its values, but not the names | + +### On re-used objects with different definitions + +In a few cases, two objects with the same name appear multiple times in the specification. +When this happens, it is preferred to find a common definition, and clarify it in the rules (see below). +However, in some cases, the object description and permissible values differ, and it needs to be defined +as two separate objects. + +Consider the following examples: + +```yaml +# reference column for channels.tsv files for EEG data +reference__eeg: + name: reference + display_name: Electrode reference + description: | + Name of the reference electrode(s). + This column is not needed when it is common to all channels. + In that case the reference electrode(s) can be specified in `*_eeg.json` as `EEGReference`). + type: string +# reference column for channels.tsv files for iEEG data +reference__ieeg: + name: reference + display_name: Electrode reference + description: | + Specification of the reference (for example, `mastoid`, `ElectrodeName01`, `intracranial`, `CAR`, `other`, `n/a`). + If the channel is not an electrode channel (for example, a microphone channel) use `n/a`. + anyOf: + - type: string + - type: string + enum: + - n/a +``` + +Here, the TSV column `"reference"` means different things when used for EEG data, +as compared to iEEG data, so two definitions are needed. +Because columns use `snake_case` (meaning they can be expected to contain underscores), +two underscores are needed to separate the column name from the string that indicates the use of the term. + +The convention can be summed up in the following rules: + +1. Each specific term takes on the form `_`, where `` is the common name that + the two (or more) terms share, `` indicates when the specific term applies. + +1. If the `` appears in `snake_case` then `` begins with an extra `_`. + +#### Valid fields for definitions by sub-namespace + +- `objects.common_principles` + | Field | Description | + | -------------- | ------------------- | + | `display_name` | Human-friendly name | + | `description` | Term definition | + +- `objects.modalities` + | Field | Description | + | -------------- | ------------------- | + | `display_name` | Human-friendly name | + | `description` | Term definition | + +- `objects.entities` + + | Field | Description | + | -------------- | ------------------------------------------------------- | + | `display_name` | Human-friendly name | + | `description` | Term definition | + | `name` | Key of entity, such as `sub` or `ses` | + | `type` | Type of value (always `string`) | + | `format` | Permissible format of values, either `label` or `index` | + | `enum` | Exclusive list of valid values, if present | + + Note that descriptions should apply to *all* uses of the entity; if additional information + applies in certain contexts, that should be written in the specification, and not the schema. + +- `objects.metadata` + | Field | Description | + | -------------- | ------------------------------------------------------------------------------------ | + | `display_name` | Human-friendly name | + | `description` | Term definition | + | `name` | Name of field in JSON object (in `CamelCase`) | + | `unit` | Interpretation of numeric values | + | `type` | Type of value (one of `array`, `string`, `integer`, `number`, `object` or `boolean`) | + | `format` | Permissible format of values, from definitions in `objects.formats` | + | `enum` | Exclusive list of valid values, if present | + | `maximum` | Maximum for numeric values | + | `minimum` | Minimum for numeric values | + | `*` | JSON-schema fields to further constrain values | + +- `objects.columns` + | Field | Description | + | -------------- | ------------------------------------------------------------------- | + | `display_name` | Human-friendly name | + | `description` | Term definition | + | `name` | Name of column in TSV file (in `snake_case`) | + | `unit` | Interpretation of numeric values | + | `type` | Type of value | + | `format` | Permissible format of values, from definitions in `objects.formats` | + | `pattern` | Regular expression constraining string values | + | `enum` | Exclusive list of valid values, if present | + | `maximum` | Maximum for numeric values | + | `minimum` | Minimum for numeric values | + | `*` | JSON-schema fields to further constrain values | + +- `objects.datatypes` + | Field | Description | + | -------------- | -------------------------- | + | `display_name` | Human-friendly name | + | `description` | Term definition | + | `value` | String value of `datatype` | + +- `objects.suffixes` + | Field | Description | + | -------------- | -------------------------------------------------------------- | + | `display_name` | Human-friendly name | + | `description` | Term definition | + | `value` | String value of `suffix` | + | `unit` | Interpretation of values in a data file with the given suffix | + | `maxValue` | Maximum permissible value in a data file with the given suffix | + | `minValue` | Minimum permissible value in a data file with the given suffix | + | `anyOf` | Used to describe multiple permissible units | + +- `objects.extensions` + | Field | Description | + | -------------- | --------------------------- | + | `display_name` | Human-friendly name | + | `description` | Term definition | + | `value` | String value of `extension` | + +- `objects.formats` + | Field | Description | + | -------------- | ---------------------------------- | + | `display_name` | Human-friendly name | + | `description` | Term definition | + | `pattern` | Regular expression defining format | + +- `objects.files` + | Field | Description | + | -------------- | ------------------------------------------------------------------------------------ | + | `display_name` | Human-friendly name | + | `description` | Term definition | + | `file_type` | Indicator that the file is a regular file (`"regular"`) or directory (`"directory"`) | + +- `objects.enums` + | Field | Description | + | -------------- | ---------------------- | + | `display_name` | Human-friendly name | + | `description` | Term definition | + | `value` | String value of `enum` | + +## Rule files + +The `rules.*` namespace contains most of the validatable content of the schema, +apart from value constraints that can be encoded in `objects`. + +There are several types of rule, and this section is subject to reconsolidation as +patterns are found. + +### Core concepts + +Core concepts are [expressions](#expressions) (defined above), requirement levels and issues. + +#### Requirement levels and severity + +BIDS follows RFC 2119 and has three requirement levels: OPTIONAL, RECOMMENDED and REQUIRED. +In the schema, we use `optional`, `recommended` and `required`. + +A rule interpreter (validator) is expected to treat: +- missing REQUIRED data/metadata as an error, +- missing RECOMMENDED data/metadata as a warning, +- and silently pass over missing OPTIONAL data. + +BIDS also defines a level `DEPRECATED`, rendered in the schema as `deprecated`, +and corresponding to a warning if the data/metadata is present. + +#### Issues + +Issues are messages intended to be communicated to a dataset curator to indicate an issue +with their dataset. + +They have a code and severity as well: + +| Field | Description | +| --------- | ---------------------------------------------- | +| `code` | Issue identifier, such as `EVENTS_TSV_MISSING` | +| `level` | Issue severity (`warning` or `error`) | +| `message` | Message for display to a user | + +A level of `warning` corresponds to a rule in the specification that is RECOMMENDED, +while a level of `error` corresponds to a rule that is REQUIRED. + +In some cases, an issue is contained next to a `level: required` or `level: recommended` +as part of a larger rule. +In these cases, the `level` field should be omitted from the issue +to avoid duplication or conflict. + +### Filename construction rules + +A significant portion of BIDS is devoted to the naming of files, +and almost all filenames consist of entities, a suffix, an extension, and a data type. +Exceptions will be noted below. + +`rules.files` contains the following subdivisions. + +| Namespace | Description | +| --------------------------- | ----------------------------------------------------------------------------------------- | +| `rules.files.common.core` | Files and directories that reside at the top level of datasets | +| `rules.files.common.tables` | Tabular metadata files that associate metadata with entities | +| `rules.files.raw.*` | Raw data and metadata files that have entities, suffixes, datatypes and extensions | +| `rules.files.deriv.*` | Derivative data and metadata files that have entities, suffixes, datatypes and extensions | + +#### Core files and directories + +`rules.files.common.core` describes files that have little-to-no variability in their form. +These either have a single `path` field, or a `stem` field and a list of `extensions`: + +| Field | Description | +| ------------ | ------------------------------------------------------------------------------------------------------------- | +| `level` | Requirement level of file, one of (`optional`, `recommended`, `required`, `deprecated`) | +| `path` | Location of file, relative to dataset root; mutually exclusive with `stem` and `extensions` | +| `stem` | Name of file, relative to dataset root, up to but not including the extension; mutually exclusive with `path` | +| `extensions` | List of valid extension strings, including the initial dot (`.`); mutually exclusive with `path` | + +These are the entries for `dataset_description.json` and `README`: + +```YAML +dataset_description: + level: required + path: dataset_description.json +README: + level: required + stem: README + extensions: + - '' + - .md + - .rst + - .txt +``` + +Here, `README` and `README.md` are both valid, while only `dataset_description.json` is permitted. + +#### Tabular metadata files + +`rules.files.common.tables` describes TSV files and their associated metadata, +including `participants.tsv`, `samples.tsv`, `*_sessions.tsv` and `*_scans.tsv`. +The first two use the `stem` field, while the latter two specify the entities used +to construct the filename. + +The valid fields are: + +| Field | Description | +| ------------ | ----------------------------------------------------------------------------------------------------------------- | +| `level` | Requirement level of file, one of (`optional`, `recommended`, `required`, `deprecated`) | +| `stem` | Name of file, relative to dataset root, up to but not including the extension; mutually exclusive with `entities` | +| `entities` | Object where the keys are entries in `objects.entities`. The value is a requirement level. | +| `extensions` | List of valid extension strings, including the initial dot (`.`) | + +For example: + +```YAML +participants: + level: optional + stem: participants + extensions: + - .tsv + - .json +sessions: + suffixes: + - sessions + extensions: + - .tsv + - .json + entities: + subject: required +``` + +Note that these files do not have a `datatype`, but otherwise follow the same rules as above. + +#### BIDS filenames + +`rules.files.raw` and `rules.files.deriv` contain series of related rules. +These are largely grouped by datatype, but file types that appear in multiple locations may be grouped together. + +The files described take the form: + +```plain +[sub-