Ejemplo n.º 1
0
def generate_qc(fn_in, fn_labeled, args, path_qc):
    """
    Generate a quick visualization of vertebral labeling
    """
    import spinalcordtoolbox.reports.qc as qc
    import spinalcordtoolbox.reports.slice as qcslice

    def label_vertebrae(self, mask):
        """
        Draw vertebrae areas, then add text showing the vertebrae names.
        """

        import matplotlib.pyplot as plt
        import scipy.ndimage

        self.listed_seg(mask)

        ax = plt.gca()
        a = [0.0]
        data = mask
        for index, val in np.ndenumerate(data):
            if val not in a:
                a.append(val)
                index = int(val)
                if index in self._labels_regions.values():
                    color = self._labels_color[index]
                    y, x = scipy.ndimage.measurements.center_of_mass(
                        np.where(data == val, data, 0))

                    # Draw text with a shadow

                    x += 10

                    label = list(self._labels_regions.keys())[list(
                        self._labels_regions.values()).index(index)]
                    ax.text(x, y, label, color='black', clip_on=True)

                    x -= 0.5
                    y -= 0.5

                    ax.text(x, y, label, color=color, clip_on=True)

    qc.add_entry(
        src=fn_in,
        process='sct_label_vertebrae',
        args=args,
        path_qc=path_qc,
        plane='Sagittal',
        dpi=100,
        qcslice=qcslice.Sagittal([Image(fn_in),
                                  Image(fn_labeled)]),
        qcslice_operations=[label_vertebrae],
        qcslice_layout=lambda x: x.single(),
    )
Ejemplo n.º 2
0
def test_label_vertebrae(t2_image, t2_seg_image, tmp_path):
    param = qc.Params(t2_image.absolutepath, 'sct_label_vertebrae', ['-a', '-b'], 'Sagittal', str(tmp_path))
    report = qc.QcReport(param, 'Test label vertebrae')

    @qc.QcImage(report, 'spline36', [qc.QcImage.label_vertebrae, ], process=param.command)
    def test(qslice):
        return qslice.single()

    test(qcslice.Sagittal([t2_image, t2_seg_image]))
    assert os.path.isfile(param.abs_bkg_img_path())
    assert os.path.isfile(param.abs_overlay_img_path())
Ejemplo n.º 3
0
def test_label_vertebrae(t2_image, t2_seg_image):
    param = qc.Params(t2_image, 'sct_label_vertebrae', ['-a', '-b'],
                      'Sagittal', '/tmp')
    report = qc.QcReport(param, 'Test label vertebrae')

    @qc.QcImage(report, 'spline36', [
        qc.QcImage.label_vertebrae,
    ])
    def test(qslice):
        return qslice.single()

    test(qcslice.Sagittal(t2_image, t2_seg_image))
    assert os.path.isfile(param.abs_bkg_img_path())
    assert os.path.isfile(param.abs_overlay_img_path())
Ejemplo n.º 4
0
def generate_qc(fname_in, fname_out, args, path_qc):
    """
    Generate a QC entry allowing to quickly review the PMJ position
    """

    import spinalcordtoolbox.reports.qc as qc
    import spinalcordtoolbox.reports.slice as qcslice

    def highlight_pmj(self, mask):
        """
        Hook to show a rectangle where PMJ is on the slice
        """

        import matplotlib.pyplot as plt
        import matplotlib.patches as patches

        y, x = np.where(mask == 50)

        ax = plt.gca()
        img = np.full_like(mask, np.nan)
        ax.imshow(img, cmap='gray', alpha=0)

        rect = patches.Rectangle((x - 10, y - 10),
                                 20,
                                 20,
                                 linewidth=2,
                                 edgecolor='lime',
                                 facecolor='none')

        ax.add_patch(rect)
        ax.get_xaxis().set_visible(False)
        ax.get_yaxis().set_visible(False)

    qc.add_entry(
        src=fname_in,
        process="sct_detect_pmj",
        args=args,
        path_qc=path_qc,
        plane="Sagittal",
        qcslice=qcslice.Sagittal([Image(fname_in),
                                  Image(fname_out)]),
        qcslice_operations=[highlight_pmj],
        qcslice_layout=lambda x: x.single(),
    )
