<commit_before>#!/usr/bin/env python
#########################################################################################
#
# Extract metrics within spinal labels as defined by the white matter atlas and the
# template
# The folder atlas should have a .txt file that lists all tract files with labels.
#
# ---------------------------------------------------------------------------------------
# Copyright (c) 2014 Polytechnique Montreal <www.neuro.polymtl.ca>
# Authors: <NAME>, <NAME>, <NAME>, <NAME>
# Modified: 2014-07-30
#
# About the license: see the file LICENSE.TXT
#########################################################################################

# Import common Python libraries
import os
import getopt
import sys
import time
import commands
import nibabel as nib
import numpy as np
import sct_utils as sct

# get path of the toolbox
status, path_sct = commands.getstatusoutput('echo $SCT_DIR')
# append path that contains scripts, to be able to load modules
sys.path.append(path_sct + '/scripts')


# constants
ALMOST_ZERO = 0.000001

class param:
    def __init__(self):
        self.debug = 0
        self.method = 'wa'
        self.path_label = path_sct+'/data/template'  # default is toolbox
        self.verbose = 1
        self.labels_of_interest = ''  # list. example: '1,3,4'. . For all labels, leave empty.
        self.vertebral_levels = ''
        self.slices_of_interest = ''  # 2-element list corresponding to zmin,zmax. example: '5:8'. For all slices, leave empty.
        self.average_all_labels = 0  # average all labels together after concatenation
        self.fname_output = 'quantif_metrics.txt'
        self.file_info_label = 'info_label.txt'
        self.vertebral_labeling_file = path_sct+'/data/template/MNI-Poly-AMU_level.nii.gz'



