示例#1
0
def structural_analysis(parser):

    print('\n\n')
    print(
        ' *********************************************************************************'
    )
    print(
        ' ************************* Structural_analysis.py script *************************'
    )
    print(
        ' *********************************************************************************'
    )
    print('\n')

    args = parser.parse_args()

    # extract path string
    source_path = manage_path_argument(args.source_folder)
    source_path = source_path.replace(
        ' ', '\ ')  # correct whitespace with backslash

    # extract base path and stack name
    base_path = os.path.dirname(os.path.dirname(source_path))
    stack_name = os.path.basename(source_path)

    # create folder path of segmented images
    segmented_path = os.path.join(base_path, 'segmented', stack_name)

    # call  ALFA_volume_analysis script
    os.system('python3 ALFA_volume_analysis.py -sf {}'.format(source_path))

    # call  ALFA_volume_analysis script
    os.system(
        'python3 GAMMA_orientation_analysis.py -sf {}'.format(segmented_path))
示例#2
0
def main(parser):

    # read args from console
    args = parser.parse_args()

    # read source path (path of binary segmentation images)
    source_path = manage_path_argument(args.source_folder)

    # take base path and stack name
    base_path = os.path.dirname(os.path.dirname(source_path))
    stack_name = os.path.basename(source_path)

    # create path and folder where save section images
    sections_path = os.path.join(base_path, 'xz_sections', stack_name)
    filled_sections_path = os.path.join(base_path, 'xz_filled_sections',
                                        stack_name)
    for path in [sections_path, filled_sections_path]:
        if not os.path.exists(path):
            os.makedirs(path)

    # portion of y axes for section estimation
    y_start = np.uint16(args.y_start[0])
    y_stop = np.uint16(args.y_stop[0])

    # preference for fill or not the holes
    _fill_holes = args.fill_holes

    print('\n\n\n _fill_holes : ', _fill_holes)

    # Def .txt filepath
    txt_parameters_path = os.path.join(base_path, 'parameters.txt')
    if _fill_holes:
        txt_results_path = os.path.join(base_path,
                                        'Measure_analysis_filled.txt')
    else:
        txt_results_path = os.path.join(base_path, 'Measure_analysis.txt')

    # SCRIPT ----------------------------------------------------------------------

    # print to video and write in results.txt init message
    init_message = [
        ' ****  Script for Estimation of Section Area and Volume **** \n \n'
        ' Source from path : {}'.format(base_path),
        ' Stack : {}'.format(stack_name), '\n\n *** Start processing... \n'
    ]
    error_message = '\n *** ERROR *** : stack in this path is None'
    with open(txt_results_path, 'w') as f:
        for line in init_message:
            print(line)
            f.write(line + '\n')

    # reads parameters
    parameters = extract_parameters(txt_parameters_path)

    # measure units
    x_step = parameters['res_xy']  # micron
    y_step = parameters['res_xy']  # micron
    z_step = parameters['res_z']  # micron
    pixel_xy_in_micron2 = x_step * y_step  # micron^2
    pixel_xz_in_micron2 = x_step * z_step  # micron^2
    voxel_in_micron3 = x_step * y_step * z_step  # micron^3

    # preferences
    _save_binary_sections = bool(parameters['save_binary_sections'])

    # create images stack from source path
    print(' *** Start to load the Stack...')

    # old version:
    # masks, mess = load_stack_into_numpy_ndarray([source_path])
    # print(mess)

    #new version:
    masks = load_tif_data(source_path)
    if len(masks.shape) == 2:
        masks = np.expand_dims(masks, axis=2)  # add the zeta axis

    # count selected sections
    total_sections = masks.shape[0]  # row -> y -> number of sections
    print('\n Volume shape:', masks.shape)
    print('Number of total sections: ', total_sections)
    print('\n')

    # set y portion
    if y_stop < y_start:
        y_start = np.uint16(total_sections / 4)
        y_stop = np.uint16(total_sections * 3 / 4)
        print(
            ' *** ATTENTION : y portion selected by DEFAULT: {} -> {}'.format(
                y_start, y_stop))

    # Every Section(y) is XZ projection of mask. Estimated Area is the sum of the Non_Zero pixel
    selected_sections = np.uint16(y_stop - y_start)
    sections_micron2 = np.zeros(selected_sections)
    print('Number of selected sections: ', y_stop - y_start)

    # Initializing to zero the Volume counter
    effective_myocites_volume = 0  # real estimated volume of myocites (sum of area of real cells)

    t_start = time.time()
    analyzed_section = 0  # counter, only for control the for loop (remove)

    with open(txt_results_path, 'a') as f:

        pre_info = list()
        pre_info.append('\nPortion of y selected: [{} -> {}]'.format(
            y_start, y_stop))
        pre_info.append('Option for fill the holes: {}'.format(_fill_holes))
        pre_info.append('\n')
        for l in pre_info:
            print(l)
            f.write(l + '\n')

        if masks is not None:

            # *** 1 ***  - Mean Section Estimation
            print('\n... Estimation of mean section...')
            for y in range(y_start, y_stop):

                # extract section
                section = masks[y, :, :]
                sec_name = create_img_name_from_index(total_sections - y -
                                                      1)  # img_name.tif

                # filled the section holes and set comment for tiff filenames
                if _fill_holes:
                    section = ndimage.morphology.binary_fill_holes(section)
                    comment = 'filled_section'
                    dest_path = filled_sections_path
                else:
                    comment = 'section'
                    dest_path = sections_path

                # count cell pixel
                pixels_with_cell = np.count_nonzero(section.astype(bool))

                if pixels_with_cell > 0:
                    area_in_micron2 = pixels_with_cell * pixel_xz_in_micron2
                    sections_micron2[y - y_start] = area_in_micron2
                    measure = ' - {}   ---->   {} um^2'.format(
                        sec_name, area_in_micron2)

                    if _save_binary_sections:
                        # transform point of view
                        section = np.rot90(m=np.flipud(section),
                                           k=1,
                                           axes=(0, 1))
                        # save section in correct path
                        save_tiff(img=section,
                                  img_name=sec_name,
                                  comment=comment,
                                  folder_path=dest_path)

                else:
                    measure = ' - {} is empty'.format(sec_name)

                analyzed_section += 1
                print(measure)
                # f.write(measure+'\n')

            # *** 2 ***  - Volume Estimation
            print('\n\n ...Estimation of Volume...\n')
            for z in range(masks.shape[2]):
                # check fill option
                if _fill_holes:
                    print('... filling {} xy frame'.format(z))
                    z_frame = ndimage.morphology.binary_fill_holes(masks[:, :,
                                                                         z])
                else:
                    z_frame = masks[:, :, z]
                # add pixels_of_real_cells of current z-slice to the counter
                effective_myocites_volume += np.count_nonzero(z_frame)

            # execution time
            (h, m, s) = seconds_to_min_sec(time.time() - t_start)

            # volumes in micron^3
            effective_volume_in_micron3 = effective_myocites_volume * voxel_in_micron3

            # count empty sections
            sections_with_cell = np.count_nonzero(sections_micron2)
            empties = selected_sections - sections_with_cell

            # Mean sections:
            mean_section = np.sum(
                sections_micron2) / sections_with_cell  # (original images)

            # create results string
            result_message = list()
            result_message.append(
                '\n ***  Process successfully completed, time of execution: {0:2d}h {1:2d}m {2:2d}s \n'
                .format(int(h), int(m), int(s)))
            result_message.append(' Total number of frames: {}'.format(
                masks.shape[2]))
            result_message.append(' Total sections: {}'.format(total_sections))
            result_message.append(
                ' Selected sections: {}'.format(selected_sections))
            result_message.append(
                ' Effective analyzed sections: {}'.format(analyzed_section))
            result_message.append(
                ' Number of empty section: {}'.format(empties))
            result_message.append(
                ' Number of section with cells: {}'.format(sections_with_cell))
            result_message.append('\n')
            result_message.append(
                ' Mean sections: {0:.3f} um^2'.format(mean_section))
            result_message.append(
                ' Effective miocytes tissue volume : {0:.6f} mm^3'.format(
                    effective_volume_in_micron3 / 10**9))

            result_message.append('\n')
            result_message.append(' \n OUTPUT SAVED IN: \n')
            result_message.append(txt_results_path)

            # write and print results
            for l in result_message:
                print(l)
                f.write(l + '\n')

        else:
            print(error_message)
            f.write(error_message)

        print(' \n \n \n ')
