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(), )
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())
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())
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(), )
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, )
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, )
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')