#=======================================================================================================================
# main
#=======================================================================================================================
def main():
    # Initialization to defaults parameters
    fname_data = '' # data is empty by default
    path_label = param.path_label
    method = param.method # extraction mode by default
    labels_of_interest = param.labels_of_interest
    slices_of_interest = param.slices_of_interest
    vertebral_levels = param.vertebral_levels
    average_all_labels = param.average_all_labels
    fname_output = param.fname_output
    file_info_label = param.file_info_label
    vertebral_labeling_path = param.vertebral_labeling_file
    start_time = time.time()  # save start time for duration
    verbose = param.verbose

    # Parameters for debug mode
    if param.debug:
        print '\n*** WARNING: DEBUG MODE ON ***\n'
        fname_data = path_sct+'/data/template/MNI-Poly-AMU_T2.nii.gz' #path_sct+'/testing/data/errsm_23/mt/mtr.nii.gz'
        path_label = path_sct+'/data/atlas' #path_sct+'/testing/data/errsm_23/label/atlas'
        method = 'wa'
        labels_of_interest = '0,1,2,3'  #'0, 2, 5, 7, 15, 22, 27, 29'
        slices_of_interest = '2:10' #'2:4'
        vertebral_levels = ''
        average_all_labels = 0
        fname_output = path_sct+'/testing/sct_extract_metric/results/quantif_mt_debug.txt'


    # Check input parameters
    try:
        opts, args = getopt.getopt(sys.argv[1:], 'haf:i:l:m:o:v:z:') # define flags
    except getopt.GetoptError as err: # check if the arguments are defined
        print str(err) # error
        usage() # display usage
    for opt, arg in opts: # explore flags
        if opt in '-a':
            average_all_labels = 1
        elif opt in '-f':
            path_label = os.path.abspath(arg)  # save path of labels folder
        elif opt == '-h': # help option
            usage() # display usage
        elif opt in '-i':
            fname_data = arg
        elif opt in '-l':
            labels_of_interest = arg
        elif opt in '-m': # method for metric extraction
            method = arg
        elif opt in '-o': # output option
            fname_output = arg  # fname of output file
        elif opt in '-v': # vertebral levels option, if the user wants to average the metric across specific vertebral levels
             vertebral_levels = arg
        elif opt in '-z': # slices numbers option
            slices_of_interest = arg # save labels numbers


    #TODO: check if the case where the input images are not in AIL orientation is taken into account (if not, implement it)

    # Display usage with tract parameters by default in case files aren't chosen in arguments inputs
    if fname_data == '' or path_label == '':
        usage()

    # Check existence of data file
    sct.printv('\nCheck data file existence...', verbose)
    sct.check_file_exist(fname_data)

    # add slash at the end
    path_label = sct.slash_at_the_end(path_label, 1)

    # Check existence of path_label
    if not os.path.isdir(path_label):
        print('\nERROR: ' + path_label + ' does not exist. Exit program.\n')
        sys.exit(2)
    else:
        print '\nOK: '+path_label

    # Check input parameters
    check_method(method)

    # Extract label info
    label_id, label_name, label_file = read_label_file(path_label)
    nb_labels_total = len(label_id)

    # check consistency of label input parameter.
    label_id_user = check_labels(labels_of_interest, nb_labels_total) # If 'labels_of_interest' is empty, then 'label_id_user' contains the index of all labels in the file info_label.txt

    # print parameters
    print '\nChecked parameters:'
    print '  data ................... '+fname_data
    print '  folder label ........... '+path_label
    print '  selected labels ........ '+str(label_id_user)
    print '  estimation method ...... '+method
    print '  vertebral levels ....... '+vertebral_levels
    print '  slices of interest ..... '+slices_of_interest

    # Load image
    sct.printv('\nLoad image...', verbose)
    data = nib.load(fname_data).get_data()
    sct.printv('  Done.', verbose)

    # Get dimensions of data
    sct.printv('\nGet dimensions of data...', verbose)
    nx, ny, nz = data.shape
    sct.printv('.. '+str(nx)+' x '+str(ny)+' x '+str(nz), verbose)

    # load label
    sct.printv('\nLoad labels...', verbose)
    labels = np.empty([nb_labels_total], dtype=object)  # labels(nb_labels_total, x, y, z)
    for i_label in range(0, nb_labels_total):
        labels[i_label] = nib.load(path_label+label_file[i_label]).get_data() # labels[i_label, :, :, :] = nib.load(path_label+label_file[label_id[i_label]]).get_data()
    sct.printv('  Done.', verbose)

    # Get dimensions of labels
    sct.printv('\nGet dimensions of label...', verbose)
    nx_atlas, ny_atlas, nz_atlas = labels[0].shape
    sct.printv('.. '+str(nx_atlas)+' x '+str(ny_atlas)+' x '+str(nz_atlas)+' x '+str(nb_labels_total), verbose)

    # Check dimensions consistency between atlas and data
    if (nx, ny, nz) != (nx_atlas, ny_atlas, nz_atlas):
        print '\nERROR: Metric data and labels DO NOT HAVE SAME DIMENSIONS.'
        sys.exit(2)

    # Update the flag "slices_of_interest" according to the vertebral levels selected by user (if it's the case)
    if vertebral_levels != '':
        if slices_of_interest != '':
            print '\nERROR: You cannot select BOTH vertebral levels AND slice numbers.'
            usage()
        else:
            if path_label.endswith('atlas/'):
                vertebral_labeling_path = path_label+'../template/MNI-Poly-AMU_level.nii.gz'
            slices_of_interest = get_slices_matching_with_vertebral_levels(data, vertebral_levels, vertebral_labeling_path)

    # select slice of interest by cropping data and labels
    if slices_of_interest != '':
        data = remove_slices(data, slices_of_interest)
        for i_label in range(0, nb_labels_total):
            labels[i_label] = remove_slices(labels[i_label], slices_of_interest)

    # if user wants to get unique value across labels, then combine all labels together
    if average_all_labels == 1:
        sum_labels_user = np.sum(labels[label_id_user]) # sum the labels selected by user
        if method == 'ml':  # in case the maximum likelihood and the average across different labels are wanted
            # TODO: make the below code more clean (no use of tmp variable)
            labels_tmp = np.empty([nb_labels_total - len(label_id_user) + 1], dtype=object)
            labels = np.delete(labels, label_id_user)  # remove the labels selected by user
            labels_tmp[0] = sum_labels_user
            for i_label in range(1, len(labels_tmp)):
                labels_tmp[i_label] = labels[i_label-1]
            labels = labels_tmp
            del labels_tmp
        else:
            labels = np.empty(1, dtype=object)
            labels[0] = sum_labels_user
        # TODO: instead of 0, make it clear for the user that all labels are concatenated
        label_id_user = [0]

    # extract metrics within labels
    # labels can be 3d or 4d
    metric_mean, metric_std = extract_metric_within_tract(data, labels, method)  # mean and std are lists.

    # update label name if average
    if average_all_labels == 1:
        # TODO: display concatenated label names
        label_name[0] = 'AVERAGED'

    #    if method == 'ml': # in case the maximum likelihood and average across selected labels are wanted
    #        metric_mean = metric_mean[0] # only output the value at the first position which corresponds to the averaged labels
    #        metric_std = metric_std [0] # idem
    #else:
    metric_mean = metric_mean[label_id_user]
    metric_std = metric_std[label_id_user]

    # display metrics
    print '\033[1m\nEstimation results:\n'
    for i in range(0, metric_mean.size):
        print '\033[1m'+str(label_id_user[i])+', '+str(label_name[label_id_user[i]])+':    '+str(metric_mean[i])+' +/- '+str(metric_std[i])+'\033[0m'

    # save and display metrics
    save_metrics(label_id_user, label_name, slices_of_interest, vertebral_levels, metric_mean, metric_std, fname_output)

    # Print elapsed time
    print 'Elapsed time : ' + str(int(round(time.time() - start_time))) + ' sec'

    # end of main.
    print