def main(parser):
    ''' Docstring (TODO) parameters '''

    # ====== 0 ====== Initial operations
    # TODO estrare dai parametri da terminale la bool 'verbose'
    verbose = False

    # read args from console
    args = parser.parse_args()
    source_path = manage_path_argument(args.source_folder)

    base_path = os.path.dirname(os.path.dirname(source_path))
    stack_name = os.path.basename(source_path)

    # Def .txt filepath
    txt_parameters_path = os.path.join(base_path, 'parameters.txt')
    txt_results_path = os.path.join(base_path, 'GAMMA_orientation_results.txt')

    Results_filename = 'orientation_Results.npy'
    Results_filepath = os.path.join(base_path, Results_filename)

    # print to video and write in results.txt init message
    init_message = [
        ' *****************   GAMMA - Orientation analysis of 3D stack   *****************\n'
        ' Source from path : {}'.format(source_path),
        ' Base path : {}'.format(base_path),
        ' Stack : {}'.format(stack_name),
    ]

    with open(txt_results_path, 'w') as f:
        for line in init_message:
            print(line)
            f.write(line + '\n')

    # reads parameters
    parameters = extract_parameters(txt_parameters_path)

    # analysis block dimension in z-axis
    num_of_slices_P = parameters['num_of_slices_P']

    # Parameters of Acquisition System:
    res_z = parameters['res_z']
    res_xy = parameters['res_xy']
    resolution_factor = res_z / res_xy
    block_side = row_P = col_P = int(num_of_slices_P * resolution_factor)
    shape_P = np.array((row_P, col_P, num_of_slices_P)).astype(np.int32)
    """ =====================================================================================
    __________________________  -1-  OPEN STACK _______________________________________________"""

    loading_mess = list()
    loading_mess.append(
        ' ***** Start load the Stack, this may take a few minutes... ')

    # extract data (OLD METHOD)
    volume = load_tif_data(source_path)
    if len(volume.shape) == 2:
        volume = np.expand_dims(volume, axis=2)  # add the zeta axis

    # extract stack (NEW METHOD)-------------------
    # infile = InputFile(source_path) # read virtual file (shape, dtype, ecc..)
    # volume = infile.whole() # load real images in RAM
    # # move axes from (z, y, x) -> to (r, c, z)=(y, x, z) [S.R. ZetaStitcher -> Strip Analysis]
    # volume = np.moveaxis(volume, 0, -1)
    # ---------------------------------------------

    loading_mess.append(' - Volume shape : {}'.format(volume.shape))
    with open(txt_results_path, 'a') as f:
        for m in loading_mess:
            print(m)
            f.write(m + '\n')

    # calculate dimension
    shape_V = np.array(volume.shape)
    """ =====================================================================================
    ________________________  -2-   CYCLE FOR BLOCKS EXTRACTION and ANALYSIS __________________"""
    t_start = time.time()

    # create empty Result matrix
    R, shape_R = create_R(shape_V, shape_P)

    # create 3D filter
    mask = create_3D_filter(block_side, res_xy, parameters['sarc_length'])

    count = 1  # count iteration
    total_iter = np.prod(shape_R)
    print(
        '\n \n ***** Start iteration of analysis, expectd iterations : {} \n'.
        format(total_iter))

    with open(txt_results_path, 'a') as f:
        for z in range(shape_R[2]):
            for r in range(shape_R[0]):
                for c in range(shape_R[1]):
                    # initialize list of string lines
                    lines = []

                    start_coord = create_coord_by_iter(r, c, z, shape_P)
                    slice_coord = create_slice_coordinate(start_coord, shape_P)
                    if verbose: lines.append('\n \n')
                    lines.append(
                        '- iter: {} - init_coord : {} - on total: {}'.format(
                            count, start_coord, total_iter))

                    # save init info in R
                    R[r, c, z]['id_block'] = count
                    R[r, c, z]['init_coord'] = start_coord

                    # extract parallelepiped
                    parall = volume[slice_coord]

                    # check dimension (if iteration is on border of volume, add zero_pad)
                    parall = pad_dimension(parall, shape_P)

                    if np.max(parall) != 0:
                        parall = (normalize(parall)).astype(
                            np.float32)  # fft analysis work with float

                        # analysis of parallelepiped extracted
                        there_is_cell, there_is_freq, results = block_analysis(
                            parall, shape_P, parameters, block_side, mask,
                            verbose, lines)
                        # save info in R[r, c, z]
                        if there_is_cell: R[r, c, z]['cell_info'] = True
                        if there_is_freq: R[r, c, z]['freq_info'] = True

                        # save results in R
                        for key in results.keys():
                            R[r, c, z][key] = results[key]

                    else:
                        if verbose: lines.append('   block rejected')

                    for l in lines:
                        print(l)
                    count += 1

    # execution time
    (h, m, s) = seconds_to_min_sec(time.time() - t_start)
    print('\n Iterations ended successfully \n')
    """ =====================================================================================
        ________________________  -3-   RESULTS ANALYSIS   __________________________________"""

    post_proc_mess = list()

    # count results, rejected and accepted blocks
    block_with_cell = np.count_nonzero(R['cell_info'])
    block_with_peak = np.count_nonzero(R['freq_info'])
    p_rejec_cell = 100 * (1 - block_with_cell / count)
    p_rejec_freq_tot = 100 * (1 - block_with_peak / count)
    p_rejec_freq = 100 * (1 - block_with_peak / block_with_cell)

    post_proc_mess.append(
        '\n ***** End of iterations, time of execution: {0:2d}h {1:2d}m {2:2d}s \n'
        .format(int(h), int(m), int(s)))
    post_proc_mess.append('\n - Expected iterations : {}'.format(total_iter))
    post_proc_mess.append(' - Total iterations : {}'.format(count - 1))
    post_proc_mess.append(
        '\n - block with cell : {}, rejected from total: {} ({}%)'.format(
            block_with_cell, count - block_with_cell, p_rejec_cell))
    post_proc_mess.append(
        ' - block with freq. info : {}'
        '\n    rejected from total: {} ({}%)'
        '\n    rejected from block with cell: {} ({}%)'.format(
            block_with_peak, count - block_with_peak, p_rejec_freq_tot,
            block_with_cell - block_with_peak, p_rejec_freq))
    with open(txt_results_path, 'a') as f:
        for m in post_proc_mess:
            print(m)
            f.write(m + '\n')

    post_proc_mess = list()

    # threshold results on frequency validation parameter and save matrix
    mess = '\n \n *** Analysis of Results : remove block with low frequency affidability \n'
    post_proc_mess.append(mess)
    print(mess)

    # - 1 normalization of psd_ratio values
    R = parameter_normalizer(R, 'psd_ratio')
    mess = '- 1 - Normalization on \'psd_ratio\': complete'
    post_proc_mess.append(mess)
    print(mess)

    # - 2 thresholding on psd_ratio values
    R, before, after = threshold_par(R, parameters, 'psd_ratio')
    mess = '- 2 - First thresholding based on PSD Information: selected {} blocks from {}'.format(
        after, before)
    post_proc_mess.append(mess)
    print(mess)

    # - 3 outlier remotion based of orientation and psd_ratio values - NOT EXECUTED
    mess = '-*** NO Outlier Remotion based on PSD Information.'
    post_proc_mess.append(mess)
    print(mess)

    # save Result matrix
    np.save(Results_filepath, R)

    # - 4 Estimate and write local disorder inside Result Matrix
    R, shape_LD, isolated_value = estimate_local_disorder(
        R, parameters, resolution_factor)
    mess = '- 4 - Local Disorder estimated inside result Matrix, with grane (r, c, z): ({}, {}, {}) ' \
           'and isolated points setted with local_disorder = {}'\
            .format(shape_LD[0], shape_LD[1], shape_LD[2], isolated_value)
    post_proc_mess.append(mess)
    print(mess)

    # se funziona tutto, salvare solo una versione di R
    Results_filename = 'orientation_Results_after_disorder.npy'
    Results_filepath = os.path.join(base_path, Results_filename)
    np.save(Results_filepath, R)

    with open(txt_results_path, 'a') as f:
        for m in post_proc_mess:
            f.write(m + '\n')
    del post_proc_mess
    """ =====================================================================================
            ________________________  -4-   STATISTICS   __________________________________"""

    stat = statistics(R, parameters)
    result_mess = list()
    result_mess.append(
        '\n \n *** Results of statistical analysis on accepted points: \n')
    result_mess.append(' - {0} : {1:.3f} um^(-1)'.format(
        'Mean module', stat['Mean Module']))
    result_mess.append(' - {0} : {1:.3f} um'.format('Mean Period',
                                                    stat['Mean Period']))
    result_mess.append(' - {0} : {1:.3f} % '.format('Alignment',
                                                    100 * stat['Alignment']))
    result_mess.append(' - {0} : {1:.3f} % '.format(
        'XZ Dispersion (area_ratio)', 100 * stat['area_ratio']))
    result_mess.append(' - {0} : {1:.3f} % '.format(
        'XZ Dispersion (sum dev.std)', 100 * stat['sum_std']))
    result_mess.append(
        ' \n \n ***************************** END GAMMA - orientation_analysis.py ********************'
        '\n \n \n \n ')

    with open(txt_results_path, 'a') as f:
        for l in result_mess:
            print(l)
            f.write(l + '\n')
