diff --git a/MANIFEST.in b/MANIFEST.in index 849247378..e9333c333 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,6 +1,5 @@ include bids/_version.py recursive-include bids/layout/config *.json -recursive-include bids/reports/config *.json recursive-include bids/tests/data * recursive-include bids/layout/tests/data * include long_description.rst diff --git a/README.md b/README.md index b1bbb56df..e03dd35c0 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ If you want to live on the bleeding edge, you can install from master: > pip install git+https://github.com/bids-standard/pybids.git #### Dependencies -PyBIDS has a number of dependencies. The core querying functionality requires only the `BIDS-Validator` package. However, most other modules require the core Python neuroimaging stack: `numpy`, `scipy`, `pandas`, and `nibabel`. The `reports` module additionally requires `num2words`. By default, all dependencies will be installed with pybids (if they aren't already available). +PyBIDS has a number of dependencies. The core querying functionality requires only the `BIDS-Validator` package. However, most other modules require the core Python neuroimaging stack: `numpy`, `scipy`, `pandas`, and `nibabel`. By default, all dependencies will be installed with pybids (if they aren't already available). ## Usage Get started by checking out [the documentation](https://bids-standard.github.io/pybids)! diff --git a/bids/__init__.py b/bids/__init__.py index 14ea1617a..4591078fa 100644 --- a/bids/__init__.py +++ b/bids/__init__.py @@ -11,7 +11,6 @@ "BIDSValidator", "config", "layout", - "reports", "utils", "variables" ] diff --git a/bids/reports/README.md b/bids/reports/README.md deleted file mode 100644 index 3e49b1483..000000000 --- a/bids/reports/README.md +++ /dev/null @@ -1,30 +0,0 @@ -## PyBIDS: Reports - -The PyBIDS reports module generates publication-quality data acquisition descriptions from BIDS datasets. - -NOTE: The reports module is experimental and currently under active development, and as such should be used with caution. -Please remember to verify any generated report before putting it to use. - -Additionally, only MRI datatypes (func, anat, fmap, and dwi) are currently supported. - -### Quickstart - -A simple example of standard usage follows. We assume that we have a root folder containing a BIDS-compliant project in `/bidsproject`. - -```python -from bids.layout import BIDSLayout -from bids.reports import BIDSReport - -# Load the BIDS dataset -layout = BIDSLayout('/bidsproject') - -# Initialize a report for the dataset -report = BIDSReport(layout) - -# Method generate returns a Counter of unique descriptions across subjects -descriptions = report.generate() - -# For datasets containing a single study design, all but the most common -# description most likely reflect random missing data. -pub_description = descriptions.most_common()[0][0] -``` diff --git a/bids/reports/__init__.py b/bids/reports/__init__.py deleted file mode 100644 index d9efbac49..000000000 --- a/bids/reports/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .report import BIDSReport - -__all__ = ["BIDSReport"] diff --git a/bids/reports/config/converters.json b/bids/reports/config/converters.json deleted file mode 100644 index 4b814b84e..000000000 --- a/bids/reports/config/converters.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "dir": { - "i": "left to right", - "i-": "right to left", - "j": "posterior to anterior", - "j-": "anterior to posterior", - "k": "inferior to superior", - "k-": "superior to inferior" - }, - "seq": { - "EP": "echo planar", - "GR": "gradient recalled", - "IR": "inversion recovery", - "RM": "research mode", - "SE": "spin echo" - }, - "seqvar": { - "MP": "MAG prepared", - "MTC": "magnetization transfer contrast", - "NONE": "no sequence variant", - "OSP": "oversampling phase", - "SK": "segmented k-space", - "SP": "spoiled", - "SS": "steady state", - "TRSS": "time reversed steady state" - } -} diff --git a/bids/reports/parameters.py b/bids/reports/parameters.py deleted file mode 100644 index 6396b69d0..000000000 --- a/bids/reports/parameters.py +++ /dev/null @@ -1,361 +0,0 @@ -"""Functions for building strings for individual parameters.""" -import logging -import math -import os -import os.path as op - -import nibabel as nib -from num2words import num2words - -from .utils import list_to_str, num_to_str, remove_duplicates - -logging.basicConfig() -LOGGER = logging.getLogger("pybids.reports.parsing") - - -def describe_slice_timing(img, metadata: dict) -> str: - """Generate description of slice timing from metadata.""" - - if "SliceTiming" in metadata: - slice_order = " in {0} order".format(get_slice_info(metadata["SliceTiming"])) - n_slices = len(metadata["SliceTiming"]) - else: - slice_order = "" - n_slices = img.shape[2] - - return "{n_slices} slices{slice_order}".format( - n_slices=n_slices, slice_order=slice_order - ) - - -def describe_repetition_time(metadata: dict): - """Generate description of repetition time from metadata.""" - tr = metadata["RepetitionTime"] * 1000 - tr = num_to_str(tr) - return "repetition time, TR={tr}ms".format(tr=tr) - - -def describe_func_duration(n_vols: int, tr) -> str: - """Generate description of functional run length from repetition time and number of volumes.""" - run_secs = math.ceil(n_vols * tr) - mins, secs = divmod(run_secs, 60) - return "{0}:{1:02.0f}".format(int(mins), int(secs)) - - -def describe_duration(files) -> str: - """Generate general description of scan length from files.""" - first_file = files[0] - metadata = first_file.get_metadata() - - tr = metadata["RepetitionTime"] - imgs = [nib.load(f) for f in files] - n_vols = [img.shape[3] for img in imgs] - - if len(set(n_vols)) > 1: - min_vols = min(n_vols) - max_vols = max(n_vols) - min_dur = describe_func_duration(min_vols, tr) - max_dur = describe_func_duration(max_vols, tr) - dur_str = "{}-{}".format(min_dur, max_dur) - n_vols = "{}-{}".format(min_vols, max_vols) - - else: - n_vols = n_vols[0] - dur_str = describe_func_duration(n_vols, tr) - - dur_str = ( - "Run duration was {0} minutes, during which {1} volumes were acquired." - ).format(dur_str, n_vols) - return dur_str - - -def describe_multiband_factor(metadata) -> str: - """Generate description of the multi-band acceleration applied, if used.""" - return ( - "MB factor={}".format(metadata["MultibandAccelerationFactor"]) - if metadata.get("MultibandAccelerationFactor", 1) > 1 - else "" - ) - - -def describe_echo_times(files): - """Generate description of echo times from metadata field. - - Parameters - ---------- - files : :obj:`list` of :obj:`bids.layout.models.BIDSFile` - List of nifti files in layout corresponding to file collection. - - Returns - ------- - te_str : str - Description of echo times. - me_str : str - Whether the data are multi-echo or single-echo. - """ - - echo_times = [f.get_metadata()["EchoTime"] for f in files] - echo_times = sorted(list(set(echo_times))) - if len(echo_times) > 1: - te = [num_to_str(t * 1000) for t in echo_times] - te = list_to_str(te) - me_str = "multi-echo" - else: - te = num_to_str(echo_times[0] * 1000) - me_str = "single-echo" - te_str = "echo time, TE={}ms".format(te) - return te_str, me_str - - -def describe_echo_times_fmap(files): - """Generate description of echo times from metadata field for fmaps - - Parameters - ---------- - files : :obj:`list` of :obj:`bids.layout.models.BIDSFile` - List of nifti files in layout corresponding to file collection. - - Returns - ------- - te_str : str - Description of echo times. - """ - # TODO handle all types of fieldmaps - - echo_times1 = [f.get_metadata()["EchoTime1"] for f in files] - echo_times2 = [f.get_metadata()["EchoTime2"] for f in files] - echo_times1 = sorted(list(set(echo_times1))) - echo_times2 = sorted(list(set(echo_times2))) - if len(echo_times1) <= 1 and len(echo_times2) <= 1: - # if that's not the case we should probably throw a warning - # because we should expect the same echo times for all values - te1 = num_to_str(echo_times1[0] * 1000) - te2 = num_to_str(echo_times2[0] * 1000) - return "echo time 1 / 2, TE1/2={0}{1}ms".format(te1, te2) - - -def describe_image_size(img): - """Generate description imaging data sizes, including FOV, voxel size, and matrix size. - - Parameters - ---------- - img : nibabel.nifti1.Nifti1Image - Image object from which to determine sizes. - - Returns - ------- - fov_str - matrixsize_str - voxelsize_str - """ - vs_str, ms_str, fov_str = get_size_str(img) - - fov_str = "field of view, FOV={}mm".format(fov_str) - voxelsize_str = "voxel size={}mm".format(vs_str) - matrixsize_str = "matrix size={}".format(ms_str) - - return fov_str, matrixsize_str, voxelsize_str - - -def describe_inplane_accel(metadata: dict) -> str: - """Generate description of in-plane acceleration factor, if any.""" - return ( - "in-plane acceleration factor={}".format( - metadata["ParallelReductionFactorInPlane"] - ) - if metadata.get("ParallelReductionFactorInPlane", 1) > 1 - else "" - ) - - -def describe_flip_angle(metadata: dict) -> str: - """Generate description of flip angle.""" - return "flip angle, FA={}".format(metadata.get("FlipAngle", "UNKNOWN")) - - -def describe_dmri_directions(img): - """Generate description of diffusion directions.""" - return "{} diffusion directions".format(img.shape[3]) - - -def describe_bvals(bval_file) -> str: - """Generate description of dMRI b-values.""" - # Parse bval file - with open(bval_file, "r") as file_object: - raw_bvals = file_object.read().splitlines() - # Flatten list of space-separated values - bvals = [ - item for sublist in [line.split(" ") for line in raw_bvals] for item in sublist - ] - bvals = sorted([int(v) for v in set(bvals)]) - bvals = [num_to_str(v) for v in bvals] - bval_str = list_to_str(bvals) - bval_str = "b-values of {} acquired".format(bval_str) - return bval_str - - -def describe_pe_direction(metadata: dict, config: dict) -> str: - """Generate description of phase encoding direction.""" - dir_str = config["dir"][metadata["PhaseEncodingDirection"]] - dir_str = "phase encoding: {}".format(dir_str) - return dir_str - - -def describe_intendedfor_targets(metadata: dict, layout) -> str: - """Generate description of intended for targets.""" - if "IntendedFor" in metadata.keys(): - - scans = metadata["IntendedFor"] - run_dict = {} - - for scan in scans: - fn = op.basename(scan) - if_file = [ - f for f in layout.get(extension=[".nii", ".nii.gz"]) if fn in f.path - ][0] - run_num = int(if_file.run) - target_type = if_file.entities["suffix"].upper() - - if target_type == "BOLD": - iff_meta = layout.get_metadata(if_file.path) - task = iff_meta.get("TaskName", if_file.entities["task"]) - target_type_str = "{0} {1} scan".format(task, target_type) - else: - target_type_str = "{0} scan".format(target_type) - - if target_type_str not in run_dict.keys(): - run_dict[target_type_str] = [] - - run_dict[target_type_str].append(run_num) - - for scan in run_dict.keys(): - run_dict[scan] = [ - num2words(r, ordinal=True) for r in sorted(run_dict[scan]) - ] - - out_list = [] - - for scan in run_dict.keys(): - - if len(run_dict[scan]) > 1: - s = "s" - else: - s = "" - - run_str = list_to_str(run_dict[scan]) - string = "{rs} run{s} of the {sc}".format(rs=run_str, s=s, sc=scan) - out_list.append(string) - - for_str = " for the {0}".format(list_to_str(out_list)) - - else: - for_str = "" - - return for_str - - -def get_slice_info(slice_times) -> str: - """Extract slice order from slice timing info. - - TODO: Be more specific with slice orders. - Currently anything where there's some kind of skipping is interpreted as - interleaved of some kind. - - Parameters - ---------- - slice_times : array-like - A list of slice times in seconds or milliseconds or whatever. - - Returns - ------- - slice_order_name : :obj:`str` - The name of the slice order sequence. - """ - # Slice order - slice_times = remove_duplicates(slice_times) - slice_order = sorted(range(len(slice_times)), key=lambda k: slice_times[k]) - - if slice_order == list(range(len(slice_order))): - slice_order_name = "sequential ascending" - - elif slice_order == list(reversed(range(len(slice_order)))): - slice_order_name = "sequential descending" - - elif slice_order[0] < slice_order[1]: - # We're allowing some wiggle room on interleaved. - slice_order_name = "interleaved ascending" - - elif slice_order[0] > slice_order[1]: - slice_order_name = "interleaved descending" - - else: - slice_order = [str(s) for s in slice_order] - raise Exception("Unknown slice order: [{0}]".format(", ".join(slice_order))) - - return slice_order_name - - -def describe_sequence(metadata: dict, config: dict): - """Extract and reformat imaging sequence(s) and variant(s) into pretty strings. - - Parameters - ---------- - metadata : :obj:`dict` - The metadata for the scan. - config : :obj:`dict` - A dictionary with relevant information regarding sequences, sequence - variants, phase encoding directions, and task names. - - Returns - ------- - seqs : :obj:`str` - Sequence names. - variants : :obj:`str` - Sequence variant names. - """ - seq_abbrs = metadata.get("ScanningSequence", "").split("_") - seqs = [config["seq"].get(seq, seq) for seq in seq_abbrs] - seqs = list_to_str(seqs) - if seq_abbrs[0]: - seqs += " ({0})".format(os.path.sep.join(seq_abbrs)) - - variants = [ - config["seqvar"].get(var, var) - for var in metadata.get("SequenceVariant", "").split("_") - ] - variants = list_to_str(variants) - - return seqs, variants - - -def get_size_str(img): - """Extract and reformat voxel size, matrix size, FOV, and number of slices into strings. - - Parameters - ---------- - img : :obj:`nibabel.Nifti1Image` - Image from scan from which to derive parameters. - - Returns - ------- - voxel_size : :obj:`str` - Voxel size string (e.g., '2x2x2') - matrix_size : :obj:`str` - Matrix size string (e.g., '128x128') - fov : :obj:`str` - Field of view string (e.g., '256x256') - """ - n_x, n_y = img.shape[:2] - import numpy as np - - matrix_size = "{0}x{1}".format(num_to_str(n_x), num_to_str(n_y)) - - voxel_dims = np.array(img.header.get_zooms()[:3]) - - voxel_size = "x".join([num_to_str(s) for s in voxel_dims]) - - - fov = [n_x, n_y] * voxel_dims[:2] - fov = "x".join([num_to_str(s) for s in fov]) - - return voxel_size, matrix_size, fov diff --git a/bids/reports/parsing.py b/bids/reports/parsing.py deleted file mode 100644 index 62e25c658..000000000 --- a/bids/reports/parsing.py +++ /dev/null @@ -1,392 +0,0 @@ -"""Parsing functions for generating BIDSReports.""" -import logging -import warnings -from pathlib import Path - -import nibabel as nib -from num2words import num2words - -from .. import __version__ -from ..utils import collect_associated_files -from . import parameters - -logging.basicConfig() -LOGGER = logging.getLogger("pybids.reports.parsing") - - -def func_info(layout, files, config): - """Generate a paragraph describing T2*-weighted functional scans. - - Parameters - ---------- - layout : :obj:`bids.layout.BIDSLayout` - Layout object for a BIDS dataset. - files : :obj:`list` of :obj:`bids.layout.models.BIDSFile` - List of nifti files in layout corresponding to DWI scan. - config : :obj:`dict` - A dictionary with relevant information regarding sequences, sequence - variants, phase encoding directions, and task names. - - Returns - ------- - desc : :obj:`str` - A description of the scan's acquisition information. - """ - first_file = files[0] - metadata = first_file.get_metadata() - img = nib.load(first_file.path) - - # General info - task_name = first_file.get_entities()["task"] + " task" - task_name = metadata.get("TaskName", task_name) - seqs, variants = parameters.describe_sequence(metadata, config) - all_runs = sorted(list(set([f.get_entities().get("run", 1) for f in files]))) - n_runs = len(all_runs) - if n_runs == 1: - run_str = "{0} run".format(num2words(n_runs).title()) - else: - run_str = "{0} runs".format(num2words(n_runs).title()) - dur_str = parameters.describe_duration(files) - - # Parameters - slice_str = parameters.describe_slice_timing(img, metadata) - tr_str = parameters.describe_repetition_time(metadata) - te_str, me_str = parameters.describe_echo_times(files) - fa_str = parameters.describe_flip_angle(metadata) - fov_str, matrixsize_str, voxelsize_str = parameters.describe_image_size(img) - mb_str = parameters.describe_multiband_factor(metadata) - inplaneaccel_str = parameters.describe_inplane_accel(metadata) - - parameters_str = [ - slice_str, - tr_str, - te_str, - fa_str, - fov_str, - matrixsize_str, - voxelsize_str, - mb_str, - inplaneaccel_str, - ] - parameters_str = [d for d in parameters_str if len(d)] - parameters_str = "; ".join(parameters_str) - - desc = ( - "{run_str} of {task} {variants} {seqs} {me_str} fMRI data were " - "collected ({parameters_str}). {dur_str}".format( - run_str=run_str, - task=task_name, - variants=variants, - seqs=seqs, - me_str=me_str, - parameters_str=parameters_str, - dur_str=dur_str, - ) - ) - return desc - - -def anat_info(layout, files, config): - """Generate a paragraph describing T1- and T2-weighted structural scans. - - Parameters - ---------- - layout : :obj:`bids.layout.BIDSLayout` - Layout object for a BIDS dataset. - files : :obj:`list` of :obj:`bids.layout.models.BIDSFile` - List of nifti files in layout corresponding to DWI scan. - config : :obj:`dict` - A dictionary with relevant information regarding sequences, sequence - variants, phase encoding directions, and task names. - - Returns - ------- - desc : :obj:`str` - A description of the scan's acquisition information. - """ - first_file = files[0] - metadata = first_file.get_metadata() - img = nib.load(first_file.path) - - # General info - seqs, variants = parameters.describe_sequence(metadata, config) - all_runs = sorted(list(set([f.get_entities().get("run", 1) for f in files]))) - n_runs = len(all_runs) - if n_runs == 1: - run_str = "{0} run".format(num2words(n_runs).title()) - else: - run_str = "{0} runs".format(num2words(n_runs).title()) - scan_type = first_file.get_entities()["suffix"].replace("w", "-weighted") - - # Parameters - slice_str = parameters.describe_slice_timing(img, metadata) - tr_str = parameters.describe_repetition_time(metadata) - te_str, me_str = parameters.describe_echo_times(files) - fa_str = parameters.describe_flip_angle(metadata) - fov_str, matrixsize_str, voxelsize_str = parameters.describe_image_size(img) - - parameters_str = [ - slice_str, - tr_str, - te_str, - fa_str, - fov_str, - matrixsize_str, - voxelsize_str, - ] - parameters_str = [d for d in parameters_str if len(d)] - parameters_str = "; ".join(parameters_str) - - desc = ( - "{run_str} of {scan_type} {variants} {seqs} {me_str} structural MRI " - "data were collected ({parameters_str}).".format( - run_str=run_str, - scan_type=scan_type, - variants=variants, - seqs=seqs, - me_str=me_str, - parameters_str=parameters_str, - ) - ) - return desc - - -def dwi_info(layout, files, config): - """Generate a paragraph describing DWI scan acquisition information. - - Parameters - ---------- - layout : :obj:`bids.layout.BIDSLayout` - Layout object for a BIDS dataset. - files : :obj:`list` of :obj:`bids.layout.models.BIDSFile` - List of nifti files in layout corresponding to DWI scan. - config : :obj:`dict` - A dictionary with relevant information regarding sequences, sequence - variants, phase encoding directions, and task names. - - Returns - ------- - desc : :obj:`str` - A description of the DWI scan's acquisition information. - """ - first_file = files[0] - metadata = first_file.get_metadata() - img = nib.load(first_file.path) - bval_file = first_file.path.replace(".nii.gz", ".bval").replace(".nii", ".bval") - - # General info - seqs, variants = parameters.describe_sequence(metadata, config) - all_runs = sorted(list(set([f.get_entities().get("run", 1) for f in files]))) - n_runs = len(all_runs) - if n_runs == 1: - run_str = "{0} run".format(num2words(n_runs).title()) - else: - run_str = "{0} runs".format(num2words(n_runs).title()) - - # Parameters - tr_str = parameters.describe_repetition_time(metadata) - te_str, me_str = parameters.describe_echo_times(files) - fa_str = parameters.describe_flip_angle(metadata) - fov_str, voxelsize_str, matrixsize_str = parameters.describe_image_size(img) - bval_str = parameters.describe_bvals(bval_file) - nvec_str = parameters.describe_dmri_directions(img) - mb_str = parameters.describe_multiband_factor(metadata) - - parameters_str = [ - tr_str, - te_str, - fa_str, - fov_str, - matrixsize_str, - voxelsize_str, - bval_str, - nvec_str, - mb_str, - ] - parameters_str = [d for d in parameters_str if len(d)] - parameters_str = "; ".join(parameters_str) - - desc = ( - "{run_str} of {variants} {seqs} diffusion-weighted (dMRI) data were " - "collected ({parameters_str}).".format( - run_str=run_str, - variants=variants, - seqs=seqs, - parameters_str=parameters_str, - ) - ) - return desc - - -def fmap_info(layout, files, config): - """Generate a paragraph describing field map acquisition information. - - Parameters - ---------- - layout : :obj:`bids.layout.BIDSLayout` - Layout object for a BIDS dataset. - files : :obj:`list` of :obj:`bids.layout.models.BIDSFile` - List of nifti files in layout corresponding to field map scan. - config : :obj:`dict` - A dictionary with relevant information regarding sequences, sequence - variants, phase encoding directions, and task names. - - Returns - ------- - desc : :obj:`str` - A description of the field map's acquisition information. - """ - first_file = files[0] - metadata = first_file.get_metadata() - img = nib.load(first_file.path) - - # General info - seqs, variants = parameters.describe_sequence(metadata, config) - - # Parameters - dir_str = parameters.describe_pe_direction(metadata, config) - slice_str = parameters.describe_slice_timing(img, metadata) - tr_str = parameters.describe_repetition_time(metadata) - te_str = parameters.describe_echo_times_fmap(files) - fa_str = parameters.describe_flip_angle(metadata) - fov_str, matrixsize_str, voxelsize_str = parameters.describe_image_size(img) - mb_str = parameters.describe_multiband_factor(metadata) - - parameters_str = [ - dir_str, - slice_str, - tr_str, - te_str, - fa_str, - fov_str, - matrixsize_str, - voxelsize_str, - mb_str, - ] - parameters_str = [d for d in parameters_str if len(d)] - parameters_str = "; ".join(parameters_str) - - for_str = parameters.describe_intendedfor_targets(metadata, layout) - - desc = ( - "A {variants} {seqs} field map ({parameters_str}) was " - "acquired{for_str}.".format( - variants=variants, - seqs=seqs, - for_str=for_str, - parameters_str=parameters_str, - ) - ) - return desc - - -def general_acquisition_info(metadata): - """General sentence on data acquisition. - - This should be the first sentence in the MRI data acquisition section. - - Parameters - ---------- - metadata : :obj:`dict` - The metadata for the dataset. - - Returns - ------- - out_str : :obj:`str` - Output string with scanner information. - """ - out_str = ( - "MR data were acquired using a {tesla}-Tesla {manu} {model} MRI " - "scanner.".format( - tesla=metadata.get("MagneticFieldStrength", "UNKNOWN"), - manu=metadata.get("Manufacturer", "MANUFACTURER"), - model=metadata.get("ManufacturersModelName", "MODEL"), - ) - ) - return out_str - - -def final_paragraph(metadata): - """Describe dicom-to-nifti conversion process and methods generation. - - Parameters - ---------- - metadata : :obj:`dict` - The metadata for the scan. - - Returns - ------- - desc : :obj:`str` - Output string with scanner information. - """ - if "ConversionSoftware" in metadata.keys(): - soft = metadata["ConversionSoftware"] - vers = metadata["ConversionSoftwareVersion"] - software_str = " using {soft} ({conv_vers})".format(soft=soft, conv_vers=vers) - else: - software_str = "" - desc = ( - "Dicoms were converted to NIfTI-1 format{software_str}. " - "This section was (in part) generated automatically using pybids " - "({meth_vers}).".format( - software_str=software_str, - meth_vers=__version__, - ) - ) - return desc - - -def parse_files(layout, data_files, sub, config): - """Loop through files in a BIDSLayout and generate appropriate descriptions. - - Then, compile all of the descriptions into a list. - - Parameters - ---------- - layout : :obj:`bids.layout.BIDSLayout` - Layout object for a BIDS dataset. - data_files : :obj:`list` of :obj:`bids.layout.models.BIDSFile` - List of nifti files in layout corresponding to subject/session combo. - sub : :obj:`str` - Subject ID. - config : :obj:`dict` - Configuration info for methods generation. - """ - # Group files into individual runs - data_files = collect_associated_files(layout, data_files, extra_entities=["run"]) - - # print(data_files) - - description_list = [] - # Assume all data have same basic info - description_list.append(general_acquisition_info(data_files[0][0].get_metadata())) - - for group in data_files: - - if group[0].entities["datatype"] == "func": - group_description = func_info(layout, group, config) - - elif (group[0].entities["datatype"] == "anat") and group[0].entities[ - "suffix" - ].endswith("w"): - group_description = anat_info(layout, group, config) - - elif group[0].entities["datatype"] == "dwi": - group_description = dwi_info(layout, group, config) - - elif (group[0].entities["datatype"] == "fmap") and group[0].entities[ - "suffix" - ] == "phasediff": - group_description = fmap_info(layout, group, config) - - elif group[0].entities["datatype"] in ["eeg", "meg", "beh", "perf"]: - warnings.warn(group[0].entities["datatype"] + " not yet supported.") - continue - - else: - warnings.warn(group[0].filename + " not yet supported.") - continue - - description_list.append(group_description) - - return description_list diff --git a/bids/reports/report.py b/bids/reports/report.py deleted file mode 100644 index af8836618..000000000 --- a/bids/reports/report.py +++ /dev/null @@ -1,234 +0,0 @@ -"""Generate publication-quality data acquisition methods section from BIDS dataset.""" -import json -import os.path as op -from collections import Counter - -from bids.reports import parsing, utils - - -class BIDSReport(object): - """Generate publication-quality data acquisition section from BIDS dataset. - - Parameters - ---------- - layout : :obj:`bids.layout.BIDSLayout` - Layout object for a BIDS dataset. - config : :obj:`str` or :obj:`dict`, optional - Configuration info for methods generation. Can be a path to a file - (str), a dictionary, or None. If None, loads and uses default - configuration information. - Keys in the dictionary include: - 'dir': a dictionary for converting encoding direction strings - (e.g., j-) to descriptions (e.g., anterior to - posterior) - 'seq': a dictionary of sequence abbreviations (e.g., EP) and - corresponding names (e.g., echo planar) - 'seqvar': a dictionary of sequence variant abbreviations - (e.g., SP) and corresponding names (e.g., spoiled) - - Warning - ------- - pybids' automatic report generation is experimental and currently under - active development, and as such should be used with caution. - Please remember to verify any generated report before putting it to use. - - Additionally, only MRI datatypes (func, anat, fmap, and dwi) are currently - supported. - """ - - def __init__(self, layout, config=None): - self.layout = layout - if config is None: - config = op.join( - op.dirname(op.abspath(__file__)), - "config", - "converters.json", - ) - - if isinstance(config, str): - with open(config) as fobj: - config = json.load(fobj) - - if not isinstance(config, dict): - raise ValueError( - "Input config must be None, dict, or path to " - "json file containing dict." - ) - - self.config = config - - def generate_from_files(self, files): - r"""Generate a methods section from a list of files. - - Parameters - ---------- - files : list of BIDSImageFile objects - List of files from which to generate methods description. - - Returns - ------- - counter : :obj:`collections.Counter` - A dictionary of unique descriptions across subjects in the file list, - along with the number of times each pattern occurred. In cases - where all subjects underwent the same protocol, the most common - pattern is most likely the most complete. In cases where the - file list contains multiple protocols, each pattern will need to be - inspected manually. - - Examples - -------- - >>> from os.path import join - >>> from bids.layout import BIDSLayout - >>> from bids.reports import BIDSReport - >>> from bids.tests import get_test_data_path - >>> layout = BIDSLayout(join(get_test_data_path(), 'synthetic')) - >>> report = BIDSReport(layout) - >>> files = layout.get(session='01', extension=['.nii.gz', '.nii']) - >>> counter = report.generate_from_files(files) - Number of patterns detected: 1 - Remember to double-check everything and to replace with a degree symbol. - - >>> counter.most_common()[0][0] # doctest: +ELLIPSIS - 'In session 01, MR data were...' - """ - descriptions = [] - - subjects = sorted(list(set([f.get_entities().get("subject") for f in files]))) - sessions = sorted(list(set([f.get_entities().get("session") for f in files]))) - for sub in subjects: - subject_files = [f for f in files if f.get_entities().get("subject") == sub] - description_list = [] - for ses in sessions: - data_files = [ - f for f in subject_files if f.get_entities().get("session") == ses - ] - - if data_files: - ses_description = parsing.parse_files( - self.layout, - data_files, - sub, - self.config, - ) - ses_description[0] = "In session {0}, ".format(ses) + ses_description[0] - description_list += ses_description - metadata = self.layout.get_metadata(data_files[0].path) - else: - raise Exception("No imaging files for subject {0}".format(sub)) - - # Assume all data were converted the same way and use the last nifti - # file's json for conversion information. - if "metadata" not in vars(): - raise Exception( - "No valid jsons found. Cannot generate final paragraph." - ) - - description = "\n\t".join(description_list) - description += "\n\n{0}".format(parsing.final_paragraph(metadata)) - descriptions.append(description) - counter = Counter(descriptions) - print("Number of patterns detected: {0}".format(len(counter.keys()))) - print(utils.reminder()) - return counter - - def generate(self, **kwargs): - r"""Generate the methods section. - - Parameters - ---------- - kwargs : dict - Keyword arguments passed to BIDSLayout to select subsets of the - dataset. - - Returns - ------- - counter : :obj:`collections.Counter` - A dictionary of unique descriptions across subjects in the dataset, - along with the number of times each pattern occurred. In cases - where all subjects underwent the same protocol, the most common - pattern is most likely the most complete. In cases where the - dataset contains multiple protocols, each pattern will need to be - inspected manually. - - Examples - -------- - >>> from os.path import join - >>> from bids.layout import BIDSLayout - >>> from bids.reports import BIDSReport - >>> from bids.tests import get_test_data_path - >>> layout = BIDSLayout(join(get_test_data_path(), 'synthetic')) - >>> report = BIDSReport(layout) - >>> counter = report.generate(session='01') - Number of patterns detected: 1 - Remember to double-check everything and to replace with a degree symbol. - - >>> counter.most_common()[0][0] # doctest: +ELLIPSIS - 'In session 01, MR data were...' - """ - descriptions = [] - - subjects = self.layout.get_subjects(**kwargs) - kwargs = {k: v for k, v in kwargs.items() if k != "subject"} - for sub in subjects: - descriptions.append(self._report_subject(subject=sub, **kwargs)) - counter = Counter(descriptions) - print("Number of patterns detected: {0}".format(len(counter.keys()))) - print(utils.reminder()) - return counter - - def _report_subject(self, subject, **kwargs): - """Write a report for a single subject. - - Parameters - ---------- - subject : :obj:`str` - Subject ID. - - Attributes - ---------- - layout : :obj:`bids.layout.BIDSLayout` - Layout object for a BIDS dataset. - config : :obj:`dict` - Configuration info for methods generation. - - Returns - ------- - description : :obj:`str` - A publication-ready report of the dataset's data acquisition - information. Each scan type is given its own paragraph. - """ - description_list = [] - # Remove session from kwargs if provided, else set session as all available - sessions = kwargs.pop( - "session", self.layout.get_sessions(subject=subject, **kwargs) - ) - if not sessions: - sessions = [None] - elif not isinstance(sessions, list): - sessions = [sessions] - - for ses in sessions: - data_files = self.layout.get( - subject=subject, - extension=[".nii", ".nii.gz"], - **kwargs, - ) - - if data_files: - ses_description = parsing.parse_files( - self.layout, - data_files, - subject, - self.config, - ) - ses_description[0] = "In session {0}, ".format(ses) + ses_description[0] - description_list += ses_description - metadata = self.layout.get_metadata(data_files[0].path) - else: - raise Exception("No imaging files for subject {0}".format(subject)) - - # Assume all data were converted the same way and use the first nifti - # file's json for conversion information. - description = "\n\t".join(description_list) - description += "\n\n{0}".format(parsing.final_paragraph(metadata)) - return description diff --git a/bids/reports/tests/__init__.py b/bids/reports/tests/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/bids/reports/tests/test_parameters.py b/bids/reports/tests/test_parameters.py deleted file mode 100644 index bc43878e0..000000000 --- a/bids/reports/tests/test_parameters.py +++ /dev/null @@ -1,302 +0,0 @@ -import pytest -import json -import nibabel as nib - -from os.path import abspath, join - -from bids.layout import BIDSLayout -from bids.reports import parameters -from bids.tests import get_test_data_path - - -@pytest.fixture -def testlayout(): - """A BIDSLayout for testing.""" - data_dir = join(get_test_data_path(), "synthetic") - return BIDSLayout(data_dir) - - -@pytest.fixture -def testimg(testlayout): - - func_files = testlayout.get( - subject="01", - session="01", - task="nback", - run="01", - extension=[".nii.gz"], - ) - return nib.load(func_files[0].path) - - -@pytest.fixture -def testdiffimg(testlayout): - - dwi_files = testlayout.get( - subject="01", - session="01", - datatype="dwi", - extension=[".nii.gz"], - ) - return nib.load(dwi_files[0].path) - - -@pytest.fixture -def testconfig(): - config_file = abspath( - join(get_test_data_path(), "../../reports/config/converters.json") - ) - with open(config_file, "r") as fobj: - config = json.load(fobj) - return config - - -@pytest.fixture -def testmeta(): - return { - "RepetitionTime": 2.0, - "MultibandAccelerationFactor": 2, - "ParallelReductionFactorInPlane": 2, - "FlipAngle": 90, - "PhaseEncodingDirection": "i", - "SliceTiming": [0, 1, 2, 3], - } - - -@pytest.fixture -def testmeta_light(): - return {"RepetitionTime": 2.0} - - -@pytest.mark.parametrize( - "ScanningSequence, expected_seq", - [ - ("EP", "echo planar (EP)"), - ("GR", "gradient recalled (GR)"), - ("IR", "inversion recovery (IR)"), - ("RM", "research mode (RM)"), - ("SE", "spin echo (SE)"), - ("SE_EP", "spin echo and echo planar (SE/EP)"), - ], -) -@pytest.mark.parametrize( - "SequenceVariant, expected_var", - [ - ("MP", "MAG prepared"), - ("MTC", "magnetization transfer contrast"), - ("NONE", "no sequence variant"), - ("OSP", "oversampling phase"), - ("SK", "segmented k-space"), - ("SS", "steady state"), - ("TRSS", "time reversed steady state"), - ("MP_SS", "MAG prepared and steady state"), - ], -) -def test_describe_sequence( - ScanningSequence, expected_seq, SequenceVariant, expected_var, testconfig -): - """test for sequence and variant type description""" - metadata = { - "SequenceVariant": SequenceVariant, - "ScanningSequence": ScanningSequence, - } - seqs, variants = parameters.describe_sequence(metadata, testconfig) - - assert seqs == expected_seq - assert variants == expected_var - - -@pytest.mark.parametrize( - "pe_direction, expected", - [ - ("i", "left to right"), - ("i-", "right to left"), - ("j", "posterior to anterior"), - ("j-", "anterior to posterior"), - ("k", "inferior to superior"), - ("k-", "superior to inferior"), - ], -) -def test_describe_pe_direction(pe_direction, expected, testconfig): - """test for phase encoding direction description""" - metadata = {"PhaseEncodingDirection": pe_direction} - dir_str = parameters.describe_pe_direction(metadata, testconfig) - assert dir_str == "phase encoding: " + expected - - -def test_describe_bvals_smoke(testlayout): - """Smoke test for parsing _dwi.bval - - It should return a str description when provided valid inputs. - """ - bval_file = testlayout.get( - subject="01", - session="01", - suffix="dwi", - extension=[".bval"], - ) - - bval_str = parameters.describe_bvals(bval_file[0]) - assert isinstance(bval_str, str) - - -def test_describe_echo_times_smoke(testlayout): - """Smoke test for parsing echo time - It should return a str description when provided valid inputs. - """ - anat_file = testlayout.get( - subject="01", - session="01", - suffix="T1w", - extension=[".nii.gz"], - ) - - te_str, me_str = parameters.describe_echo_times(anat_file) - assert isinstance(te_str, str) - - -def test_describe_echo_times_fmap(testlayout): - """Smoke test for parsing echo time - - It should return a str description when provided valid inputs. - """ - fmap_file = testlayout.get( - subject="01", - session="01", - suffix="phasediff", - extension=[".nii.gz"], - ) - - te_str = parameters.describe_echo_times_fmap(fmap_file) - assert isinstance(te_str, str) - - -def test_describe_repetition_time_smoke(testmeta): - - # when - tr_str = parameters.describe_repetition_time(testmeta) - # then - expected = "repetition time, TR=2000ms" - assert tr_str == expected - - -def test_describe_func_duration_smoke(): - - # given - n_vols = 100 - tr = 2.5 - # when - duration = parameters.describe_func_duration(n_vols, tr) - # then - expected = "4:10" - assert duration == expected - - -def test_describe_multiband_factor_smoke(testmeta, testmeta_light): - - # when - mb_str = parameters.describe_multiband_factor(testmeta) - # then - expected = "MB factor=2" - assert mb_str == expected - - # when - mb_str = parameters.describe_multiband_factor(testmeta_light) - # then - expected = "" - assert mb_str == expected - - -def test_describe_inplane_accel_smoke(testmeta, testmeta_light): - - # when - mb_str = parameters.describe_inplane_accel(testmeta) - # then - expected = "in-plane acceleration factor=2" - assert mb_str == expected - - # when - mb_str = parameters.describe_inplane_accel(testmeta_light) - # then - expected = "" - assert mb_str == expected - - -def test_describe_flip_angle_smoke(testmeta): - - # when - mb_str = parameters.describe_flip_angle(testmeta) - # then - expected = "flip angle, FA=90" - assert mb_str == expected - - -@pytest.mark.parametrize( - "slice_times, expected", - [ - ((1, 2, 3, 4), "sequential ascending"), - ([4, 3, 2, 1], "sequential descending"), - ([1, 3, 2, 4], "interleaved ascending"), - ([4, 2, 3, 1], "interleaved descending"), - ], -) -def test_get_slice_info(slice_times, expected): - - slice_order_name = parameters.get_slice_info(slice_times) - assert slice_order_name == expected - - -def test_describe_slice_timing(testimg, testmeta, testmeta_light): - - slice_str = parameters.describe_slice_timing(testimg, testmeta) - expected = "4 slices in sequential ascending order" - assert slice_str == expected - - slice_str = parameters.describe_slice_timing(testimg, testmeta_light) - expected = "64 slices" - assert slice_str == expected - - -def test_get_size_str(testimg): - - voxel_size, matrix_size, fov = parameters.get_size_str(testimg) - expected_vox = "2x2x2" - expected_mat = "64x64" - expected_fov = "128x128" - assert voxel_size == expected_vox - assert matrix_size == expected_mat - assert fov == expected_fov - - -def test_describe_image_size(testimg): - - fov_str, matrixsize_str, voxelsize_str = parameters.describe_image_size(testimg) - expected_vox = "voxel size=2x2x2mm" - expected_mat = "matrix size=64x64" - expected_fov = "field of view, FOV=128x128mm" - assert voxelsize_str == expected_vox - assert matrixsize_str == expected_mat - assert fov_str == expected_fov - - -def test_describe_dmri_directions(testdiffimg): - - dif_dir_str = parameters.describe_dmri_directions(testdiffimg) - expected = "64 diffusion directions" - assert dif_dir_str == expected - - -def test_describe_intendedfor_targets(testmeta_light, testlayout): - - for_str = parameters.describe_intendedfor_targets(testmeta_light, testlayout) - assert for_str == "" - - fmap_files = testlayout.get( - subject="01", - session="01", - suffix="phasediff", - extension=[".nii.gz"], - ) - metadata = fmap_files[0].get_metadata() - for_str = parameters.describe_intendedfor_targets(metadata, testlayout) - assert for_str == " for the first and second runs of the N-Back BOLD scan" diff --git a/bids/reports/tests/test_parsing.py b/bids/reports/tests/test_parsing.py deleted file mode 100644 index 61fdab3dc..000000000 --- a/bids/reports/tests/test_parsing.py +++ /dev/null @@ -1,129 +0,0 @@ -"""Tests for bids.reports.parsing.""" -import json -from os.path import abspath, join - -import pytest -from bids.layout import BIDSLayout -from bids.reports import parsing -from bids.tests import get_test_data_path - - -@pytest.fixture -def testlayout(): - """A BIDSLayout for testing.""" - data_dir = join(get_test_data_path(), "synthetic") - return BIDSLayout(data_dir) - - -@pytest.fixture -def testconfig(): - config_file = abspath( - join(get_test_data_path(), "../../reports/config/converters.json") - ) - with open(config_file, "r") as fobj: - config = json.load(fobj) - return config - - -@pytest.fixture -def testmeta(): - metadata = {"RepetitionTime": 2.0} - return metadata - - -def test_anat_info_smoke(testlayout, testconfig): - """Smoke test for parsing.anat_info. - - It should return a str description when provided valid inputs. - """ - anat_files = testlayout.get( - subject="01", - session="01", - suffix="T1w", - extension=[".nii.gz"], - ) - - desc = parsing.anat_info(testlayout, anat_files, testconfig) - assert isinstance(desc, str) - - -def test_dwi_info_smoke(testlayout, testconfig): - """Smoke test for parsing.dwi_info. - - It should return a str description when provided valid inputs. - """ - dwi_files = testlayout.get( - subject="01", - session="01", - datatype="dwi", - extension=[".nii.gz"], - ) - - desc = parsing.dwi_info(testlayout, dwi_files, testconfig) - assert isinstance(desc, str) - - -def test_fmap_info_smoke(testlayout, testconfig): - """Smoke test for parsing.fmap_info. - - It should return a str description when provided valid inputs. - """ - fmap_files = testlayout.get( - subject="01", - session="01", - datatype="fmap", - suffix="phasediff", - extension=[".nii.gz"], - ) - - desc = parsing.fmap_info(testlayout, fmap_files, testconfig) - assert isinstance(desc, str) - - -def test_func_info_smoke(testlayout, testconfig): - """Smoke test for parsing.func_info. - - It should return a str description when provided valid inputs. - """ - func_files = testlayout.get( - subject="01", - session="01", - task="nback", - run="01", - extension=[".nii.gz"], - ) - - desc = parsing.func_info(testlayout, func_files, testconfig) - assert isinstance(desc, str) - - -def test_general_acquisition_info_smoke(testmeta): - """Smoke test for parsing.general_acquisition_info. - - It should return a str description when provided valid inputs. - """ - desc = parsing.general_acquisition_info(testmeta) - assert isinstance(desc, str) - - -def test_final_paragraph_smoke(testmeta): - """Smoke test for parsing.final_paragraph. - - It should return a str description when provided valid inputs. - """ - desc = parsing.final_paragraph(testmeta) - assert isinstance(desc, str) - - -def test_parse_files_smoke(testlayout, testconfig): - """Smoke test for parsing.parse_files. - - It should return a list of string descriptions when provided valid inputs, - with each string containing the description for a single nifti file - (except functional data, which is combined within task, across runs). - """ - subject = "01" - niftis = testlayout.get(subject=subject, extension=[".nii", ".nii.gz"]) - desc = parsing.parse_files(testlayout, niftis, subject, testconfig) - assert isinstance(desc, list) - assert isinstance(desc[0], str) diff --git a/bids/reports/tests/test_report.py b/bids/reports/tests/test_report.py deleted file mode 100644 index df8943f42..000000000 --- a/bids/reports/tests/test_report.py +++ /dev/null @@ -1,79 +0,0 @@ -"""Tests for bids.reports.report.""" -import json -from collections import Counter -from os.path import abspath, join - -import pytest -from bids.layout import BIDSLayout -from bids.reports import BIDSReport -from bids.tests import get_test_data_path - - -@pytest.fixture -def testlayout(): - """A BIDSLayout for testing.""" - data_dir = join(get_test_data_path(), "synthetic") - return BIDSLayout(data_dir) - - -def test_report_init(testlayout): - """Report initialization should return a BIDSReport object.""" - report = BIDSReport(testlayout) - assert isinstance(report, BIDSReport) - - -def test_report_gen(testlayout): - """Report generation should return a counter of unique descriptions in the dataset.""" - report = BIDSReport(testlayout) - descriptions = report.generate() - assert isinstance(descriptions, Counter) - - -def test_report_gen_from_files(testlayout): - """Report generation from file list should return a counter of unique - descriptions in the dataset. - """ - report = BIDSReport(testlayout) - files = testlayout.get(extension=[".nii.gz", ".nii"]) - descriptions = report.generate_from_files(files) - assert isinstance(descriptions, Counter) - - -def test_report_subject(testlayout): - """Generating a report for one subject should only return one subject's - description (i.e., one pattern with a count of one). - """ - report = BIDSReport(testlayout) - descriptions = report.generate(subject="01") - assert sum(descriptions.values()) == 1 - - -def test_report_session(testlayout): - """Generating a report for one session should mean that no other sessions - appear in any of the unique descriptions. - """ - report = BIDSReport(testlayout) - descriptions = report.generate(session="01") - assert "session 02" not in " ".join(descriptions.keys()) - - -def test_report_file_config(testlayout): - """Report initialization should take in a config file and use that if provided.""" - config_file = abspath( - join(get_test_data_path(), "../../reports/config/converters.json") - ) - report = BIDSReport(testlayout, config=config_file) - descriptions = report.generate() - assert isinstance(descriptions, Counter) - - -def test_report_dict_config(testlayout): - """Report initialization should take in a config dict and use that if provided.""" - config_file = abspath( - join(get_test_data_path(), "../../reports/config/converters.json") - ) - with open(config_file, "r") as fobj: - config = json.load(fobj) - report = BIDSReport(testlayout, config=config) - descriptions = report.generate() - assert isinstance(descriptions, Counter) diff --git a/bids/reports/utils.py b/bids/reports/utils.py deleted file mode 100644 index e85cf4c45..000000000 --- a/bids/reports/utils.py +++ /dev/null @@ -1,64 +0,0 @@ -"""Generate publication-quality data acquisition methods section from BIDS dataset. - -Utilities to generate the MRI data acquisition portion of a -methods section from a BIDS dataset. -""" -import logging - -logging.basicConfig() -LOGGER = logging.getLogger("pybids.reports.utils") - - -def reminder(): - """Remind users about things they need to do after generating the report.""" - return ( - "Remember to double-check everything and to replace with " - "a degree symbol." - ) - - -def remove_duplicates(seq): - """Return unique elements from list while preserving order. - - From https://stackoverflow.com/a/480227/2589328 - """ - seen = set() - seen_add = seen.add - return [x for x in seq if not (x in seen or seen_add(x))] - - -def num_to_str(num): - """Convert an int or float to a nice string. - - E.g., - 21 -> '21' - 2.500 -> '2.5' - 3. -> '3' - """ - return "{0:0.02f}".format(num).rstrip("0").rstrip(".") - - -def list_to_str(lst): - """Turn a list into a comma- and/or and-separated string. - - Parameters - ---------- - lst : :obj:`list` - A list of strings to join into a single string. - - Returns - ------- - str_ : :obj:`str` - A string with commas and/or ands separating the elements from ``lst``. - - """ - if len(lst) == 1: - str_ = lst[0] - elif len(lst) == 2: - str_ = " and ".join(lst) - elif len(lst) > 2: - str_ = ", ".join(lst[:-1]) - str_ += ", and {0}".format(lst[-1]) - else: - raise ValueError("List of length 0 provided.") - return str_ diff --git a/doc/api.rst b/doc/api.rst index f65cc86d3..21cc8d740 100644 --- a/doc/api.rst +++ b/doc/api.rst @@ -43,22 +43,6 @@ API Reference .. _calibration_ref: -:mod:`bids.reports`: Data acquisition report generation --------------------------------------------------------- - -.. autosummary:: bids.reports - :toctree: generated/ - - bids.reports.BIDSReport - bids.reports.parsing - bids.reports.parameters - bids.reports.utils - -.. currentmodule:: bids - -.. _calibration_ref: - - :mod:`bids.variables`: Variables -------------------------------------------------- diff --git a/doc/reports/index.rst b/doc/reports/index.rst deleted file mode 100644 index 9e9456227..000000000 --- a/doc/reports/index.rst +++ /dev/null @@ -1,92 +0,0 @@ -.. _reports: - -===================================================== -``reports``: Data acquisition report generation -===================================================== - -.. contents:: **Contents** - :local: - :depth: 1 - -| - -.. currentmodule:: pybids.reports - -.. _initializing_reports: - -Initializing reports -=========================================== - -The :obj:`bids.reports.BIDSReport` class requires a :obj:`bids.BIDSLayout` object as an argument:: - - >>> from os.path import join - >>> from bids import BIDSLayout - >>> from bids.reports import BIDSReport - >>> from bids.tests import get_test_data_path - >>> layout = BIDSLayout(join(get_test_data_path(), 'synthetic')) - >>> report = BIDSReport(layout) - -.. _generating_reports: - -Generating reports -=========================================== - -Pybids reports are then generated with the ``generate`` method, which returns a -:obj:`collections.Counter` of reports:: - - >>> counter = report.generate() - >>> main_report = counter.most_common()[0][0] - >>> print(main_report) - r""" - For session 01: - MR data were acquired using a UNKNOWN-Tesla MANUFACTURER MODEL MRI scanner. - Ten runs of N-Back UNKNOWN-echo fMRI data were collected (64 slices; repetition time, TR=2500ms; - echo time, TE=UNKNOWNms; flip angle, FA=UNKNOWN; field of view, FOV=128x128mm; - matrix size=64x64; voxel size=2x2x2mm). Each run was 2:40 minutes in length, during - which 64 functional volumes were acquired. - Five runs of Rest UNKNOWN-echo fMRI data were collected (64 slices; repetition time, TR=2500ms; - echo time, TE=UNKNOWNms; flip angle, FA=UNKNOWN; field of view, FOV=128x128mm; - matrix size=64x64; voxel size=2x2x2mm). Each run was 2:40 minutes in length, during - which 64 functional volumes were acquired. - - For session 02: - MR data were acquired using a UNKNOWN-Tesla MANUFACTURER MODEL MRI scanner. - Ten runs of N-Back UNKNOWN-echo fMRI data were collected (64 slices; repetition time, TR=2500ms; - echo time, TE=UNKNOWNms; flip angle, FA=UNKNOWN; field of view, FOV=128x128mm; - matrix size=64x64; voxel size=2x2x2mm). Each run was 2:40 minutes in length, during - which 64 functional volumes were acquired. - Five runs of Rest UNKNOWN-echo fMRI data were collected (64 slices; repetition time, TR=2500ms; - echo time, TE=UNKNOWNms; flip angle, FA=UNKNOWN; field of view, FOV=128x128mm; - matrix size=64x64; voxel size=2x2x2mm). Each run was 2:40 minutes in length, during - which 64 functional volumes were acquired. - - Dicoms were converted to NIfTI-1 format. This section was (in part) generated - automatically using pybids (0.5).""" - -.. _generating_subreports: - -Generating reports on subsets of the data -------------------------------------------- - -The ``generate`` method allows for keyword restrictions, just like -:obj:`bids.BIDSLayout`'s ``get`` method. For example, to -generate a report only for ``nback`` task data in session ``01``:: - - >>> counter = report.generate(session='01', task='nback') - >>> main_report = counter.most_common()[0][0] - >>> print(main_report) - r""" - For session 01: - MR data were acquired using a UNKNOWN-Tesla MANUFACTURER MODEL MRI scanner. - Ten runs of N-Back fMRI data were collected (64 slices; repetition time, - TR=2500ms; echo time, TE=UNKNOWNms; flip angle, FA=UNKNOWN; field of - view, FOV=128x128mm; matrix size=64x64; voxel size=2x2x2mm). Each run was - 2:40 minutes in length, during which 64 functional volumes were acquired. - - Dicoms were converted to NIfTI-1 format. This section was (in part) - generated automatically using pybids (0.5).""" - - -.. note:: - - For a more detailed set of examples, please refer to the Tutorial: :doc:`/examples/reports_tutorial`. \ No newline at end of file diff --git a/doc/user_guide.rst b/doc/user_guide.rst index 5b2df8a35..8ec342fb5 100644 --- a/doc/user_guide.rst +++ b/doc/user_guide.rst @@ -15,4 +15,3 @@ User guide introduction.rst layout/index.rst analysis/index.rst - reports/index.rst \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index e9ce6e00e..86c0f4df5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,7 +29,6 @@ dependencies = [ "formulaic >=0.2.4, <0.6", # Tested on 0.2.4-0.5.2 "sqlalchemy <1.4.0.dev0", "bids-validator", - "num2words", "click >=8.0", ] dynamic = ["version"]