#=======================================================================================================================
# Read label.txt file which is located inside label folder
#=======================================================================================================================
def read_label_file(path_info_label):

    # file name of info_label.txt
    fname_label = path_info_label+param.file_info_label

    # Check info_label.txt existence
    sct.check_file_exist(fname_label)

    # Read file
    f = open(fname_label)

    # Extract all lines in file.txt
    lines = [lines for lines in f.readlines() if lines.strip()]

    # read each line
    label_id = []
    label_name = []
    label_file = []
    for i in range(0, len(lines)-1):
        line = lines[i].split(',')
        label_id.append(int(line[0]))
        label_name.append(line[1])
        label_file.append(line[2][:-1].strip()) #replace(" ", "").replace("\r", ""))
    # An error could occur at the last line (deletion of the last character of the .txt file), the 5 following code lines enable to avoid this error:
    line = lines[-1].split(',')
    label_id.append(int(line[0]))
    label_name.append(line[1])
    line[2]=line[2]+' '
    label_file.append(line[2].strip())


    # check if all files listed are present in folder. If not, WARNING.
    print '\nCheck if all files listed in '+param.file_info_label+' are indeed present in '+path_info_label+' ...'
    for fname in label_file:
        if os.path.isfile(path_info_label+fname) or os.path.isfile(path_info_label+fname + '.nii') or os.path.isfile(path_info_label+fname + '.nii.gz'):
            print('  OK: '+path_info_label+fname)
            pass
        else:
            print('  WARNING: ' + path_info_label+fname + ' does not exist but is listed in '+param.file_info_label+'.\n')


    # Close file.txt
    f.close()

    return [label_id, label_name, label_file]