Ejemplo n.º 5
0
def generate_qc(fn_input, fn_centerline, fn_output, args, path_qc):
    """
    Generate a QC entry allowing to quickly review the straightening process.
    """

    import spinalcordtoolbox.reports.qc as qc
    import spinalcordtoolbox.reports.slice as qcslice

    # Just display the straightened spinal cord
    img_out = Image(fn_output)
    foreground = qcslice.Sagittal([img_out]).single()[0]

    qc.add_entry(
     src=fn_input,
     process="sct_straighten_spinalcord",
     args=args,
     path_qc=path_qc,
     plane="Sagittal",
     foreground=foreground,
    )
Ejemplo n.º 6
0
def generate_qc(fname_in1, fname_in2=None, fname_seg=None, angle_line=None, args=None, path_qc=None,
                dataset=None, subject=None, path_img=None, process=None, fps=None):
    """
    Generate a QC entry allowing to quickly review results. This function is the entry point and is called by SCT
    scripts (e.g. sct_propseg).

    :param fname_in1: str: File name of input image #1 (mandatory)
    :param fname_in2: str: File name of input image #2
    :param fname_seg: str: File name of input segmentation
    :param angle_line: list: Angle [in rad, wrt. vertical line, must be between -pi and pi] to apply to the line overlaid on the image, for\
    each slice, for slice that don't have an angle to display, a nan is expected. To be used for assessing cord orientation.
    :param args: args from parent function
    :param path_qc: str: Path to save QC report
    :param dataset: str: Dataset name
    :param subject: str: Subject name
    :param path_img: dict: Path to image to display (e.g., a graph), instead of computing the image from MRI.
    :param process: str: Name of SCT function. e.g., sct_propseg
    :param fps: float: Number of frames per second for output gif images. Used only for sct_frmi_moco and sct_dmri_moco.
    :return: None
    """
    logger.info('\n*** Generate Quality Control (QC) html report ***')
    dpi = 300
    plane = None
    qcslice_type = None
    qcslice_operations = None
    qcslice_layout = None

    # Get QC specifics based on SCT process
    # Axial orientation, switch between two input images
    if process in ['sct_register_multimodal', 'sct_register_to_template']:
        plane = 'Axial'
        qcslice_type = qcslice.Axial([Image(fname_in1), Image(fname_in2), Image(fname_seg)])
        qcslice_operations = [QcImage.no_seg_seg]
        def qcslice_layout(x): return x.mosaic()[:2]
    # Rotation visualisation
    elif process in ['rotation']:
        plane = 'Axial'
        qcslice_type = qcslice.Axial([Image(fname_in1), Image(fname_seg)])
        qcslice_operations = [QcImage.line_angle]
        def qcslice_layout(x): return x.mosaic(return_center=True)
    # Axial orientation, switch between the image and the segmentation
    elif process in ['sct_propseg', 'sct_deepseg_sc', 'sct_deepseg_gm']:
        plane = 'Axial'
        qcslice_type = qcslice.Axial([Image(fname_in1), Image(fname_seg)])
        qcslice_operations = [QcImage.listed_seg]
        def qcslice_layout(x): return x.mosaic()
    # Axial orientation, switch between the image and the centerline
    elif process in ['sct_get_centerline']:
        plane = 'Axial'
        qcslice_type = qcslice.Axial([Image(fname_in1), Image(fname_seg)])
        qcslice_operations = [QcImage.label_centerline]
        def qcslice_layout(x): return x.mosaic()
    # Axial orientation, switch between the image and the white matter segmentation (linear interp, in blue)
    elif process in ['sct_warp_template']:
        plane = 'Axial'
        qcslice_type = qcslice.Axial([Image(fname_in1), Image(fname_seg)])
        qcslice_operations = [QcImage.template]
        def qcslice_layout(x): return x.mosaic()
    # Axial orientation, switch between gif image (before and after motion correction) and grid overlay
    elif process in ['sct_dmri_moco', 'sct_fmri_moco']:
        plane = 'Axial'
        if fname_seg is None:
            raise Exception("Segmentation is needed to ensure proper cropping around spinal cord.")
        qcslice_type = qcslice.Axial([Image(fname_in1), Image(fname_in2), Image(fname_seg)])
        qcslice_operations = [QcImage.grid]
        def qcslice_layout(x): return x.mosaics_through_time()
    # Sagittal orientation, display vertebral labels
    elif process in ['sct_label_vertebrae']:
        plane = 'Sagittal'
        dpi = 100  # bigger picture is needed for this special case, hence reduce dpi
        qcslice_type = qcslice.Sagittal([Image(fname_in1), Image(fname_seg)], p_resample=None)
        qcslice_operations = [QcImage.label_vertebrae]
        def qcslice_layout(x): return x.single()
    #  Sagittal orientation, display posterior labels
    elif process in ['sct_label_utils']:
        plane = 'Sagittal'
        dpi = 100  # bigger picture is needed for this special case, hence reduce dpi
        # projected_image = projected(Image(fname_seg))
        qcslice_type = qcslice.Sagittal([Image(fname_in1), Image(fname_seg)], p_resample=None)
        qcslice_operations = [QcImage.label_utils]
        def qcslice_layout(x): return x.single()
    # Sagittal orientation, display PMJ box
    elif process in ['sct_detect_pmj']:
        plane = 'Sagittal'
        qcslice_type = qcslice.Sagittal([Image(fname_in1), Image(fname_seg)], p_resample=None)
        qcslice_operations = [QcImage.highlight_pmj]
        def qcslice_layout(x): return x.single()
    # Sagittal orientation, static image
    elif process in ['sct_straighten_spinalcord']:
        plane = 'Sagittal'
        dpi = 100
        qcslice_type = qcslice.Sagittal([Image(fname_in1), Image(fname_in1)], p_resample=None)
        qcslice_operations = [QcImage.vertical_line]
        def qcslice_layout(x): return x.single()
    # Metric outputs (only graphs)
    elif process in ['sct_process_segmentation']:
        assert os.path.isfile(path_img)
    else:
        raise ValueError("Unrecognized process: {}".format(process))

    add_entry(
        src=fname_in1,
        process=process,
        args=args,
        path_qc=path_qc,
        dataset=dataset,
        subject=subject,
        plane=plane,
        path_img=path_img,
        dpi=dpi,
        qcslice=qcslice_type,
        qcslice_operations=qcslice_operations,
        qcslice_layout=qcslice_layout,
        stretch_contrast_method='equalized',
        angle_line=angle_line,
        fps=fps,
    )
