├── .circleci └── config.yml ├── .gitignore ├── BUILD ├── Dockerfile ├── LICENSE.txt ├── MANIFEST.in ├── README.md ├── bin └── data2bids ├── data2bids ├── __init__.py ├── data2bids.py └── utils.py ├── example └── config.json ├── requirements.txt ├── setup.py └── tests ├── __init__.py ├── test_add.py └── test_sup.py /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | jobs: 4 | build: 5 | docker: 6 | - image: python:3.6.8 7 | steps: 8 | - checkout 9 | - run: pip install -r requirements.txt 10 | # cache the installed libraries 11 | - save_cache: 12 | key: pip-packages-v1-{{ .Branch }}-{{ checksum "requirements.txt" }} 13 | paths: 14 | - "/usr/local/bin" 15 | - "/usr/local/lib/python3.6/site-packages" 16 | 17 | test-sup: 18 | docker: 19 | - image: python:3.6.8 20 | steps: 21 | - checkout 22 | # use the saved cache from the build to avoid reinstalling the libs 23 | - restore_cache: 24 | keys: 25 | - pip-packages-v1-{{ .Branch }}-{{ checksum "requirements.txt" }} 26 | - run: 27 | name: sup 28 | command: pytest ./tests/test_sup.py 29 | test-add: 30 | docker: 31 | - image: python:3.6.8 32 | steps: 33 | - checkout 34 | # use the saved cache from the build to avoid reinstalling the libs 35 | - restore_cache: 36 | keys: 37 | - pip-packages-v1-{{ .Branch }}-{{ checksum "requirements.txt" }} 38 | - run: 39 | name: add 40 | command: pytest ./tests/test_add.py 41 | 42 | workflows: 43 | version: 2 44 | tests: 45 | jobs: 46 | - build 47 | - test-add: 48 | requires: 49 | - build 50 | - test-sup: 51 | requires: 52 | - build 53 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled python modules. 2 | *.pyc 3 | 4 | # Setuptools distribution folder. 5 | /dist/ 6 | 7 | # Python egg metadata, regenerated from source files by setuptools. 8 | /*.egg-info 9 | 10 | # Build dir 11 | /build/ 12 | -------------------------------------------------------------------------------- /BUILD: -------------------------------------------------------------------------------- 1 | python3 setup.py sdist bdist_wheel 2 | python3 -m twine upload dist/* 3 | rm -r build 4 | rm -r dist 5 | rm -r *.egg-info 6 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.6.7 2 | 3 | LABEL maintainer=Loic Tetrel 4 | 5 | USER jovyan 6 | 7 | RUN pip3 install numpy \ 8 | nibabel \ 9 | data2bids 10 | 11 | COPY . /home/jovyan 12 | 13 | CMD data2bids 14 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019 SIMEXP 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md 2 | include LICENSE.txt 3 | include MANIFEST.in 4 | include example/config.json 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Data2Bids 2 | 3 | Data2Bids is a **python3** package that converts fmri files from extension supported by [nibabel](http://nipy.org/nibabel/api.html) into [NIfTI](https://nifti.nimh.nih.gov/nifti-1/) and then make them [Brain Imaging Data Structure](http://bids.neuroimaging.io/) compliant. 4 | The user specify how the files should be read into a directory. Then the tool scan all the files and move them into the right directories. 5 | 6 | *Disclaimer*: This tool is intended to convert data **other than** [DICOM](https://www.dicomstandard.org/about/). If you have DICOM data, please use more recognized tools as [heudiconv](https://github.com/nipy/heudiconv) or [Dcm2Bids](https://github.com/cbedetti/Dcm2Bids). It also does not handle properly the headers of the images (we rely entirely on nibabel). Finally, do not use this tool for too complicated raw data structures, in this case you should use a GUI converter like [bidscoin](https://github.com/Donders-Institute/bidscoin). 7 | 8 | ###### Input 9 | 10 | A directory containing some files in any extension, with names containing at minimum the information of modality and patient number. 11 | A `.JSON` configuration file explaining how the filenames should be read. 12 | 13 | ###### Output 14 | 15 | A directory as specified by the BIDS standard. 16 | [BIDS validator](https://github.com/bids-standard/bids-validator) is used to check the conformity of the output. 17 | 18 | ###### Example 19 | 20 | A directory `MyDataset` with the following files : 21 | ``` 22 | MyDataset/ 23 | | 24 | └── adhd_41278_FU12_T1_001.nii 25 | | 26 | └── adhd_41278_FU24_T1_001.nii 27 | | 28 | └── adhd_41578_BL00_RSFMRI_001.nii 29 | | 30 | └── adhd_41578_BL00_RSFMRI_002.nii 31 | ``` 32 | 33 | Will be transformed as : 34 | 35 | ``` 36 | MyDataset/ 37 | | 38 | └── sub-41278/ 39 | | | 40 | | └── anat/ 41 | | | 42 | | └── adhd_41278_FU12_T1_001.nii.gz 43 | | | 44 | | └── adhd_41278_FU24_T1_001.nii.gz 45 | └── sub-41578/ 46 | | 47 | └── func/ 48 | | 49 | └── adhd_41578_BL00_RSFMRI_001.nii.gz 50 | | 51 | └── adhd_41578_BL00_RSFMRI_002.nii.gz 52 | ``` 53 | ## Heuristic 54 | 55 | The package is using heuristics on the filenames to see how files should be reorganized. 56 | You will first probably need to write your own configuration file. 57 | Please take example from this file : https://github.com/SIMEXP/Data2Bids/blob/master/example/config.json 58 | 59 | The first mandatory field is `dataFormat` which specify what is the extension of the images in your input folder. `Data2Bids` accept all file formats available in [nibabel](http://nipy.org/nibabel/api.html). 60 | 61 | If you have `NIfTI` files, you can choose to whether compress the data or not. 62 | If the files are not `NIfTI`, then the compression in `.nii.gz` is done automatically. 63 | 64 | `repetitionTimeInSec` and `delayTimeInSec` are two mandatory fields needed by bids, because of some [issues with nibabel](https://github.com/nipy/nibabel/issues/712), this field is asked to the user. 65 | 66 | The remainind fields correspond to bids structure. For each of them you need to specify what content match for what type, with the left and right delimiter with perl regexp format. 67 | 68 | For example with `adhd_41578_NAPBL00_RSFMRI_001.mnc` if you want to match: 69 | * the fmri field, you need to specify the left and right delimiter `_` and the content `RSFMRI`. 70 | * the run number, the left delimiter is `_` but the right is `\\.` with content `[0-9]{3}` which means match exactly 3 integers. (\*) 71 | * the session `BL00`, you can use left delimiter `_.*?` (match a `_` and everything after), right delimiter `_` with content `BL00`. 72 | 73 | Everything shoud be in a list. For structural mri `anat`, functionnal mri `func` and tasks `func.task`, we use a sub-list to indentify the image sub-type (to differentiate `T1w` and `T2w` for example). 74 | 75 | (\*) Do not forget to use an escape character to match for example a "." 76 | ## Dependencies 77 | 78 | * [BIDS validator](https://github.com/bids-standard/bids-validator) 79 | * nibabel 80 | * numpy 81 | 82 | ## Install 83 | 84 | ###### with pip 85 | 86 | `pip3 install data2bids` 87 | 88 | ###### with container 89 | 90 | *Docker and singularity comming soon* 91 | 92 | ## Usage 93 | 94 | If you have your configuration file `config.json` in the directory `myFmriData` where your data is just do: 95 | 96 | ``` 97 | cd myFmriData 98 | data2bids 99 | ``` 100 | 101 | The resulting bids directory will be `myFmriData/myFmriData_BIDS`. 102 | 103 | You can also use `-c` to provide a path to the configuration. 104 | 105 | To change the input directory use `-d`, you can also change the output bids directory with `-o`. 106 | -------------------------------------------------------------------------------- /bin/data2bids: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Created on Mon Jan 28 09:58:27 2019 5 | 6 | @author: ltetrel 7 | """ 8 | 9 | import argparse 10 | from data2bids.data2bids import Data2Bids 11 | 12 | def get_parser(): 13 | parser = argparse.ArgumentParser( 14 | formatter_class=argparse.RawDescriptionHelpFormatter 15 | , description="" 16 | , epilog=""" 17 | Documentation at https://github.com/SIMEXP/Data2Bids 18 | """) 19 | 20 | parser.add_argument( 21 | "-d" 22 | , "--input_dir" 23 | , required=False 24 | , default=None 25 | , help="Input data directory(ies), Default: current directory", 26 | ) 27 | 28 | parser.add_argument( 29 | "-c" 30 | , "--config" 31 | , required=False 32 | , default=None 33 | , help="JSON configuration file (see example/config.json)", 34 | ) 35 | 36 | parser.add_argument( 37 | "-o" 38 | , "--output_dir" 39 | , required=False 40 | , default=None 41 | , help="Output BIDS directory, Default: Inside current directory ", 42 | ) 43 | 44 | # Add logging 45 | # parser.add_argument( 46 | # "-l" 47 | # , "--log_level" 48 | # , required=False 49 | # , default="INFO" 50 | # , choices=["DEBUG","INFO","WARNING","ERROR","CRITICAL"] 51 | # , help="Set logging level", 52 | # ) 53 | 54 | return parser 55 | 56 | def main(): 57 | args = get_parser().parse_args() 58 | data2bids = Data2Bids(**vars(args)) 59 | data2bids.run() 60 | 61 | if __name__ == '__main__': 62 | main() -------------------------------------------------------------------------------- /data2bids/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SIMEXP/Data2Bids/cbe50ad438def5609c9c3e0f086d587f7e4e73f2/data2bids/__init__.py -------------------------------------------------------------------------------- /data2bids/data2bids.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Created on Fri Dec 21 11:55:12 2018 5 | 6 | @author: ltetrel 7 | """ 8 | import os 9 | import re 10 | import shutil 11 | import json 12 | import subprocess 13 | import gzip 14 | import numpy as np 15 | import nibabel as nib 16 | import data2bids.utils as utils 17 | 18 | class Data2Bids(): 19 | 20 | def __init__(self, input_dir=None, config=None, output_dir=None): 21 | self._input_dir = None 22 | self._config_path = None 23 | self._config = None 24 | self._bids_dir = None 25 | self._bids_version = "1.1.1" 26 | self._dataset_name = None 27 | 28 | self.set_data_dir(input_dir) 29 | self.set_config_path(config) 30 | self.set_bids_dir(output_dir) 31 | 32 | def set_data_dir(self, data_dir): 33 | if data_dir is None: 34 | self._data_dir = os.getcwd() 35 | else: 36 | self._data_dir = data_dir 37 | 38 | self._dataset_name = os.path.basename(self._data_dir) 39 | 40 | def _set_config(self): 41 | with open(self._config_path, 'r') as fst: 42 | data = fst.read() 43 | try: 44 | self._config = json.loads(data) 45 | except json.JSONDecodeError as exc: 46 | if exc.msg == 'Invalid \\escape': 47 | data = data[:exc.pos] + '\\' + data[exc.pos:] 48 | self._config = json.loads(data) 49 | else: 50 | raise 51 | 52 | def set_config(self, config): 53 | self._config = config 54 | 55 | def set_config_path(self, config_path): 56 | if config_path is None: 57 | # Checking if a config.json is present 58 | if os.path.isfile(os.path.join(os.getcwd(), "config.json")): 59 | self._config_path = os.path.join(os.getcwd(), "config.json") 60 | else: 61 | self._config_path = config_path 62 | 63 | assert self._config_path is not None, "Please provide config file" 64 | self._set_config() 65 | 66 | def set_bids_dir(self, bids_dir): 67 | if bids_dir is None: 68 | # Creating a new directory for BIDS 69 | try: 70 | self._bids_dir = os.path.join(self._data_dir, self._dataset_name + "_BIDS") 71 | except TypeError: 72 | print("Error: Please provice input data directory if no BIDS directory...") 73 | else: 74 | self._bids_dir = bids_dir 75 | 76 | def match_regexp(self, config_regexp, filename, subtype=False): 77 | delimiter_left = config_regexp["left"] 78 | delimiter_right = config_regexp["right"] 79 | match_found = False 80 | 81 | if subtype: 82 | for to_match in config_regexp["content"]: 83 | if re.match(".*?" 84 | + delimiter_left 85 | + '(' + to_match[1] + ')' 86 | + delimiter_right 87 | + ".*?", filename): 88 | match = to_match[0] 89 | match_found = True 90 | else: 91 | for to_match in config_regexp["content"]: 92 | if re.match(".*?" 93 | + delimiter_left 94 | + '(' + to_match + ')' 95 | + delimiter_right 96 | + ".*?", filename): 97 | match = re.match(".*?" 98 | + delimiter_left 99 | + '(' + to_match + ')' 100 | + delimiter_right 101 | + ".*?", filename).group(1) 102 | match_found = True 103 | 104 | assert match_found 105 | return match 106 | 107 | def bids_validator(self): 108 | assert self._bids_dir is not None, "Cannot launch bids-validator without specifying bids directory !" 109 | try: 110 | subprocess.check_call(['bids-validator', self._bids_dir]) 111 | except FileNotFoundError: 112 | print("bids-validator does not appear to be installed") 113 | 114 | def description_dump(self): 115 | with open(self._bids_dir + "/dataset_description.json", 'w') as fst: 116 | data = {'Name': self._dataset_name, 117 | 'BIDSVersion': self._bids_version} 118 | json.dump(data, fst, ensure_ascii=False) 119 | 120 | def bold_dump(self, dst_file_path, new_name, task_label_match): 121 | with open(dst_file_path + new_name + ".json", 'w') as fst: 122 | data = {'RepetitionTime': float(self._config["repetitionTimeInSec"]), 123 | 'TaskName': task_label_match, 124 | 'DelayTime' : float(self._config["delayTimeInSec"])} 125 | json.dump(data, fst, ensure_ascii=False) 126 | 127 | def to_NIfTI(self, src_file_path, dst_file_path, new_name): 128 | # loading the original image 129 | nib_img = nib.load(src_file_path) 130 | nib_affine = np.array(nib_img.affine) 131 | nib_data = np.array(nib_img.dataobj) 132 | 133 | # create the nifti1 image 134 | # if minc format, invert the data and change the affine transformation 135 | # there is also an issue on minc headers, TO CHECK... 136 | if self._config["dataFormat"] == ".mnc": 137 | if len(nib_img.shape) > 3: 138 | nib_affine[0:3, 0:3] = nib_affine[0:3, 0:3] \ 139 | @ utils.rot_z(np.pi/2) \ 140 | @ utils.rot_y(np.pi) \ 141 | @ utils.rot_x(np.pi/2) 142 | nib_data = nib_data.T 143 | nib_data = np.swapaxes(nib_data, 0, 1) 144 | 145 | nifti_img = nib.Nifti1Image(nib_data, nib_affine, nib_img.header) 146 | nifti_img.header.set_xyzt_units(xyz="mm", t="sec") 147 | zooms = np.array(nifti_img.header.get_zooms()) 148 | zooms[3] = self._config["repetitionTimeInSec"] 149 | nifti_img.header.set_zooms(zooms) 150 | elif len(nib_img.shape) == 3: 151 | nifti_img = nib.Nifti1Image(nib_data, nib_affine, nib_img.header) 152 | nifti_img.header.set_xyzt_units(xyz="mm") 153 | else: 154 | nifti_img = nib.Nifti1Image(nib_data, nib_affine, nib_img.header) 155 | 156 | #saving the image 157 | nib.save(nifti_img, dst_file_path + new_name + ".nii.gz") 158 | 159 | def copy_NIfTI(self, src_file_path, dst_file_path, new_name): 160 | shutil.copy(src_file_path, dst_file_path + new_name + ".nii") 161 | #compression just if .nii files 162 | if self._config["compress"] is True: 163 | with open(dst_file_path + new_name + ".nii", 'rb') as f_in: 164 | with gzip.open(dst_file_path + new_name + ".nii.gz", 'wb') as f_out: 165 | shutil.copyfileobj(f_in, f_out) 166 | os.remove(dst_file_path + new_name + ".nii") 167 | 168 | def maybe_create_BIDS_dir(self): 169 | if os.path.exists(self._bids_dir): 170 | shutil.rmtree(self._bids_dir) 171 | os.makedirs(self._bids_dir) 172 | 173 | def run(self): 174 | # First we check that every parameters are configured 175 | if (self._data_dir is not None 176 | and self._config_path is not None 177 | and self._config is not None 178 | and self._bids_dir is not None): 179 | 180 | print("---- data2bids starting ----") 181 | print(self._data_dir) 182 | print("\n BIDS version:") 183 | print(self._bids_version) 184 | print("\n Config from file :") 185 | print(self._config_path) 186 | print("\n Ouptut BIDS directory:") 187 | print(self._bids_dir) 188 | print("\n") 189 | 190 | # Maybe create the output BIDS directory 191 | self.maybe_create_BIDS_dir() 192 | 193 | #dataset_description.json must be included in the folder root foolowing BIDS specs 194 | self.description_dump() 195 | 196 | # now we can scan all files and rearrange them 197 | for root, _, files in os.walk(self._data_dir): 198 | for file in files: 199 | src_file_path = os.path.join(root, file) 200 | dst_file_path = self._bids_dir 201 | 202 | part_match = None 203 | sess_match = None 204 | data_type_match = None 205 | run_match = None 206 | new_name = None 207 | 208 | # if the file doesn't match the extension, we skip it 209 | if not re.match(".*?" + self._config["dataFormat"], file): 210 | print("Warning : Skipping %s" %src_file_path) 211 | continue 212 | 213 | # Matching the participant label 214 | try: 215 | part_match = self.match_regexp(self._config["partLabel"], file) 216 | dst_file_path = dst_file_path + "/sub-" + part_match 217 | new_name = "/sub-" + part_match 218 | except AssertionError: 219 | print("No participant found for %s" %src_file_path) 220 | continue 221 | 222 | # Matching the session 223 | try: 224 | sess_match = self.match_regexp(self._config["sessLabel"], file) 225 | dst_file_path = dst_file_path + "/ses-" + sess_match 226 | new_name = new_name + "_ses-" + sess_match 227 | except AssertionError: 228 | print("No session found for %s" %src_file_path) 229 | continue 230 | 231 | # Matching the anat/fmri data type and task 232 | try: 233 | data_type_match = self.match_regexp(self._config["anat"] 234 | , file 235 | , subtype=True) 236 | dst_file_path = dst_file_path + "/anat" 237 | except AssertionError: 238 | # If no anatomical, trying functionnal 239 | try: 240 | data_type_match = self.match_regexp(self._config["func"] 241 | , file 242 | , subtype=True) 243 | dst_file_path = dst_file_path + "/func" 244 | # Now trying to match the task 245 | try: 246 | task_label_match = self.match_regexp(self._config["func.task"] 247 | , file 248 | , subtype=True) 249 | new_name = new_name + "_task-" + task_label_match 250 | except AssertionError: 251 | print("No task found for %s" %src_file_path) 252 | continue 253 | except AssertionError: 254 | print("No anat nor func data type found for %s" %src_file_path) 255 | continue 256 | 257 | # Matching the run number 258 | try: 259 | run_match = self.match_regexp(self._config["runIndex"], file) 260 | new_name = new_name + "_run-" + run_match 261 | except AssertionError: 262 | print("No run found for %s" %src_file_path) 263 | 264 | # Adding the modality to the new filename 265 | new_name = new_name + "_" + data_type_match 266 | 267 | # Creating the directory where to store the new file 268 | if not os.path.exists(dst_file_path): 269 | os.makedirs(dst_file_path) 270 | 271 | # finally, if the file is not nifti, we convert it using nibabel 272 | if self._config["dataFormat"] != ".nii" or self._config["dataFormat"] != ".nii.gz": 273 | self.to_NIfTI(src_file_path, dst_file_path, new_name) 274 | 275 | # if it is already a nifti file, no need to convert it so we just copy rename 276 | else: 277 | self.copy_NIfTI(src_file_path, dst_file_path, new_name) 278 | 279 | # finally, if it is a bold experiment, we need to add the JSON file 280 | if data_type_match == "bold": 281 | ### https://github.com/nipy/nibabel/issues/712, that is why we take the 282 | ### scanner parameters from the config.json 283 | #nib_img = nib.load(src_file_path) 284 | #TR = nib_img.header.get_zooms()[3] 285 | try: 286 | self.bold_dump(dst_file_path, new_name, task_label_match) 287 | except FileNotFoundError: 288 | print("Cannot write %s" %(dst_file_path + new_name + ".nii.gz")) 289 | continue 290 | 291 | # Output 292 | utils.tree(self._bids_dir) 293 | 294 | # Finally, we check with bids_validator if everything went alright 295 | self.bids_validator() 296 | 297 | else: 298 | print("Warning: No parameters are defined !") -------------------------------------------------------------------------------- /data2bids/utils.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Created on Mon Jan 28 09:55:12 2019 5 | 6 | @author: ltetrel 7 | """ 8 | from pathlib import Path 9 | import numpy as np 10 | 11 | # tree shell-like in python from 12 | # (stackoverflow.com/questions/9727673/list-directory-tree-structure-in-python) 13 | class DisplayablePath(): 14 | display_filename_prefix_middle = '├──' 15 | display_filename_prefix_last = '└──' 16 | display_parent_prefix_middle = ' ' 17 | display_parent_prefix_last = '│ ' 18 | 19 | def __init__(self, path, parent_path, is_last): 20 | self.path = Path(str(path)) 21 | self.parent = parent_path 22 | self.is_last = is_last 23 | if self.parent: 24 | self.depth = self.parent.depth + 1 25 | else: 26 | self.depth = 0 27 | 28 | @property 29 | def displayname(self): 30 | if self.path.is_dir(): 31 | return self.path.name + '/' 32 | return self.path.name 33 | 34 | @classmethod 35 | def make_tree(cls, root, parent=None, is_last=False, criteria=None): 36 | root = Path(str(root)) 37 | criteria = criteria or cls._default_criteria 38 | 39 | displayable_root = cls(root, parent, is_last) 40 | yield displayable_root 41 | 42 | children = sorted(list(path 43 | for path in root.iterdir() 44 | if criteria(path)), 45 | key=lambda s: str(s).lower()) 46 | count = 1 47 | for path in children: 48 | is_last = count == len(children) 49 | if path.is_dir(): 50 | yield from cls.make_tree(path, 51 | parent=displayable_root, 52 | is_last=is_last, 53 | criteria=criteria) 54 | else: 55 | yield cls(path, displayable_root, is_last) 56 | count += 1 57 | 58 | @classmethod 59 | def _default_criteria(cls, infile): 60 | import os 61 | if os.path.isdir(infile): 62 | return True 63 | else: 64 | return False 65 | 66 | def displayable(self): 67 | if self.parent is None: 68 | return self.path 69 | 70 | _filename_prefix = (self.display_filename_prefix_last 71 | if self.is_last 72 | else self.display_filename_prefix_middle) 73 | 74 | parts = ['{!s} {!s}'.format(_filename_prefix, 75 | self.displayname)] 76 | 77 | parent = self.parent 78 | while parent and parent.parent is not None: 79 | parts.append(self.display_parent_prefix_middle 80 | if parent.is_last 81 | else self.display_parent_prefix_last) 82 | parent = parent.parent 83 | 84 | return ''.join(reversed(parts)) 85 | 86 | def tree(path): 87 | paths = DisplayablePath.make_tree(Path(path)) 88 | for path_to_display in paths: 89 | print(path_to_display.displayable()) 90 | 91 | def rot_x(alpha): 92 | return np.array([[1, 0, 0] 93 | , [0, np.cos(alpha), np.sin(alpha)] 94 | , [0, -np.sin(alpha), np.cos(alpha)]]) 95 | 96 | def rot_y(alpha): 97 | return np.array([[np.cos(alpha), 0, -np.sin(alpha)] 98 | , [0, 1, 0] 99 | , [np.sin(alpha), 0, np.cos(alpha)]]) 100 | 101 | def rot_z(alpha): 102 | return np.array([[np.cos(alpha), np.sin(alpha), 0] 103 | , [-np.sin(alpha), np.cos(alpha), 0] 104 | , [0, 0, 1]]) 105 | -------------------------------------------------------------------------------- /example/config.json: -------------------------------------------------------------------------------- 1 | {"dataFormat" : ".mnc" 2 | , "compress" : true 3 | , "repetitionTimeInSec" : 2 4 | , "delayTimeInSec" : 0.06 5 | , "anat": {"left" : "_" 6 | , "right" : "_" 7 | , "content" : [ ["T1w", "adniT1"] 8 | ,["T2w", ""] 9 | ,["T1rho", ""] 10 | ,["T1map", ""] 11 | ,["T2map", ""] 12 | ,["T2star", ""] 13 | ,["FLAIR", ""] 14 | ,["FLASH", ""] 15 | ,["PD", ""] 16 | ,["PDmap", ""] 17 | ,["PDT2", ""] 18 | ,["inplaneT1", ""] 19 | ,["inplaneT2", ""] 20 | ,["angio", ""] ] 21 | } 22 | , "func": {"left" : "_" 23 | , "right" : "_" 24 | , "content" : [ ["bold","Resting"] 25 | , ["cbv",""] ] 26 | } 27 | , "func.task": {"left" : "_" 28 | , "right" : "_" 29 | , "content" : [ ["rest","Resting"] ] 30 | } 31 | , "sessLabel" : {"left" : "_.*?" 32 | , "right" : "_" 33 | , "content" : ["WA00" 34 | , "BL00" 35 | , "EN00" 36 | , "FU03" 37 | , "FU12" 38 | , "FU24" 39 | , "FU36" 40 | , "FU48"] 41 | } 42 | , "runIndex" : {"left" : "_" 43 | , "right" : "\\." 44 | , "content" : ["[0-9]{3}"] 45 | } 46 | , "partLabel" : {"left" : "_" 47 | , "right" : "_" 48 | , "content" : ["[0-9]{6}"] 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | numpy 2 | nibabel 3 | pytest 4 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup(name='data2bids', 4 | version='1.2.1', 5 | description='Reorganize fmri files to make them BIDS compliant.', 6 | url='https://github.com/SIMEXP/Data2Bids', 7 | author='Loic TETREL', 8 | author_email='loic.tetrel.pro@gmail.com', 9 | license='MIT', 10 | packages=['data2bids'], 11 | scripts=['bin/data2bids'], 12 | install_requires=[ 13 | 'nibabel', 14 | 'numpy', 15 | ], 16 | include_package_data=True, 17 | zip_safe=False) 18 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | -------------------------------------------------------------------------------- /tests/test_add.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Created on Mon Mar 4 16:26:45 2019 5 | 6 | @author: ltetrel 7 | """ 8 | 9 | import unittest 10 | from operations.operations import add 11 | 12 | class Test(unittest.TestCase): 13 | def test_add(self): 14 | res = add(1, 2) 15 | self.assertEqual(res, 3) -------------------------------------------------------------------------------- /tests/test_sup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Created on Mon Mar 4 16:26:45 2019 5 | 6 | @author: ltetrel 7 | """ 8 | 9 | import unittest 10 | from operations.operations import sup 11 | 12 | class Test(unittest.TestCase): 13 | def test_sup(self): 14 | res = sup(3, 1) 15 | self.assertTrue(res) --------------------------------------------------------------------------------