#=======================================================================================================================
# get_slices_matching_with_vertebral_levels
#=======================================================================================================================
def get_slices_matching_with_vertebral_levels(metric_data, vertebral_levels,vertebral_labeling_path):
    """Return the slices of the input image corresponding to the vertebral levels given as argument."""

    # check existence of a vertebral labeling file
    sct.check_file_exist(vertebral_labeling_path)

    # Convert the selected vertebral levels chosen into a 2-element list [start_level end_level]
    vert_levels_list = [int(x) for x in vertebral_levels.split(':')]

    # If only one vertebral level was selected (n), consider as n:n
    if len(vert_levels_list) == 1:
        vert_levels_list = [vert_levels_list[0], vert_levels_list[0]]

    # Check if there are only two values [start_level, end_level] and if the end level is higher than the start level
    if (len(vert_levels_list) > 2) or (vert_levels_list[0] > vert_levels_list[1]):
        print '\nERROR:  "' + vertebral_levels + '" is not correct. Enter format "1:4". Exit program.\n'
        sys.exit(2)

    # Read files vertebral_labeling.nii.gz
    print '\nRead files '+vertebral_labeling_path+'...'

    # Load the vertebral labeling file and get the data in array format
    data_vert_labeling = nib.load(vertebral_labeling_path).get_data()

    # Extract metric data size X, Y, Z
    [mx, my, mz] = metric_data.shape
    # Extract vertebral labeling data size X, Y, Z
    [vx, vy, vz] = data_vert_labeling.shape

    # Initialisation of check error flag
    exit_program = 0

    # Check if sizes along X are the same
    if mx != vx:
        print '\tERROR: Size of vertebral_labeling.nii.gz along X is not the same as the metric data.'
        exit_program = 1
    # Check if sizes along Y are the same
    if my != vy:
        print '\tERROR: Size of vertebral_labeling.nii.gz along Y is not the same as the metric data.'
        exit_program = 1
    # Check if sizes along Z are the same
    if mz != vz:
        print '\tERROR: Size of vertebral_labeling.nii.gz along Z is not the same as the metric data.'
        exit_program = 1

    # Check if the vertebral levels selected are not available in the input image
    if vert_levels_list[0] < int(np.ndarray.min(data_vert_labeling)) or vert_levels_list[1] > int(np.ndarray.max(data_vert_labeling)):
        print '\tERROR: The vertebral levels you selected are not available in the input image.'
        exit_program = 1

    # Exit program if an error was detected
    if exit_program == 1 :
        print '\nExit program.\n'
        sys.exit(2)


    # Extract the X, Y, Z positions of voxels belonging to the first vertebral level
    X_bottom_level, Y_bottom_level, Z_bottom_level = (data_vert_labeling==vert_levels_list[0]).nonzero()
    # Record the bottom of slice of this level
    slice_min_bottom = min(Z_bottom_level)

    # Extract the X, Y, Z positions of voxels belonging to the last vertebral level
    X_top_level, Y_top_level, Z_top_level = (data_vert_labeling==vert_levels_list[1]).nonzero()
    # Record the top slice of this level
    slice_max_top = max(Z_top_level)

    # Take into account the case where the ordering of the slice is reversed compared to the ordering of the vertebral level
    if slice_min_bottom > slice_max_top:
        slice_min = min(Z_top_level)
        slice_max = max(Z_bottom_level)
    else:
        slice_min = min(Z_bottom_level)
        slice_max = max(Z_top_level)

    # Return the slice numbers in the right format ("-1" because the function "remove_slices", which runs next, add 1 to the top slice
    return str(slice_min)+':'+str(slice_max)

#=======================================================================================================================
# Crop data to only keep the slices asked by user
#=======================================================================================================================
def remove_slices(data_to_crop, slices_of_interest):

    # extract slice numbers
    slices_list = [int(x) for x in slices_of_interest.split(':')] # 2-element list

    # Remove slices that are not wanted (+1 is to include the last selected slice as Python "includes -1"
    data_cropped = data_to_crop[..., slices_list[0]:slices_list[1]+1]

    return data_cropped


#=======================================================================================================================
# Save in txt file
#=======================================================================================================================
def save_metrics(ind_labels, label_name, slices_of_interest, vertebral_levels, metric_mean, metric_std, fname_output):

    # TODO: add file data
    # TODO: add method

    # CSV format, header lines start with "#"

    # Save metric in a .txt file
    print '\nWrite results in ' + fname_output + ' ...'

    # Write mode of file
    fid_metric = open(fname_output, 'w')

    # WRITE HEADER:
    # TODO: add date and time

    # Write selected vertebral levels
    if vertebral_levels != '':
        fid_metric.write('%s\t%s to %s\n'% ('# Vertebral levels: ', vertebral_levels.split(':')[0], vertebral_levels.split(':')[1]))
    else:
        fid_metric.write('# Vertebral levels: ALL\n')

    # Write selected slices
    if slices_of_interest != '':
        fid_metric.write('%s\t%s to %s\n'% ('# Slices: ', slices_of_interest.split(':')[0], slices_of_interest.split(':')[1]))
    else:
        fid_metric.write('# Slices: ALL\n')

    # label info
    fid_metric.write('%s\n' % ('# ID, label name, mean, std'))

    # WRITE RESULTS<commit_msg>OPT: improve display when choosing the flag -a<commit_after>#!/usr/bin/env python