Ejemplo n.º 7
0
def generate_qc(fname_in1,
                fname_in2=None,
                fname_seg=None,
                args=None,
                path_qc=None,
                dataset=None,
                subject=None,
                process=None):
    """
    Generate a QC entry allowing to quickly review results. This function is called by SCT scripts (e.g. sct_propseg).

    :param fname_in1: str: File name of input image #1 (mandatory)
    :param fname_in2: str: File name of input image #2
    :param fname_seg: str: File name of input segmentation
    :param args: args from parent function
    :param path_qc: str: Path to save QC report
    :param dataset: str: Dataset name
    :param subject: str: Subject name
    :param process: str: Name of SCT function. e.g., sct_propseg
    :return: None
    """
    dpi = 300
    # Get QC specifics based on SCT process
    # Axial orientation, switch between two input images
    if process in ['sct_register_multimodal', 'sct_register_to_template']:
        plane = 'Axial'
        qcslice_type = qcslice.Axial(
            [Image(fname_in1),
             Image(fname_in2),
             Image(fname_seg)])
        qcslice_operations = [QcImage.no_seg_seg]
        qcslice_layout = lambda x: x.mosaic()[:2]
    # Axial orientation, switch between the image and the segmentation
    elif process in ['sct_propseg', 'sct_deepseg_sc', 'sct_deepseg_gm']:
        plane = 'Axial'
        qcslice_type = qcslice.Axial([Image(fname_in1), Image(fname_seg)])
        qcslice_operations = [QcImage.listed_seg]
        qcslice_layout = lambda x: x.mosaic()
    # Axial orientation, switch between the image and the white matter segmentation (linear interp, in blue)
    elif process in ['sct_warp_template']:
        plane = 'Axial'
        qcslice_type = qcslice.Axial([Image(fname_in1), Image(fname_seg)])
        qcslice_operations = [QcImage.template]
        qcslice_layout = lambda x: x.mosaic()
    # Sagittal orientation, display vertebral labels
    elif process in ['sct_label_vertebrae']:
        plane = 'Sagittal'
        dpi = 100  # bigger picture is needed for this special case, hence reduce dpi
        qcslice_type = qcslice.Sagittal(
            [Image(fname_in1), Image(fname_seg)], p_resample=None)
        qcslice_operations = [QcImage.label_vertebrae]
        qcslice_layout = lambda x: x.single()
    # Sagittal orientation, display PMJ box
    elif process in ['sct_detect_pmj']:
        plane = 'Sagittal'
        qcslice_type = qcslice.Sagittal(
            [Image(fname_in1), Image(fname_seg)], p_resample=None)
        qcslice_operations = [QcImage.highlight_pmj]
        qcslice_layout = lambda x: x.single()
    else:
        raise ValueError("Unrecognized process: {}".format(process))

    add_entry(
        src=fname_in1,
        process=process,
        args=args,
        path_qc=path_qc,
        dataset=dataset,
        subject=subject,
        plane=plane,
        dpi=dpi,
        qcslice=qcslice_type,
        qcslice_operations=qcslice_operations,
        qcslice_layout=qcslice_layout,
        stretch_contrast_method='equalized',
    )
