Skip to content

pvs_quantification

Current SHiVAi pipeline (2025-06) seems to have problem handling html/pdf reports (for example, when facing NaN value). A workaround is to modify the code in post.py to simply skip the report generation step, which is not critical for the pipeline to run.

class SummaryReport(BaseInterface):
    """Make a summary report of preprocessing and prediction"""
    input_spec = SummaryReportInputSpec
    output_spec = SummaryReportOutputSpec

    # def _run_interface(self, runtime):
    #     """
    #     Build the report for the whole workflow. It contains segmentation statistics and
    #     quality control figures.

    #     """
    #     if self.inputs.anonymized:
    #         subject_id = ''
    #     else:
    #         subject_id = self.inputs.subject_id

    #     brain_vol_vox = nib.load(self.inputs.brainmask).get_fdata().astype(bool).sum()  # in voxels
    #     pred_metrics_dict = {}  # Will contain the stats dataframe for each biomarker
    #     pred_census_im_dict = {}  # Will contain the path to the swarm plot for each biomarker
    #     pred_overlay_im_dict = {}  # Will contain the path to the figure with biomarkers overlaid on the brain
    #     models_uid = {}  # Will contain the md5 hash for each file of each predictive model
    #     pred_and_acq = self.inputs.pred_and_acq

    #     # Generate the distribution figures for each prediction and fill models_uid
    #     for pred in pred_and_acq:
    #         lpred = pred.lower()  # "pred" is uppercase, so we also need a lowercase version
    #         if pred == 'LAC':
    #             name_in_plot = 'Lacuna'
    #         else:
    #             name_in_plot = pred
    #         models_uid[pred] = {}
    #         pred_metrics_dict[pred] = pd.read_csv(getattr(self.inputs, f'{lpred}_metrics_csv'))
    #         if pred_metrics_dict[pred]['Number of clusters'].sum() == 0:  # No biomarker detected
    #             pred_census_im_dict[pred] = None
    #         else:
    #             pred_census_im_dict[pred] = violinplot_from_census(getattr(self.inputs, f'{lpred}_census_csv'),
    #                                                                self.inputs.resolution,
    #                                                                name_in_plot)
    #         pred_overlay_im_dict[pred] = getattr(self.inputs, f'{lpred}_overlay')
    #         ids, url = get_md5_from_json(getattr(self.inputs, f'{lpred}_model_descriptor'), get_url=True)
    #         models_uid[pred]['id'] = ids
    #         if url:
    #             models_uid[pred]['url'] = url

    #     # set optional inputs to None if undefined
    #     if isdefined(self.inputs.overlayed_brainmask_1):
    #         overlayed_brainmask_1 = self.inputs.overlayed_brainmask_1
    #     else:
    #         overlayed_brainmask_1 = None
    #     if isdefined(self.inputs.crop_brain_img):
    #         crop_brain_img = self.inputs.crop_brain_img
    #     else:
    #         crop_brain_img = None
    #     if isdefined(self.inputs.isocontour_slides_FLAIR_T1):
    #         isocontour_slides_FLAIR_T1 = self.inputs.isocontour_slides_FLAIR_T1
    #     else:
    #         isocontour_slides_FLAIR_T1 = None
    #     if isdefined(self.inputs.overlayed_brainmask_2):
    #         overlayed_brainmask_2 = self.inputs.overlayed_brainmask_2
    #     else:
    #         overlayed_brainmask_2 = None
    #     if isdefined(self.inputs.wf_graph):
    #         wf_graph = self.inputs.wf_graph
    #     else:
    #         wf_graph = None
    #     if isdefined(self.inputs.db):
    #         db = self.inputs.db
    #     else:
    #         db = ''
    #     # process
    #     html_report = make_report(
    #         pred_metrics_dict=pred_metrics_dict,
    #         pred_census_im_dict=pred_census_im_dict,
    #         pred_overlay_im_dict=pred_overlay_im_dict,
    #         pred_and_acq=pred_and_acq,
    #         brain_vol_vox=brain_vol_vox,
    #         thr_cluster_vals=self.inputs.thr_cluster_vals,
    #         min_seg_size=self.inputs.min_seg_size,
    #         models_uid=models_uid,
    #         bounding_crop=crop_brain_img,
    #         overlayed_brainmask_1=overlayed_brainmask_1,
    #         overlayed_brainmask_2=overlayed_brainmask_2,
    #         isocontour_slides_FLAIR_T1=isocontour_slides_FLAIR_T1,
    #         subject_id=subject_id,
    #         image_size=self.inputs.image_size,
    #         resolution=self.inputs.resolution,
    #         percentile=self.inputs.percentile,
    #         threshold=self.inputs.threshold,
    #         wf_graph=wf_graph
    #     )

    #     with open('Shiva_report.html', 'w', encoding='utf-8') as fid:
    #         fid.write(html_report)

    #     # Convert the HTML file to PDF using CSS
    #     # Creating custom CSSin addition to the main one for the pages header and the logos
    #     postproc_dir = os.path.dirname(postproc_init)
    #     css = CSS(os.path.join(postproc_dir, 'printed_styling.css'))
    #     now = datetime.now(timezone.utc).strftime("%Y/%m/%d - %H:%M (UTC)")
    #     content_sub_id = f'Patient ID: {subject_id} \A ' if subject_id else ''
    #     header = (
    #         '@page {'
    #         '   @top-left {'
    #         f'      content: "{content_sub_id}Data-base: {db}";'
    #         '       font-size: 10pt;'
    #         '       white-space: pre;'
    #         '   }'
    #         '   @top-center {'
    #         f'      content: "{now}";'
    #         '       font-size: 10pt;'
    #         '   }'
    #         '}'
    #     )
    #     css_header = CSS(string=header)
    #     shiva_logo_file = os.path.join(postproc_dir, 'logo_shiva.png')
    #     other_logos_file = os.path.join(postproc_dir, 'logos_for_shiva.png')
    #     with open(shiva_logo_file, 'rb') as f:
    #         image_data = f.read()
    #         shiva_logo = base64.b64encode(image_data).decode()
    #     with open(other_logos_file, 'rb') as f:
    #         image_data = f.read()
    #         other_logo = base64.b64encode(image_data).decode()
    #     logo = (
    #         '@page {'
    #         '   @bottom-left {'
    #         f'      background-image: url(data:image/png;base64,{other_logo});'
    #         '       background-size: 552px 45px;'
    #         '       display: inline-block;'
    #         '       width: 560px; '
    #         '       height: 60px;'
    #         '       content:"";'
    #         '       background-repeat: no-repeat;'
    #         '   }'
    #         '   @top-right-corner {'
    #         f'      background-image: url(data:image/png;base64,{shiva_logo});'
    #         '       background-size: 70px 70px;'
    #         '       display: inline-block;'
    #         '       width: 70px; '
    #         '       height: 70px;'
    #         '       content:"";'
    #         '       background-repeat: no-repeat;'
    #         '   }'
    #         '}'
    #     )
    #     logo_css = CSS(string=logo)
    #     HTML('Shiva_report.html').write_pdf('Shiva_report.pdf',
    #                                         stylesheets=[css, css_header, logo_css])

    #     setattr(self, 'html_report', os.path.abspath('Shiva_report.html'))
    #     setattr(self, 'pdf_report', os.path.abspath('Shiva_report.pdf'))
    #     return runtime

    def _run_interface(self, runtime):
        """Skip actual report generation but create placeholder files."""
        print("SummaryReport disabled: generating empty HTML and PDF files to avoid workflow errors.")

        # Create empty HTML file
        html_path = os.path.abspath('Shiva_report.html')
        with open(html_path, 'w', encoding='utf-8') as f:
            f.write('<html><body><p>Summary report generation skipped.</p></body></html>')

        # Create empty (but valid) PDF file header to avoid downstream rendering issues
        pdf_path = os.path.abspath('Shiva_report.pdf')
        with open(pdf_path, 'wb') as f:
            f.write(b'%PDF-1.4\n%EOF\n')  # minimal valid PDF structure

        setattr(self, 'html_report', html_path)
        setattr(self, 'pdf_report', pdf_path)
        return runtime


    def _list_outputs(self):
        """Fill in the output structure."""
        outputs = self.output_spec().trait_get()
        outputs['html_report'] = getattr(self, 'html_report')
        outputs['pdf_report'] = getattr(self, 'pdf_report')

        return outputs