示例#4
0
def main(parser):

    # --- PRELIMINARY OPERATIONS --------------------------------------------------------

    # read args from console
    args = parser.parse_args()
    source_path = manage_path_argument(args.source_folder)

    # take base path and stack name
    base_path = os.path.dirname(os.path.dirname(source_path))
    stack_name = os.path.basename(source_path)

    # Def .txt filepath
    txt_parameters_path = os.path.join(base_path, 'parameters.txt')
    txt_results_path = os.path.join(base_path, 'ALFA_volume_results.txt')
    txt_metadata_path = os.path.join(base_path, 'metadata.txt')

    process_names = [
        'clahe', 'mask_bin', 'segmented', 'contourned', 'contours'
    ]

    # create destination paths where save images
    destination_paths = []
    for process in process_names:
        destination_paths.append(os.path.join(base_path, process, stack_name))

    # if those folders do not exist, this creates them
    for path in destination_paths:
        if not os.path.exists(path):
            os.makedirs(path)

    # --- SCRIPT ---------------------------------------------------------------

    # reads parameters
    parameters = extract_parameters(txt_parameters_path)

    # read user preference (if save images or not)
    _save_clahe = bool(parameters['save_clahe'])
    _save_binary_mask = bool(parameters['save_binary_mask'])
    _save_segmented = bool(parameters['save_segmented'])
    _save_countourned = bool(parameters['save_countourned'])
    _save_contours = bool(parameters['save_contours'])

    # print to video and write in results.txt init message
    init_message = [
        ' Source from path : {} \n'.format(base_path),
        ' Stack : {} \n'.format(stack_name),
        '\n\n - Start load the Stack, this may take a few minutes... '
    ]
    error_message = '\n *** ERROR *** : stack in this path is None'

    with open(txt_results_path, 'w') as f:
        for line in init_message:
            print(line, end='')
            f.write(line + '\n')

    # measure units
    x_step = parameters['res_xy']  # micron
    y_step = parameters['res_xy']  # micron
    z_step = parameters['res_z']  # micron
    voxel_in_micron3 = x_step * y_step * z_step  # micron^3

    # extract stack (OLD METHOD)
    # volume, mess = load_stack_into_numpy_ndarray([source_path])
    # img_shape = volume[:, :, 0].shape

    # extract stack (NEW METHOD)---------------
    volume = load_tif_data(source_path)
    if len(volume.shape) == 2:
        volume = np.expand_dims(volume, axis=2)  # add the zeta axis
    img_shape = (volume.shape[0], volume.shape[1])
    # ---------------------------------------------

    print(' Images shape : ', img_shape, '\n')

    # measure of imaging volume
    area_of_slice = img_shape[0] * img_shape[1]
    number_of_slices = volume.shape[2]

    # Estimated Volume
    total_imaging_volume = area_of_slice * number_of_slices  # total volume, from z=0 to z maximum

    # Initializing to zero the Volume counter
    effective_myocites_volume = 0  # real estimated volume of myocites (sum of area of real cells)

    # Boolean vector with length = number_of_slices.
    # Element i-th is:
    # True - if i-th slice have info
    # False - if i-th lice i empty
    slices_info = np.zeros(number_of_slices).astype(
        bool)  # created with all False values.

    # save elaboration time for each iteration (for estimate mean time of elaboration)
    slice_elab_time_list = list()
    t_start = time.time()

    print(' EQUALIZATION and SEGMENTATION of every frame:')

    with open(txt_results_path, 'a') as f:
        if volume is not None:

            for z in range(number_of_slices):

                # extract current slice
                img = volume[:, :, z]
                img_name = create_img_name_from_index(z)  # img_name.tif

                #  check if img is empty (too black)
                if image_have_info(img, parameters['t_rate_info']):
                    elab_start = time.time()

                    slices_info[z] = True
                    img = normalize(img)

                    equalized_img = clahe(img,
                                          parameters,
                                          img_name,
                                          destination_paths[0],
                                          _save=_save_clahe)

                    bw_mask, pixels_of_real_cells = create_byn_mask(
                        equalized_img,
                        parameters,
                        img_name,
                        destination_paths[1],
                        _save=_save_binary_mask)

                    effective_myocites_volume += pixels_of_real_cells

                    create_and_save_segmented_images(bw_mask,
                                                     equalized_img,
                                                     img_name,
                                                     destination_paths[2],
                                                     _save=_save_segmented)

                    contours = create_surrounded_images(
                        bw_mask,
                        equalized_img,
                        img_name,
                        destination_paths[3],
                        _save=_save_countourned)

                    if _save_contours:
                        # save image with only contours for fiji visualization:
                        save_contours(img_name, contours, bw_mask.shape,
                                      destination_paths[4])
                        save_contours(img_name, contours, bw_mask.shape,
                                      destination_paths[4])

                    slice_elab_time_list.append(time.time() - elab_start)
                    elapsed_time = (number_of_slices - z -
                                    1) * np.mean(slice_elab_time_list)
                    (h, m, s) = seconds_to_min_sec(elapsed_time)

                    measure = ' - {0} --> {1:.1f} um^3   -   ET: {2:2d}h {3:2d}m {4:2d}s'.format(
                        img_name, pixels_of_real_cells * voxel_in_micron3,
                        int(h), int(m), int(s))

                else:
                    # black = np.zeros(img.shape)
                    # for path in destination_paths:
                    #     save_tiff(img=black, img_name=img_name, comment='empty', folder_path=path)
                    measure = ' - {} is black, rejected'.format(img_name)

                print(measure)
                f.write(measure + '\n')

            # execution time
            (h, m, s) = seconds_to_min_sec(time.time() - t_start)

            # Num of saved slices
            saved = np.count_nonzero(slices_info)

            # Num of empty slice on top = Index of first slice with info
            empty_on_top = np.where(slices_info == True)[0][0]

            # Num of empty slice on bottom = Index of first slice with info, searching from from z=z_max to z=0
            empty_on_bottom = np.where(np.flipud(slices_info) == True)[0][0]

            # volumes in micron^3
            total_imaging_volume_in_micron = total_imaging_volume * voxel_in_micron3
            effective_volume_in_micron3 = effective_myocites_volume * voxel_in_micron3

            # in percentage
            myocites_perc = 100 * effective_myocites_volume / total_imaging_volume

            result_message = list()
            result_message.append(
                '\n ***  Process successfully completed, time of execution: {0:2d}h {1:2d}m {2:2d}s \n'
                .format(int(h), int(m), int(s)))
            result_message.append(' Number of saved slices: {}'.format(saved))
            result_message.append(
                ' Number of rejected slices (because empty) on the top of Volume: {}'
                .format(empty_on_top))
            result_message.append(
                ' Number of rejected slices (because empty) on the bottom of Volume: {}'
                .format(empty_on_bottom))
            result_message.append(
                ' Total Imaging volume : {0:.6f} mm^3'.format(
                    total_imaging_volume_in_micron / 10**9))
            result_message.append(
                ' Effective miocytes tissue volume : {0:.6f} mm^3, {1:.3f}% of Imaging volume'
                .format(effective_volume_in_micron3 / 10**9, myocites_perc))
            result_message.append('\n')
            result_message.append(' OUTPUT SAVED IN: \n')

            for path in destination_paths:
                result_message.append(path)

            # write and print results
            for l in result_message:
                print(l)
                f.write(l + '\n')

            # write metadata
            with open(txt_metadata_path, 'w') as m:
                m.write('empty_on_top = {}\n'.format(empty_on_top))
                m.write('empty_on_bottom = {}\n'.format(empty_on_bottom))
                m.write('saved = {}'.format(saved))

        else:
            print(error_message)
            f.write(error_message)

        print(' \n \n \n ')