def main(args=None):

    # initializations
    initz = ''
    initcenter = ''
    initc2 = 'auto'
    param = Param()

    # check user arguments
    if not args:
        args = sys.argv[1:]

    # Get parser info
    parser = get_parser()
    arguments = parser.parse(args)
    fname_in = arguments["-i"]
    fname_seg = arguments['-s']
    contrast = arguments['-c']
    path_template = sct.slash_at_the_end(arguments['-t'], 1)
    # if '-o' in arguments:
    #     file_out = arguments["-o"]
    # else:
    #     file_out = ''
    if '-ofolder' in arguments:
        path_output = sct.slash_at_the_end(os.path.abspath(
            arguments['-ofolder']),
                                           slash=1)
    else:
        path_output = sct.slash_at_the_end(os.path.abspath(os.curdir), slash=1)
    if '-initz' in arguments:
        initz = arguments['-initz']
    if '-initcenter' in arguments:
        initcenter = arguments['-initcenter']
    # if user provided text file, parse and overwrite arguments
    if '-initfile' in arguments:
        # open file
        file = open(arguments['-initfile'], 'r')
        initfile = ' ' + file.read().replace('\n', '')
        arg_initfile = initfile.split(' ')
        for i in xrange(len(arg_initfile)):
            if arg_initfile[i] == '-initz':
                initz = [int(x) for x in arg_initfile[i + 1].split(',')]
            if arg_initfile[i] == '-initcenter':
                initcenter = int(arg_initfile[i + 1])
    if '-initc2' in arguments:
        initc2 = 'manual'
    if '-param' in arguments:
        param.update(arguments['-param'][0])
    verbose = int(arguments['-v'])
    remove_tmp_files = int(arguments['-r'])
    denoise = int(arguments['-denoise'])
    laplacian = int(arguments['-laplacian'])

    # create temporary folder
    sct.printv('\nCreate temporary folder...', verbose)
    path_tmp = sct.tmp_create(verbose=verbose)

    # Copying input data to tmp folder
    sct.printv('\nCopying input data to tmp folder...', verbose)
    sct.run('sct_convert -i ' + fname_in + ' -o ' + path_tmp + 'data.nii')
    sct.run('sct_convert -i ' + fname_seg + ' -o ' + path_tmp +
            'segmentation.nii.gz')

    # Go go temp folder
    os.chdir(path_tmp)

    # create label to identify disc
    sct.printv('\nCreate label to identify disc...', verbose)
    initauto = False
    if initz:
        create_label_z('segmentation.nii.gz', initz[0],
                       initz[1])  # create label located at z_center
    elif initcenter:
        # find z centered in FOV
        nii = Image('segmentation.nii.gz')
        nii.change_orientation('RPI')  # reorient to RPI
        nx, ny, nz, nt, px, py, pz, pt = nii.dim  # Get dimensions
        z_center = int(round(nz / 2))  # get z_center
        create_label_z('segmentation.nii.gz', z_center,
                       initcenter)  # create label located at z_center
    else:
        initauto = True
        # printv('\nERROR: You need to initialize the disc detection algorithm using one of these two options: -initz, -initcenter\n', 1, 'error')

    # Straighten spinal cord
    sct.printv('\nStraighten spinal cord...', verbose)
    # check if warp_curve2straight and warp_straight2curve already exist (i.e. no need to do it another time)
    if os.path.isfile('../warp_curve2straight.nii.gz') and os.path.isfile(
            '../warp_straight2curve.nii.gz') and os.path.isfile(
                '../straight_ref.nii.gz'):
        # if they exist, copy them into current folder
        sct.printv(
            'WARNING: Straightening was already run previously. Copying warping fields...',
            verbose, 'warning')
        shutil.copy('../warp_curve2straight.nii.gz',
                    'warp_curve2straight.nii.gz')
        shutil.copy('../warp_straight2curve.nii.gz',
                    'warp_straight2curve.nii.gz')
        shutil.copy('../straight_ref.nii.gz', 'straight_ref.nii.gz')
        # apply straightening
        sct.run(
            'sct_apply_transfo -i data.nii -w warp_curve2straight.nii.gz -d straight_ref.nii.gz -o data_straight.nii'
        )
    else:
        sct.run(
            'sct_straighten_spinalcord -i data.nii -s segmentation.nii.gz -r 0 -qc 0'
        )

    # resample to 0.5mm isotropic to match template resolution
    sct.printv('\nResample to 0.5mm isotropic...', verbose)
    sct.run(
        'sct_resample -i data_straight.nii -mm 0.5x0.5x0.5 -x linear -o data_straightr.nii',
        verbose)
    # sct.run('sct_resample -i segmentation.nii.gz -mm 0.5x0.5x0.5 -x linear -o segmentationr.nii.gz', verbose)
    # sct.run('sct_resample -i labelz.nii.gz -mm 0.5x0.5x0.5 -x linear -o labelzr.nii', verbose)

    # Apply straightening to segmentation
    # N.B. Output is RPI
    sct.printv('\nApply straightening to segmentation...', verbose)
    sct.run(
        'sct_apply_transfo -i segmentation.nii.gz -d data_straightr.nii -w warp_curve2straight.nii.gz -o segmentation_straight.nii.gz -x linear',
        verbose)
    # Threshold segmentation at 0.5
    sct.run(
        'sct_maths -i segmentation_straight.nii.gz -thr 0.5 -o segmentation_straight.nii.gz',
        verbose)

    if initauto:
        init_disc = []
    else:
        # Apply straightening to z-label
        sct.printv('\nDilate z-label and apply straightening...', verbose)
        sct.run(
            'sct_apply_transfo -i labelz.nii.gz -d data_straightr.nii -w warp_curve2straight.nii.gz -o labelz_straight.nii.gz -x nn',
            verbose)
        # get z value and disk value to initialize labeling
        sct.printv('\nGet z and disc values from straight label...', verbose)
        init_disc = get_z_and_disc_values_from_label('labelz_straight.nii.gz')
        sct.printv('.. ' + str(init_disc), verbose)

    # denoise data
    if denoise:
        sct.printv('\nDenoise data...', verbose)
        sct.run(
            'sct_maths -i data_straightr.nii -denoise h=0.05 -o data_straightr.nii',
            verbose)

    # apply laplacian filtering
    if laplacian:
        sct.printv('\nApply Laplacian filter...', verbose)
        sct.run(
            'sct_maths -i data_straightr.nii -laplacian 1 -o data_straightr.nii',
            verbose)

    # detect vertebral levels on straight spinal cord
    vertebral_detection('data_straightr.nii',
                        'segmentation_straight.nii.gz',
                        contrast,
                        param,
                        init_disc=init_disc,
                        verbose=verbose,
                        path_template=path_template,
                        initc2=initc2,
                        path_output=path_output)

    # un-straighten labeled spinal cord
    sct.printv('\nUn-straighten labeling...', verbose)
    sct.run(
        'sct_apply_transfo -i segmentation_straight_labeled.nii.gz -d segmentation.nii.gz -w warp_straight2curve.nii.gz -o segmentation_labeled.nii.gz -x nn',
        verbose)

    # Clean labeled segmentation
    sct.printv(
        '\nClean labeled segmentation (correct interpolation errors)...',
        verbose)
    clean_labeled_segmentation('segmentation_labeled.nii.gz',
                               'segmentation.nii.gz',
                               'segmentation_labeled.nii.gz')

    # label discs
    sct.printv('\nLabel discs...', verbose)
    label_discs('segmentation_labeled.nii.gz', verbose=verbose)

    # come back to parent folder
    os.chdir('..')

    # Generate output files
    path_seg, file_seg, ext_seg = sct.extract_fname(fname_seg)
    sct.printv('\nGenerate output files...', verbose)
    sct.generate_output_file(path_tmp + 'segmentation_labeled.nii.gz',
                             path_output + file_seg + '_labeled' + ext_seg)
    sct.generate_output_file(
        path_tmp + 'segmentation_labeled_disc.nii.gz',
        path_output + file_seg + '_labeled_discs' + ext_seg)
    # copy straightening files in case subsequent SCT functions need them
    sct.generate_output_file(path_tmp + 'warp_curve2straight.nii.gz',
                             path_output + 'warp_curve2straight.nii.gz',
                             verbose)
    sct.generate_output_file(path_tmp + 'warp_straight2curve.nii.gz',
                             path_output + 'warp_straight2curve.nii.gz',
                             verbose)
    sct.generate_output_file(path_tmp + 'straight_ref.nii.gz',
                             path_output + 'straight_ref.nii.gz', verbose)

    # Remove temporary files
    if remove_tmp_files == 1:
        sct.printv('\nRemove temporary files...', verbose)
        shutil.rmtree(path_tmp, ignore_errors=True)

    # Generate QC report
    try:
        if '-qc' in arguments and not arguments.get('-noqc', False):
            qc_path = arguments['-qc']

            import spinalcordtoolbox.reports.qc as qc
            import spinalcordtoolbox.reports.slice as qcslice

            qc_param = qc.Params(fname_in, 'sct_label_vertebrae', args,
                                 'Sagittal', qc_path)
            report = qc.QcReport(qc_param, '')

            @qc.QcImage(report, 'none', [
                qc.QcImage.label_vertebrae,
            ])
            def test(qslice):
                return qslice.single()

            labeled_seg_file = path_output + file_seg + '_labeled' + ext_seg
            test(qcslice.Sagittal(Image(fname_in), Image(labeled_seg_file)))
            sct.printv('Sucessfully generated the QC results in %s' %
                       qc_param.qc_results)
            sct.printv(
                'Use the following command to see the results in a browser:')
            sct.printv('sct_qc -folder %s' % qc_path, type='info')
    except Exception as err:
        sct.printv(err, verbose, 'warning')
        sct.printv('WARNING: Cannot generate report.', verbose, 'warning')

    # to view results
    sct.printv('\nDone! To view results, type:', verbose)
    sct.printv(
        'fslview ' + fname_in + ' ' + path_output + file_seg + '_labeled' +
        ' -l Random-Rainbow -t 0.5 &\n', verbose, 'info')