diff --git a/src/fmripost_aroma/interfaces/reportlets.py b/src/fmripost_aroma/interfaces/reportlets.py index 4a01917..728c85f 100644 --- a/src/fmripost_aroma/interfaces/reportlets.py +++ b/src/fmripost_aroma/interfaces/reportlets.py @@ -96,87 +96,74 @@ def _generate_segment(self): raise NotImplementedError -class _MELODICInputSpecRPT(nrb._SVGReportCapableInputSpec, fsl.model.MELODICInputSpec): - out_report = File( - 'melodic_reportlet.svg', - usedefault=True, - desc='Filename for the visual report generated by Nipype.', - ) - report_mask = File( - desc=( - 'Mask used to draw the outline on the reportlet. ' - 'If not set the mask will be derived from the data.' - ), +class SubjectSummaryInputSpec(BaseInterfaceInputSpec): + subject_id = Str(desc='Subject ID') + bold = InputMultiObject( + traits.Either(File(exists=True), traits.List(File(exists=True))), + desc='BOLD functional series', ) + std_spaces = traits.List(Str, desc='list of standard spaces') + nstd_spaces = traits.List(Str, desc='list of non-standard spaces') -class _MELODICOutputSpecRPT( - reporting.ReportCapableOutputSpec, - fsl.model.MELODICOutputSpec, -): - pass - - -class MELODICRPT(fsl.MELODIC): - """Create a reportlet for MELODIC outputs.""" - - input_spec = _MELODICInputSpecRPT - output_spec = _MELODICOutputSpecRPT - _out_report = None - - def __init__(self, generate_report=False, **kwargs): - """Create the reportlet.""" - super().__init__(**kwargs) - self.generate_report = generate_report - - def _post_run_hook(self, runtime): - # Run _post_run_hook of super class - runtime = super()._post_run_hook(runtime) - # leave early if there's nothing to do - if not self.generate_report: - return runtime - - NIWORKFLOWS_LOG.info('Generating report for MELODIC.') - _melodic_dir = runtime.cwd - if isdefined(self.inputs.out_dir): - _melodic_dir = self.inputs.out_dir - self._melodic_dir = os.path.abspath(_melodic_dir) - - self._out_report = self.inputs.out_report - if not os.path.isabs(self._out_report): - self._out_report = os.path.abspath(os.path.join(runtime.cwd, self._out_report)) - - mix = os.path.join(self._melodic_dir, 'melodic_mix') - if not os.path.exists(mix): - NIWORKFLOWS_LOG.warning("MELODIC outputs not found, assuming it didn't converge.") - self._out_report = self._out_report.replace('.svg', '.html') - snippet = '

MELODIC did not converge, no output

' - with open(self._out_report, 'w') as fobj: - fobj.write(snippet) - return runtime - - self._generate_report() - return runtime +class SubjectSummary(SummaryInterface): + input_spec = SubjectSummaryInputSpec + output_spec = SummaryOutputSpec - def _list_outputs(self): - try: - outputs = super()._list_outputs() - except NotImplementedError: - outputs = {} - if self._out_report is not None: - outputs['out_report'] = self._out_report - return outputs + def _generate_segment(self): + BIDS_NAME = re.compile( + r'^(.*\/)?' + '(?Psub-[a-zA-Z0-9]+)' + '(_(?Pses-[a-zA-Z0-9]+))?' + '(_(?Ptask-[a-zA-Z0-9]+))?' + '(_(?Pacq-[a-zA-Z0-9]+))?' + '(_(?Prec-[a-zA-Z0-9]+))?' + '(_(?Prun-[a-zA-Z0-9]+))?' + ) - def _generate_report(self): - from niworkflows.viz.utils import plot_melodic_components + # Add list of tasks with number of runs + bold_series = self.inputs.bold if isdefined(self.inputs.bold) else [] + bold_series = [s[0] if isinstance(s, list) else s for s in bold_series] - plot_melodic_components( - melodic_dir=self._melodic_dir, - in_file=self.inputs.in_files[0], - tr=self.inputs.tr_sec, - out_file=self._out_report, - compress=self.inputs.compress_report, - report_mask=self.inputs.report_mask, + counts = Counter( + BIDS_NAME.search(series).groupdict()['task_id'][5:] for series in bold_series + ) + + tasks = '' + if counts: + header = '\t\t
    ' + footer = '\t\t
' + lines = [ + '\t\t\t
  • Task: {task_id} ({n_runs:d} run{s})
  • '.format( + task_id=task_id, n_runs=n_runs, s='' if n_runs == 1 else 's' + ) + for task_id, n_runs in sorted(counts.items()) + ] + tasks = '\n'.join([header] + lines + [footer]) + + return SUBJECT_TEMPLATE.format( + subject_id=self.inputs.subject_id, + n_bold=len(bold_series), + tasks=tasks, + std_spaces=', '.join(self.inputs.std_spaces), + nstd_spaces=', '.join(self.inputs.nstd_spaces), + ) + + +class AboutSummaryInputSpec(BaseInterfaceInputSpec): + version = Str(desc='FMRIPREP version') + command = Str(desc='FMRIPREP command') + # Date not included - update timestamp only if version or command changes + + +class AboutSummary(SummaryInterface): + input_spec = AboutSummaryInputSpec + + def _generate_segment(self): + return ABOUT_TEMPLATE.format( + version=self.inputs.version, + command=self.inputs.command, + date=time.strftime('%Y-%m-%d %H:%M:%S %z'), ) @@ -243,74 +230,3 @@ def _run_interface(self, runtime): ) self._results['out_report'] = out_file return runtime - - -class SubjectSummaryInputSpec(BaseInterfaceInputSpec): - subject_id = Str(desc='Subject ID') - bold = InputMultiObject( - traits.Either(File(exists=True), traits.List(File(exists=True))), - desc='BOLD functional series', - ) - std_spaces = traits.List(Str, desc='list of standard spaces') - nstd_spaces = traits.List(Str, desc='list of non-standard spaces') - - -class SubjectSummary(SummaryInterface): - input_spec = SubjectSummaryInputSpec - output_spec = SummaryOutputSpec - - def _generate_segment(self): - BIDS_NAME = re.compile( - r'^(.*\/)?' - '(?Psub-[a-zA-Z0-9]+)' - '(_(?Pses-[a-zA-Z0-9]+))?' - '(_(?Ptask-[a-zA-Z0-9]+))?' - '(_(?Pacq-[a-zA-Z0-9]+))?' - '(_(?Prec-[a-zA-Z0-9]+))?' - '(_(?Prun-[a-zA-Z0-9]+))?' - ) - - # Add list of tasks with number of runs - bold_series = self.inputs.bold if isdefined(self.inputs.bold) else [] - bold_series = [s[0] if isinstance(s, list) else s for s in bold_series] - - counts = Counter( - BIDS_NAME.search(series).groupdict()['task_id'][5:] for series in bold_series - ) - - tasks = '' - if counts: - header = '\t\t
      ' - footer = '\t\t
    ' - lines = [ - '\t\t\t
  • Task: {task_id} ({n_runs:d} run{s})
  • '.format( - task_id=task_id, n_runs=n_runs, s='' if n_runs == 1 else 's' - ) - for task_id, n_runs in sorted(counts.items()) - ] - tasks = '\n'.join([header] + lines + [footer]) - - return SUBJECT_TEMPLATE.format( - subject_id=self.inputs.subject_id, - n_bold=len(bold_series), - tasks=tasks, - std_spaces=', '.join(self.inputs.std_spaces), - nstd_spaces=', '.join(self.inputs.nstd_spaces), - ) - - -class AboutSummaryInputSpec(BaseInterfaceInputSpec): - version = Str(desc='FMRIPREP version') - command = Str(desc='FMRIPREP command') - # Date not included - update timestamp only if version or command changes - - -class AboutSummary(SummaryInterface): - input_spec = AboutSummaryInputSpec - - def _generate_segment(self): - return ABOUT_TEMPLATE.format( - version=self.inputs.version, - command=self.inputs.command, - date=time.strftime('%Y-%m-%d %H:%M:%S %z'), - ) diff --git a/src/fmripost_aroma/workflows/aroma.py b/src/fmripost_aroma/workflows/aroma.py index 360493b..6fb6d09 100644 --- a/src/fmripost_aroma/workflows/aroma.py +++ b/src/fmripost_aroma/workflows/aroma.py @@ -109,7 +109,7 @@ def init_ica_aroma_wf( from fmripost_aroma.interfaces.confounds import ICAConfounds from fmripost_aroma.interfaces.nilearn import MeanImage, MedianValue - from fmripost_aroma.interfaces.reportlets import ICAAROMARPT, MELODICRPT + from fmripost_aroma.interfaces.reportlets import ICAAROMARPT workflow = Workflow(name=_get_wf_name(bold_file, 'aroma')) workflow.__postdesc__ = f"""\ @@ -200,7 +200,7 @@ def init_ica_aroma_wf( # ICA with MELODIC melodic = pe.Node( - MELODICRPT( + fsl.MELODIC( no_bet=True, tr_sec=float(metadata['RepetitionTime']), mm_thresh=0.5, @@ -214,20 +214,6 @@ def init_ica_aroma_wf( (smooth, melodic, [('smoothed_file', 'in_files')]), ]) # fmt:skip - ds_report_melodic = pe.Node( - DerivativesDataSink( - base_directory=config.execution.fmripost_aroma_dir, - source_file=bold_file, - datatype='figures', - desc='melodic', - dismiss_entities=('echo', 'den', 'res'), - ), - name='ds_report_melodic', - run_without_submitting=True, - mem_gb=config.DEFAULT_MEMORY_MIN_GB, - ) - workflow.connect([(melodic, ds_report_melodic, [('out_report', 'in_file')])]) - select_melodic_files = pe.Node( niu.Function( function=_select_melodic_files,