#########################################################################################
#
# Extract metrics within spinal labels as defined by the white matter atlas and the
# template
# The folder atlas should have a .txt file that lists all tract files with labels.
#
# ---------------------------------------------------------------------------------------
# Copyright (c) 2014 Polytechnique Montreal <www.neuro.polymtl.ca>
# Authors: <NAME>, <NAME>, <NAME>, <NAME>
# Modified: 2014-07-30
#
# About the license: see the file LICENSE.TXT
#########################################################################################

# Import common Python libraries
import os
import getopt
import sys
import time
import commands
import nibabel as nib
import numpy as np
import sct_utils as sct

# get path of the toolbox
status, path_sct = commands.getstatusoutput('echo $SCT_DIR')
# append path that contains scripts, to be able to load modules
sys.path.append(path_sct + '/scripts')


# constants
ALMOST_ZERO = 0.000001

class param:
    def __init__(self):
        self.debug = 0
        self.method = 'wa'
        self.path_label = path_sct+'/data/template'  # default is toolbox
        self.verbose = 1
        self.labels_of_interest = ''  # list. example: '1,3,4'. . For all labels, leave empty.
        self.vertebral_levels = ''
        self.slices_of_interest = ''  # 2-element list corresponding to zmin,zmax. example: '5:8'. For all slices, leave empty.
        self.average_all_labels = 0  # average all labels together after concatenation
        self.fname_output = 'quantif_metrics.txt'
        self.file_info_label = 'info_label.txt'
        self.vertebral_labeling_file = path_sct+'/data/template/MNI-Poly-AMU_level.nii.gz'



#=======================================================================================================================
# main
#=======================================================================================================================
def main():
    # Initialization to defaults parameters
    fname_data = '' # data is empty by default
    path_label = param.path_label
    method = param.method # extraction mode by default
    labels_of_interest = param.labels_of_interest
    slices_of_interest = param.slices_of_interest
    vertebral_levels = param.vertebral_levels
    average_all_labels = param.average_all_labels
    fname_output = param.fname_output
    file_info_label = param.file_info_label
    vertebral_labeling_path = param.vertebral_labeling_file
    start_time = time.time()  # save start time for duration
    verbose = param.verbose

    # Parameters for debug mode
    if param.debug:
        print '\n*** WARNING: DEBUG MODE ON ***\n'
        fname_data = path_sct+'/data/template/MNI-Poly-AMU_T2.nii.gz' #path_sct+'/testing/data/errsm_23/mt/mtr.nii.gz'
        path_label = path_sct+'/data/atlas' #path_sct+'/testing/data/errsm_23/label/atlas'
        method = 'wa'
        labels_of_interest = '0,1,4,7'  #'0, 2, 5, 7, 15, 22, 27, 29'
        slices_of_interest = '2:10' #'2:4'
        vertebral_levels = ''
        average_all_labels = 1
        fname_output = path_sct+'/testing/sct_extract_metric/results/quantif_mt_debug.txt'


    # Check input parameters
    try:
        opts, args = getopt.getopt(sys.argv[1:], 'haf:i:l:m:o:v:z:') # define flags
    except getopt.GetoptError as err: # check if the arguments are defined
        print str(err) # error
        usage() # display usage
    for opt, arg in opts: # explore flags
        if opt in '-a':
            average_all_labels = 1
        elif opt in '-f':
            path_label = os.path.abspath(arg)  # save path of labels folder
        elif opt == '-h': # help option
            usage() # display usage
        elif opt in '-i':
            fname_data = arg
        elif opt in '-l':
            labels_of_interest = arg
        elif opt in '-m': # method for metric extraction
            method = arg
        elif opt in '-o': # output option
            fname_output = arg  # fname of output file
        elif opt in '-v': # vertebral levels option, if the user wants to average the metric across specific vertebral levels
             vertebral_levels = arg
        elif opt in '-z': # slices numbers option
            slices_of_interest = arg # save labels numbers


    #TODO: check if the case where the input images are not in AIL orientation is taken into account (if not, implement it)

    # Display usage with tract parameters by default in case files aren't chosen in arguments inputs
    if fname_data == '' or path_label == '':
        usage()

    # Check existence of data file
    sct.printv('\nCheck data file existence...', verbose)
    sct.check_file_exist(fname_data)

    # add slash at the end
    path_label = sct.slash_at_the_end(path_label, 1)

    # Check existence of path_label
    if not os.path.isdir(path_label):
        print('\nERROR: ' + path_label + ' does not exist. Exit program.\n')
        sys.exit(2)
    else:
        print '\nOK: '+path_label

    # Check input parameters
    check_method(method)

    # Extract label info
    label_id, label_name, label_file = read_label_file(path_label)
    nb_labels_total = len(label_id)

    # check consistency of label input parameter.
    label_id_user = check_labels(labels_of_interest, nb_labels_total) # If 'labels_of_interest' is empty, then 'label_id_user' contains the index of all labels in the file info_label.txt

    # print parameters
    print '\nChecked parameters:'
    print '  data ................... '+fname_data
    print '  folder label ........... '+path_label
    print '  selected labels ........ '+str(label_id_user)
    print '  estimation method ...... '+method
    print '  vertebral levels ....... '+vertebral_levels
    print '  slices of interest ..... '+slices_of_interest

    # Load image
    sct.printv('\nLoad image...', verbose)
    data = nib.load(fname_data).get_data()
    sct.printv('  Done.', verbose)

    # Get dimensions of data
    sct.printv('\nGet dimensions of data...', verbose)
    nx, ny, nz = data.shape
    sct.printv('.. '+str(nx)+' x '+str(ny)+' x '+str(nz), verbose)

    # load label
    sct.printv('\nLoad labels...', verbose)
    labels = np.empty([nb_labels_total], dtype=object)  # labels(nb_labels_total, x, y, z)
    for i_label in range(0, nb_labels_total):
        labels[i_label] = nib.load(path_label+label_file[i_label]).get_data() # labels[i_label, :, :, :] = nib.load(path_label+label_file[label_id[i_label]]).get_data()
    sct.printv('  Done.', verbose)

    # Get dimensions of labels
    sct.printv('\nGet dimensions of label...', verbose)
    nx_atlas, ny_atlas, nz_atlas = labels[0].shape
    sct.printv('.. '+str(nx_atlas)+' x '+str(ny_atlas)+' x '+str(nz_atlas)+' x '+str(nb_labels_total), verbose)

    # Check dimensions consistency between atlas and data
    if (nx, ny, nz) != (nx_atlas, ny_atlas, nz_atlas):
        print '\nERROR: Metric data and labels DO NOT HAVE SAME DIMENSIONS.'
        sys.exit(2)

    # Update the flag "slices_of_interest" according to the vertebral levels selected by user (if it's the case)
    if vertebral_levels != '':
        if slices_of_interest != '':
            print '\nERROR: You cannot select BOTH vertebral levels AND slice numbers.'
            usage()
        else:
            if path_label.endswith('atlas/'):
                vertebral_labeling_path = path_label+'../template/MNI-Poly-AMU_level.nii.gz'
            slices_of_interest = get_slices_matching_with_vertebral_levels(data, vertebral_levels, vertebral_labeling_path)

    # select slice of interest by cropping data and labels
    if slices_of_interest != '':
        data = remove_slices(data, slices_of_interest)
        for i_label in range(0, nb_labels_total):
            labels[i_label] = remove_slices(labels[i_label], slices_of_interest)

    # if user wants to get unique value across labels, then combine all labels together
    if average_all_labels == 1:
        sum_labels_user = np.sum(labels[label_id_user]) # sum the labels selected by user
        if method == 'ml':  # in case the maximum likelihood and the average across different labels are wanted
            # TODO: make the below code more clean (no use of tmp variable)
            labels_tmp = np.empty([nb_labels_total - len(label_id_user) + 1], dtype=object)
            labels = np.delete(labels, label_id_user)  # remove the labels selected by user
            labels_tmp[0] = sum_labels_user
            for i_label in range(1, len(labels_tmp)):
                labels_tmp[i_label] = labels[i_label-1]
            labels = labels_tmp
            del labels_tmp
        else:
            labels = np.empty(1, dtype=object)
            labels[0] = sum_labels_user

    # extract metrics within labels
    metric_mean, metric_std = extract_metric_within_tract(data, labels, method)  # mean and std are lists.

    # update label name if average
    if average_all_labels == 1:
        label_name[0] = 'AVERAGED'+' -'.join(label_name[i] for i in label_id_user )
        # TODO: instead of 0, make it clear for the user that all labels are concatenated
        label_id_user = [0]

    metric_mean = metric_mean[label_id_user]
    metric_std = metric_std[label_id_user]

    # display metrics
    print '\033[1m\nEstimation results:\n'
    for i in range(0, metric_mean.size):
        print '\033[1m'+str(label_id_user[i])+', '+str(label_name[label_id_user[i]])+':    '+str(metric_mean[i])+' +/- '+str(metric_std[i])+'\033[0m'

    # save and display metrics
    save_metrics(label_id_user, label_name, slices_of_interest, vertebral_levels, metric_mean, metric_std, fname_output)

    # Print elapsed time
    print 'Elapsed time : ' + str(int(round(time.time() - start_time))) + ' sec'

    # end of main.
    print



#=======================================================================================================================
# Read label.txt file which is located inside label folder
#=======================================================================================================================
def read_label_file(path_info_label):

    # file name of info_label.txt
    fname_label = path_info_label+param.file_info_label

    # Check info_label.txt existence
    sct.check_file_exist(fname_label)

    # Read file
    f = open(fname_label)

    # Extract all lines in file.txt
    lines = [lines for lines in f.readlines() if lines.strip()]

    # read each line
    label_id = []
    label_name = []
    label_file = []
    for i in range(0, len(lines)-1):
        line = lines[i].split(',')
        label_id.append(int(line[0]))
        label_name.append(line[1])
        label_file.append(line[2][:-1].strip()) #replace(" ", "").replace("\r", ""))
    # An error could occur at the last line (deletion of the last character of the .txt file), the 5 following code lines enable to avoid this error:
    line = lines[-1].split(',')
    label_id.append(int(line[0]))
    label_name.append(line[1])
    line[2]=line[2]+' '
    label_file.append(line[2].strip())


    # check if all files listed are present in folder. If not, WARNING.
    print '\nCheck if all files listed in '+param.file_info_label+' are indeed present in '+path_info_label+' ...'
    for fname in label_file:
        if os.path.isfile(path_info_label+fname) or os.path.isfile(path_info_label+fname + '.nii') or os.path.isfile(path_info_label+fname + '.nii.gz'):
            print('  OK: '+path_info_label+fname)
            pass
        else:
            print('  WARNING: ' + path_info_label+fname + ' does not exist but is listed in '+param.file_info_label+'.\n')


    # Close file.txt
    f.close()

    return [label_id, label_name, label_file]


#=======================================================================================================================
# get_slices_matching_with_vertebral_levels
#=======================================================================================================================
def get_slices_matching_with_vertebral_levels(metric_data, vertebral_levels,vertebral_labeling_path):
    """Return the slices of the input image corresponding to the vertebral levels given as argument."""

    # check existence of a vertebral labeling file
    sct.check_file_exist(vertebral_labeling_path)

    # Convert the selected vertebral levels chosen into a 2-element list [start_level end_level]
    vert_levels_list = [int(x) for x in vertebral_levels.split(':')]

    # If only one vertebral level was selected (n), consider as n:n
    if len(vert_levels_list) == 1:
        vert_levels_list = [vert_levels_list[0], vert_levels_list[0]]

    # Check if there are only two values [start_level, end_level] and if the end level is higher than the start level
    if (len(vert_levels_list) > 2) or (vert_levels_list[0] > vert_levels_list[1]):
        print '\nERROR:  "' + vertebral_levels + '" is not correct. Enter format "1:4". Exit program.\n'
        sys.exit(2)

    # Read files vertebral_labeling.nii.gz
    print '\nRead files '+vertebral_labeling_path+'...'

    # Load the vertebral labeling file and get the data in array format
    data_vert_labeling = nib.load(vertebral_labeling_path).get_data()

    # Extract metric data size X, Y, Z
    [mx, my, mz] = metric_data.shape
    # Extract vertebral labeling data size X, Y, Z
    [vx, vy, vz] = data_vert_labeling.shape

    # Initialisation of check error flag
    exit_program = 0

    # Check if sizes along X are the same
    if mx != vx:
        print '\tERROR: Size of vertebral_labeling.nii.gz along X is not the same as the metric data.'
        exit_program = 1
    # Check if sizes along Y are the same
    if my != vy:
        print '\tERROR: Size of vertebral_labeling.nii.gz along Y is not the same as the metric data.'
        exit_program = 1
    # Check if sizes along Z are the same
    if mz != vz:
        print '\tERROR: Size of vertebral_labeling.nii.gz along Z is not the same as the metric data.'
        exit_program = 1

    # Check if the vertebral levels selected are not available in the input image
    if vert_levels_list[0] < int(np.ndarray.min(data_vert_labeling)) or vert_levels_list[1] > int(np.ndarray.max(data_vert_labeling)):
        print '\tERROR: The vertebral levels you selected are not available in the input image.'
        exit_program = 1

    # Exit program if an error was detected
    if exit_program == 1 :
        print '\nExit program.\n'
        sys.exit(2)


    # Extract the X, Y, Z positions of voxels belonging to the first vertebral level
    X_bottom_level, Y_bottom_level, Z_bottom_level = (data_vert_labeling==vert_levels_list[0]).nonzero()
    # Record the bottom of slice of this level
    slice_min_bottom = min(Z_bottom_level)

    # Extract the X, Y, Z positions of voxels belonging to the last vertebral level
    X_top_level, Y_top_level, Z_top_level = (data_vert_labeling==vert_levels_list[1]).nonzero()
    # Record the top slice of this level
    slice_max_top = max(Z_top_level)

    # Take into account the case where the ordering of the slice is reversed compared to the ordering of the vertebral level
    if slice_min_bottom > slice_max_top:
        slice_min = min(Z_top_level)
        slice_max = max(Z_bottom_level)
    else:
        slice_min = min(Z_bottom_level)
        slice_max = max(Z_top_level)

    # Return the slice numbers in the right format ("-1" because the function "remove_slices", which runs next, add 1 to the top slice
    return str(slice_min)+':'+str(slice_max)

#=======================================================================================================================
# Crop data to only keep the slices asked by user
#=======================================================================================================================
def remove_slices(data_to_crop, slices_of_interest):

    # extract slice numbers
    slices_list = [int(x) for x in slices_of_interest.split(':')] # 2-element list

    # Remove slices that are not wanted (+1 is to include the last selected slice as Python "includes -1"
    data_cropped = data_to_crop[..., slices_list[0]:slices_list[1]+1]

    return data_cropped


#=======================================================================================================================
# Save in txt file
#=======================================================================================================================
def save_metrics(ind_labels, label_name, slices_of_interest, vertebral_levels, metric_mean, metric_std, fname_output):

    # TODO: add file data
    # TODO: add method

    # CSV format, header lines start with "#"

    # Save metric in a .txt file
    print '\nWrite results in ' + fname_output + ' ...'

    # Write mode of file
    fid_metric = open(fname_output, 'w')

    # WRITE HEADER:
    # TODO: add date and time

    # Write selected vertebral levels
    if vertebral_levels != '':
        fid_metric.write('%s\t%s to %s\n'% ('# Vertebral levels: ', vertebral_levels.split(':')[0], vertebral_levels.split(':')[1]))
    else:
        fid_metric.write('# Vertebral levels: ALL\n')

    # Write selected slices
    if slices_of_interest != '':
        fid_metric.write('%s\t%s to %s\n'% ('# Slices: ', slices_of_interest.split(':')[0], slices_of_interest.split(':')[1]))
    else:
        fid_metric.write('# Slices: ALL\n')

    # label info
    fid_metric.write('\n%s' % ('# ID, label name, mean, std'))

    # WRITE RESULTS
{'fraction_non_alphanumeric': 0.17826882477128783, 'fraction_numerical': 0.0076002814919071075, 'mean_word_length': 3.2968069666182873, 'pattern_counts': {'":': 0, '<': 15, '<?xml version=': 0, '>': 21, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '335814', 'n_tokens_mistral': 11220, 'n_tokens_neox': 10330, 'n_words': 3120}