# Module `crisprme_off`

CRISPRme performs predictive analysis and result assessment on population and individual specific CRISPR/Cas experiments. CRISPRme enumerates on- and off-target accounting simultaneously for substitutions, DNA/RNA bulges and common genetic variants from the 1000 genomes project. CRISPRme is based on CRISPRitz (Cancellieri, Samuele, et al. "Crispritz: rapid, high-throughput, and variant-aware in silico off-target site identification for crispr genome editing." Bioinformatics (2019).) a software tool for population target analyses. CRISPRme is devoted to individual specific on- and off-target analyses.

Documentation updated up to 07/07/2020

Launch crisprme, NOTE - conda must be active when starting the app:

- python crisprme_off.py
- gunicorn -b :8080 crisprme_off:app.server

Generate docs:

- pdoc crisprme_off.py -o docsCrisprme/ –force –html

Expand source code

    '''
    CRISPRme performs predictive analysis and result assessment on population and individual specific CRISPR/Cas experiments.
    CRISPRme enumerates on- and off-target accounting simultaneously for substitutions, DNA/RNA bulges and common genetic variants from the 1000
    genomes project. CRISPRme is based on CRISPRitz (Cancellieri, Samuele, et al. "Crispritz: rapid, high-throughput, and variant-aware in silico off-target site identification for crispr genome editing." Bioinformatics (2019).)
    a software tool for population target analyses. CRISPRme is devoted to individual specific on- and off-target analyses.

    Documentation updated up to 07/07/2020

    Launch crisprme, NOTE - conda must be active when starting the app:

    + python crisprme_off.py
    + gunicorn -b :8080 crisprme_off:app.server

    Generate docs:

    + pdoc crisprme_off.py -o docsCrisprme/ --force --html
    '''
    import dash
    from dash.dependencies import Input, Output, State
    import dash_core_components as dcc
    import dash_html_components as html
    import dash_daq as daq
    import dash_table
    from dash.exceptions import PreventUpdate
    from os import listdir                      #for getting directories
    from os.path import isfile, isdir,join      #for getting directories
    import subprocess
    import base64                               #for decoding upload content
    import io                                   #for decoding upload content
    import pandas as pd                         #for dash table
    import json                                 #for getting and saving report images list
    from os import getcwd
    import time                                 #measure time for loading df table
    from flask_caching import Cache             #for cache of .targets or .scores
    import os
    import string                               #for job id
    import random                               #for job id
    import sys                                  #for sys.exit()
    import filecmp                              #check if Params files are equals
    import dash_bootstrap_components as dbc
    import collections                          #For check if guides are the same in two results
    from datetime import datetime               #For time when job submitted
    from seq_script import extract_seq, convert_pam
    from additional_pages import help_page
    from additional_pages import contacts_page
    from additional_pages import genomes_page as gen_page
    import re                                   #For sort chr filter values
    import concurrent.futures                           #For workers and queue
    import math
    from PostProcess.supportFunctions.loadSample import associateSample
    from PostProcess import CFDGraph
    from additional_pages import get_genomes
    try:
        from GUI import GUImessage as Gmsg
    except ImportError:
        pass
    import webbrowser as wb     #Open CRISPRme on browser

    try:
        import Tkinter as tk
    except ImportError:
        import tkinter as tk

    try:
        import ttk
        py3 = False
    except ImportError:
        import tkinter.ttk as ttk
        py3 = True

    from tkinter import filedialog, END, IntVar, messagebox

    #Warning symbol \u26A0
    app_location = os.path.realpath(__file__)
    app_main_directory = os.path.dirname(app_location) + '/'    #This for scripts
    current_working_directory = os.getcwd() + '/'               #This for files

    exeggutor = concurrent.futures.ProcessPoolExecutor(max_workers=2)

    PAGE_SIZE = 10                    #number of entries in each page of the table in view report
    BARPLOT_LEN = 4                   #number of barplots in each row of Populations Distributions
    #Columns for dash datatable in REF search
    COL_REF = ['Bulge Type', 'crRNA', 'DNA', 'Chromosome', 'Position', 'Cluster Position' ,'Direction', 'Mismatches', 'Bulge Size', 'Total', 'Annotation Type']
    COL_REF_TYPE = ['text','text','text','text','numeric', 'numeric','text','numeric', 'numeric', 'numeric', 'text']
    COL_REF_RENAME = {0:'Bulge Type', 1:'crRNA', 2:'DNA', 3:'Chromosome', 4:'Position', 5:'Cluster Position', 6:'Direction',
                    7:'Mismatches', 8:'Bulge Size', 9:'Total',10:'Correct Guide', 11:'Annotation Type'}
    #Columns for dash datatable in VAR and BOTH search
    COL_BOTH = ['Bulge Type', 'crRNA', 'DNA', 'Chromosome', 'Position', 'Cluster Position','Direction', 'Mismatches', 'Bulge Size', 'Total', 'PAM Creation', 'Samples Summary', 'Annotation Type']
    COL_BOTH_TYPE = ['text','text','text','text','numeric','numeric', 'text','numeric', 'numeric', 'numeric', 'text', 'text', 'text']
    COL_BOTH_RENAME = {0:'Bulge Type', 1:'crRNA', 2:'DNA', 3:'Chromosome', 4:'Position', 5:'Cluster Position', 6:'Direction',
            7:'Mismatches', 8:'Bulge Size', 9:'Total', 10:'PAM Creation', 11 : 'Variant Unique', 12:'Samples', 13:'Annotation Type', 14:'Correct Guide'}
    GENOME_DATABASE = ['Reference', 'Enriched', 'Samples', 'Dictionary', 'Annotation']
    ONLINE = False              #NOTE change to True for online version, False for offline
    if ONLINE:
        DISPLAY_OFFLINE = 'none'
        DISPLAY_ONLINE = ''
    else:
        DISPLAY_OFFLINE = ''
        DISPLAY_ONLINE = 'none'
    VALID_CHARS = {'a', 'A', 't', 'T', 'c', 'C','g', 'G',
                "R",
                "Y",
                "S",
                "W",
                "K",
                "M",
                "B" ,
                "D" ,
                "H" ,
                "V" ,
                "r",
                "y",
                "s",
                "w",
                "k",
                "m",
                "b" ,
                "d" ,
                "h" ,
                "v"
                }
    URL = 'http://157.27.85.10:8050'       #Change for online version
    external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css', dbc.themes.BOOTSTRAP]
    app = dash.Dash(__name__, external_stylesheets=external_stylesheets)

    app.title = 'CRISPRme'
    app.config['suppress_callback_exceptions'] = True       #necessary if update element in a callback generated in another callback
    app.css.config.serve_locally = True
    app.scripts.config.serve_locally = True
    CACHE_CONFIG = {
        # try 'filesystem' if you don't want to setup redis
        'CACHE_TYPE': 'filesystem',
        'CACHE_DIR': ('Cache')#os.environ.get('REDIS_URL', 'localhost:6379')
    }
    cache = Cache()
    cache.init_app(app.server, config=CACHE_CONFIG)
    app_location = os.path.dirname(os.path.abspath(__file__)) + '/'
    operators = [['ge ', '>='],
                 ['le ', '<='],
                 ['lt ', '<'],
                 ['gt ', '>'],
                 ['ne ', '!='],
                 ['eq ', '='],
                 ['contains ']]     #for filtering

    #Populations 1000 gen proj
    # population_1000gp = {
    #     'EAS':['CHB', 'JPT', 'CHS', 'CDX', 'KHV'],
    #     'EUR':['CEU', 'TSI', 'FIN', 'GBR', 'IBS'],
    #     'AFR':['YRI', 'LWK', 'GWD', 'MSL', 'ESN', 'ASW', 'ACB'],
    #     'AMR':['MXL', 'PUR', 'CLM', 'PEL'],
    #     'SAS':['GIH', 'PJL', 'BEB', 'STU', 'ITU']
    # }
    # dict_pop_to_superpop = {'CHB':'EAS', 'JPT':'EAS', 'CHS':'EAS', 'CDX':'EAS', 'KHV':'EAS',
    #                     'CEU':'EUR', 'TSI':'EUR', 'FIN':'EUR', 'GBR':'EUR', 'IBS':'EUR',
    #                     'YRI':'AFR', 'LWK':'AFR', 'GWD':'AFR', 'MSL':'AFR', 'ESN':'AFR', 'ASW':'AFR', 'ACB':'AFR',
    #                     'MXL':'AMR', 'PUR':'AMR', 'CLM':'AMR', 'PEL':'AMR',
    #                     'GIH':'SAS', 'PJL':'SAS', 'BEB':'SAS', 'STU':'SAS', 'ITU':'SAS'
    # }
    #List of all samples
    # pop_file = pd.read_excel(os.path.dirname(os.path.realpath(__file__)) + '/PostProcess/20130606_sample_info.xlsx')
    # all_samples = pop_file.Sample.to_list()
    # all_pop = pop_file.Population.to_list()
    # dict_pop = dict()
    # dict_sample_to_pop = dict()
    # for  pos, i in enumerate(all_pop):
        # try:
        #     dict_pop[i].append(all_samples[pos])
        # except:
        #     dict_pop[i] = [all_samples[pos]]

        # dict_sample_to_pop[all_samples[pos]] = i
    # dropdown_all_samples = [{'label': sam, 'value' : sam} for sam in all_samples]
    #Dropdown available genomes
    def availableGenomes():
        '''
        Returns a list of dictionaries of the available genomes in the 'Genomes' directory.

        ***Returns***

        + **gen_dir** (*list* of {'label': genome, 'value': genome}): list containing a series of dictionaries, one for each directory (genome) found in
        the 'Genomes' directory. Used as input parameter for the 'options' element of a Dash Droplist
        '''
        onlydir = [f for f in listdir(current_working_directory + 'Genomes') if isdir(join(current_working_directory + 'Genomes', f))]
        onlydir = [x.replace('_', ' ') for x in onlydir]
        gen_dir = []
        for dir in onlydir:
            gen_dir.append({'label': dir, 'value' : dir})
        return gen_dir

    #Dropdown available PAM
    def availablePAM():
        '''
        Returns a list of dictionaries of the available PAMs in the 'PAM' directory.

        ***Returns***

        + **pam_file** (*list* of {'label': pam, 'value': pam}): list containing a series of dictionaries, one for each PAM file found in
            the 'PAM' directory. Used as input parameter for the 'options' element of a Dash Droplist
        '''
        onlyfile = [f for f in listdir(current_working_directory + 'pam') if isfile(join(current_working_directory + 'pam', f))]
        onlyfile = [x.replace('.txt', '') for x in onlyfile]            #removed .txt for better visualization
        pam_file = []
        for pam_name in onlyfile:
            if 'tempPAM' in pam_name:   #Skip the temp pam used for updating dictionaries
                pass
            else:
                pam_file.append({'label':pam_name, 'value':pam_name})
        return pam_file

    #Available mismatches and bulges
    if ONLINE:
        set_max_mms = 7
        set_max_bulges = 3
    else:
        set_max_mms = 7             #NOTE modify value for increasing/decreasing max mms or bulges available on Dropdown selection
        set_max_bulges = 3

    av_mismatches = [{'label': i, 'value': i} for i in range(0, set_max_mms)]
    av_bulges = [{'label': i, 'value': i} for i in range(0, set_max_bulges)]
    av_guide_sequence = [{'label': i, 'value': i} for i in range(15, 26)]
    search_bar = dbc.Row(
        [
            #dbc.Col(dbc.Input(type="search", placeholder="Search")),
            dbc.Col(dbc.NavLink(
                html.A('HOME', href = URL, target = '_blank', style = {'text-decoration':'none', 'color':'white'}),
                active = True,
                className= 'testHover', style = {'text-decoration':'none', 'color':'white', 'font-size':'1.5rem'})),
            dbc.Col(dbc.NavLink(
                html.A('MANUAL', href = URL + '/user-guide',target = '_blank', style = {'text-decoration':'none', 'color':'white'}),
                active = True,
                className= 'testHover', style = {'text-decoration':'none', 'color':'white', 'font-size':'1.5rem'})),
            dbc.Col(dbc.NavLink(
                html.A('CONTACTS', href = URL + '/contacts', target = '_blank', style = {'text-decoration':'none', 'color':'white'}),
                active = True,
                className= 'testHover', style = {'text-decoration':'none', 'color':'white', 'font-size':'1.5rem'})),
            dbc.Col(dbc.NavLink(
                html.A('GENOMES', href = URL + '/genomes', target = '_blank', style = {'text-decoration':'none', 'color':'white'}),
                active = True,
                className= 'testHover', style = {'text-decoration':'none', 'color':'white', 'font-size':'1.5rem', 'display':DISPLAY_OFFLINE})),
            dbc.Col(dbc.NavLink(
                html.A('HISTORY', href = URL + '/history', target = '_blank', style = {'text-decoration':'none', 'color':'white'}),
                active = True,
                className= 'testHover', style = {'text-decoration':'none', 'color':'white', 'font-size':'1.5rem', 'display':DISPLAY_OFFLINE}))
        ],
        no_gutters=True,
        className="ml-auto flex-nowrap mt-3 mt-md-0",
        align="center",
    )
    PLOTLY_LOGO = 'assets/37143442.png'#"https://images.plot.ly/logo/new-branding/plotly-logomark.png"

    navbar = dbc.Navbar(
        [
            html.A(
                # Use row and col to control vertical alignment of logo / brand
                dbc.Row(
                    [
                        dbc.Col(html.Img(src=PLOTLY_LOGO, height="60px")),
                        dbc.Col(dbc.NavbarBrand("CRISPRme", className="ml-2", style = {'font-size': '30px'}))
                    ],
                    align="center",
                    no_gutters=True,
                ),
                href=URL,
            ),
            dbc.NavbarToggler(id="navbar-toggler"),
            dbc.Collapse(search_bar, id="navbar-collapse", navbar=True),
        ],
        color="dark",
        dark=True,
    )

    #For multipage
    app.layout = html.Div([
        navbar,
        dcc.Location(id='url', refresh=False),
        html.Div(id='page-content'),
        html.P(id = 'signal', style = {'visibility':'hidden'})
    ])

    #Main Page
    def indexPage():
        '''
        Creates the layout of the main page ('/'). The creation of the main page is put under a function in order to reload the genome and pam dropdown
        if a new genome is added, simply by reloading the page.

        ***Returns***

        + **index_page** (*list*): list of html, dcc and dbc components for the layout.
        '''
        final_list = []
        final_list.extend([
            html.Div([
                'CRISPRme  performs  predictive analysis and result assessment on population and individual specific CRISPR/Cas experiments.' +
                ' CRISPRme enumerates on- and off-target accounting simultaneously for  substitutions, DNA/RNA bulges and common genetic variants from the 1000 genomes project.'+
                ' CRISPRme is based on CRISPRitz [1] a software tool for population target analyses.'
            + ' CRISPRme is devoted to individual specific on- and off-target analyses.'
            ]),
            ])

        final_list.append(
            html.Div(
                [
                    # html.P(['Download the offline version here: ', html.A('InfOmics/CRISPRitz', href = 'https://github.com/InfOmics/CRISPRitz', target="_blank"), ' or ', html.A('Pinellolab/CRISPRitz', href = 'https://github.com/pinellolab/CRISPRitz', target="_blank") ])
                    html.Br()
                ]
            )
        )
        checklist_div = html.Div(
            [
                dbc.FormGroup(
                    [
                        dbc.Checkbox(
                            id="checkbox-gecko", className="form-check-input", checked = True
                        ),
                        dbc.Label(
                            #html.P(['Activate Gecko ', html.Abbr('comparison', title ='The results of your test guides will be compared with results obtained from a previous computed analysis on gecko library')]) ,
                            html.P('Compare your results with the GeCKO v2 library'),
                            html_for="checkbox-gecko",
                            className="form-check-label",
                        ),
                        dbc.Checkbox(
                            id="checkbox-ref-comp", className="form-check-input"
                        ),
                        dbc.Label(
                            html.P('Compare your results with the corresponding reference genome'),
                            html_for="checkbox-ref-comp",
                            className="form-check-label",
                        )

                    ],
                    check = True
                )
            ],
            id = 'checklist-test-div'
        )

        modal = html.Div(
            [
                dbc.Modal(
                    [
                        dbc.ModalHeader("WARNING! Missing inputs"),
                        dbc.ModalBody('The following inputs are missing, please select values before submitting the job', id = 'warning-list'),
                        dbc.ModalFooter(
                            dbc.Button("Close", id="close" , className="modal-button")
                        ),
                    ],
                    id="modal",
                    centered=True
                ),
            ]
        )

        tab_guides_content = html.Div(
            [
                html.P([
                    'Insert crRNA sequence(s), one per line.',
                    html.P('Sequences must have the same length and be provided without the PAM sequence', id = 'testP') ,
                ],
                style = {'word-wrap': 'break-word'}),

                dcc.Textarea(id = 'text-guides', placeholder = 'GAGTCCGAGCAGAAGAAGAA\nCCATCGGTGGCCGTTTGCCC', style = {'width':'450px', 'height':'160px', 'font-family':'monospace', 'font-size':'large'}),
                dbc.FormText('Note: a maximum number of 1000 sequences can be provided', color = 'secondary')
            ],
            style = {'width':'450px'} #NOTE same as text-area
        )
        tab_sequence_content = html.Div(
            [
                html.P(['Search crRNAs by inserting one or more genomic sequences.', html.P('Chromosome ranges can also be supplied')],
                style = {'word-wrap': 'break-word'}),

                dcc.Textarea(id = 'text-sequence', placeholder = '>sequence 1\nAAGTCCCAGGACTTCAGAAGagctgtgagaccttggc\n>sequence2\nchr1:11,130,540-11,130,751', style = {'width':'450px', 'height':'160px', 'font-family':'monospace', 'font-size':'large'}),
                dbc.FormText('Note: a maximum number of 1000 characters can be provided', color = 'secondary')
            ],
            style = {'width':'450px'} #NOTE same as text-area
        )
        final_list.append(
            html.Div(
                html.Div(
                    [
                        modal,
                        html.Div(
                            [
                                html.Div([
                                    html.H3('STEP 1', style = {'margin-top':'0'}),
                                    html.Div([
                                        html.P([html.Button(html.P("Load example", style={'color':'rgb(46,140,187)','text-decoration-line': 'underline', 'font-size':'initial'}), id='example-parameters',
                                                    style={ 'border': 'None', 'text-transform': 'capitalize','height':'12','font-weight': '500', 'padding': '0 0px','textcolor':'blu'}), ' - ',
                                        #html.Br(),
                                        html.Button(html.P(children="Reset", style={'color':'rgb(46,140,187)','text-decoration-line': 'underline','font-size':'initial'}), id='remove-parameters',
                                                    style={'border': 'None', 'text-transform': 'capitalize','height':'12','font-weight': '500', 'padding': '0 0px'})])
                                    ], style = {'display':DISPLAY_ONLINE})
                                ], className = 'flex-div-insert-delete-example'),

                                html.P(html.P('Select a genome') ),
                                html.Div(
                                    dcc.Dropdown(options = availableGenomes(), clearable = False, id = "available-genome",) #style = {'width':'75%'})
                                ),
                                dbc.FormText('Note: Genomes enriched with variants are indicated with a \'+\' symbol', color='secondary'),

                                html.Div(
                                    [
                                        html.Div(
                                            [
                                                html.P(html.P('Select PAM')),
                                                html.Div(
                                                    dcc.Dropdown(options = availablePAM(), clearable = False, id = 'available-pam')
                                                )
                                            ],
                                            style = {'flex':'0 0 100%', 'margin-top': '10%'}
                                        )
                                    ],
                                    id = 'div-pam',
                                    className = 'flex-div-pam'
                                ),
                                html.Br(),
                                html.Button("Add New Genome", id = 'add-genome', style = {'margin-right':'5px', 'display':DISPLAY_OFFLINE}),
                                html.Div('', id = 'genome-job', style = {'display':'none'}),
                                html.Button("Update dictionary", id = 'update-dict', style = {'margin-left':'5px', 'display':DISPLAY_OFFLINE}),
                                html.Div('', id = 'dict-job', style = {'display':'none'}),
                                html.Div(
                                    [
                                        html.Ul(
                                            [html.Li(
                                                [html.A('Contact us', href = URL + '/contacts', target="_blank"),' to request new genomes availability in the dropdown list'],
                                                style = {'margin-top':'5%', 'display': DISPLAY_ONLINE}
                                            ),
                                            ],
                                            style = {'list-style':'inside'}
                                        ),
                                    ],
                                    style = {'height':'50%'}
                                ),
                            ],
                            id = 'step1',
                            style = {'flex':'0 0 30%', 'tex-align':'center'}
                        ),
                        html.Div(style = {'border-right':'solid 1px white'}),
                        html.Div(
                            [
                                html.H3('STEP 2', style = {'margin-top':'0'}),
                                html.Div(
                                    [
                                        html.Div(
                                            [   html.P('Select the input type'),
                                                dbc.Tabs(
                                                    [
                                                        dbc.Tab(tab_guides_content, label='Guides', tab_id= 'guide-tab'),
                                                        dbc.Tab(tab_sequence_content, label='Sequence', tab_id = 'sequence-tab')
                                                    ],
                                                    active_tab='guide-tab',
                                                    id = 'tabs'
                                                )
                                            ],
                                            id = 'div-guides'
                                        ),
                                        html.Div(
                                            [
                                                html.P('Allowed mismatches'),
                                                dcc.Dropdown(options = av_mismatches, clearable = False, id = 'mms', style = {'width':'60px'}),
                                                html.P('Bulge DNA size'),
                                                dcc.Dropdown(options = av_bulges, clearable = False, id = 'dna', style = {'width':'60px'}),
                                                html.P('Bulge RNA size'),
                                                dcc.Dropdown(options = av_bulges, clearable = False, id = 'rna', style = {'width':'60px'}),
                                                dbc.Fade(
                                                    [
                                                        html.P('crRNA length (without PAM)'),
                                                        dcc.Dropdown(options = av_guide_sequence, clearable = False, id = 'len-guide-sequence-ver', style = {'width':'60px'})
                                                    ],
                                                    id = 'fade-len-guide', is_in= False, appear= False
                                                )
                                            ]
                                        )
                                    ],
                                    className = 'flex-step2'
                                )

                            ],
                            id = 'step2',
                            style = {'flex':'0 0 40%'}

                        ),
                        html.Div(style = {'border-right':'solid 1px white'}),
                        html.Div(
                            [
                                html.H3('Advanced Options', style = {'margin-top':'0px'}),
                                checklist_div,
                                dcc.Checklist(
                                    options = [
                                        {'label':'Notify me by email','value':'email', 'disabled':False}
                                    ],
                                    id = 'checklist-advanced',
                                    style = {'display': DISPLAY_ONLINE}
                                ),
                                dbc.Fade(
                                    [
                                        dbc.FormGroup(
                                            [
                                                dbc.Label("Email", html_for="example-email"),
                                                dbc.Input(type="email", id="example-email", placeholder="Enter email", className='exampleEmail')
                                            ]
                                        )
                                    ],
                                    id = 'fade', is_in= False, appear= False,
                                ),
                                html.Div(
                                    [
                                        html.Button('Submit', id = 'check-job', style = {'background-color':'skyblue'}),
                                        html.Button('', id = 'submit-job', style = {'display':'none'})
                                    ],
                                    style = {'display':'inline-block', 'margin':'0 auto'}   #style="height:55px; width:150px"
                                )
                            ],
                            id = 'step3',
                            style = {'tex-align':'center'},
                            className = 'flex-step3'
                        )
                    ],
                    id = 'div-steps',
                    style = {'margin':'1%'},
                    className = 'flex-div-steps'
                ),
                style = {'background-color':'rgba(154, 208, 150, 0.39)', 'border-radius': '10px', 'border':'1px solid black'},
                id = 'steps-background'
            )
        )
        final_list.append(html.Br())
        final_list.append(html.P('[1] Cancellieri, Samuele, et al. \"Crispritz: rapid, high-throughput, and variant-aware in silico off-target site identification for crispr genome editing.\" Bioinformatics (2019).'))
        final_list.append(
            html.P(['Download CRISPRitz here: ', html.A('InfOmics/CRISPRitz', href = 'https://github.com/InfOmics/CRISPRitz', target="_blank"), ' or ', html.A('Pinellolab/CRISPRitz', href = 'https://github.com/pinellolab/CRISPRitz', target="_blank") ])
        )
        index_page = html.Div(final_list, style = {'margin':'1%'})
        return index_page

    #Load Page
    final_list = []
    final_list.append(
        html.Div(
            html.Div(
                html.Div(
                    [
                        html.P('Job submitted. Copy this link to view the status and the result page '),
                        html.Div(
                            html.P('link', id = 'job-link', style = {'margin-top':'0.75rem', 'font-size':'large'}),
                            style = {'border-radius':'5px','border':'2px solid', 'border-color':'blue' ,'width':'100%','display':'inline-block', 'margin':'5px'}
                        ),
                        html.P('Results will be kept available for 3 days')
                    ],
                    style = {'display':'inline-block'}
                ),
                style = {'display':'inline-block','background-color':'rgba(154, 208, 150, 0.39)', 'border-radius': '10px', 'border':'1px solid black', 'width':'70%'}
            ),
            style = {'text-align':'center'}
        )
    )

    final_list.append(
        html.Div(
            [
                html.H4('Status report'),
                html.Div(
                    [
                        html.Div(
                            html.Ul(
                                [
                                    html.Li('Searching crRNA'),
                                    html.Li('Processing data'),
                                    #html.Li('Annotating result'),
                                    html.Li('Generating report')
                                ]
                            ),
                            style = {'flex':'0 0 20%'}
                        ),
                        html.Div(
                            html.Ul(
                                [
                                    html.Li('To do', style = {'color':'red'}, id = 'search-status'),
                                    html.Li('To do', style = {'color':'red'}, id = 'post-process-status'),
                                    #html.Li('To do', style = {'color':'red'}, id = 'annotate-result-status'),
                                    html.Li('To do', style = {'color':'red'}, id = 'generate-report-status')
                                ],
                                style = {'list-style-type':'none'}
                            )
                        )
                    ],
                    className = 'flex-status'
                ),
                html.Div(
                    [
                        dcc.Link('View Results', style = {'visibility':'hidden'}, id = 'view-results', href = URL),
                        html.Div(id = 'no-directory-error')
                    ]
                )
            ],
            id = 'div-status-report'
        )
    )

    final_list.append(html.P('', id = 'done'))

    final_list.append(dcc.Interval(id = 'load-page-check', interval=3*1000))
    load_page = html.Div(final_list, style = {'margin':'1%'})

    #Test page, go to /test-page to see
    def test_page():
        final_list = []
        final_list.append(html.Div(id='test-div-for-button'))
        final_list.append(
            html.H3('Genomes')
        )
        final_list.append(
            html.P('Select one of the two available Tabs and fill in the field to add a new Genome or to update and existing dictionary.')
        )

        #Check if an already processing add new genome is currently active. If yes, set the button to be disabled
        already_processing = False
        status_label = 'Status: No Job Submitted'
        status_value = 0
        style_disabled = {}
        if isfile(current_working_directory + 'Genomes/' + 'aggiunta_nuovo_genoma.txt' ):
            already_processing = True
            style_disabled = {'background-color':'darkgrey'}
            with open(current_working_directory + 'Genomes/' + 'aggiunta_nuovo_genoma.txt') as info_status:
                status_general = next(info_status).strip().split(' ') #Get current step number [0] and step name [1] on accessing page for first time
                status_label = 'Status: ' + status_general[1]
                status_value = int(status_general[0]) * 25

        new_genome_content = html.Div(
            [
                html.Br(),
                html.P('1) Select a Reference Genome'),
                html.Br(),
                dbc.Row(
                    [
                        dbc.Col(
                            [
                                dbc.Row(
                                    [
                                        dbc.Col(html.Button('Select Reference Genome Directory', id = 'button-select-refgenome')),
                                        dbc.Col(html.P('Selected: None', id = 'selected-referencegenome'))
                                    ]
                                )
                            ]
                        ),
                        dbc.Col(
                            [
                                dbc.Row(
                                    [
                                        dbc.Col(html.Button('Select PAM File', id = 'button-select-pam')),
                                        dbc.Col(html.P('Selected: None', id = 'selected-pamfile'))
                                    ]
                                )
                            ]
                        ),

                    ]
                ),
                html.Br(),
                dbc.Row(
                    [
                        dbc.Col(
                            [
                                dbc.Row(
                                    [
                                        dbc.Col(html.Button('Select Annotation File', id = 'button-select-annotation')),
                                        dbc.Col(html.P('Selected: None', id = 'selected-annotationfile'))
                                    ]
                                ),

                            ]
                        ),
                        dbc.Col(
                                dbc.Row(
                                    [
                                        dbc.Col(dcc.Input(placeholder = 'Select number of max Bulges', type = 'number', min = 0, id = 'input-max-bulges'))
                                    ]
                                )
                            )

                    ]
                ),
                html.Hr(),
                html.P('2) (Optional) Enrich the previously selected Reference Genome with samples informations'),
                html.Br(),
                dbc.Row(
                    [
                        dbc.Col(
                            [
                                dbc.Row(
                                    [
                                        dbc.Col(html.Button('Select VCFs Directory', id = 'button-select-vcf')),
                                        dbc.Col(html.P('Selected: None', id = 'selected-vcf'))
                                    ]
                                )
                            ]
                        ),
                        dbc.Col(
                            [
                                dbc.Row(
                                    [
                                        dbc.Col(html.Button('Select Samples ID File', id = 'button-select-sampleID')),
                                        dbc.Col(html.P('Selected: None', id = 'selected-sampleIDfile'))
                                    ]
                                ),

                            ]
                        )
                    ]
                ),
                html.Br(),
                dbc.Row(
                    [
                        dbc.Col(
                            dbc.Row(
                                [
                                    dbc.Col(html.P('Enriched Genome Name')),
                                    dbc.Col(dcc.Input(placeholder = 'Example: 1000genomeproject', id = 'input-enriched-name'))
                                ]
                            )
                        )
                    ]
                ),
                html.Hr(),
                dbc.Row(
                    [
                        dbc.Col(
                            html.Button('Start add new genome', id = 'button-add-new-genome', disabled = already_processing, style = style_disabled)
                        ),
                        dbc.Col(
                            [
                                dbc.Row(
                                    dbc.Col(
                                        html.P(status_label, id = 'status-add-new-genome')
                                    )
                                ),
                                dbc.Row(
                                    dbc.Col(
                                        html.Div(
                                            [
                                                dbc.Progress(value = status_value, id = 'progress-add-new-genome'),
                                                dcc.Interval(interval = 30*1000,id='interval-add-new-genome')
                                            ]
                                        )
                                    )
                                )
                            ]
                        )
                    ]
                )
            ]
        )

        update_dictionary_content = html.Div() #TODO finire con layout simile a quello per aggiungere nuovo genoma

        final_list.append(
            dbc.Tabs(
                [
                    dbc.Tab(new_genome_content, label='Add New Genome', tab_id= 'add-genome-tab'),
                    dbc.Tab(update_dictionary_content, label='Update Dictionary', tab_id = 'update-dictionary')
                ],
                active_tab='add-genome-tab',
                id = 'tabs-new-genome-or-dictionary'
            )
        )

        final_list.append(html.Div(id = 'div-targetoutput', style = {'display':'none'}))
        return html.Div(final_list, style = {'margin':'1%'})
    # test_page = html.Div(final_list, style = {'margin':'1%'})

    #TEST PAGE 2
    final_list = []

    test_page2 = html.Div(final_list, style = {'margin':'1%'})

    #TEST page3 for new result page
    final_list = []

    test_page3 = html.Div(final_list, style = {'margin':'1%'})

    #ABOUT PAGE

    about_page =  html.Div(help_page.helpPage(), style = {'margin':'1%'})
    #Contacts page
    final_list = []
    contacts_page =  html.Div(contacts_page.contactPage(), style = {'margin':'1%'})

    ##################################################CALLBACKS##################################################
    #Test callbacks

    #General function for selecting a Directory or a file
    def openDialog(n, type_ask, start_dir = './'):
        if n is None:
            raise PreventUpdate
        root = tk.Tk()
        root.withdraw()
        if type_ask == 'D':
            selected = filedialog.askdirectory(initialdir = current_working_directory + start_dir)
        else:
            selected = filedialog.askopenfilename(initialdir = current_working_directory + start_dir)
        root.destroy()
        if selected == '' or selected == '()':
            selected = 'None'
        return str(selected)

    #Callbacks for generating filebrowser for addition of new genome
    @app.callback(
        Output('selected-referencegenome', 'children'),
        [Input('button-select-refgenome','n_clicks')]
    )
    def fileDialogRefGenome(n):
        return 'Selected: ' + openDialog(n, 'D','Genomes')

    @app.callback(
        Output('selected-pamfile', 'children'),
        [Input('button-select-pam','n_clicks')]
    )
    def fileDialogPam(n):
        return 'Selected: ' + openDialog(n, 'F')

    @app.callback(
        Output('selected-annotationfile', 'children'),
        [Input('button-select-annotation','n_clicks')]
    )
    def fileDialogAnnotation(n):
        return 'Selected: ' + openDialog(n, 'F','annotations')

    @app.callback(
        Output('selected-vcf', 'children'),
        [Input('button-select-vcf','n_clicks')]
    )
    def fileDialogVCF(n):
        return 'Selected: ' + openDialog(n, 'D')

    @app.callback(
    Output('selected-sampleIDfile', 'children'),
    [Input('button-select-sampleID','n_clicks')]
    )
    def fileDialogSamplesID(n):
        return 'Selected: ' + openDialog(n, 'F','samplesID')

    #Callback creazione nuovo genoma
    @app.callback(
        Output('div-targetoutput','children'),
        [Input('button-add-new-genome', 'n_clicks')]
    )
    def startAddNewGenome(n):
        if n is None:
            raise PreventUpdate
        #TODO: prende gli state dei valori selezionati dall'utente e controlla se sono stati tutti selezionati, altrimenti fa comparire un
        #avviso che manca qualcosa (cfr. funzione checkInput, ma basta solo avere l'elenco dei campi da compilare)
        #...
        #...

        #input corretti
        #Faccio un subprocess.Popen facendo partire lo script per generare il nuovo genoma
        #Lo script.sh crea un file nella cartella Genomes chiamato 'status_creation_nomegenoma.txt' dove nomegenoma è ref o enr in base alla
        #selezione dell'utente
        #In questo script faccio echo dei vari step in cui sono in questo momento con l'analisi, in modo poi da leggere questo file e con un interval
        #dire all'utente a che punto siamo
        print('Button clicked')
        subprocess.Popen([app_main_directory + 'PostProcess/aggiunta_genoma_test_script.sh', current_working_directory])
        return ''

    #Callback per aggiornare la barra progresso
    @app.callback(
        [Output('progress-add-new-genome', 'value'),
        Output('status-add-new-genome','children'),
        Output('button-add-new-genome', 'disabled'),
        Output('button-add-new-genome', 'style')],
        [Input('interval-add-new-genome','n_intervals'),
        Input('button-add-new-genome','n_clicks')]
    )
    def updateStatusCreateNewGenome(n, n_button):
        if n is None and n_button is None:
            raise PreventUpdate

        context = dash.callback_context.triggered[0]['prop_id'].split('.')[0] #id of input that triggered callback
        if context == 'button-add-new-genome':
            return 0, 'Status: Copy Genome', True,  {'background-color':'darkgrey'}
        #TODO questa funzione legge il file creato dalla funzione startAddNewGenome e in base allo step in cui siamo (Copia file, indicizzazione, enrichment etc)
        #ritorna in output una valore della barra (eg +25 per ogni step fatto) e lo step a cui siamo (eg Status: Enrichment Genome)

        #controlla se esiste il file aggiunta nuovo genoma, e aggiorna la barra e mette il bottone a Disabled = True
        if isfile(current_working_directory + 'Genomes/aggiunta_nuovo_genoma.txt'):
            with open(current_working_directory + 'Genomes/aggiunta_nuovo_genoma.txt') as info_status:
                a = next(info_status).strip().split(' ') #Get current step number [0] and step name [1]
                if 'Done' == a[1]:
                    subprocess.run(['rm', current_working_directory + 'Genomes/' + 'aggiunta_nuovo_genoma.txt'])
                    return 100, 'Status: Done', False, {}
            return str(int(a[0]) * 25), 'Status: ' + a[1], True,  {'background-color':'darkgrey'}
        return 0 , 'Status: No Job Submitted', False, {}
    #################################################
    #Fade in/out email
    @app.callback(
        Output("fade", "is_in"),
        [Input("checklist-advanced", "value")],
        [State("fade", "is_in")],
    )
    def toggle_fade(selected_options, is_in):
        '''
        Manages the fading of the InputBox for the email when the option 'Notify me by email' is selected/deselected.

        ***Args***

        + [**selected_options**] **checklist-advanced** (*value*): list of IDs of the options checked
        + [**is_in**] **fade** (*is_in*): True if the Input email element is displayed, False otherwise

        ***Returns***

        + **fade** (*is_in*): True in order to show the Input email element, False to hide it
        '''
        if  selected_options is None:
            return False
        if 'email' in selected_options:
            return True
        return False

    # Insert/Delete example input
    @app.callback(
        [Output('available-genome', 'value'),
         Output('available-pam', 'value'),
         Output('text-guides', 'value'),
         Output('mms', 'value'),
         Output('dna', 'value'),
         Output('rna', 'value'),
         Output('len-guide-sequence-ver', 'value'),
         Output('text-sequence', 'value')],
         [Input('example-parameters', 'n_clicks_timestamp'),
         Input('remove-parameters', 'n_clicks_timestamp')]
    )
    def inExample(nI, nR):
        '''
        Inserts an example value in all fields to show the user an example result, or reset the input fields. NOT available in OFFLINE version.

        ***Args***

        + [**nI**] **example-parameters** (*n_clicks_timestamp*): button that inserts the example values
        + [**nR**] **remove-parameters** (*n_clicks_timestamp*): button that removes the values and resets all the fields

        ***Returns***

        + **available-genome** (*value*): 'hg38 ref+hg38 1000genomeproject' as input example, or '' to reset the value
        + **available-pam** (*value*): '20bp-NGG-SpCas9' as input example, or '' to reset the value
        + **text-guides** (*value*): 'GAGTCCGAGCAGAAGAAGAA\\nCCATCGGTGGCCGTTTGCCC' as input example, or '' to reset the value
        + **mms** (*value*): '4' as input example, or '' to reset the value
        + **dna** (*value*): '1' as input example, or '' to reset the value
        + **rna** (*value*): '1' as input example, or '' to reset the value
        + **len-guide-sequence-ver** (*value*): '20' as input example, or '' to reset the value
        + **text-sequence** (*value*): '>sequence\\nTACCCCAAACGCGGAGGCGCCTCGGGAAGGCGAGGTGGGCAAGTTCAATGCCAAGCGTGACGGGGGA' as input example,
        or '' to reset the value
        '''

        if (nI is None) and (nR is None):
            raise PreventUpdate

        if nI is None:
            nI = 0

        if nR is None:
            nR = 0

        if nI > 0:
            if nI > nR:
                return 'hg38 ref+hg38 1000genomeproject', '20bp-NGG-SpCas9', 'GAGTCCGAGCAGAAGAAGAA\nCCATCGGTGGCCGTTTGCCC', '4', '1', '1', '20', '>sequence\nTACCCCAAACGCGGAGGCGCCTCGGGAAGGCGAGGTGGGCAAGTTCAATGCCAAGCGTGACGGGGGA'

        if nR > 0:
            if nR > nI:
                return '', '', '', '', '', '', '', ''

        # return '', '', '', '', '', '', ''

    #If selected genome has a '+', update advanced options comparison with reference
    @app.callback(
        Output('checkbox-ref-comp', 'checked'),
        [Input('available-genome', 'value')]
    )
    def suggestComparison(value):
        '''
        Update to Checked the Option 'Compare your results with the corresponding reference genome' if an Enriched Genome is selected ('+' is in the
        name of the genome)

        ***Args***

        + [**value**] **available-genome** (*value*): the name of the genome selected by the user in the Dropdown

        ***Returns***

        + **checkbox-ref-comp** [*checked*]: True if an Enriched genome is selected
        '''
        if value is None:
            raise PreventUpdate
        if '+' in value:
            return True
        raise PreventUpdate

    #Email validity
    @app.callback(
        Output('example-email', 'style'),
        [Input('example-email', 'value')]
    )
    def checkEmailValidity(val):
        '''
        Checks email validity (i.e. an '@' is present) and changes the border to green or red accordingly.

        ***Args***

        + [**val**] **example-email** (*value*): string of the email

        ***Returns***

        + **example-email** (*style*): dictionary of the style for the Input email: change border to red (if no '@' in **val**) or to green
        '''
        if val is None:
            raise PreventUpdate

        if '@' in val:
            return {'border':'1px solid #94f033', 'outline':'0'}
        return {'border':'1px solid red'}

    #Fade in guide len dropdown for sequence tabs version
    @app.callback(
        Output('fade-len-guide', 'is_in'),
        [Input('tabs', 'active_tab')],
        [State('fade-len-guide', 'is_in')]
    )
    def resetTab(current_tab, is_in):
        '''
        Manages the fading of the Dropdown for the guide length when the tab 'Sequence' is active.

        ***Args***

        + [**current_tab**] **tabs** (*active_tab*): string of the ID of the current active tab
        + [**is_in**] **fade-len-guide** (*is_in*): True if the Dropdown guide length element is displayed, False otherwise

        ***Returns***

        + **fade-len-guide** (*is_in*): True in order to show the Dropdown guide length element, False to hide it
        '''
        if current_tab is None:
            raise PreventUpdate

        if current_tab == 'guide-tab':
            return False
        return True

    #Check input presence
    @app.callback(
        [Output('submit-job', 'n_clicks'),
        Output('modal', 'is_open'),
        Output('available-genome', 'className'),
        Output('available-pam', 'className'),
        Output('text-guides', 'style'),
        Output('mms', 'className'),
        Output('dna', 'className'),
        Output('rna', 'className'),
        Output('len-guide-sequence-ver', 'className'),
        Output('warning-list', 'children')],
        [Input('check-job','n_clicks'),
        Input('close','n_clicks')],
        [State('available-genome', 'value'),
        State('available-pam','value'),
        State('text-guides', 'value'),
        State('mms','value'),
        State('dna','value'),
        State('rna','value'),
        State('len-guide-sequence-ver','value'),
        State('tabs','active_tab'),
        State("modal", "is_open")]
    )
    def checkInput(n, n_close, genome_selected, pam, text_guides, mms, dna, rna, len_guide_seq, active_tab ,is_open):
        '''
        Checks the presence and correctness of the input fields, changing their border to red if it's missing and displaying a Modal element with
        a list of the missing inputs. The callback is triggered when the user clicks on the 'Submit' button or when the modal is closed
        (by the 'Close' button or by clicking on-screen when the Modal element is open)

        ***Args***

        + [**n**] **check-job** (*n_clicks*): button that starts the job after checking the correctness of the input
        + [**n_close**] **close** (*n_clicks*): button that closes the opened modal
        + [**genome_selected**] **available-genome** (*value*): string of the selected genome from the Dropdown. `None` if not selected, '' if selected
        and then deleted
        + [**pam**] **available-pam** (*value*): string of the selected PAM from the Dropdown. `None` if not selected, '' if selected
        and then deleted
        + [**text_guides**] **text-guides** (*value*): string of the input guides from the Textarea. `None` if not selected, '' if selected
        and then deleted
        + [**mms**] **mms** (*value*): int of the selected mismatch value. `None` if not selected, '' if selected
        and then deleted
        + [**dna**] **dna** (*value*): int of the selected DNA bulge value. `None` if not selected, '' if selected
        and then deleted
        + [**rna**] **rna** (*value*): int of the selected RNA bulge value. `None` if not selected, '' if selected
        and then deleted
        + [**len_guide_seq**] **len-guide-sequence-ver** (*value*): int value of the length of the guides (available when 'Sequence' tab is active). `None` if not selected, '' if selected
        and then deleted
        + [**active_tab**] **tabs** (*active_tab*): string of the ID of the active tab ('Guide' or 'Sequence')
        + [**is_open**] **modal** (*is_open*): True if the modal is displayed, false otherwise

        ***Returns***

        + **submit-job** (*n_clicks*): reset the click counter (`None`) if some inputs are missing, else put to `1` in order to trigger the `changeUrl()`
        function and proceed with the job
        + **modal** (*is_open*): `True` if some inputs are missing, `False` otherwise
        + **available-genome** (*className*): string containing the name of the css class for the red-border ('missing-input'), indicating a missing input. `None` if
        input is ok
        + **available-pam** (*className*): string containing the name of the css class for the red-border ('missing-input'), indicating a missing input. `None` if
        input is ok
        + **text-guides** (*style*): dictionary for the style element (NOTE not updated if input is missing)
        + **mms** (*className*): string containing the name of the css class for the red-border ('missing-input'), indicating a missing input. `None` if
        input is ok
        + **dna** (*className*): string containing the name of the css class for the red-border ('missing-input'), indicating a missing input. `None` if
        input is ok
        + **rna** (*className*): string containing the name of the css class for the red-border ('missing-input'), indicating a missing input. `None` if
        input is ok
        + **len-guide-sequence-ver** (*className*): string containing the name of the css class for the red-border ('missing-input'), indicating a missing input. `None` if
        input is ok
        + **warning-list** (*children*): html.Div for the Modal component, listing the missing inputs
        '''
        if n is None:
            raise PreventUpdate
        if is_open is None:
            is_open = False

        classname_red = 'missing-input'
        genome_update = None
        pam_update = None
        text_update = {'width':'450px', 'height':'160px'}
        mms_update = None
        dna_update = None
        rna_update = None
        len_guide_update = None
        update_style = False
        miss_input_list = []

        if genome_selected is None or genome_selected == '':
            genome_update = classname_red
            update_style = True
            miss_input_list.append('Genome')
        if pam is None or pam == '':
            pam_update = classname_red
            update_style = True
            miss_input_list.append('PAM')
        # if text_guides is None or text_guides == '':
            # text_update = {'width':'450px', 'height':'160px','border': '1px solid red'}
            # update_style = True
            # miss_input_list.append('crRNA sequence(s)')
        if mms is None or str(mms) == '':
            mms_update = classname_red
            update_style = True
            miss_input_list.append('Allowed Mismatches')
        if dna is None or str(dna) == '':
            dna_update = classname_red
            update_style = True
            miss_input_list.append('Bulge DNA size')
        if rna is None or str(rna) == '':
            rna_update = classname_red
            update_style = True
            miss_input_list.append('Bulge RNA size')
        if (len_guide_seq is None or str(len_guide_seq) == '') and ('sequence-tab' in active_tab):
            len_guide_update = classname_red
            update_style = True
            miss_input_list.append('crRNA length')
        miss_input = html.Div(
            [
                html.P('The following inputs are missing:'),
                html.Ul([html.Li(x) for x in miss_input_list]),
                html.P('Please fill in the values before submitting the job')
            ]
        )

        if not update_style:
            return 1, False, genome_update, pam_update, text_update, mms_update, dna_update, rna_update, len_guide_update, miss_input
        return None, not is_open, genome_update, pam_update, text_update, mms_update, dna_update, rna_update, len_guide_update, miss_input

    #Submit Job, change url
    @app.callback(
        [Output('url', 'pathname'),
        Output('url','search')],
        [Input('submit-job','n_clicks')],
        [State('url', 'href'),
        State('available-genome', 'value'),
        State('available-pam','value'),
        State('text-guides', 'value'),
        State('mms','value'),
        State('dna','value'),
        State('rna','value'),
        State('checkbox-gecko','checked'),
        State('checkbox-ref-comp', 'checked'),
        State('checklist-advanced', 'value'),
        State('example-email','value'),
        State('tabs','active_tab'),
        State('text-sequence','value'),
        State('len-guide-sequence-ver', 'value')]
    )
    def changeUrl(n, href, genome_selected, pam, text_guides, mms, dna, rna, gecko_opt, genome_ref_opt, adv_opts,dest_email, active_tab, text_sequence, len_guide_sequence):      #NOTE startJob
        '''
        Main function that generates the inputs for the Post/Process/submit_job.final.sh script and launches the search analysis.

        ***Args***

        + [**n**] **submit-job** (*n_clicks*): this value comes from the output of the `checkInput()` function. If `1`, it means that the inputs are
        correct, and the function can proceed. If `None` it means that some inputs are missing, so a `raise PreventUpdate` is returned.
        + [**href**] **url** (*href*): string for the href part of the url. NOTE not used
        + [**genome_selected**] **available-genome** (*value*): string for the selected genome
        + [**pam**] **available-pam** (*value*): string for the selected PAM
        + [**text_guides**] **text-guides** (*value*): string for the input guides
        + [**mms**] **mms** (*value*): int for the mismatch selected
        + [**dna**] **dna** (*value*): int for the DNA bulge selected
        + [**rna**] **rna** (*value*): int for the RNA bbulge selected
        + [**gecko_opt**] **checkbox-gecko** (*checked*): bool for the GeCKO Checkbox
        + [**genoma_ref_opt**] **checkbox-ref-comp** (*checked*): bool for the reference genome comparison
        + [**adv_opts**] **checklist-advanced** (*value*): list of advanced options selected (NOTE only email)
        + [**dest_email**] **example-email** (*value*): string of the input email
        + [**active_tab**] **tabs** (*active_tab*): string of the ID of the active tab ('Guide' or 'Sequence')
        + [**text_sequence**] **text-sequence** (*value*): string of the input sequence
        + [**len_guide_sequence**] **len-guide-sequence-ver** (*value*): int of the selected guide len (available only if 'Sequence' tab is active)

        ***Returns***

        + **url** (*pathname*): string '/load' to go to the Load Page
        + **url** (*search*): string '?job=' + the job ID, to check the status of the specific job

        ***Details***

        + 1) (NOTE already done in `checkInput()`) The function checks the validity of the input parameters.
        + 2) In order to differentiante various analysis submissions, an ID is given to each analysis (Job ID), consisting of a string of 10 alphanumeric
        characters (A-Z 0-9). If a generated ID is already assigned, retry and compute another one. Every 7 iterations, add +1 to the length of the
        ID, up to a maximum of 20 chars. Then the JobID directory is created and inside is created a `queue.txt` file in order to implement a job queue
        + 3) The parameters used as input for the submit_job.final.sh script are created based on user input:
            + Email: the email address, link for the results and start date are written in `Genomes/JobID/email.txt`
            + PAM: get PAM len and direction (beginning or end), in order to write the correct files for pam and guides used in Crispritz.
            + Guides: if the 'Sequence' tab is selected, the input is processed in the `extract_seq` module, that returns a list of guides. Non valid
            characters are then eliminated, along with the exceeding guides (only 1000 guides are acceptable). The guides are then modified by adding
            N characters, following PAM type input (check Crispritz), and saved into file `Genomes/JobID/guides.txt`. The PAM is also saved in the
            `Genomes/JobID/pam.txt` file, following the Crispritz standard
            + Indexing: if the selected Genome-PAM has no index in `genome_library`, set a flag to calculate the index when submitting the job. The PAM
            used for indexing is `Genomes/JobID/pam_indexing.txt`
            + Params file: the informations about the job are saved into the `Params.txt` file
            + Annotation: based on the selected genome reference part, the annotation is chosen from the `annotations` directory
            + Sample: based on the enriched genome selected, the sampleID file is chosen from the `samplesID` directory
            + Dictionary: based on the enriched genome selected, the Dictionary for sample extraction is selected from the `dictionaries` directory
        + 4) If the input is equal to an already calculated analysis, then the function modify the job id to the one of the already existing analysis
        (even if it's completed/currently submitted/in queue), update the `email` file and the `log` file (in order to reset the 3 days availability on the server)
        + Launch the submit_job.final.sh script using `exeggutor`, to a max of 2 concurrent jobs, the others are put into a queue
        '''
        if n is None:
            raise PreventUpdate

        # 1) Check input, else give simple input
        if genome_selected is None or genome_selected == '':
            genome_selected = 'hg38_ref'
        if pam is None or pam == '':
            pam = '20bp-NGG-SpCas9'
        if text_guides is None or text_guides == '':
            text_guides = 'GAGTCCGAGCAGAAGAAGAA\nCCATCGGTGGCCGTTTGCCC'
        else:
            text_guides = text_guides.strip()
            if ( not all(len(elem) == len(text_guides.split('\n')[0]) for elem in text_guides.split('\n'))):
                text_guides = selectSameLenGuides(text_guides)
        if (len_guide_sequence is None or str(len_guide_sequence) == '') and ('sequence-tab' in active_tab):
            len_guide_sequence = 20
        if (text_sequence is None or text_sequence == '') and ('sequence-tab' in active_tab):
            text_sequence = '>sequence\nTACCCCAAACGCGGAGGCGCCTCGGGAAGGCGAGGTGGGCAAGTTCAATGCCAAGCGTGACGGGGGA'

        # 2) Get random string for job id
        n_it = 10
        len_id = 10
        for i in range(n_it):
            assigned_ids = [o for o in os.listdir(current_working_directory + 'Results/') if os.path.isdir(os.path.join(current_working_directory + 'Results/',o))]
            job_id = ''.join(random.choices(string.ascii_uppercase + string.digits, k = len_id))
            if job_id not in assigned_ids:
                break
            if i > 7:
                i = 0
                len_id += 1
                if len_id > 20:
                    break
        result_dir = current_working_directory + 'Results/' + job_id
        subprocess.run(['mkdir ' + result_dir], shell = True)
        #NOTE test command per queue
        subprocess.run(['touch ' + current_working_directory + 'Results/' + job_id + '/queue.txt'], shell = True)

        # 3) Set parameters
        generate_index_ref = False
        generate_index_enr = False
        search_index = True
        search = True
        annotation = True
        report =  True
        gecko_comp = False
        ref_comparison = False
        send_email = False
        if adv_opts is None:
            adv_opts = []
        if gecko_opt:
            gecko_comp = True
        if genome_ref_opt:
            ref_comparison = True
        if 'email' in adv_opts and dest_email is not None and len(dest_email.split('@')) > 1 and dest_email.split('@')[-1] != '':
            send_email = True
            with open(result_dir + '/email.txt', 'w') as e:
                e.write(dest_email + '\n')
                e.write(URL + '/load?job=' + job_id + '\n')
                e.write(datetime.utcnow().strftime("%m/%d/%Y, %H:%M:%S") + '\n')
                #e.write('Job done. Parameters: etc etc')
                e.close()

        genome_selected = genome_selected.replace(' ', '_')
        genome_ref = genome_selected.split('+')[0]              # + char to separate ref and enr, eg Human_Genome_ref+Human_Genome_1000_genome_project
        if genome_ref == genome_selected:
            ref_comparison = False
        #NOTE Indexed genomes names are PAM + _ + bMax + _ + genome_selected

        pam_len = 0
        with open(current_working_directory + 'pam/' + pam + '.txt') as pam_file:
            pam_char = pam_file.readline()
            index_pam_value = pam_char.split(' ')[-1]
            if int(pam_char.split(' ')[-1]) < 0:
                end_idx = int(pam_char.split(' ')[-1]) * (-1)
                pam_char = pam_char.split(' ')[0][0 : end_idx]
                pam_len = end_idx
                pam_begin = True
            else:
                end_idx = int(pam_char.split(' ')[-1])
                pam_char = pam_char.split(' ')[0][end_idx * (-1):]
                pam_len = end_idx
                pam_begin = False

        if 'sequence-tab' in active_tab:
            #Extract sequence and create the guides
            guides = []
            for name_and_seq in text_sequence.split('>'):
                if '' == name_and_seq:
                    continue
                name = name_and_seq[:name_and_seq.find('\n')]
                seq = name_and_seq[name_and_seq.find('\n'):]
                seq = seq.strip().split()
                seq = ''.join(seq)
                # name, seq = name_and_seq.strip().split('\n')
                if 'chr' in seq:
                    extracted_seq = extract_seq.extractSequence(name, seq, genome_ref.replace(' ', '_'))
                else:
                    extracted_seq = seq.strip()

                guides.extend(convert_pam.getGuides(extracted_seq, pam_char, len_guide_sequence, pam_begin))
                text_guides = '\n'.join(guides).strip()

        text_guides = text_guides.upper()
        for g in text_guides.split('\n'):
            for c in g:
                if c not in VALID_CHARS:
                    text_guides = text_guides.replace(c,'')
        if len(text_guides.split('\n')) > 1000:
            text_guides = '\n'.join(text_guides.split('\n')[:1000]).strip()
        len_guides = len(text_guides.split('\n')[0])
        #Adjust guides by adding Ns to make compatible with Crispritz
        if (pam_begin):
            pam_to_file = pam_char + ('N' * len_guides) + ' ' + index_pam_value
            pam_to_indexing = pam_char + ('N' * 25) + ' ' + index_pam_value
        else:
            pam_to_file = ('N' * len_guides) + pam_char + ' ' + index_pam_value
            pam_to_indexing = ('N' * 25) + pam_char + ' ' + index_pam_value

        save_pam_file = open(result_dir + '/pam.txt', 'w')
        save_pam_file.write(pam_to_file)
        save_pam_file.close()
        pam = result_dir + '/pam.txt'

        guides_file = result_dir + '/guides.txt'
        if text_guides is not None and text_guides != '':
            save_guides_file = open(result_dir + '/guides.txt', 'w')
            if (pam_begin):
                text_guides = 'N' * pam_len + text_guides.replace('\n', '\n' + 'N' * pam_len)
            else:
                text_guides = text_guides.replace('\n', 'N' * pam_len + '\n') + 'N' * pam_len
            save_guides_file.write(text_guides)
            save_guides_file.close()

        if (int(dna) == 0 and int(rna) == 0):
            search_index = False
        max_bulges = rna
        if (int(dna) > int(rna)):
            max_bulges = dna

        if (search_index):
            search = False

        #Check if index exists, otherwise set generate_index to true
        genome_indices_created =  [f for f in listdir(current_working_directory + 'genome_library') if isdir(join(current_working_directory + 'genome_library', f))]
        genome_idx = ''
        genome_idx_ref = ''
        for gidx in genome_indices_created:
            if pam_char in gidx and genome_selected in gidx:
                if int(gidx.split('_')[1]) >= int(max_bulges):
                    if '+' in gidx:
                        genome_idx = gidx
                    genome_idx_ref = gidx.split('+')[0]

        if genome_idx == '':
            if int(max_bulges) > 0:
                generate_index_enr = True
                with open(result_dir + '/pam_indexing.txt', 'w+') as pam_id_file:
                    pam_id_file.write(pam_to_indexing)
            genome_idx = pam_char + '_' + str(max_bulges) + '_' + genome_selected
        if genome_idx_ref == '':
            if int(max_bulges) > 0:
                generate_index_ref = True
                with open(result_dir + '/pam_indexing.txt', 'w+') as pam_id_file:
                    pam_id_file.write(pam_to_indexing)
            genome_idx_ref = pam_char + '_' + str(max_bulges) + '_' + genome_selected.split('+')[0]

        if genome_ref == genome_selected:
            generate_index_enr = False
        # genome_idx = pam_char + '_' + '2' + '_' + genome_selected
        # genome_idx_ref = genome_idx.split('+')[0]

        #Create Params.txt file
        with open(result_dir + '/Params.txt', 'w') as p:
            p.write('Genome_selected\t' + genome_selected + '\n')
            p.write('Genome_ref\t' + genome_ref + '\n')
            if search_index:
                p.write('Genome_idx\t' + genome_idx + '\n')
            else:
                p.write('Genome_idx\t' + 'None\n')
            p.write('Pam\t' + pam_char + '\n')
            p.write('Max_bulges\t' + str(max_bulges) + '\n')
            p.write('Mismatches\t' + str(mms) + '\n')
            p.write('DNA\t' + str(dna) + '\n')
            p.write('RNA\t' + str(rna) + '\n')
            p.write('Gecko\t' + str(gecko_comp) + '\n')
            p.write('Ref_comp\t' + str(ref_comparison) + '\n')
            p.close()

        # 4) Check if input parameters (mms, bulges, pam, guides, genome) are the same as a previous search
        all_result_dirs = [f for f in listdir(current_working_directory + 'Results') if isdir(join(current_working_directory + 'Results', f))]
        all_result_dirs.remove(job_id)
        #all_result_dirs.remove('test')
        for check_param_dir in all_result_dirs:
            if os.path.exists(current_working_directory + 'Results/' + check_param_dir + '/Params.txt'):
                #if os.path.exists(current_working_directory + 'Results/' + check_param_dir + '/log.txt'):
                    #with open(current_working_directory + 'Results/' + check_param_dir + '/log.txt') as log:
                        #if ('Job\tDone' in log.read()):
                if (filecmp.cmp(current_working_directory + 'Results/' + check_param_dir + '/Params.txt', result_dir + '/Params.txt' )):
                        guides1 = open(current_working_directory + 'Results/' + check_param_dir + '/guides.txt').read().split('\n')
                        guides2 = open(current_working_directory + 'Results/' + job_id + '/guides.txt').read().split('\n')
                        if (collections.Counter(guides1) == collections.Counter(guides2)):
                            if os.path.exists(current_working_directory + 'Results/' + check_param_dir + '/log.txt'):
                                adj_date = False
                                with open(current_working_directory + 'Results/' + check_param_dir + '/log.txt') as log:
                                    log_content = log.read().strip()
                                    if ('Job\tDone' in log_content):
                                        adj_date = True
                                        log_content = log_content.split('\n')
                                        new_date = subprocess.Popen(['echo $(date)'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell = True)
                                        out, err = new_date.communicate()
                                        rewrite = '\n'.join(log_content[:-1]) + '\nJob\tDone\t' + out.decode('UTF-8').strip()
                                if adj_date:
                                    with open(current_working_directory + 'Results/' + check_param_dir + '/log.txt' ,'w+') as log:
                                        log.write(rewrite)
                                        #Send mail
                                    if send_email:
                                        with open(current_working_directory + 'Results/' + job_id + '/email.txt' ,'w+') as e:
                                            e.write(dest_email + '\n')
                                            e.write(URL + '/load?job=' + check_param_dir + '\n')
                                            e.write(datetime.utcnow().strftime("%m/%d/%Y, %H:%M:%S") + '\n')
                                        #Send mail with file in job_id dir with link to job already done, note that job_id directory will be deleted
                                        subprocess.call(['python ' + app_main_directory + 'send_mail.py ' + current_working_directory + 'Results/' + job_id ], shell = True)

                                elif send_email:
                                    #Job is not finished, add this user email to email.txt and when job is done send to both
                                    if os.path.exists(current_working_directory + 'Results/' + check_param_dir + '/email.txt'):
                                        with open(current_working_directory + 'Results/' + check_param_dir + '/email.txt' ,'a+') as e:
                                            e.write('--OTHEREMAIL--')
                                            e.write(dest_email + '\n')
                                            e.write(URL + '/load?job=' + check_param_dir + '\n')
                                            e.write(datetime.utcnow().strftime("%m/%d/%Y, %H:%M:%S") + '\n')
                                    else:
                                        with open(current_working_directory + 'Results/' + check_param_dir + '/email.txt' ,'w+') as e:
                                            e.write(dest_email + '\n')
                                            e.write(URL + '/load?job=' + check_param_dir + '\n')
                                            e.write(datetime.utcnow().strftime("%m/%d/%Y, %H:%M:%S") + '\n')

                                subprocess.call(['rm -r ' + current_working_directory + 'Results/' + job_id], shell = True)
                                return '/load','?job=' + check_param_dir
                            else:
                                #We may have entered a jobdir that was in queue
                                if os.path.exists(current_working_directory + 'Results/' + check_param_dir + '/queue.txt'):
                                    if send_email:
                                        if os.path.exists(current_working_directory + 'Results/' + check_param_dir + '/email.txt'):
                                            with open(current_working_directory + 'Results/' + check_param_dir + '/email.txt' ,'a+') as e:
                                                e.write('--OTHEREMAIL--')
                                                e.write(dest_email + '\n')
                                                e.write(URL + '/load?job=' + check_param_dir + '\n')
                                                e.write(datetime.utcnow().strftime("%m/%d/%Y, %H:%M:%S") + '\n')
                                        else:
                                            with open(current_working_directory + 'Results/' + check_param_dir + '/email.txt' ,'w+') as e:
                                                e.write(dest_email + '\n')
                                                e.write(URL + '/load?job=' + check_param_dir + '\n')
                                                e.write(datetime.utcnow().strftime("%m/%d/%Y, %H:%M:%S") + '\n')
                                    return '/load','?job=' + check_param_dir

        #Annotation
        if (not search and not search_index):
            annotation = False

        #Generate report
        if (not search and not search_index):
            report = False

        genome_type = 'ref'     #Indicates if search is 'ref', 'var' or 'both'
        if '+' in genome_selected:
            genome_type = 'var'
        if ref_comparison:
            genome_type = 'both'

        #Get annotation file, already with the absolute path. TODO currently only one annotation per genome
        #Also get dictionary and sample dictionary files
        annotation_file = [f for f in listdir(current_working_directory + 'annotations/') if isfile(join(current_working_directory + 'annotations/', f)) and f.startswith(genome_ref)][0]

        if genome_type == 'ref':
            sample_list = None
            dictionary_directory = None
        else:
            sample_list = current_working_directory + 'samplesID/samples_' + genome_selected + '.txt'
            dictionary_directory = current_working_directory + 'dictionaries/dictionary_' + genome_selected

        command = app_main_directory + 'PostProcess/./submit_job.final.sh ' + current_working_directory + 'Results/' + job_id + ' ' + current_working_directory +  'Genomes/' + genome_selected + ' ' + current_working_directory + 'Genomes/' + genome_ref + ' ' + current_working_directory + 'genome_library/' + genome_idx + (
            ' ' + pam + ' ' + guides_file + ' ' + str(mms) + ' ' + str(dna) + ' ' + str(rna) + ' ' + str(search_index) + ' ' + str(search) + ' ' + str(annotation) + (
                ' ' + str(report) + ' ' + str(gecko_comp) + ' ' + str(ref_comparison) + ' ' + current_working_directory +  'genome_library/' + genome_idx_ref + ' ' + str(send_email) + ' '  + current_working_directory +  'annotations/' + annotation_file +
                ' ' + genome_type + ' ' + app_main_directory + ' ' + str(dictionary_directory) + ' ' + str(sample_list) + ' ' + str(generate_index_ref) + ' ' + str(generate_index_enr) + ' ' + current_working_directory))

        exeggutor.submit(subprocess.run, command, shell=True)
        return '/load','?job=' + job_id

    #When url changed, load new page
    @app.callback(
        [Output('page-content', 'children'),
        Output('job-link', 'children')],
        [Input('url', 'href'), Input('url','pathname'), Input('url','search')],[State('url','hash')]
    )
    def changePage( href, path, search, hash_guide):
        '''
        Change the page layout based on the URL.

        ***Args***

        + [**href**] **url** (*href*): string containing the whole address, eg 'http://127.0.0.1:8080/result?job=QV99PN6XDL#CCATCGGTGGCCGTTTGCCCNNNnewX00'
        + [**path**] **url** (*pathname*): string containing the page, eg '/result'
        + [**search**] **url** (*search*): string containing the job ID, eg '?job=QV99PN6XDL'
        + [**hash_guide**] **url** (*hash*): string containing the view result query, eg '#CCATCGGTGGCCGTTTGCCCNNNnewX00'

        ***Returns***

        + **page-content** (*children*): the layout of the page, based on the URL.
            + '/': returns the index page (main page)
            + '/load': returns the load page. Called after submitting a job
            + '/result': returns the result page. Based on the job ID in the **search** parameter, the user can also access to:
                + '#GUIDEnew': returns the table visualizing the targets based on Bulge and Mismatch values
                + '-Sample-': returns the table visualizing the targets for each sample
                + '-Pos-': returns the table visualizing the targets based on the selected chromosome position
            + '/user-guide': returns the help page
            + '/contacts': returns the contacts page
            + '/history': returns the history page. Available only offline
            + '/genomes': returns the genomes page. Available only offline
        + **job-link** (*children*): the link displayed to the user in the '/load' page, used to access the status of the job. By saving this URL the
        the user can monitor the status of the job and see the corresponding '/result' page
        '''

        if path == '/load':
            return load_page, URL + '/load' + search
        if path == '/result':
            job_id = search.split('=')[-1]
            if hash_guide is None or hash_guide == '':
                return resultPage(job_id), URL + '/load' + search
            if 'new' in hash_guide:         #TODO cambiare nome alla pagina delle guide
                return guidePagev3(job_id, hash_guide.split('#')[1]), URL + '/load' + search
            if '-Sample-' in hash_guide:
                return samplePage(job_id, hash_guide.split('#')[1]), URL + '/load' + search
            if '-Pos-' in hash_guide:
                return clusterPage(job_id, hash_guide.split('#')[1]), URL + '/load' + search
            return resultPage(job_id), URL + '/load' + search
        if path == '/test-page':
            return test_page(), URL + '/load' + search
        if path == '/test-page2':
            return test_page2, URL + '/load' + search
        if path == '/test-page3':
            return test_page3, URL + '/load' + search
        if path == '/user-guide':
            return about_page, URL + '/load' + search
        if path == '/contacts':
            return contacts_page, URL + '/load' + search
        if path == '/history':
            if ONLINE:
                return indexPage(), ''
            return historyPage(), URL + '/load' + search
        if path == '/genomes':
            if ONLINE:
                return indexPage(), ''
            genomes_page = html.Div(gen_page.genomesPage(current_working_directory), style = {'margin':'1%'})
            return genomes_page, URL + '/load' + search
        return indexPage(), ''

    #Check end job
    @app.callback(
        [Output('view-results', 'style'),
        Output('search-status', 'children'),
        Output('generate-report-status', 'children'),
        Output('post-process-status', 'children'),
        Output('view-results','href'),
        Output('no-directory-error', 'children')],
        [Input('load-page-check', 'n_intervals')],
        [State('url', 'search')]
    )
    def refreshSearch(n, dir_name):
        '''
        The Interval component of the '/load' page calls this function every 3 seconds. The function checks the status of the submitted job by means
        of the log and output file created and updated by the submit_job.final.sh script, and notifies the user about the current status ('To Do', 'Done' etc.)
        When the job is completed, a link directing to the '/result' page is shown. If the job doesn't exists or is in the queue,
        a notification will appear.

        ***Args***

        + [**n**] **load-page-check** (*n_intervals*): element of the Interval component that shows the number of time it has been fired
        + [**dir_name**] **url** (*search*): string containing the job ID, eg '?job=QV99PN6XDL'

        ***Returns***

        + **view-results** (*style*): dictionary for the style element of the link that redirects to the '/result' page. If the job is completed
        then the {'visibility':'visible'} is returned, else {'visibility':'visible'}
        + **search-status** (*children*): html.P() that contains the status of the search phase. Can be:
            + To Do, red
            + Done, green
            + Currently ongoing, orange
            + Not Available, red
        + **generate-report-status** (*children*): html.P() that contains the status of the generate report. Can be as above
        + **post-process-status** (*children*): html.P() that contains the status of the post process phase. Can be as above
        + **view-results** (*href*): link that redirects to the '/result' page
        + **no-directory-error** (*children*): Alert component that notify the user that the result is not present in the server, or is in the queue
        '''

        if n is None:
            raise PreventUpdate

        onlydir = [f for f in listdir(current_working_directory + 'Results') if isdir(join(current_working_directory + 'Results', f))]
        current_job_dir = current_working_directory + 'Results/' +  dir_name.split('=')[-1] + '/'
        if dir_name.split('=')[-1] in onlydir:
            onlyfile = [f for f in listdir(current_job_dir) if isfile(join(current_job_dir, f))]
            if os.path.exists(current_job_dir + 'guides.txt'):
                with open(current_job_dir + 'guides.txt') as guides:
                    n_guides = len(guides.read().strip().split('\n'))
            else:
                n_guides = -1
            if 'log.txt' in onlyfile:
                with open(current_job_dir + 'log.txt') as log:
                    all_done = 0

                    search_status = html.P('To do', style = {'color':'red'})
                    report_status = html.P('To do', style = {'color':'red'})
                    post_process_status = html.P('To do', style = {'color':'red'})
                    current_log = log.read()

                    if ('Search-index\tDone' in current_log and 'Search\tDone' in current_log):
                        search_status = html.P('Done', style = {'color':'green'})
                        all_done = all_done + 1
                    elif os.path.exists(current_job_dir + 'output.txt'):                #Extract % of search done from output.txt
                        with open(current_job_dir + 'output.txt', 'r') as output_status:
                            line = output_status.read().strip()
                            if 'Indexing_Reference' in line:
                                search_status = html.P('Indexing Reference Genome...' + ' ' + 'Step [1/2]', style = {'color':'orange'})
                            if 'Indexing_Enriched' in line:
                                search_status = html.P('Indexing Enriched Genome...' + ' ' + 'Step [2/2]', style = {'color':'orange'})
                            if 'Search_output' in line:
                                if 'both' in line:
                                    last_percent = line.rfind('%')
                                    if last_percent > 0:
                                        last_percent = line[line[:last_percent].rfind(' '): last_percent]
                                        search_status_message = last_percent + '%'
                                    else:
                                        search_status_message = 'Searching...'

                                    steps = 'Step [1/2]'
                                    if 'Search_output_ref' in line:
                                        steps = 'Step [2/2]'

                                else:
                                    last_percent = line.rfind('%')
                                    if last_percent > 0:
                                        last_percent = line[line[:last_percent].rfind(' '): last_percent]
                                        search_status_message = last_percent + '%'
                                    else:
                                        search_status_message = 'Searching...'
                                    steps = ''
                                search_status = html.P(search_status_message + ' ' + steps, style = {'color':'orange'})

                    if ('Report\tDone' in current_log):
                        report_status = html.P('Done', style = {'color':'green'})
                        all_done = all_done + 1
                    elif os.path.exists(current_job_dir + 'output.txt'):                #Extract % of search done
                        with open(current_job_dir + 'output.txt', 'r') as output_status:
                            line = output_status.read().strip()
                            if 'Generate_report' in line:
                                if n_guides < 0:
                                    report_status = html.P('Generating Report...', style = {'color':'orange'})
                                else:
                                    status_message = round((len(line.split('\n')) - 1) / n_guides, 2)
                                    report_status = html.P(str(status_message * 100) + '%', style = {'color':'orange'})
                    if ('PostProcess\tDone' in current_log):
                        post_process_status = html.P('Done', style = {'color':'green'})
                        all_done = all_done + 1
                    elif os.path.exists(current_job_dir + 'output.txt'):                #Extract % of search done
                        with open(current_job_dir + 'output.txt', 'r') as output_status:
                            line = output_status.read().strip()
                            if 'PostProcess_output' in line:
                                line = line.split('\n')
                                if line[-1] == 'PostProcess_output':
                                    post_process_status = html.P('Processing data...', style = {'color':'orange'})
                                else:
                                    if 'Annotating...' in line:
                                        last_percent = line[-1].rfind('%')
                                        if last_percent > 0:
                                            last_percent = line[line[:last_percent].rfind(' '): last_percent]
                                            status_message = last_percent + '%'
                                        else:
                                            status_message = 'Annotating...'
                                    else:
                                        status_message = line[-1]
                                    post_process_status = html.P(status_message, style = {'color':'orange'})
                    if all_done == 3 or 'Job\tDone' in current_log:
                        return {'visibility':'visible'},  search_status, report_status, post_process_status ,'/result?job=' + dir_name.split('=')[-1], ''
                    else:
                        return {'visibility':'hidden'},  search_status, report_status, post_process_status,'', ''
            elif 'queue.txt' in onlyfile:
                return {'visibility':'hidden'},  html.P('To do', style = {'color':'red'}), html.P('To do', style = {'color':'red'}), html.P('To do', style = {'color':'red'}),'', dbc.Alert("Job submitted. Current status: in queue", color = "info")
        return {'visibility':'hidden'},  html.P('Not available', style = {'color':'red'}), html.P('Not available', style = {'color':'red'}), html.P('Not available', style = {'color':'red'}) ,'', dbc.Alert("The selected result does not exist", color = "danger")

    #Perform expensive loading of a dataframe and save result into 'global store'
    #Cache are in the Cache directory
    @cache.memoize()
    def global_store(value):
        '''
        Caching of files, mainly the ones for the 'Show Targets' tables, to improve loading speed. NOTE that if two files have the same name, then the
        generated cache will be the same, so a regular cleaning of the 'Cache' directory is mandatory.

        ***Args***

        + **value**: string of the job ID to load

        ***Returns***

        + **df**: dataframe for the 'Show Targets' table
        '''
        if value is None:
            return ''
        target = [f for f in listdir(current_working_directory + 'Results/' + value) if isfile(join(current_working_directory + 'Results/'+value, f)) and f.endswith('scores.txt') ]
        if not target:
            target = [f for f in listdir(current_working_directory + 'Results/' + value) if isfile(join(current_working_directory + 'Results/'+value, f)) and f.endswith('targets.txt') ]

        df = pd.read_csv(current_working_directory + 'Results/' +value + '/' + target[0], sep = '\t')
        df.rename(columns = {"#Bulge type":'BulgeType', '#Bulge_type':'BulgeType','Bulge Size': 'BulgeSize', 'Bulge_Size': 'BulgeSize', 'Doench 2016':'Doench2016','Doench_2016':'Doench2016'}, inplace = True)
        return df

    #Send the data when next or prev button is clicked on the result table
    @app.callback(
        Output('result-table', 'data'),
        [Input('result-table', "page_current"),
         Input('result-table', "page_size"),
         Input('result-table', "sort_by"),
         Input('result-table', 'filter_query')],
         [State('url', 'search'),
         State('url', 'hash')]
    )
    def update_table(page_current, page_size, sort_by, filter, search, hash_guide):
        '''
        La funzione ritorna uno split dei risultati in base ad un filtering o a un sort da parte dell'utente. Inoltre aggiorna i risultati
        visualizzati quando il bottone next page / prev page è cliccato. (Codice preso dalla pagina dash datatable sul sorting con python)
        Inoltre carica i file targets, o scores se presente, e lo trasforma in un dataframe, cambiando il nome delle colonne per farle corrispondere
        all'id delle colonne della tabella nella pagina.
        Se non ci sono targets ritorna un avviso di errore
        '''
        job_id = search.split('=')[-1]
        job_directory = current_working_directory + 'Results/' + job_id + '/'
        guide = hash_guide.split('#')[1]
        value = job_id
        if search is None:
            raise PreventUpdate

        filtering_expressions = filter.split(' && ')
        #filtering_expressions.append(['{crRNA} = ' + guide])
        df = global_store(value)
        dff = df[df['crRNA'] == guide]

        sort_by.insert(0, {'column_id' : 'Mismatches', 'direction': 'asc'})
        sort_by.insert(1, {'column_id' : 'BulgeSize', 'direction': 'asc'})
        #sort_by.insert(2, {'column_id': 'CFD', 'direction':'desc'})
        for filter_part in filtering_expressions:
            col_name, operator, filter_value = split_filter_part(filter_part)

            if operator in ('eq', 'ne', 'lt', 'le', 'gt', 'ge'):
                # these operators match pandas series operator method names
                dff = dff.loc[getattr(dff[col_name], operator)(filter_value)].sort_values([col['column_id'] for col in sort_by],
                ascending=[
                    col['direction'] == 'asc'
                    for col in sort_by
                ],
                inplace=False)
            elif operator == 'contains':
                dff = dff.loc[dff[col_name].str.contains(filter_value)]
            elif operator == 'datestartswith':
                # this is a simplification of the front-end filtering logic,
                # only works with complete fields in standard format
                dff = dff.loc[dff[col_name].str.startswith(filter_value)]

        #NOTE sort_by: [{'column_id': 'BulgeType', 'direction': 'asc'}, {'column_id': 'crRNA', 'direction': 'asc'}]
        #sort_by.insert(0, {'column_id' : 'Mismatches', 'direction': 'asc'})
        #sort_by.insert(0, {'column_id' : 'BulgeSize', 'direction': 'asc'})
        if len(sort_by):
            dff = dff.sort_values(
                ['Samples' if col['column_id'] == 'Samples Summary' else col['column_id'] for col in sort_by],
                ascending=[
                    col['direction'] == 'asc'
                    for col in sort_by
                ],
                inplace=False
            )

        #Check if results are not 0
        warning_no_res = ''
        with open(job_directory + job_id + '.targets.txt') as t:
            no_result = False
            t.readline()
            last_line = t.readline()
            if (last_line == '' or last_line == '\n'):
                no_result = True

        if (no_result):
            warning_no_res = dbc.Alert("No results were found with the given parameters", color = "warning")

        return dff.iloc[
            page_current*page_size:(page_current+ 1)*page_size
        ].to_dict('records')

    #For filtering
    def split_filter_part(filter_part):
        '''
        Split the input filter, used for Dash DataTables. See https://dash.plotly.com/datatable/filtering
        '''
        for operator_type in operators:
            for operator in operator_type:
                if operator in filter_part:
                    name_part, value_part = filter_part.split(operator, 1)
                    name = name_part[name_part.find('{') + 1: name_part.rfind('}')]

                    value_part = value_part.strip()
                    v0 = value_part[0]
                    if (v0 == value_part[-1] and v0 in ("'", '"', '`')):
                        value = value_part[1: -1].replace('\\' + v0, v0)
                    else:
                        try:
                            value = float(value_part)
                        except ValueError:
                            value = value_part

                    # word operators need spaces after them in the filter string,
                    # but we don't want these later
                    return name, operator_type[0].strip(), value

        return [None] * 3

    #Read the uploaded file and converts into bit
    def parse_contents(contents):
        '''
        Read the uploaded file and converts into bit
        '''
        content_type, content_string = contents.split(',')
        decoded = base64.b64decode(content_string)
        return decoded

    #Load the table/children under the tab value
    @app.callback(
        Output('div-tab-content', 'children'),
        [Input('tabs-reports', 'value'),
        Input('general-profile-table','selected_cells')],
        [State('general-profile-table', 'data'),
        State('url', 'search'),
        State('div-genome-type', 'children')]
    )
    def updateContentTab(value, sel_cel, all_guides, search, genome_type):
        '''
        Load the layout based on the selected Tab in the '/result' page

        ***Args***

        + [**value**] **tabs-reports** (*value*): string representing the ID of the active Tab
        + [**sel_cel**] **general-profile-table** (*selected_cells*): list contaning the selected row of the main guide table
        + [**all_guides**] **general-profile-table** (*data*): list of all the rows that are currenty displayed in the main guide table
        + [**search**] **url** (*search*): string containing the job ID, eg '?job=QV99PN6XDL'
        + [**genome_type**] **div-genome-type** (*children*): string containing the type of the search ('ref', 'var' or 'both')

        ***Returns***

        + **div-tab-content** (*children*): return the layout based on the selected Tab:
            + 'tab-summary-by-guide': show the general table of the targets divided by Bulge and Mismatch values
            + 'tab-summary-by-sample': show the general table of the targets divided by sample
            + 'tab-summary-by-position': show the general table of the targets divided by chromosome and position
            + 'tab-summary-graphical': show the graphical report. A total of 10 Buttons, one for each mms value, are created, but only some of them
            are set to be visible (the number of mms selected by the user)
        '''

        if value is None or sel_cel is None or not sel_cel or not all_guides:
            raise PreventUpdate

        guide = all_guides[int(sel_cel[0]['row'])]['Guide']
        job_id = search.split('=')[-1]
        job_directory = current_working_directory + 'Results/' + job_id + '/'

        with open(current_working_directory + 'Results/' + job_id + '/Params.txt') as p:
            all_params = p.read()
            mms = (next(s for s in all_params.split('\n') if 'Mismatches' in s)).split('\t')[-1]
            genome_selected = (next(s for s in all_params.split('\n') if 'Genome_selected' in s)).split('\t')[-1]
            max_bulges = (next(s for s in all_params.split('\n') if 'Max_bulges' in s)).split('\t')[-1]

        fl = []
        fl.append(html.Br())
        fl.append(html.H5('Focus on: ' + guide))

        if value == 'tab-summary-by-guide': #BUG se cambio guida selezionata due volte mi cambia il mms mettendo a 0, provare con un div nascosto
            #Show Summary by Guide table
            fl.append(
                html.P(
                    ['Summary table counting the number of targets found in the Enriched Genome for each combination of Bulge Type, Bulge Size and Mismatch. Select \'Show Targets\' to view the corresponding list of targets. ',
                    # html.A('Click here', href = URL + '/data/' + job_id + '/' + job_id + '.targets.' + guide + '.zip' ,target = '_blank', id = 'download-full-list' ), ' to download the full list of targets.'
                    ]
                    )
            )
            fl.append(
                html.Div(
                    html.Button(html.A('Download Full list of targets', href = URL + '/data/' + job_id + '/' + job_id + '.targets.' + guide + '.zip' ,target = '_blank', style = {'text-decoration':'none', 'color':'black'} )),
                    style = {'display':DISPLAY_ONLINE}
                )
            )
            fl.append(
                html.Div(
                    html.Button('Open Result Directory', id = 'button-open-result-directory'),
                    style = {'display':DISPLAY_OFFLINE}
                )
            )
            fl.append(html.Div(guide,id = 'div-open-result-directory', style = {'display':'none'}))
            fl.append(html.Br())
            df = pd.read_pickle(job_directory + job_id + '.summary_by_guide.' + guide + '.txt')
            if genome_type == 'both':
                df.drop( df[(df['Bulge Size'] == 0) & ((df['Bulge Type'] == 'DNA') | ((df['Bulge Type'] == 'RNA'))) | ((df['Targets in Reference'] == 0) & (df['Targets in Enriched'] == 0))  ].index, inplace = True)
            elif genome_type == 'var':
                df.drop( df[(df['Bulge Size'] == 0) & ((df['Bulge Type'] == 'DNA') | ((df['Bulge Type'] == 'RNA'))) | ((df['Targets in Enriched'] == 0))  ].index, inplace = True)
                del df['Targets in Reference']
            else:
                df.drop( df[(df['Bulge Size'] == 0) & ((df['Bulge Type'] == 'DNA') | ((df['Bulge Type'] == 'RNA'))) | ((df['Targets in Reference'] == 0))  ].index, inplace = True)
            more_info_col = []
            total_col = []
            for i in range(df.shape[0]):
                more_info_col.append('Show Targets')
                total_col.append(df['Bulge Size'])

            #Swap pam creation and Combined column
            if genome_type == 'both':   #TODO fixare per var e ref
                df['Combined'] = df['Targets in Reference'] + df['Targets in Enriched']
                #['Guide' 'Bulge Type' 'Bulge Size' 'Mismatches' 'Targets in Reference', 'Targets in Enriched' 'PAM Creation' 'Combined']
                df = df[['Guide' , 'Bulge Type', 'Mismatches', 'Bulge Size' , 'Targets in Reference', 'Targets in Enriched', 'Combined', 'PAM Creation']]
            df[''] = more_info_col
            df['Total'] = df['Bulge Size'] + df['Mismatches']
            if genome_type == 'both' and genome_type == 'var':
                df = df.sort_values(['Total', 'Targets in Enriched'], ascending = [True, False])
            else:
                df = df.sort_values('Total', ascending = True)
            del df['Total']
            del df['Guide']
            fl.append(html.Div(
                    generate_table(df, 'table-summary-by-guide', genome_type, guide, job_id ), style = {'text-align': 'center'}
                )
            )
            return fl
        elif value == 'tab-summary-by-sample':
            #Show Summary by Sample table
            fl.append(
                html.P('Summary table counting the number of targets found in the Enriched Genome for each sample. Filter the table by selecting the Population or Superpopulation desired from the dropdowns.')
            )
            if genome_type == 'both':
                col_names_sample = ['Sample', 'Gender', 'Population', 'Super Population',  'Targets in Reference', 'Targets in Enriched', 'Targets in Population', 'Targets in Super Population', 'PAM Creation', 'Class']
                df = pd.read_csv(job_directory + job_id + '.summary_by_samples.' + guide + '.txt', sep = '\t', names = col_names_sample, skiprows = 1)
                df = df.sort_values('Targets in Enriched', ascending = False)
                df.drop(['Targets in Reference'], axis = 1, inplace = True)
            else:
                col_names_sample = ['Sample', 'Gender', 'Population', 'Super Population',  'Targets in Reference', 'Targets in Enriched', 'Targets in Population', 'Targets in Super Population', 'PAM Creation', 'Class']
                df = pd.read_csv(job_directory + job_id + '.summary_by_samples.' + guide + '.txt', sep = '\t', names = col_names_sample, skiprows = 1)
                df = df.sort_values('Targets in Enriched', ascending = False)
                df.drop(['Targets in Reference'], axis = 1, inplace = True)
                df.drop(['Class'], axis = 1, inplace = True)
            more_info_col = []
            for i in range(df.shape[0]):
                more_info_col.append('Show Targets')
            df[''] = more_info_col

            population_1000gp = associateSample.loadSampleAssociation(job_directory + 'sampleID.txt')[2]
            super_populations = [{'label':i, 'value':i} for i in population_1000gp.keys()]
            populations = []
            for k in population_1000gp.keys():
                for i in population_1000gp[k]:
                    populations.append({'label':i, 'value':i})
            fl.append(
                html.Div
                    (
                        [
                            dbc.Row(
                                [
                                    dbc.Col(html.Div(dcc.Dropdown(options = super_populations, id = 'dropdown-superpopulation-sample', placeholder = 'Select a Super Population'))),
                                    dbc.Col(html.Div(dcc.Dropdown(options = populations, id = 'dropdown-population-sample', placeholder = 'Select a Population'))),
                                    #dbc.Col(html.Div(dcc.Dropdown( id = 'dropdown-sample', placeholder = 'Select a Sample'))),
                                    dbc.Col(html.Div(dcc.Input(id = 'input-sample', placeholder = 'Select a Sample' ))),
                                    dbc.Col(html.Div(html.Button('Filter', id = 'button-filter-population-sample')))
                                ]
                            ),
                        ],
                        style = {'width':'50%'}
                    )
            )
            fl.append(html.Div('None,None,None',id = 'div-sample-filter-query', style = {'display':'none'})) #Folr keep current filter:  Superpop,Pop
            fl.append(html.Div(
                    generate_table_samples(df, 'table-samples', 1, guide, job_id ), style = {'text-align': 'center'}, id = 'div-table-samples'
                )
            )
            fl.append(
                html.Div(
                    [
                        html.Button('Prev', id = 'prev-page-sample'),
                        html.Button('Next', id = 'next-page-sample')
                    ],
                    style = {'text-align': 'center'}
                )
            )
            max_page = len(df.index)
            max_page = math.floor(max_page / 10) + 1
            fl.append(html.Div('1/' + str(max_page), id= 'div-current-page-table-samples'))
            return fl
        elif value == 'tab-summary-by-position':
            #Show Summary by position table
            fl.append(
                html.P('Summary table containing all the targets found in a specific position of the genome. For each position, the enriched target with the lowest Mismatch + Bulge count is shown (if no target was found in the Enriched Genome, the correspondig reference one is shown), along with his Mismatch and Bulge Size values.' +
                ' The subtable \'Targets in Cluster by Mismatch Value\' represents the number of targets found in that position for a particular Mismatch-Bulge Size pair.')
            )

            fl.append(
                html.P('Filter the table by selecting the chromosome of interest and writing the start and/or end position of the region to view.')
            )
            #Dropdown chromosomes
            try:
                onlyfile = [f for f in listdir(current_working_directory + 'Genomes/' + genome_selected) if (isfile(join(current_working_directory + 'Genomes/' + genome_selected, f)) and (f.endswith('.fa') or f.endswith('.fasta')))]
            except:
                onlyfile = ['chr' + str(i) + '.fa' for i in range(1,23)]
                onlyfile.append('chrX.fa')
                onlyfile.append('chrY.fa')                                              #NOTE in case no chr in GENOMES/ i put 22 chr + X Y M
                onlyfile.append('chrM.fa')
            onlyfile = [x[:x.rfind('.')] for x in onlyfile]            #removed .fa for better visualization
            chr_file = []
            chr_file_unset = []
            for chr_name in onlyfile:
                chr_name = chr_name.replace('.enriched', '')
                if '_' in chr_name:
                    chr_file_unset.append(chr_name)
                else:
                    chr_file.append(chr_name)
            #Show first 'normal' chromosomes (chr1) then the others (chr1_KI270706v1_random)
            chr_file.sort(key = lambda s: [int(t) if t.isdigit() else t.lower() for t in re.split(r'(\d+)', s)])
            chr_file_unset.sort(key = lambda s: [int(t) if t.isdigit() else t.lower() for t in re.split(r'(\d+)', s)])
            chr_file += chr_file_unset
            chr_file = [{'label': chr_name, 'value' : chr_name} for chr_name in chr_file]

            # Colonne tabella: chr, pos, target migliore, min mm, min bulges, num target per ogni categoria di mm e bulge, show targets; ordine per total, poi mm e poi bulge
            start_time = time.time()
            df = pd.read_csv( job_directory + job_id + '.summary_by_position.' + guide +'.txt', sep = '\t')
            df.rename(columns = {'#Chromosome':'Chromosome'}, inplace = True)
            more_info_col = []
            for i in range(df.shape[0]):
                more_info_col.append('Show Targets')
            df[''] = more_info_col
            fl.append(
                html.Div
                    (
                        [
                            dbc.Row(
                                [
                                    dbc.Col(html.Div(dcc.Dropdown(options = chr_file, id = 'dropdown-chr-table-position', placeholder = 'Select a chromosome'))),
                                    dbc.Col(html.Div(dcc.Input(placeholder = 'Start Position', id = 'input-position-start'))),
                                    dbc.Col(html.Div(dcc.Input(placeholder = 'End Position', id = 'input-position-end'))),
                                    dbc.Col(html.Div(html.Button('Filter', id = 'button-filter-position')))
                                ]
                            ),
                        ],
                        style = {'width':'50%'}
                    )
            )
            fl.append(html.Div('None,None,None',id = 'div-position-filter-query', style = {'display':'none'})) #Folr keep current filter:  chr,pos_start,pos_end
            start_time = time.time()
            fl.append(html.Div(
                    generate_table_position(df, 'table-position', 1 , int(mms), int(max_bulges), guide, job_id ), style = {'text-align': 'center'}, id = 'div-table-position'
                )
            )
            fl.append(
                html.Div(
                    [
                        html.Button('Prev', id = 'prev-page-position'),
                        html.Button('Next', id = 'next-page-position')
                    ],
                    style = {'text-align': 'center'}
                )
            )
            max_page = len(df.index)
            max_page = math.floor(max_page / 10) + 1
            fl.append(html.Div('1/' + str(max_page), id= 'div-current-page-table-position'))
            fl.append(html.Div(mms + '-' + max_bulges, id = 'div-mms-bulges-position', style = {'display':'none'}))
            return fl
        else:
            #Show Report images
            samp_style = {}
            if genome_type == 'ref':
                samp_style = {'display':'none'}

            fl.append(html.Br())

            fl_buttons = []
            for i in range (10):
                if (i <= int(mms)): #TODO change into (i <= (int(mms) + int(max_bulges)))
                    fl_buttons.append(
                        html.Button(str(i) + ' mm',id = 'btn' + str(i)),
                    )
                else:
                    fl_buttons.append(
                        html.Button(str(i) + ' mm',id = 'btn' + str(i), style = {'display':'none'}), #Hide buttons for mms non selected
                    )

            fl.append(html.Br())

            radar_img = 'summary_single_guide_' + guide + '_' + str(0) + 'mm.png'

            barplot_img = 'summary_histogram_' + guide + '_' + str(0) + 'mm.png'
            try:            #NOTE serve per non generare errori se il barplot non è stato fatto
                barplot_src = 'data:image/png;base64,{}'.format(base64.b64encode(open(current_working_directory + 'Results/' + job_id + '/' + barplot_img, 'rb').read()).decode())
            except:
                barplot_src = ''
            try:
                barplot_href = 'assets/Img/' + job_id + '/' + barplot_img
            except:
                barplot_href = ''

            try:
                radar_src = 'data:image/png;base64,{}'.format(base64.b64encode(open(current_working_directory + 'Results/' + job_id + '/' + radar_img, 'rb').read()).decode())
            except:
                radar_src = ''
            try:
                radar_href = 'assets/Img/' + job_id + '/' + radar_img
            except:
                radar_href = ''

            if genome_type != 'ref':
                population_1000gp = associateSample.loadSampleAssociation(job_directory + 'sampleID.txt')[2]

                super_populations = [{'label':i, 'value':i} for i in population_1000gp.keys()]
                populations = []
                for k in population_1000gp.keys():
                    for i in population_1000gp[k]:
                        populations.append({'label':i, 'value':i})
            else:
                super_populations = []
                populations = []

            fl.append(
                html.Div(
                    [   dbc.Row(
                            [
                                dbc.Col(
                                    [
                                        html.P('Select Mismatch Value'),
                                        dbc.Row(
                                            html.Div(fl_buttons),
                                        )
                                    ]
                                ),
                                dbc.Col(
                                    [
                                        html.P('Select Individual Data', style = samp_style ),
                                        dbc.Row([
                                        dbc.Col(html.Div(dcc.Dropdown(options = super_populations, id = 'dropdown-superpopulation-sample', placeholder = 'Select a Super Population', style = samp_style))),
                                        dbc.Col(html.Div(dcc.Dropdown(options = populations, id = 'dropdown-population-sample', placeholder = 'Select a Population', style = samp_style))),
                                        dbc.Col(html.Div(dcc.Dropdown( id = 'dropdown-sample', placeholder = 'Select a Sample', style = samp_style))),
                                        ])
                                    ]
                                )
                            ]
                        ),
                    ]
                )
            )
            fl.append(html.Hr())
            fl.append(
                    html.Div(
                        [
                            dbc.Row(
                                [
                                    dbc.Col([    #Guide part
                                        html.Div(
                                            [

                                                dbc.Row(html.Br()),
                                                dbc.Row(
                                                    [
                                                        dbc.Col(
                                                            html.A(
                                                                html.Img(src = radar_src, id = 'barplot-img-guide', width="100%", height="auto"),
                                                                target="_blank",
                                                                href = radar_href
                                                            ),
                                                            width = 10
                                                        )
                                                    ]
                                                ),
                                                dbc.Row(html.Br()),
                                                dbc.Row(
                                                    [
                                                        dbc.Col(
                                                            html.A(
                                                                html.Img(src = barplot_src,id = 'radar-img-guide', width="100%", height="auto"),
                                                                target="_blank",
                                                                href = barplot_href
                                                            ),
                                                            width = 10
                                                        )
                                                    ]
                                                )

                                            ],
                                            id = 'div-guide-image'
                                        )
                                    ]),
                                    dbc.Col([    #Sample part
                                        html.Div(
                                            [

                                            ],
                                            id = 'div-sample-image'
                                        )
                                    ])
                                ]
                            )
                        ]

                    )
                )

            fl.append(html.Br())
            fl.append(html.Br())

            #TODO codice per l'integrazione del CFD graph. When .CFDGraph.txt will be integrated, remove the try/except

            cfd_path = job_directory + job_id +'.CFDGraph.txt'
            if not isfile(cfd_path): #No file found
                return fl

            fl.extend(
                CFDGraph.CFDGraph(cfd_path)
            )

            return fl
        # guide = all_guides[int(sel_cel[0]['row'])]['State']
            # return guide + value
        raise PreventUpdate

    #Open in browser the result directory
    @app.callback(
        Output('div-open-result-directory', 'children'),
        [Input('button-open-result-directory', 'n_clicks')],
        [State('url', 'search'),
        State('div-open-result-directory', 'children')]
    )
    def openResultDirectory(n, search, guide):
        '''
        Opens a new tab where the final list of off-targets is shown (NOT available when ONLINE mode is selected)

        ***Args***

        + [**n**] **button-open-result-directory** (*n_cliks*): number of times the button was pressed
        + [**search**] **url** (*search*): string containing the job ID, eg '?job=QV99PN6XDL'
        + [**guide**] **div-open-result-directory** (*children*): string of the currently selected guide

        ***Returns***

        + Opens a new tab showing the off-target list of jobID.targets.GUIDE.txt final file
        '''
        if n is None or ONLINE:
            raise PreventUpdate
        job_id = search.split('=')[-1]  #TODO decidere se aprire tutta la cartella, solo il file, e nel secondo caso creare copia submit job che non rimuova .targets.GUIDE.txt
        wb.open_new_tab(current_working_directory + 'Results/' + job_id + '/' + job_id + '.targets.' + guide + '.txt')
        raise PreventUpdate

    #Select figures on mms value, sample value
    @app.callback(
        [Output('div-guide-image', 'children'),
        Output('div-sample-image', 'children')],
        [Input('btn0', 'n_clicks_timestamp'),
        Input('btn1', 'n_clicks_timestamp'),
        Input('btn2', 'n_clicks_timestamp'),
        Input('btn3', 'n_clicks_timestamp'),
        Input('btn4', 'n_clicks_timestamp'),
        Input('btn5', 'n_clicks_timestamp'),
        Input('btn6', 'n_clicks_timestamp'),
        Input('btn7', 'n_clicks_timestamp'),
        Input('btn8', 'n_clicks_timestamp'),
        Input('btn9', 'n_clicks_timestamp'),
        Input('dropdown-superpopulation-sample', 'value'),
        Input('dropdown-population-sample', 'value'),
        Input('dropdown-sample', 'value'),
        Input('general-profile-table', 'selected_cells')],
        [State('url', 'search'),
        State('general-profile-table', 'data')]
    )
    def updateImagesTabs(n0, n1, n2, n3, n4, n5, n6, n7, n8, n9, superpopulation, population, sample, sel_cel, search, all_guides):
        '''
        Loads the images on the Graphical Report based on the selected mismatch value and/or sample, population, superpopulation value.

        ***Args***

        + [**n_**] **btn_** (*n_clicks_timestam*): string of the timestamp of the last time the button was pressed.
        + [**superpopulation**] **dropdown-superpopulation-sample** (*value*): string of the selected superpopulation
        + [**population**] **dropdown-population-sample** (*value*): string of the selected population
        + [**sample**] **dropdown-sample** (*value*): string of the selected sample
        + [**sel_cel**] **general-profile-table** (*selected_cells*): list contaning the selected row of the main guide table
        + [**search**] **url** (*search*): string containing the job ID, eg '?job=QV99PN6XDL'
        + [**all_guides**] **general-profile-table** (*data*): list of all the rows that are currenty displayed in the main guide table

        ***Returns***

        + **div-guide-image** (*children*): returns barplot and radarchart of the selected guide
        + **div-sample-image** (*children*): returns radarchart specific for sample/pop/superpop of the selected guide

        ***Details***

        + The function sort all the timestamps of the buttons (can be up to 10 buttons, one for each mms value) and select the one that was clicked last.
        Since it is not possible in the current Dash version to create callbacks of components that are not available in the app.layout, but the buttons
        are created dynamically (we do not know how many mms the user selects), we create 10 buttons and show only the ones corresponding to the number
        of selected mms, the others are hidden using `{'display':'none'}`. In this way we can create this callback even when we do not know how may mms
        the user will set.
        + If the image for a specific sample/pop/superpop is not available, the function calls `crispritz.py generate-report` in order to create the image
        + The corresponding image is then loaded from the `assets/Img/jobID` directory, in order to be also available to be opened in a new tab (by
        clicking on it)
        '''

        if sel_cel is None :
            raise PreventUpdate
        job_id = search.split('=')[-1]
        job_directory = current_working_directory + 'Results/' + job_id + '/'
        guide = all_guides[int(sel_cel[0]['row'])]['Guide']

        guide_images = []
        sample_images = []
        with open(current_working_directory + 'Results/' + job_id + '/Params.txt') as p:
            all_params = p.read()
            gecko_comp = (next(s for s in all_params.split('\n') if 'Gecko' in s)).split('\t')[-1]

        gecko_string = ''
        if gecko_comp == 'True':
            gecko_string = '-gecko'

        #Initialize to 0 Buttons not pressed
        if not n0:
            n0 = 0
        if not n1:
            n1 = 0
        if not n2:
            n2 = 0
        if not n3:
            n3 = 0
        if not n4:
            n4 = 0
        if not n5:
            n5 = 0
        if not n6:
            n6 = 0
        if not n7:
            n7 = 0
        if not n8:
            n8 = 0
        if not n9:
            n9 = 0
        btn_group = []
        btn_group.append(n0)
        btn_group.append(n1)
        btn_group.append(n2)
        btn_group.append(n3)
        btn_group.append(n4)
        btn_group.append(n5)
        btn_group.append(n6)
        btn_group.append(n7)
        btn_group.append(n8)
        btn_group.append(n9)

        #Check last pressed button
        if max(btn_group) == n0:
            mm_show = 0
        if max(btn_group) == n1:
            mm_show = 1
        if max(btn_group) == n2:
            mm_show = 2
        if max(btn_group) == n3:
            mm_show = 3
        if max(btn_group) == n4:
            mm_show = 4
        if max(btn_group) == n5:
            mm_show = 5
        if max(btn_group) == n6:
            mm_show = 6
        if max(btn_group) == n7:
            mm_show = 7
        if max(btn_group) == n8:
            mm_show = 8
        if max(btn_group) == n9:
            mm_show = 9
        if max(btn_group) == 0:
            mm_show = 0
        radar_img = 'summary_single_guide_' + guide + '_' + str(mm_show) + 'mm.png'

        barplot_img = 'summary_histogram_' + guide + '_' + str(mm_show) + 'mm.png'
        try:            #NOTE if the image is not available, show nothing
            barplot_src = 'data:image/png;base64,{}'.format(base64.b64encode(open(current_working_directory + 'Results/' + job_id + '/' + barplot_img, 'rb').read()).decode())
        except:
            barplot_src = ''
        try:
            barplot_href = 'assets/Img/' + job_id + '/' + barplot_img
        except:
            barplot_href = ''

        try:
            radar_src = 'data:image/png;base64,{}'.format(base64.b64encode(open(current_working_directory + 'Results/' + job_id + '/' + radar_img, 'rb').read()).decode())
        except:
            radar_src = ''
        try:
            radar_href = 'assets/Img/' + job_id + '/' + radar_img
        except:
            radar_href = ''

        guide_images.extend([

            dbc.Row(html.Br()),
            dbc.Row(
                [
                    dbc.Col(
                        html.A(
                            html.Img(src = radar_src, id = 'radar-img-guide', width="100%", height="auto"),
                            target="_blank",
                            href = radar_href
                        ),
                        width = 10
                    )
                ]
            ),
            dbc.Row(html.Br()),
            dbc.Row(
                [
                    dbc.Col(
                        html.A(
                            html.Img(src = barplot_src,id = 'barplot-img-guide', width="100%", height="auto"),
                            target="_blank",
                            href = barplot_href
                        ),
                        width = 10
                    )
                ]
            )
        ])
        class_images = [(sample, 'Samples'), (population, 'Population'), (superpopulation, 'Superpopulation')]

        #If the user selects a sample/pop/superpop not already processed, call crispritz generate-report and create the image
        for c in class_images:
            if c[0]:
                try:
                    first_img_source = 'data:image/png;base64,{}'.format(base64.b64encode(open(job_directory + 'summary_single_guide_' + c[0] +'_' + guide + '_' + str(mm_show) + 'mm.png', 'rb').read()).decode())
                except:
                    #create image from annotation file of samples
                    subprocess.call(['cd '+job_directory +';'+
                    ' crispritz.py generate-report ' + guide + ' -mm ' + str(mm_show) + ' -annotation ' + job_id + '.sample_annotation.' + guide +
                    '.' + c[1].lower() + '.txt '+ gecko_string +' -ws -sample ' + c[0]], shell = True)

                    copy_img = subprocess.Popen(['cp ' + job_directory + 'summary_single_guide_' + c[0] +'_' + guide + '_' + str(mm_show) + 'mm.png assets/Img/' + job_id], shell = True)
                    copy_img.wait() #BUG se metto il copia link, mi si ricarica la pagina, forse il problema non c'è se uso gnicorn
                    first_img_source = 'data:image/png;base64,{}'.format(base64.b64encode(open(job_directory + 'summary_single_guide_' + c[0] +'_' + guide + '_' + str(mm_show) + 'mm.png', 'rb').read()).decode())
                try:
                    first_img_href = 'assets/Img/' + job_id + '/' + 'summary_single_guide_' + c[0] +'_' + guide + '_' + str(mm_show) + 'mm.png'
                except:
                    first_img_href = ''
                sample_images.append(dbc.Row(html.Br()))

                sample_images.append(
                    dbc.Row(
                    [
                        dbc.Col(
                            html.A(
                                html.Img(src = first_img_source, width="100%", height="auto"),
                                target="_blank",
                                href = first_img_href
                            ),
                            width = 10
                        ),
                    ],
                    no_gutters=True
                ),
                )
        return guide_images, sample_images

    def generate_table(dataframe, id_table, genome_type, guide='', job_id='', max_rows=2600):
        '''
        Generates an html.Table for the Summary by Guide section.

        ***Args***

        + **dataframe**: dataframe containing the data to display
        + **id_table**: string that identifies the ID of the new generated html.Table
        + **genome_type**: string containing the type of the search ('ref', 'var' or 'both')
        + **guide**: string of the selected guide
        + **job_id**: string of the current jobID
        + **max_rows**: int of the max number of rows of the table

        ***Returns***

        + html.Table() containing data for the Summary by Guide section
        '''
        if genome_type == 'both':
            header = [html.Tr([
                html.Th('Bulge Type', rowSpan = '2', style = {'vertical-align':'middle', 'text-align':'center'}),
                html.Th('Mismatches', rowSpan = '2', style = {'vertical-align':'middle', 'text-align':'center'}),
                html.Th('Bulge Size', rowSpan = '2', style = {'vertical-align':'middle', 'text-align':'center'}),
                html.Th('Targets found in Genome', colSpan = str(3), style = {'vertical-align':'middle', 'text-align':'center'}),
                html.Th('PAM Creation', rowSpan = '2', style = {'vertical-align':'middle', 'text-align':'center'}),
                html.Th('', rowSpan = '2'),
                ])
            ]
        # 'Bulge Type' 'Mismatches' 'Bulge Size' 'Targets in Reference' 'Targets in Enriched' 'Combined' 'PAM Creation' ''

            header.append(html.Tr([html.Th(x, style = {'vertical-align':'middle', 'text-align':'center'}) for x in ['Reference', 'Enriched','Combined']]))
        else:
            header = [html.Tr([html.Th(col, style = {'vertical-align':'middle', 'text-align':'center'}) for col in dataframe.columns])]
        return html.Table(
            # Header
            # [html.Tr([html.Th(col) if col != 'Targets in Enriched' else html.Th(html.Abbr(col, title = 'Counting of targets that are generated by the insertion of a IUPAC nucleotide of a sample',
            # style = {'text-decoration':'underline'})) for col in dataframe.columns])] +
            # [html.Tr([html.Th(col, rowSpan = 2) if 'Targets' not in col or 'Combined' not in col else html.Th('Targets') for col in dataframe.columns])] +
            header  +
            # Body
            [html.Tr([
                html.Td(html.A(dataframe.iloc[i][col],  href = 'result?job=' + job_id + '#' + guide +'new' + dataframe.iloc[i]['Bulge Type'] + str(dataframe.iloc[i]['Bulge Size']) + str(dataframe.iloc[i]['Mismatches']) , target = '_blank' ), style = {'vertical-align':'middle', 'text-align':'center'}) if col == '' else  html.Td(dataframe.iloc[i][col],style = {'vertical-align':'middle', 'text-align':'center'}) for col in dataframe.columns
            ]) for i in range(min(len(dataframe), max_rows))],
            style = {'display':'inline-block'},
            id = id_table
        )

    def generate_table_samples(dataframe, id_table, page ,guide='', job_id='', max_rows=10):
        '''
        Generates an html.Table for the Summary by Sample section.

        ***Args***

        + **dataframe**: dataframe containing the data to display
        + **id_table**: string that identifies the ID of the new generated html.Table
        + **page**: int containing the current page of the table
        + **guide**: string of the selected guide
        + **job_id**: string of the current jobID
        + **max_rows**: int of the max number of rows of the table

        ***Returns***

        + html.Table() containing data for the Summary by Sample section
        '''
        rows_remaining = len(dataframe) - (page - 1) * max_rows
        return html.Table(
            # Header
            # [html.Tr([html.Th(col) if col != 'Targets in Enriched'  else html.Th(html.Abbr(col, title = 'Counting of targets that are generated by the insertion of a IUPAC nucleotide of a sample',
            # style = {'text-decoration':'underline'})) for col in dataframe.columns]) ] +
            [html.Tr([html.Th(col) for col in dataframe.columns]) ] +
            # Body
            [html.Tr([
                html.Td(html.A(dataframe.iloc[i + (page - 1)*max_rows][col],  href = 'result?job=' + job_id + '#' + guide +'-Sample-' + dataframe.iloc[i  + (page - 1)*max_rows]['Sample'] , target = '_blank' )) if col == '' else  html.Td(dataframe.iloc[i + (page - 1)*max_rows][col]) for col in dataframe.columns
            ]) for i in range(min(rows_remaining, max_rows))],
            style = {'display':'inline-block'},
            id = id_table
        )

    def generate_table_position(dataframe, id_table, page, mms, bulges, guide = '', job_id = '', max_rows = 10): #NOTE v2 della tabella posizioni       #TODO modifica layout righe per allinearle
        '''
        Generates an html.Table for the Summary by Position section.

        ***Args***

        + **dataframe**: dataframe containing the data to display
        + **id_table**: string that identifies the ID of the new generated html.Table
        + **page**: int containing the current page of the table
        + **mms**: int value of the max allowed mismatched
        + **bulges**: int value of the max allowed bulges
        + **guide**: string of the selected guide
        + **job_id**: string of the current jobID
        + **max_rows**: int of the max number of rows of the table

        ***Returns***

        + html.Table() containing data for the Summary by Position section
        '''
        rows_remaining = len(dataframe) - (page - 1) * max_rows
        header = [html.Tr([
            html.Th('Chromosome', rowSpan = '2', style = {'vertical-align':'middle', 'text-align':'center'}),
            html.Th('Position', rowSpan = '2', style = {'vertical-align':'middle', 'text-align':'center'}),
            html.Th('Best Target', rowSpan = '2', style = {'vertical-align':'middle', 'text-align':'center'}),
            html.Th('Min Mismatch', rowSpan = '2', style = {'vertical-align':'middle', 'text-align':'center'}),
            html.Th('Min Bulge', rowSpan = '2', style = {'vertical-align':'middle', 'text-align':'center'}),
            html.Th('Bulge', rowSpan = '2', style = {'vertical-align':'middle', 'text-align':'center'}),
            html.Th('Targets in Cluster by Mismatch Value', colSpan = str(mms +1), style = {'vertical-align':'middle', 'text-align':'center'}),
            html.Th('', rowSpan = '2'),
            ])
        ]
        mms_header = []
        for mm in range (mms +1):
            mms_header.append(html.Th(str(mm) + ' MM', style = {'vertical-align':'middle', 'text-align':'center'}))
        header.append(html.Tr(mms_header))

        data = []
        for i in range(min(rows_remaining, max_rows)):
            first_cells = [
                html.Td(dataframe.iloc[i + (page - 1)*max_rows]['Chromosome'], rowSpan = str(bulges +1),  style = {'vertical-align':'middle', 'text-align':'center'}),
                html.Td(dataframe.iloc[i + (page - 1)*max_rows]['Position'], rowSpan = str(bulges +1),  style = {'vertical-align':'middle', 'text-align':'center'}),
                html.Td(dataframe.iloc[i + (page - 1)*max_rows]['Best Target'], rowSpan = str(bulges+1),  style = {'vertical-align':'middle', 'text-align':'center'}),
                html.Td(dataframe.iloc[i + (page - 1)*max_rows]['Min Mismatch'], rowSpan = str(bulges+1),  style = {'vertical-align':'middle', 'text-align':'center'}),
                html.Td(dataframe.iloc[i + (page - 1)*max_rows]['Min Bulge'], rowSpan = str(bulges+1),  style = {'vertical-align':'middle', 'text-align':'center'}),
                html.Th('0 Bulge', style = {'vertical-align':'middle', 'text-align':'center', 'padding-left':'0'})
            ]

            mm_cells = [html.Td(dataframe.iloc[i + (page - 1)*max_rows][col], style = {'vertical-align':'middle', 'text-align':'center'}) for col in dataframe.columns[5:5+mms+1]]
            data.append(html.Tr(first_cells + mm_cells + [html.Td(
                    html.A('Show Targets',  href = 'result?job=' + job_id + '#' + guide +'-Pos-' + str(dataframe.iloc[i + (page - 1)*max_rows]['Chromosome']) + '-' +  str(dataframe.iloc[i + (page - 1)*max_rows]['Position']) , target = '_blank'),
                    rowSpan = str(bulges+1), style = {'vertical-align':'middle', 'text-align':'center'})
                ]))
            for b in range (bulges):
                data.append(
                    html.Tr(
                        [html.Th(str(b +1) + ' Bulge', style = {'vertical-align':'middle', 'text-align':'center'} )]
                        +
                        [html.Td(dataframe.iloc[i + (page - 1)*max_rows][col]) for col in dataframe.columns[5 + (b +1) *(mms +1) : 5 + (b +1) *(mms+1) + mms +1]]
                    )
                )

        return html.Table(header + data, style = {'display':'inline-block'}, id = id_table)

    #Callback to update the population tab based on superpopulation selected
    @app.callback(
        [Output('dropdown-population-sample', 'options'),
        Output('dropdown-population-sample', 'value')],
        [Input('dropdown-superpopulation-sample', 'value')],
        [State('url', 'search')]
    )
    def updatePopulationDrop(superpop, search):
        '''
        Update the Dropdown containing the Population list based on the selected Superpopulation.

        ***Args***

        + [**superpop**] **dropdown-superpopulation-sample** (*value*): string of the selected Superpopulation
        + [**search**] **url** (*search*): string containing the job ID, eg '?job=QV99PN6XDL'

        ***Returns***

        + **dropdown-population-sample** (*options*): list of dictionaries for the option element of the Population dropdown
        + **dropdown-population-sample** (*value*): currently selected value (reinitialized to be empty)
        '''
        if superpop is None or superpop == '':
            raise PreventUpdate
        job_id = search.split('=')[-1]
        job_directory = current_working_directory + 'Results/' + job_id + '/'
        population_1000gp = associateSample.loadSampleAssociation(job_directory + 'sampleID.txt')[2]
        return [{'label':i, 'value':i} for i in population_1000gp[superpop]], None

    #Callback to update the sample based on population selected
    @app.callback(
        [Output('dropdown-sample','options'),
        Output('dropdown-sample','value')],
        [Input('dropdown-population-sample', 'value')],
        [State('url', 'search')]
    )
    def updateSampleDrop(pop, search):
        '''
        Update the Dropdown containing the Sample list based on the selected Population.

        ***Args***

        + [**pop**] **dropdown-population-sample** (*value*): string of the selected Population
        + [**search**] **url** (*search*): string containing the job ID, eg '?job=QV99PN6XDL'

        ***Returns***

        + **dropdown-sample** (*options*): list of dictionaries for the option element of the Sample dropdown
        + **dropdown-sample** (*value*): currently selected value (reinitialized to be empty)
        '''
        if pop is None or pop == '':
            return [], None
        job_id = search.split('=')[-1]
        job_directory = current_working_directory + 'Results/' + job_id + '/'
        dict_pop = associateSample.loadSampleAssociation(job_directory + 'sampleID.txt')[3]
        return [{'label': sam, 'value' : sam} for sam in dict_pop[pop]], None

    #Callback to update the hidden div filter
    @app.callback(
        Output('div-sample-filter-query', 'children'),
        [Input('button-filter-population-sample', 'n_clicks')],
        [State('dropdown-superpopulation-sample', 'value'),
        State('dropdown-population-sample', 'value'),
        State('input-sample', 'value')]
    )
    def updateSampleFilter(n, superpopulation, population, sample):
        '''
        Update the filter (superpop,pop,sample) for the filtering of the Summary by Sample table

        ***Args***

        + [**n**] **button-filter-population-sample** (*n_clicks*): int for the number of times the button was clicked
        + [**superpopulation**] **dropdown-superpopulation-sample** (*value*): string containing the selected Superpopulation
        + [**population**] **dropdown-population-sample** (*value*): string containing the selected Population
        + [**sample**] **input-sample** (*value*): string containing the Sample, given in input by the user

        ***Returns***

        + **div-simple-filter-query** (**children**): string in the format of 'superpop,pop,sample', or 'None' if a parameter was not provided (eg
        'EU,TSI,None')
        '''
        if n is None:
            raise PreventUpdate
        return str(superpopulation) + ',' + str(population) + ',' + str(sample).replace(' ','').upper()

    #Callback to view next/prev page on sample table
    @app.callback(
        [Output('div-table-samples', 'children'),
        Output('div-current-page-table-samples', 'children')],
        [Input('prev-page-sample', 'n_clicks_timestamp'),
        Input('next-page-sample', 'n_clicks_timestamp'),
        Input('div-sample-filter-query', 'children')],
        [State('button-filter-population-sample', 'n_clicks_timestamp'),
        State('url', 'search'),
        State('general-profile-table', 'selected_cells'),
        State('general-profile-table', 'data'),
        State('div-current-page-table-samples', 'children')]
    )
    def filterSampleTable( nPrev, nNext, filter_q, n, search, sel_cel, all_guides, current_page):
        '''
        Filtering/changing page of the table in the Summary by Sample tab.

        ***Args***
        + [**nPrev**] **prev-page-sample** (*n_clicks_timestamp*): int timestamp of last click on Previous Page button
        + [**nNext**] **next-page-sample** (*n_clicks_timestamp*): int timestamp of last click on Next Page button
        + [**filter_q**] **div-sample-filter-query** (*children*): string of the filter query
        + [**n**] **button-filter-population-sample** (*n_clicks_timestamp*): int timestamp of the Apply Filter button
        + [**search**] **url** (*search*): string containing the job ID, eg '?job=QV99PN6XDL'
        + [**sel_cel**] **general-profile-table** (*selected_cells*): list contaning the selected row of the main guide table
        + [**all_guides**] **general-profile-table** (*data*): list of all the rows that are currenty displayed in the main guide table
        + [**current_page**] **div-current-page-table-samples** (*children*): string containing the current page of the table (eg '1/15')

        ***Returns***

        + **div-table-samples** (*children*): html.Table with filtering applied and/or next/previous page of data
        + **div-current-page-table-samples** (*children*): string of the currently displayed page
        '''
        if sel_cel is None:
            raise PreventUpdate
        if nPrev is None and nNext is None and n is None:
            raise PreventUpdate

        if nPrev is None:
            nPrev = 0
        if nNext is None:
            nNext = 0
        if n is None:
            n = 0

        sup_pop = filter_q.split(',')[0]
        pop = filter_q.split(',')[1]
        samp = filter_q.split(',')[2]
        if sup_pop == 'None':
            sup_pop = None
        if pop == 'None':
            pop = None
        if samp == 'None' or samp == 'NONE':
            samp = None
        current_page = current_page.split('/')[0]
        current_page = int(current_page)
        btn_sample_section = []
        btn_sample_section.append(n)
        btn_sample_section.append(nPrev)
        btn_sample_section.append(nNext)
        job_id = search.split('=')[-1]
        job_directory = current_working_directory + 'Results/' + job_id + '/'
        population_1000gp = associateSample.loadSampleAssociation(job_directory + 'sampleID.txt')[2]
        with open(current_working_directory + 'Results/' + job_id + '/Params.txt') as p:
            all_params = p.read()
            genome_type_f = (next(s for s in all_params.split('\n') if 'Genome_selected' in s)).split('\t')[-1]
            ref_comp = (next(s for s in all_params.split('\n') if 'Ref_comp' in s)).split('\t')[-1]

        genome_type = 'ref'
        if '+' in genome_type_f:
            genome_type = 'var'
        if 'True' in ref_comp:
            genome_type = 'both'

        guide = all_guides[int(sel_cel[0]['row'])]['Guide']
        if genome_type == 'both':
            col_names_sample = ['Sample', 'Gender', 'Population', 'Super Population',  'Targets in Reference', 'Targets in Enriched', 'Targets in Population', 'Targets in Super Population', 'PAM Creation', 'Class']
        else:
            col_names_sample = ['Sample', 'Gender', 'Population', 'Super Population',  'Targets in Reference', 'Targets in Enriched', 'Targets in Population', 'Targets in Super Population', 'PAM Creation', 'Class']
        if max(btn_sample_section) == n:              #Last button pressed is filtering, return the first page of the filtered table
            if genome_type == 'both':
                df = pd.read_csv(job_directory + job_id + '.summary_by_samples.' + guide + '.txt', sep = '\t', names = col_names_sample, skiprows = 1)
                df = df.sort_values('Targets in Enriched', ascending = False)
                df.drop(['Targets in Reference'], axis = 1, inplace = True)
            else:
                df = pd.read_csv(job_directory + job_id + '.summary_by_samples.' + guide + '.txt', sep = '\t', names = col_names_sample, skiprows = 1)
                df = df.sort_values('Targets in Reference', ascending = False)
                df.drop(['Targets in Reference'], axis = 1, inplace = True)
                df.drop(['Class'], axis = 1, inplace = True)
            more_info_col = []
            for i in range(df.shape[0]):
                more_info_col.append('Show Targets')
            df[''] = more_info_col
            if (sup_pop is None or sup_pop == '') and (pop is None or pop == '') and (samp is None or samp == ''):   #No filter value selected
                max_page = len(df.index)
                max_page = math.floor(max_page / 10) + 1
                return generate_table_samples(df, 'table-samples', 1, guide, job_id ), '1/' + str(max_page)
            if samp is None or samp == '':
                if pop is None or pop == '':
                    df.drop(df[(~(df['Population'].isin(population_1000gp[sup_pop])))].index , inplace = True)
                else:
                    df.drop(df[(df['Population'] != pop)].index , inplace = True)
            else:
                df.drop(df[(df['Sample'] != samp)].index , inplace = True)
            max_page = len(df.index)
            max_page = math.floor(max_page / 10) + 1
            return generate_table_samples(df, 'table-samples', 1, guide, job_id ), '1/' + str(max_page)
        else:
            if max(btn_sample_section) == nNext:
                current_page = current_page + 1
                if genome_type == 'both':
                    df = pd.read_csv(job_directory + job_id + '.summary_by_samples.' + guide + '.txt', sep = '\t', names = col_names_sample, skiprows = 1)
                    df = df.sort_values('Targets in Enriched', ascending = False)
                    df.drop(['Targets in Reference'], axis = 1, inplace = True)
                else:
                    df = pd.read_csv(job_directory + job_id + '.summary_by_samples.' + guide + '.txt', sep = '\t', names = col_names_sample, skiprows = 1)
                    df = df.sort_values('Targets in Reference', ascending = False)
                    df.drop(['Targets in Reference'], axis = 1, inplace = True)
                    df.drop(['Class'], axis = 1, inplace = True)
                more_info_col = []
                for i in range(df.shape[0]):
                    more_info_col.append('Show Targets')
                df[''] = more_info_col
                #Active filter
                if pop or sup_pop or samp:
                    if samp is None or samp == '':
                        if pop is None or pop == '':
                            df.drop(df[(~(df['Population'].isin(population_1000gp[sup_pop])))].index , inplace = True)
                        else:
                            df.drop(df[(df['Population'] != pop)].index , inplace = True)
                    else:
                        df.drop(df[(df['Sample'] != samp)].index , inplace = True)

                if ((current_page - 1) * 10) > len(df):
                    current_page = current_page -1
                    if current_page < 1:
                        current_page = 1
                max_page = len(df.index)
                max_page = math.floor(max_page / 10) + 1
                return generate_table_samples(df, 'table-samples', current_page, guide, job_id ), str(current_page) + '/' + str(max_page)

            else:   #Go to previous page
                current_page = current_page - 1
                if current_page < 1:
                    current_page = 1
                if genome_type == 'both':
                    df = pd.read_csv(job_directory + job_id + '.summary_by_samples.' + guide + '.txt', sep = '\t', names = col_names_sample, skiprows = 1)
                    df = df.sort_values('Targets in Enriched', ascending = False)
                    df.drop(['Targets in Reference'], axis = 1, inplace = True)
                else:
                    df = pd.read_csv(job_directory + job_id + '.summary_by_samples.' + guide + '.txt', sep = '\t', names = col_names_sample, skiprows = 1)
                    df = df.sort_values('Targets in Reference', ascending = False)
                    df.drop(['Targets in Reference'], axis = 1, inplace = True)
                    df.drop(['Class'], axis = 1, inplace = True)
                more_info_col = []
                for i in range(df.shape[0]):
                    more_info_col.append('Show Targets')
                df[''] = more_info_col
                if pop or sup_pop or samp:
                    if samp is None or samp == '':
                        if pop is None or pop == '':
                            df.drop(df[(~(df['Population'].isin(population_1000gp[sup_pop])))].index , inplace = True)
                        else:
                            df.drop(df[(df['Population'] != pop)].index , inplace = True)
                    else:
                        df.drop(df[(df['Sample'] != samp)].index , inplace = True)
                max_page = len(df.index)
                max_page = math.floor(max_page / 10) + 1
                return generate_table_samples(df, 'table-samples', current_page, guide, job_id ), str(current_page) + '/' + str(max_page)
        raise PreventUpdate

    #Callback to update the hidden div filter position
    @app.callback(
        Output('div-position-filter-query', 'children'),
        [Input('button-filter-position', 'n_clicks')],
        [State('dropdown-chr-table-position', 'value'),
        State('input-position-start', 'value'),
        State('input-position-end', 'value')]
    )
    def updatePositionFilter(n, chr, pos_start, pos_end):
        '''
        Update the filter (chr,start,end) for the filtering of the Summary by Position table

        ***Args***

        + [**n**] **button-filter-population-sample** (*n_clicks*): int for the number of times the button was clicked
        + [**chr**] **dropdown-chr-table-position** (*value*): string containing the selected chromosome
        + [**pos_start**] **input-position-start** (*value*): string containing the start position
        + [**pos_end**] **input-position-end** (*value*): string containing the end position

        ***Returns***

        + **div-position-filter-query** (**children**): string in the format of 'chr,start,end', or 'None' if a parameter was not provided (eg
        'chr4,1010,None')
        '''
        if n is None:
            raise PreventUpdate
        if pos_start == '':
            pos_start = 'None'
        if pos_end == '':
            pos_end = 'None'
        return str(chr) + ',' + str(pos_start) + ',' + str(pos_end)

    #Callback to filter chr from Summary by Position table, and to show next/prev page
    @app.callback(
        [Output('div-table-position', 'children'),
        Output('div-current-page-table-position', 'children')],
        [Input('prev-page-position','n_clicks_timestamp'),
        Input('next-page-position', 'n_clicks_timestamp'),
        Input('div-position-filter-query', 'children')],
        [State('button-filter-position', 'n_clicks_timestamp'),
        State('url', 'search'),
        State('general-profile-table', 'selected_cells'),
        State('general-profile-table', 'data'),
        State('div-current-page-table-position', 'children'),
        State('div-mms-bulges-position', 'children')]
    )
    def filterPositionTable(nPrev, nNext, filter_q, n, search, sel_cel, all_guides, current_page, mms_bulge):
        '''
        Filtering/changing page of the table in the Summary by Position tab.

        ***Args***
        + [**nPrev**] **prev-page-position** (*n_clicks_timestamp*): int timestamp of last click on Previous Page button
        + [**nNext**] **next-page-position** (*n_clicks_timestamp*): int timestamp of last click on Next Page button
        + [**filter_q**] **div-position-filter-query** (*children*): string of the filter query
        + [**n**] **button-filter-position** (*n_clicks_timestamp*): int timestamp of the Apply Filter button
        + [**search**] **url** (*search*): string containing the job ID, eg '?job=QV99PN6XDL'
        + [**sel_cel**] **general-profile-table** (*selected_cells*): list contaning the selected row of the main guide table
        + [**all_guides**] **general-profile-table** (*data*): list of all the rows that are currenty displayed in the main guide table
        + [**current_page**] **div-current-page-table-samples** (*children*): string containing the current page of the table (eg '1/15')
        + [**mms_bulge**] **div-mms-bulge-position** (*children*): string containing the value of the max mismatch and bulge value (eg. '6-2')

        ***Returns***

        + **div-table-position** (*children*): html.Table with filtering applied and/or next/previous page of data
        + **div-current-page-table-position** (*children*): string of the currently displayed page
        '''

        if sel_cel is None:
            raise PreventUpdate
        if nPrev is None and nNext is None and n is None:
            raise PreventUpdate

        if nPrev is None:
            nPrev = 0
        if nNext is None:
            nNext = 0
        if n is None:
            n = 0

        filter_q = filter_q.split(',')
        chr = filter_q[0]
        if chr == 'None':
            chr = None
        pos_begin = filter_q[1]
        if pos_begin == 'None':
            pos_begin = None
        pos_end = filter_q[2]
        if pos_end == 'None':
            pos_end = None

        current_page = current_page.split('/')[0]
        current_page = int(current_page)
        mms = int(mms_bulge.split('-')[0])
        max_bulges = int(mms_bulge.split('-')[1])
        btn_position_section = []
        btn_position_section.append(n)
        btn_position_section.append(nPrev)
        btn_position_section.append(nNext)
        job_id = search.split('=')[-1]
        job_directory = current_working_directory + 'Results/' + job_id + '/'
        guide = all_guides[int(sel_cel[0]['row'])]['Guide']
        if max(btn_position_section) == n:              #Last button pressed is filtering, return the first page of the filtered table
            if pos_begin is None or pos_begin == '':
                pos_begin = 0
            if pos_end == '':
                pos_end = None
            if pos_end:
                if int(pos_end) < int(pos_begin):
                    pos_end = None
            df = pd.read_csv(job_directory + job_id + '.summary_by_position.' + guide +'.txt', sep = '\t')

            df.rename(columns = {'#Chromosome':'Chromosome'}, inplace = True)
            more_info_col = []
            for i in range(df.shape[0]):
                more_info_col.append('Show Targets')
            df[''] = more_info_col
            if chr is None or chr == '':
                max_page = len(df.index)
                max_page = math.floor(max_page / 10) + 1
                return generate_table_position(df, 'table-position', 1, mms, max_bulges,guide, job_id ), '1/' + str(max_page)
            if pos_end is None:
                df.drop(df[(df['Chromosome'] != chr) | ((df['Chromosome'] == chr) & (df['Position'] < int(pos_begin)) )].index , inplace = True)
            else:
                df.drop(df[(df['Chromosome'] != chr) | ((df['Chromosome'] == chr) & (df['Position'] < int(pos_begin)) | (df['Position'] > int(pos_end)))].index , inplace = True)
            max_page = len(df.index)
            max_page = math.floor(max_page / 10) + 1
            return generate_table_position(df, 'table-position', 1, mms, max_bulges,guide, job_id ), '1/'+ str(max_page)
        else:

            if max(btn_position_section) == nNext:
                current_page = current_page + 1
                if chr:
                    if pos_begin is None or pos_begin == '':
                        pos_begin = 0
                    if pos_end == '':
                        pos_end = None
                    if pos_end:
                        if int(pos_end) < int(pos_begin):
                            pos_end = None
                    df = pd.read_csv(job_directory + job_id + '.summary_by_position.' + guide +'.txt', sep = '\t')
                else:
                    df = pd.read_csv(job_directory + job_id + '.summary_by_position.' + guide +'.txt', sep = '\t')#, nrows = current_page * 10)
                df.rename(columns = {'#Chromosome':'Chromosome'}, inplace = True)
                more_info_col = []
                for i in range(df.shape[0]):
                    more_info_col.append('Show Targets')
                df[''] = more_info_col
                if chr:
                    if pos_end is None:
                        df.drop(df[(df['Chromosome'] != chr) | ((df['Chromosome'] == chr) & (df['Position'] < int(pos_begin)) )].index , inplace = True)
                    else:
                        df.drop(df[(df['Chromosome'] != chr) | ((df['Chromosome'] == chr) & (df['Position'] < int(pos_begin)) | (df['Position'] > int(pos_end)))].index , inplace = True)
                if ((current_page - 1) * 10) > len(df):
                    current_page = current_page -1
                    if current_page < 1:
                        current_page = 1
                max_page = len(df.index)
                max_page = math.floor(max_page / 10) + 1
                return generate_table_position(df, 'table-position', current_page, mms, max_bulges,guide, job_id ), str(current_page) + '/' + str(max_page)
            else:                       #Go to previous page
                current_page = current_page - 1
                if current_page < 1:
                    current_page = 1

                if chr:
                    if pos_begin is None or pos_begin == '':
                        pos_begin = 0
                    if pos_end == '':
                        pos_end = None
                    if pos_end:
                        if int(pos_end) < int(pos_begin):
                            pos_end = None
                    df = pd.read_csv(job_directory + job_id + '.summary_by_position.' + guide +'.txt', sep = '\t')
                else:
                    df = pd.read_csv(job_directory + job_id + '.summary_by_position.' + guide +'.txt', sep = '\t')#, nrows = current_page * 10)
                df.rename(columns = {'#Chromosome':'Chromosome'}, inplace = True)
                more_info_col = []
                for i in range(df.shape[0]):
                    more_info_col.append('Show Targets')
                df[''] = more_info_col
                if chr:
                    if pos_end is None:
                        df.drop(df[(df['Chromosome'] != chr) | ((df['Chromosome'] == chr) & (df['Position'] < int(pos_begin)) )].index , inplace = True)
                    else:
                        df.drop(df[(df['Chromosome'] != chr) | ((df['Chromosome'] == chr) & (df['Position'] < int(pos_begin)) | (df['Position'] > int(pos_end)))].index , inplace = True)

                max_page = len(df.index)
                max_page = math.floor(max_page / 10) + 1
                return generate_table_position(df, 'table-position', current_page, mms, max_bulges,guide, job_id ), str(current_page) + '/' + str(max_page)

    #If the input guides are different len, select the ones with same length as the first
    def selectSameLenGuides(list_guides):
        '''
        If the user provide guides with different lengths, the function select the first guide, calculate its length, and discards all the guides with
        different lengths.

        ***Args***:

        + **list_guides**: list of the input guides

        ***Returns***

        + **same_len_guides**: list of guides with the same length
        '''
        selected_length = len(list_guides.split('\n')[0])
        same_len_guides_list = []
        for guide in list_guides.split('\n'):
            if len(guide) == selected_length:
                same_len_guides_list.append(guide)
        same_len_guides = '\n'.join(same_len_guides_list).strip()
        return same_len_guides

    def resultPage(job_id):
        '''
        The function creates the layout for the Result Page, with the general summary table at the top, the popup Population histogram section, the
        Tabs for the three different summaries and report (Guide, Sample, Position, Graphical Sumamary).

        ***Args***

        + **job_id**: string containing the jobbID of the current job

        ***Returns***

        + **result_page**: list of Dash Components for the layout of the page
        '''
        value = job_id
        job_directory = current_working_directory + 'Results/' + job_id + '/'
        if (not isdir(job_directory)):
            return html.Div(dbc.Alert("The selected result does not exist", color = "danger"))

        #Load informations from the Params file
        with open(current_working_directory + 'Results/' + value + '/Params.txt') as p:
            all_params = p.read()
            mms = (next(s for s in all_params.split('\n') if 'Mismatches' in s)).split('\t')[-1]
            bulge_dna = (next(s for s in all_params.split('\n') if 'DNA' in s)).split('\t')[-1]
            bulge_rna = (next(s for s in all_params.split('\n') if 'RNA' in s)).split('\t')[-1]
            genome_type_f = (next(s for s in all_params.split('\n') if 'Genome_selected' in s)).split('\t')[-1]
            ref_comp = (next(s for s in all_params.split('\n') if 'Ref_comp' in s)).split('\t')[-1]
            max_bulges = (next(s for s in all_params.split('\n') if 'Max_bulges' in s)).split('\t')[-1]

        genome_name = genome_type_f
        genome_type = 'ref'
        if '+' in genome_type_f:
            genome_type = 'var'
            genome_name = genome_name.split('_')[0] + ' Variants'
        else:
            genome_name = genome_name.split('_')[0] + ' Reference'
        if 'True' in ref_comp:
            genome_type = 'both'
        mms = int(mms[0])

        #load acfd for each guide
        with open(current_working_directory + 'Results/' + value + '/acfd.txt') as a:
            all_scores = a.read().strip().split('\n')

        list_error_guides = []
        if os.path.exists(current_working_directory + 'Results/' + value + '/guides_error.txt'):
            with open(current_working_directory + 'Results/' + value + '/guides_error.txt') as error_g:
                for e_g in error_g:
                    list_error_guides.append(e_g.strip())

        #Columns for the main table
        col_targetfor = '('
        for i in range(1, mms + int(max_bulges)):
            col_targetfor = col_targetfor + str(i) + '-'
        col_targetfor = col_targetfor + str(mms + int(max_bulges))
        col_targetfor = col_targetfor + ' Mismatches + Bulges)'

        columns_profile_table = [
            {'name':['', 'Guide'], 'id':'Guide', 'type':'text'},
            {'name':['', 'CFD'], 'id':'CFD', 'type':'text'},
            {'name':['', 'Doench 2016'], 'id':'Doench 2016', 'type':'text'},    #Doench, only for REF or VAR
            {'name':['Doench 2016', 'Reference'], 'id':'Reference', 'type':'text'},        #REF Doench, only for Both
            {'name':['Doench 2016', 'Enriched'], 'id':'Enriched', 'type':'text'},          #VAR Doench, only for Both
            {'name':['', 'On-Targets Reference'], 'id':'On-Targets Reference', 'type':'text'},
            {'name':['', 'On-Targets Enriched'], 'id':'On-Targets Enriched', 'type':'text'},
            {'name':['', 'Samples in Class 0 - 0+ - 1 - 1+'], 'id':'Samples in Class 0 - 0+ - 1 - 1+', 'type':'text'},
            {'name':['', 'Genome'], 'id':'Genome', 'type':'text'},
            {'name':['Off-targets for Mismatch (MM) + Bulge (B) Value', 'Total'], 'id':'Total', 'type':'text'}
            ]    #Column of headers . Remove the entries accordingly when checking type of genome

        for i in range (1, mms + int(max_bulges) + 1):
            columns_profile_table.append({'name':['Off-targets for Mismatch (MM) + Bulge (B) Value', str(i) + ' MM + B'], 'id': str(i) + ' MM + B', 'type':'text'})
        remove_indices = set()

        if 'NO SCORES' in all_scores:
            # remove_indices.update([1,2,3,4])    #Remove CFD and Doench header
            remove_indices.update('CFD', 'Doench 2016', 'Reference', 'Enriched')

        if genome_type == 'ref':
            # remove_indices.update([3,4,6,7])
            remove_indices.update(['Reference', 'Enriched', 'On-Targets Enriched', 'Samples in Class 0 - 0+ - 1 - 1+' ])
        elif genome_type == 'both':
            # remove_indices.update([6])
            remove_indices.update(['Doench 2016','On-Targets Enriched'])
        else:
            # remove_indices.update([3,4,5,7])
            remove_indices.update(['Reference', 'Enriched', 'On-Targets Reference', 'Samples in Class 0 - 0+ - 1 - 1+'])

        #Remove headers not used in selected search result
        columns_profile_table = [i for j, i in enumerate(columns_profile_table) if columns_profile_table[j]['id'] not in remove_indices]

        final_list = []
        if list_error_guides:   #If some guides were not processed due to too many targets
            final_list.append(
                dbc.Alert(
                    [
                        'Warning: Some guides have too many targets! ',
                        html.A("Click here", href= URL + "/data/" + job_id + '/guides_error.txt', className="alert-link"),
                        ' to view them'
                    ], color='warning')
            )
        final_list.append(
            html.H3('Result Summary - ' + genome_name + ' - Mismatches ' + str(mms) + ' - DNA ' + bulge_dna + ' - RNA ' + bulge_rna)
        )

        add_to_description = html.P(
            'General summary for the given guides. For each guide, the number of Off-Targets found for each Mismatch + Bulge value is shown.'
        )
        if genome_type == 'both':
            add_to_description = html.P(
                [
                    'General summary for the given guides. For each guide, the number of ',
                    html.Span(
                        "Samples for each Class is provided",
                        id="tooltip-sample-class",
                        style={"textDecoration": "underline", "cursor": "pointer"}
                    ),
                    ', along with the number of Off-Targets found for each Mismatch + Bulge value, for both Reference and Enriched Genomes.',
                    dbc.Tooltip(
                        [
                            html.Div([html.P([html.B('Class 0:'), ' Samples that does not have any On-Targets']),
                            html.P([html.B('Class 0+:'), ' Samples that have a subset of the Reference Genome On-Targets']),
                            html.P([html.B('Class 1:'), ' Samples that have the same On-Targets as the Reference Genome']),
                            html.P([html.B('Class 1+:'), ' Samples that creates at least a new On-Target, that is not present in the Reference Genome'])],
                            style = {'display':'inline-block'})
                        ],
                        target="tooltip-sample-class", style = {'font-size': '12px'}
                    )
                ]
            )
        final_list.append(add_to_description)
        final_list.append(
            html.Div(
                dash_table.DataTable(
                    id = 'general-profile-table',
                    columns = columns_profile_table,
                    merge_duplicate_headers=True,
                    #fixed_rows={ 'headers': True, 'data': 0 },
                    selected_cells = [{'row':0, 'column':0}],
                    css= [{ 'selector': 'td.cell--selected, td.focused', 'rule': 'background-color: rgba(0, 0, 255,0.15) !important;' }, { 'selector': 'td.cell--selected *, td.focused *', 'rule': 'background-color: rgba(0, 0, 255,0.15) !important;'}],
                    page_current= 0,
                    page_size= PAGE_SIZE,
                    page_action='custom',
                    #virtualization = True,
                    filter_action='custom',
                    filter_query='',

                    sort_action='custom',
                    sort_mode='multi',
                    sort_by=[],
                    style_table={
                        'max-height': '260px',
                        'overflowY': 'scroll',
                    },
                    style_data={
                        'whiteSpace': 'pre',
                        'height': 'auto',
                        'font-size':'1.30rem'
                    },
                    style_data_conditional = [
                        {
                            'if': {
                                    'column_id' :'Genome'
                                },
                                'font-weight':'bold',
                                'textAlign': 'center'

                        }
                    ]
                )
                ,id = 'div-general-profile-table')
        )

        final_list.append(html.Br())

        if genome_type == 'ref':
            final_list.append(
            dcc.Tabs(id="tabs-reports", value='tab-summary-by-guide', children=[
                dcc.Tab(label='Summary by Guide', value='tab-summary-by-guide'),
                dcc.Tab(label='Summary by Position', value='tab-summary-by-position'),
                dcc.Tab(label='Graphical Summary', value='tab-summary-graphical'),
            ])
        )
        else:
            #Barplot for population distributions
            final_list.append(
                html.Div(
                    [
                        dbc.Row(
                            [
                                dbc.Col(html.Button("Show/Hide Target Distribution in SuperPopulations", id="btn-collapse-populations")),
                            ]
                        ),
                        dbc.Collapse(
                            dbc.Card(dbc.CardBody(
                                html.Div(id = 'content-collapse-population')
                            )),
                            id="collapse-populations",
                        ),
                    ]
                )
            )
            final_list.append(html.Br())
            final_list.append(
                dcc.Tabs(id="tabs-reports", value='tab-summary-by-guide', children=[
                    dcc.Tab(label='Summary by Guide', value='tab-summary-by-guide'),
                    dcc.Tab(label='Summary by Sample', value='tab-summary-by-sample'),
                    dcc.Tab(label='Summary by Position', value='tab-summary-by-position'),
                    dcc.Tab(label='Graphical Summary', value='tab-summary-graphical'),
                ])
            )
        final_list.append(html.Div(id = 'div-tab-content'))

        final_list.append(html.Div(genome_type, style = {'display':'none'}, id = 'div-genome-type'))
        result_page = html.Div(final_list, style = {'margin':'1%'})
        return result_page

    #Update color on selected row
    @app.callback(
        Output('general-profile-table', 'style_data_conditional'),
        [Input('general-profile-table', 'selected_cells')],
        [State('general-profile-table', 'data')]
    )
    def colorSelectedRow(sel_cel, all_guides):
        '''
        Update the background color of the row of the main table when the user clicks on a cell. Also change to bold the 'Reference' and 'Enriched'
        labels.

        ***Args***

        + [**sel_cel**] **general-profile-table** (*selected_cells*): list contaning the selected row of the main guide table
        + [**all_guides**] **general-profile-table** (*data*): list of all the rows that are currenty displayed in the main guide table

        ***Returns***

        + **general-profile-table** (*style_data_conditional*): dictionary of the style for the main table
        '''
        if sel_cel is None or not sel_cel or not all_guides:
            raise PreventUpdate
        guide = all_guides[int(sel_cel[0]['row'])]['Guide']
        #Change background color of all the rows that have the 'Guide' column equal to the selected guide (guides are unique)
        return [
            {
                'if': {
                        'filter_query': '{Guide} eq "' + guide + '"'
                    },
                    'background-color':'rgba(0, 0, 255,0.15)'
                },
                {
                'if': {
                        'column_id' :'Genome'
                    },
                    'font-weight':'bold',
                    'textAlign': 'center'
                }
        ]

    # Filtering e sorting e load data per la pagina principale delle guide
    @app.callback(
        [Output('general-profile-table', 'data'),
        Output('general-profile-table', 'selected_cells')],
        [Input('general-profile-table', "page_current"),
         Input('general-profile-table', "page_size"),
         Input('general-profile-table', 'sort_by'),
         Input('general-profile-table', 'filter_query')],
        [State('url', 'search')]
    )
    def update_table_general_profile(page_current, page_size, sort_by, filter, search):
        '''
        The function loads the data to be shown into the table.

        ***Args***

        + [**page_current**] **general-profile-table** (*page_current*): int of the current page to show
        + [**page_size**] **general-profile-table** (*page_size*): int of the maximum number of row for each page
        + [**sort_by**] **general-profile-table** (*sort_by*): list dictionaries of column IDs and sorting direction
        + [**filter**] **general-profile-table** (*filter_query*): list dictionaries for the data filtering
        + [**search**] **url** (*search*): string containing the job ID, eg '?job=QV99PN6XDL'

        ***Returns***

        + **general-profile-table** (*data*): dictionary of the data to be shown
        + **general-profile-table** (*selected_cells*): dictionary containing the selected cell, default at first row
        '''
        job_id = search.split('=')[-1]

        with open(current_working_directory + 'Results/' + job_id + '/Params.txt') as p:
            all_params = p.read()
            genome_type_f = (next(s for s in all_params.split('\n') if 'Genome_selected' in s)).split('\t')[-1]
            ref_comp = (next(s for s in all_params.split('\n') if 'Ref_comp' in s)).split('\t')[-1]
            mms = int((next(s for s in all_params.split('\n') if 'Mismatches' in s)).split('\t')[-1])
            max_bulges = int((next(s for s in all_params.split('\n') if 'Max_bulges' in s)).split('\t')[-1])

        genome_type = 'ref'
        if '+' in genome_type_f:
            genome_type = 'var'
        if 'True' in ref_comp:
            genome_type = 'both'

        filtering_expressions = filter.split(' && ')

        #Get error guides
        list_error_guides = []
        if os.path.exists(current_working_directory + 'Results/' + job_id + '/guides_error.txt'):
            with open(current_working_directory + 'Results/' + job_id + '/guides_error.txt') as error_g:
                for e_g in error_g:
                    list_error_guides.append(e_g.strip())

        #Get guide from guide.txt
        with open(current_working_directory + 'Results/' + job_id + '/guides.txt') as g:
            guides = g.read().strip().split('\n')
            guides.sort()

        #load acfd for each guide
        with open(current_working_directory + 'Results/' + job_id + '/acfd.txt') as a:
            all_scores = a.read().strip().split('\n')

        #Load scores
        if 'NO SCORES' not in all_scores:
            all_scores.sort()
            acfd = [float(a.split('\t')[1]) for a in all_scores if a.split('\t')[0] not in list_error_guides]
            doench = [int(a.split('\t')[2]) for a in all_scores if a.split('\t')[0] not in list_error_guides]
            if genome_type == 'both':
                doench_enr = [int(a.split('\t')[3]) for a in all_scores if a.split('\t')[0] not in list_error_guides]
            acfd  = [int(round((100/(100 + x))*100)) for x in acfd]

        #Get target counting from summary by guide
        column_on_target = []
        column_off_target_ref = []
        column_sample_class = []
        column_total = []
        if genome_type == 'ref' or genome_type == 'var':
            for g in guides:
                one_to_n_mms = []
                df_profile = pd.read_pickle(current_working_directory + 'Results/' + job_id + '/' + job_id + '.summary_by_guide.' + g + '.txt')
                on_t_ref = int(df_profile[(df_profile.Mismatches == 0) & (df_profile['Bulge Type'] == 'X')].iloc[0]['Targets in Reference'])
                try: #For VAR, read enriched values
                    on_t_enr = int(df_profile[(df_profile.Mismatches == 0) & (df_profile['Bulge Type'] == 'X')].iloc[0]['Targets in Enriched'])
                    column_on_target.append(str(on_t_enr))
                except: #For REF, read only reference values
                    column_on_target.append(str(on_t_ref))

                for i in range (1, mms + 1 + int(max_bulges)):         #For column Targets for 1-2 Total (Mismatches + Bulges), sum values for row with same total
                    try:    #For VAR
                        one_to_n_mms.append(sum(df_profile[((df_profile['Mismatches'] + df_profile['Bulge Size']) == i)]['Targets in Enriched'].to_list()))
                    except: #For REF
                        one_to_n_mms.append(sum(df_profile[((df_profile['Mismatches'] + df_profile['Bulge Size']) == i)]['Targets in Reference'].to_list()))
                column_total.append(int(sum(one_to_n_mms)))
                column_off_target_ref.append([int(x) for x in one_to_n_mms])  #[[1, 5, 10], [3, 7, 20]]       each internal list is the number of off-target for a guide, divided by value (from 1 to mm+max_bulge)
        else:
            #NOTE  USO IL CONTEGGIO PRESO DA jobid.general_target_count.txt
            with open(current_working_directory + 'Results/' + job_id + '/' + job_id + '.general_target_count.txt') as general_count:
                header_general = next(general_count) #skip header
                general_count_content = general_count.read().strip().split('\n')
                general_count_content.sort(key = lambda x : x[0])
            guides = []
            column_on_target_old = dict()      #will contain GUIDE -> 3(1 - 2), then later, in column_on_target, only the 1 will be kept
            for tmp in general_count_content:
                tmp = tmp.split('\t')   #[GUIDE, total_onT (onT_ref - onTvar), total_offT_ref ( - - - ), total_offT_var ( - - -)]
                guides.append(tmp[0])
                column_on_target_old[tmp[0]] = tmp[1]      #tmp[1] =  3(1 - 2)
                a = tmp[2].replace('(','').replace(')','').replace('-','').replace('  ',' ').strip().split(' ') # ottengo in a[0] il total, da a[1:] il numero di targets per 1 2 3 ... Total del REF
                b = tmp[3].replace('(','').replace(')','').replace('-','').replace('  ',' ').strip().split(' ') # ottengo in a[0] il total, da a[1:] il numero di targets per 1 2 3 ... Total del VAR
                column_total.append(a[0] + '\n' + b[0] + '\n' + str(int(a[0]) + int(b[0])))
                column_off_target_ref.append(
                    [a[x] + '\n' + b[x] + '\n' + str(int(a[x]) + int(b[x])) for x in range(len(a))][1:]     #[1:] to skip total value
                )   # in the end i obtain [ ['1\n3\n5', '12\n21\n54'] , ['0\n3\n4', '12\n25\n34']], one list per guide, inside each list string for each total value

            with open(current_working_directory + 'Results/' + job_id + '/' + job_id + '.SampleClasses.txt') as samp_classes:
                header_classes = next(samp_classes).strip().split('\t')[1:]     #List of Guides
                for line in samp_classes:
                    if 'Total for Class' in line:       #Last line
                        value_classes = line.strip().split('\t')[1:]           #List of values of classes for each guide
            #NOTE just for changing '-' to ' - '
            for pos_vc, vc in enumerate(value_classes):
                value_classes[pos_vc] = ' - '.join(vc.split('-'))

            dict_classes = dict(zip(header_classes, value_classes))
            column_on_target = []
            for g in guides:
                column_sample_class.append(dict_classes[g])
                column_on_target.append(column_on_target_old[g].split('(')[-1].split('-')[0])

        data_guides = dict()
        data_guides['Guide'] = guides
        if 'NO SCORES' not in all_scores:
            data_guides['CFD'] = acfd
            if genome_type == 'both':
                data_guides['Reference'] = doench
                data_guides['Enriched'] = doench_enr
            else:
                data_guides['Doench 2016'] = doench

        if genome_type == 'ref' or genome_type == 'both':
            data_guides['On-Targets Reference'] = column_on_target
            data_guides['Genome'] = 'REFERENCE'     #NOTE if genome_type == 'both', it will be updated
        else:
            data_guides['On-Targets Enriched'] = column_on_target
            data_guides['Genome'] = 'ENRICHED'

        if genome_type == 'both':
            data_guides['Samples in Class 0 - 0+ - 1 - 1+'] = column_sample_class
            data_guides['Genome'] = 'REFERENCE\nENRICHED\nCOMBINED'
        data_guides['Total'] = column_total
        for i in range (1, mms + int(max_bulges) + 1):  #add count target for each total value
            data_guides[str(i) + ' MM + B'] = [str(x[i-1]) for x in column_off_target_ref]      #NOTE i-1 since i starts from 1

        dff = pd.DataFrame(data_guides)

        if 'NO SCORES' not in all_scores:
            try:
                dff = dff.sort_values(['CFD', 'Doench 2016'], ascending = [False, False])
            except: #for BOTH
                dff = dff.sort_values(['CFD', 'Enriched'], ascending = [False, False])
        else:
            try:
                dff = dff.sort_values('On-Targets Reference', ascending = True)
            except:
                dff = dff.sort_values('On-Targets Enriched', ascending = True)

        for filter_part in filtering_expressions:
            col_name, operator, filter_value = split_filter_part(filter_part)

            if operator in ('eq', 'ne', 'lt', 'le', 'gt', 'ge'):
                # these operators match pandas series operator method names
                dff = dff.loc[getattr(dff[col_name], operator)(filter_value)]
            elif operator == 'contains':
                dff = dff.loc[dff[col_name].str.contains(filter_value)]
            elif operator == 'datestartswith':
                # this is a simplification of the front-end filtering logic,
                # only works with complete fields in standard format
                dff = dff.loc[dff[col_name].str.startswith(filter_value)]

        if len(sort_by):
            dff = dff.sort_values(
                ['Samples' if col['column_id'] == 'Samples Summary' else col['column_id'] for col in sort_by],
                ascending=[
                    col['direction'] == 'asc'
                    for col in sort_by
                ],
                inplace=False
            )

        data_to_send=dff.iloc[
            page_current*page_size:(page_current+ 1)*page_size
        ].to_dict('records')
        return data_to_send, [{'row':0, 'column':0}]

    #Open/close barplot for population distribution
    @app.callback(
        Output("collapse-populations", "is_open"),
        [Input("btn-collapse-populations", "n_clicks")],
        [State("collapse-populations", "is_open")],
    )
    def toggleCollapseDistributionPopulations(n, is_open):
        '''
        Open/Close the section containing the Population Distributions barplots.

        ***Args***

        + [**n**] **btn-collapse-populations** (*n_clicks*): int for the number of times the button was clicked
        + [**is_open**] **collapse-populations** (*is_open*): bool for the status of the component (True: is open, False: is closed)

        ***Returns***

        + **collapse-populations** (*is_open*): True to show, False to hide
        '''
        if n:
            return not is_open
        return is_open

    #Load barplot of population distribution for selected guide
    @app.callback(
        Output('content-collapse-population', 'children'),
        [Input('general-profile-table', 'selected_cells')],
        [State('general-profile-table', 'data'),
        State('url','search')]
    )
    def loadDistributionPopulations(sel_cel, all_guides, job_id):
        '''
        Load the barplot images from the 'assets' directory.

        ***Args***

        + [**sel_cel**] **general-profile-table** (*selected_cells*): list contaning the selected row of the main guide table
        + [**all_guides**] **general-profile-table** (*data*): list of all the rows that are currenty displayed in the main guide table
        + [**search**] **url** (*search*): string containing the job ID, eg '?job=QV99PN6XDL'

        ***Returns***

        + **content-collapse-population** (*children*): list of Dash Components with the images of the Populations Distribution
        '''
        if sel_cel is None or not sel_cel or not all_guides:
            raise PreventUpdate
        guide = all_guides[int(sel_cel[0]['row'])]['Guide']
        job_id = job_id.split('=')[-1]

        with open(current_working_directory + 'Results/' + job_id + '/Params.txt') as p:
            all_params = p.read()
            mms = int((next(s for s in all_params.split('\n') if 'Mismatches' in s)).split('\t')[-1])
            max_bulges = int((next(s for s in all_params.split('\n') if 'Max_bulges' in s)).split('\t')[-1])

        distributions = [dbc.Row(html.P('On- and Off-Targets distributions in the Reference and Enriched Genome. For the Enriched Genome, the targets are divided into 5 SuperPopulations (EAS, EUR, AFR, AMR, SAS).', style = {'margin-left':'0.75rem'}))]

        for i in range(math.ceil((mms + max_bulges + 1) / BARPLOT_LEN)):
            all_images = []
            for mm in range (i * BARPLOT_LEN, (i + 1) * BARPLOT_LEN ):
                if mm < (mms + max_bulges + 1):
                    try:
                        all_images.append(
                            dbc.Col(
                                [
                                    html.A(
                                        html.Img(
                                            src = 'data:image/png;base64,{}'.format(base64.b64encode(open(current_working_directory + 'Results/' + job_id + '/populations_distribution_' + guide + '_' + str(mm) + 'total.png', 'rb').read()).decode()),
                                            id = 'distribution-population' + str(mm), width="100%", height="auto"
                                        ),
                                        target="_blank",
                                        href = 'assets/Img/' + job_id + '/' + 'populations_distribution_' + guide + '_' + str(mm) + 'total.png'
                                    ),
                                    html.Div(html.P('Distribution ' + str(mm) + ' Mismatches + Bulges ', style = {'display':'inline-block'} ),style = {'text-align':'center'})
                                ]
                            )
                        )
                    except:
                        all_images.append(
                            dbc.Col(
                                [
                                    html.Div(html.P('No Targets found with ' + str(mm) + ' Mismatches + Bulges' , style = {'display':'inline-block'}), style = {'text-align':'center'}),
                                    # html.Div(html.P('Distribution ' + str(mm) + ' Mismatches + Bulges ', style = {'display':'inline-block'} ),style = {'text-align':'center'})
                                ],
                                align = 'center'
                            )
                        )
                else:
                    all_images.append(dbc.Col(html.P('')))

            distributions.append(
                html.Div(
                    [
                        dbc.Row(
                            all_images
                        )
                    ]
                )
            )
        return distributions

    @cache.memoize()
    def global_store_subset(value, bulge_t, bulge_s, mms, guide):
        '''
        Caching of files for the subset table in the 'Show Targets' link of the Summary by Guide tabe, to improve loading speed. NOTE that if two
        files have the same name, then the generated cache will be the same, so a regular cleaning of the 'Cache' directory is mandatory.

        ***Args***

        + **value**: string of the job ID to load
        + **bulge_t**: string of the type of bulge selected
        + **bulge_s**: string of the number of bulges selected
        + **mms**: string of the number of mismatches selected
        + **guide**: string of the selected guide

        ***Returns***

        + **df**: dataframe for the subset table of the 'Show Targets' table of the Summary by Guide
        '''
        if value is None:
            return ''
        #Skiprows = 1 to skip header of file
        df = pd.read_csv( current_working_directory + 'Results/' + value + '/' + value + '.' + bulge_t + '.' + bulge_s + '.' + mms + '.' + guide +'.txt', sep = '\t', header = None) #, skiprows = 1)
        return df

    #'Show target' page of the Summary by Guide tab
    def guidePagev3(job_id, hash):
        '''
        Creates the layout for the 'Show Targets' page of the Summary by Guide tab.

        ***Args***

        + **job_id**: string of the jobID
        + **hash**: string containing the informations about the selected 'Show Targets' row, eg '#CCATCGGTGGCCGTTTGCCCNNNnewDNA10'

        ***Returns***

        + list of Dash Components containing the layout of the page
        '''
        guide = hash[:hash.find('new')]
        mms = hash[-1:]
        bulge_s = hash[-2:-1]
        if 'DNA' in hash:
            bulge_t = 'DNA'
        elif 'RNA' in hash:
            bulge_t = 'RNA'
        else:
            bulge_t = 'X'
        add_header = ' - Mismatches ' + str(mms)
        if bulge_t != 'X':
            add_header += ' - ' + str(bulge_t) + ' ' + str(bulge_s)
        value = job_id
        if (not isdir(current_working_directory + 'Results/' + job_id)):
            return html.Div(dbc.Alert("The selected result does not exist", color = "danger"))
        with open(current_working_directory + 'Results/' + value + '/Params.txt') as p:
            all_params = p.read()
            genome_type_f = (next(s for s in all_params.split('\n') if 'Genome_selected' in s)).split('\t')[-1]
            ref_comp = (next(s for s in all_params.split('\n') if 'Ref_comp' in s)).split('\t')[-1]

        genome_type = 'ref'
        style_hide_reference = {'display':'none'}
        value_hide_reference = []
        if '+' in genome_type_f:
            genome_type = 'var'
        if 'True' in ref_comp:
            genome_type = 'both'
            style_hide_reference = {}
            value_hide_reference = ['hide-ref']
        final_list = []
        final_list.append(html.H3('Selected Guide: ' + guide + add_header))
        final_list.append(
            html.P(
                [
                    'List of Targets found for the selected guide. Select a row to view other possible configurations of the target, along with the corresponding samples list.', # 'Select a row to view the target IUPAC character scomposition. The rows highlighted in red indicates that the target was found only in the genome with variants.',
                    dcc.Checklist(options = [{'label': 'Hide Reference Targets', 'value': 'hide-ref'}], id='hide-reference-targets', value = value_hide_reference, style = style_hide_reference),
                    html.Div(
                        [
                            html.P('Generating download link, Please wait...', id = 'download-link-sumbyguide'),
                            dcc.Interval(interval = 5*1000, id = 'interval-sumbyguide')
                        ]
                    )
                ]
            )
        )
        if genome_type == 'ref':
            cols = [{"name": i, "id": i, 'type':t, 'hideable':True} for i,t in zip(COL_REF, COL_REF_TYPE)]
            file_to_grep = '.Annotation.targets.txt'#'.top_1.txt'
        else:
            cols = [{"name": i, "id": i, 'type':t, 'hideable':True} for i,t in zip(COL_BOTH, COL_BOTH_TYPE)]
            file_to_grep = '.samples.annotation.txt'

        job_directory = current_working_directory + 'Results/' + job_id + '/'
        guide_grep_result = job_directory + job_id + '.' + bulge_t + '.' + bulge_s + '.' + mms + '.' + guide + '.txt'
        put_header = 'head -1 ' + job_directory + job_id + file_to_grep + ' > ' + guide_grep_result + ' ; '
        final_list.append(
            html.Div(job_id + '.' + bulge_t + '.' + bulge_s + '.' + mms + '.' + guide,style = {'display':'none'}, id = 'div-info-sumbyguide-targets')
        )

        if not os.path.exists(guide_grep_result):    #Example    job_id.X.0.4.guide.txt #NOTE HEADER NON SALVATO
            subprocess.call([put_header + 'LC_ALL=C fgrep ' + guide + ' ' + job_directory + job_id + file_to_grep + ' | LC_ALL=C fgrep ' + bulge_t + ' | awk \'$8==' + mms + ' && $9==' + bulge_s + '\'> ' + guide_grep_result], shell = True)
            subprocess.Popen(['zip ' + guide_grep_result.replace('.txt','.zip') + ' ' + guide_grep_result], shell = True)
        global_store_subset(job_id, bulge_t, bulge_s, mms,guide)

        final_list.append(
            html.Div(
                dash_table.DataTable(
                    id='table-subset-target',
                    columns=cols,
                    virtualization = True,
                    fixed_rows={ 'headers': True, 'data': 0 },
                    style_cell={'width': '150px'},
                    page_current=0,
                    page_size=PAGE_SIZE,
                    page_action='custom',
                    sort_action='custom',
                    sort_mode='multi',
                    sort_by=[],
                    filter_action='custom',
                    filter_query='',
                    style_table={
                        'max-height': '600px'
                        #'overflowY': 'scroll',
                    },
                    style_cell_conditional=[
                        {
                            'if': {'column_id': 'Samples'},
                            'textAlign': 'left'
                        }
                    ],
                    css= [{ 'selector': 'td.cell--selected, td.focused', 'rule': 'background-color: rgba(0, 0, 255,0.15) !important;' }, { 'selector': 'td.cell--selected *, td.focused *', 'rule': 'background-color: rgba(0, 0, 255,0.15) !important;'}],
                    style_data_conditional = [
                        {
                            'if': {
                                    'filter_query': '{Variant Unique} eq F',
                                },
                                'background-color':'rgba(0, 0, 0,0.15)'
                            },
                    ]
                ),
                id = 'div-result-table',
            )
        )
        final_list.append(html.Br())
        final_list.append(
            html.Div(
                id = 'div-second-table-subset-targets'
            )
        )

        return html.Div(final_list, style = {'margin':'1%'})

    #Update primary table of 'Show targets' of Summary by Guide
    @app.callback(
        Output('table-subset-target', 'data'),
        [Input('table-subset-target', "page_current"),
         Input('table-subset-target', "page_size"),
         Input('table-subset-target', "sort_by"),
         Input('table-subset-target', 'filter_query'),
         Input('hide-reference-targets', 'value')],
         [State('url', 'search'),
         State('url', 'hash')]
    )
    def update_table_subset(page_current, page_size, sort_by, filter, hide_reference, search, hash_guide):
        '''
        Function that returns the data to be shown in the 'Show Targets' table of the Summary by Guide.

        ***Args***

        + [**page_current**] **table-subset-target** (*page_current*): int of the current page to show
        + [**page_size**] **table-subset-target** (*page_size*): int of the maximum number of row for each page
        + [**sort_by**] **table-subset-target** (*sort_by*): list dictionaries of column IDs and sorting direction
        + [**filter**] **table-subset-target** (*filter_query*): list dictionaries for the data filtering
        + [**hide_reference**] **hide-reference-targets** (*value*): if the value is not None, then the Reference targets are removed from the shown data
        + [**search**] **url** (*search*): string containing the job ID, eg '?job=QV99PN6XDL'
        + [**hash_guide**] **url** (*hash*): string containing the informations about the selected 'Show Targets' row, eg '#CCATCGGTGGCCGTTTGCCCNNNnewDNA10'

        ***Returns***

        + **table-subset-target** (*data*): dictionary of the data to be shown

        '''
        job_id = search.split('=')[-1]
        job_directory = current_working_directory + 'Results/' + job_id + '/'
        with open(current_working_directory + 'Results/' + job_id + '/Params.txt') as p:
            all_params = p.read()
            genome_type_f = (next(s for s in all_params.split('\n') if 'Genome_selected' in s)).split('\t')[-1]
            ref_comp = (next(s for s in all_params.split('\n') if 'Ref_comp' in s)).split('\t')[-1]

        genome_type = 'ref'
        if '+' in genome_type_f:
            genome_type = 'var'
        if 'True' in ref_comp:
            genome_type = 'both'
        value = job_id
        if search is None:
            raise PreventUpdate
        filtering_expressions = filter.split(' && ')
        #filtering_expressions.append(['{crRNA} = ' + guide])
        guide = hash_guide[1:hash_guide.find('new')]
        mms = hash_guide[-1:]
        bulge_s = hash_guide[-2:-1]
        if 'DNA' in hash_guide:
            bulge_t = 'DNA'
        elif 'RNA' in hash_guide:
            bulge_t = 'RNA'
        else:
            bulge_t = 'X'
        df = global_store_subset(value, bulge_t, bulge_s, mms, guide)
        dff = df
        if genome_type == 'ref':
            dff.rename(columns = COL_REF_RENAME, inplace = True)
        else:
            dff.rename(columns = COL_BOTH_RENAME , inplace = True)

        if 'hide-ref' in hide_reference or genome_type == 'var':
            dff.drop( df[(df['Samples'] == 'n')].index, inplace = True)

        try:    #For VAR and BOTH
            del dff['Variant Unique']
        except: #For REF
            pass

        for filter_part in filtering_expressions:
            col_name, operator, filter_value = split_filter_part(filter_part)
            if col_name == 'Samples Summary':
                col_name = 'Samples'
            if operator in ('eq', 'ne', 'lt', 'le', 'gt', 'ge'):
                # these operators match pandas series operator method names
                dff = dff.loc[getattr(dff[col_name], operator)(filter_value)]
            elif operator == 'contains':
                dff = dff.loc[dff[col_name].str.contains(filter_value)]
            elif operator == 'datestartswith':
                # this is a simplification of the front-end filtering logic,
                # only works with complete fields in standard format
                dff = dff.loc[dff[col_name].str.startswith(filter_value)]

        if len(sort_by):
            dff = dff.sort_values(
                ['Samples' if col['column_id'] == 'Samples Summary' else col['column_id'] for col in sort_by],
                ascending=[
                    col['direction'] == 'asc'
                    for col in sort_by
                ],
                inplace=False
            )

        cells_style = [

                            # {
                            # 'if': {
                            #         'filter_query': '{Variant Unique} eq y',
                            #         #'filter_query': '{Direction} eq +',
                            #         #'column_id' :'Bulge Type'
                            #     },
                            #     #'border-left': '5px solid rgba(255, 26, 26, 0.9)',
                            #     'background-color':'rgba(255, 0, 0,0.15)'#'rgb(255, 102, 102)'

                            # },
                            {
                                'if': {
                                        'filter_query': '{Cluster Position} eq "' + guide + '"',
                                        #'column_id' :'{#Bulge type}',
                                        #'column_id' :'{Total}'
                                    },
                                    #'border-left': '5px solid rgba(255, 26, 26, 0.9)',
                                    'background-color':'rgba(0, 0, 255,0.15)'#'rgb(255, 102, 102)'

                            },

                            # {
                            # 'if': {
                            #         'filter_query': '{Chromosome} eq "chr2"',
                            #         #'filter_query': '{Direction} eq +',
                            #         #'column_id' :'Bulge Type'
                            #     },
                            #     #'border-left': '5px solid rgba(255, 26, 26, 0.9)',
                            #     'background-color':'rgba(255, 69, 0,0.15)'#'rgb(255, 102, 102)'

                            # },
                            # {
                            #     'if': {
                            #             'filter_query': '{Variant Unique} eq n',
                            #             'column_id' :'Bulge Type'
                            #         },
                            #         'border-left': '5px solid rgba(26, 26, 255, 0.9)',

                            # }

                    ]

        #Calculate sample count

        data_to_send=dff.iloc[
            page_current*page_size:(page_current+ 1)*page_size
        ].to_dict('records')
        if genome_type != 'ref':
            dict_sample_to_pop, dict_pop_to_superpop = associateSample.loadSampleAssociation(job_directory + 'sampleID.txt')[:2]
            for row in data_to_send:
                summarized_sample_cell = dict()
                for s in row['Samples'].split(','):
                    if s == 'n':
                        break
                    try:
                        summarized_sample_cell[dict_pop_to_superpop[dict_sample_to_pop[s]]] += 1
                    except:
                        summarized_sample_cell[dict_pop_to_superpop[dict_sample_to_pop[s]]] = 1
                if summarized_sample_cell:
                    row['Samples Summary'] = ', '.join([str(summarized_sample_cell[sp]) + ' ' + sp for sp in summarized_sample_cell])
                else:
                    row['Samples Summary'] = 'n'
        return data_to_send#, cells_style + style_data_table

    #Create second table for subset targets page, and show corresponding samples    -> CHANGED, now show IUPAC scomposition
    @app.callback(
        [Output('div-second-table-subset-targets', 'children'),
        Output('table-subset-target', 'style_data_conditional')],
        [Input('table-subset-target', 'active_cell')],
        [State('table-subset-target', 'data'),
        State('table-subset-target', 'columns'),
        State('url', 'search'),
        State('table-subset-target', 'style_data_conditional'),
        State('table-subset-target', 'selected_cells')]
    )
    def loadFullSubsetTable(active_cel, data, cols, search, style_data, sel_cell):
        #NOTE tabella secondaria della scomposizione ora non serve, non cancello il codice ma uso PreventUpdate per non azionare la funzione
        if False:
            raise PreventUpdate
        if active_cel is  None:
            raise PreventUpdate
        fl = []
        job_id = search.split('=')[-1]
        with open(current_working_directory + 'Results/' + job_id + '/Params.txt') as p:
            all_params = p.read()
            genome_type_f = (next(s for s in all_params.split('\n') if 'Genome_selected' in s)).split('\t')[-1]
            ref_comp = (next(s for s in all_params.split('\n') if 'Ref_comp' in s)).split('\t')[-1]

        genome_type = 'ref'
        if '+' in genome_type_f:
            genome_type = 'var'
        if 'True' in ref_comp:
            genome_type = 'both'

        if genome_type == 'ref':
            raise PreventUpdate
        #fl.append(html.Br())
        fl.append(html.Hr())
        #Table for IUPAC scomposition
        #fl.append(html.Br())
        fl.append('List of all the configurations for the selected target.')
        fl.append(html.Br())
        cols.append({"name": 'Samples', "id": 'Samples', 'type':'text', 'hideable':True})

        fl.append(
            html.Div(
                dash_table.DataTable(
                    id='second-table-subset-targets',
                    columns=cols,
                    virtualization = True,
                    fixed_rows={ 'headers': True, 'data': 0 },
                    style_cell={'width': '150px'},
                    page_current=0,
                    page_size=PAGE_SIZE,
                    page_action='custom',
                    sort_action='custom',
                    sort_mode='multi',
                    sort_by=[],
                    filter_action='custom',
                    filter_query='',
                    style_table={
                        'max-height': '600px'
                    },
                    style_cell_conditional=[
                        {
                            'if': {'column_id': 'Samples'},
                            'textAlign': 'left'
                        }
                    ],
                    css= [{ 'selector': 'td.cell--selected, td.focused', 'rule': 'background-color: rgba(0, 0, 255,0.15) !important;' }, { 'selector': 'td.cell--selected *, td.focused *', 'rule': 'background-color: rgba(0, 0, 255,0.15) !important;'}],
                    style_data_conditional = [
                            # {
                            # 'if': {
                            #         'filter_query': '{Variant Unique} eq y',
                            #     },
                            #     #'border-left': '5px solid rgba(255, 26, 26, 0.9)',
                            #     'background-color':'rgba(255, 0, 0,0.15)'#'rgb(255, 102, 102)'

                            # },
                            {
                            'if': {
                                    'filter_query': '{Variant Unique} eq F',
                                    #'filter_query': '{Direction} eq +',
                                    #'column_id' :'Bulge Type'
                                },
                                #'border-left': '5px solid rgba(255, 26, 26, 0.9)',
                                'background-color':'rgba(0, 0, 0,0.15)'#'rgb(255, 102, 102)'
                            }
                    ]
                )
            )
        )

        pos_cluster = data[int(sel_cell[0]['row'])]['Cluster Position']
        chrom = data[int(sel_cell[0]['row'])]['Chromosome']
        cells_style = [
                           style_data[0],
                            {
                                'if': {
                                        'filter_query': '{Cluster Position} eq "' + str(pos_cluster) + '"',
                                        ##'filter_query': '{Chromosome} eq "' + str(chrom) + '"',
                                        #'column_id' :'{#Bulge type}',
                                        #'column_id' :'{Total}'
                                    },
                                    #'border-left': '5px solid rgba(255, 26, 26, 0.9)',
                                    'background-color':'rgba(0, 0, 255,0.15)'#'rgb(255, 102, 102)'

                            }

                            # {
                            # 'if': {
                            #         'filter_query': '{Chromosome} eq "chr2"',
                            #         #'filter_query': '{Direction} eq +',
                            #         #'column_id' :'Bulge Type'
                            #     },
                            #     #'border-left': '5px solid rgba(255, 26, 26, 0.9)',
                            #     'background-color':'rgba(255, 69, 0,0.15)'#'rgb(255, 102, 102)'

                            # },
                            # {
                            #     'if': {
                            #             'filter_query': '{Variant Unique} eq n',
                            #             'column_id' :'Bulge Type'
                            #         },
                            #         'border-left': '5px solid rgba(26, 26, 255, 0.9)',

                            # }

                    ]
        return  fl, cells_style

    #Filter etc for second tabe
    @app.callback(
        [Output('second-table-subset-targets', 'data'),                         #Table showing iupac scomposition
        Output('second-table-subset-targets', 'style_data_conditional')],
        [Input('second-table-subset-targets', "page_current"),
         Input('second-table-subset-targets', "page_size"),
         Input('second-table-subset-targets', "sort_by"),
         Input('second-table-subset-targets', 'filter_query')],
         [State('url', 'search'),
         State('url', 'hash'),
         State('table-subset-target', 'active_cell'),
        State('table-subset-target', 'data')]
    )
    def update_table_subsetSecondTable(page_current, page_size, sort_by, filter, search, hash_guide, active_cel, data):
        #NOTE tabella secondaria della scomposizione ora non serve, non cancello il codice ma uso PreventUpdate per non azionare la funzione
        if False:
            raise PreventUpdate
        if active_cel is None:
            raise PreventUpdate
        job_id = search.split('=')[-1]
        job_directory = current_working_directory + 'Results/' + job_id + '/'
        with open(current_working_directory + 'Results/' + job_id + '/Params.txt') as p:
            all_params = p.read()
            genome_type_f = (next(s for s in all_params.split('\n') if 'Genome_selected' in s)).split('\t')[-1]
            ref_comp = (next(s for s in all_params.split('\n') if 'Ref_comp' in s)).split('\t')[-1]

        guide = hash_guide[1:hash_guide.find('new')]
        genome_type = 'ref'
        if '+' in genome_type_f:
            genome_type = 'var'
        if 'True' in ref_comp:
            genome_type = 'both'
        if search is None:
            raise PreventUpdate

        if genome_type == 'ref':
            raise PreventUpdate

        filtering_expressions = filter.split(' && ')
        bulge_t =  data[active_cel['row']]['Bulge Type']
        bulge_s = str(data[active_cel['row']]['Bulge Size'])
        mms = str(data[active_cel['row']]['Mismatches'])
        chrom = str(data[active_cel['row']]['Chromosome'])
        pos = str(data[active_cel['row']]['Cluster Position'])
        # annotation_type = str(data[active_cel['row']]['Annotation Type'])

        scomposition_file = job_directory + job_id + '.' + bulge_t + '.' + bulge_s + '.' + mms + '.' + guide + '.' + chrom + '.' + pos + '.scomposition.txt'
        file_to_grep = '.samples.annotation.txt'

        if not os.path.exists(scomposition_file):    #Example    job_id.X.0.4.GUIDE.chrom.position.scomposition.txt
            # subprocess.call(['LC_ALL=C fgrep ' + guide + ' ' + current_working_directory + 'Results/'+ job_id + '/' + job_id + file_to_grep + ' |  awk \'$6==' + pos + ' && $4==\"' + chrom + '\" && $8==' + mms + ' && $9==' + bulge_s +'\' > ' + scomposition_file], shell = True)
            subprocess.call(['LC_ALL=C fgrep ' + guide + ' ' + current_working_directory + 'Results/'+ job_id + '/' + job_id + file_to_grep + ' |  awk \'$6==' + pos + ' && $4==\"' + chrom + '\" && $9==' + bulge_s + ' && $13!=\"n\"' +'\' > ' + scomposition_file], shell = True)

        if os.path.getsize(scomposition_file) > 0:          #Check if result grep has at least 1 result
            df = pd.read_csv(scomposition_file, header = None, sep = '\t')
            # df['Annotation Type'] = annotation_type
        else:
            raise PreventUpdate

        df.rename(columns = COL_BOTH_RENAME , inplace = True)
        df.drop(df[(~( df['Cluster Position'] == int(data[active_cel['row']]['Cluster Position']))) | (~( df['Chromosome'] == data[active_cel['row']]['Chromosome']))].index, inplace = True)
        dff = df

        for filter_part in filtering_expressions:
            col_name, operator, filter_value = split_filter_part(filter_part)

            if operator in ('eq', 'ne', 'lt', 'le', 'gt', 'ge'):
                # these operators match pandas series operator method names
                dff = dff.loc[getattr(dff[col_name], operator)(filter_value)]
            elif operator == 'contains':
                dff = dff.loc[dff[col_name].str.contains(filter_value)]
            elif operator == 'datestartswith':
                # this is a simplification of the front-end filtering logic,
                # only works with complete fields in standard format
                dff = dff.loc[dff[col_name].str.startswith(filter_value)]

        if len(sort_by):
            dff = dff.sort_values(
                ['Samples' if col['column_id'] == 'Samples Summary' else col['column_id'] for col in sort_by],
                ascending=[
                    col['direction'] == 'asc'
                    for col in sort_by
                ],
                inplace=False
            )

        cells_style = [
                            # {
                            # 'if': {
                            #         'filter_query': '{Variant Unique} eq y',
                            #         #'filter_query': '{Direction} eq +',
                            #         #'column_id' :'Bulge Type'
                            #     },
                            #     #'border-left': '5px solid rgba(255, 26, 26, 0.9)',
                            #     'background-color':'rgba(255, 0, 0,0.15)'#'rgb(255, 102, 102)'

                            # },
                            {
                            'if': {
                                    'filter_query': '{Variant Unique} eq F',
                                    #'filter_query': '{Direction} eq +',
                                    #'column_id' :'Bulge Type'
                                },
                                #'border-left': '5px solid rgba(255, 26, 26, 0.9)',
                                'background-color':'rgba(0, 0, 0,0.15)'#'rgb(255, 102, 102)'
                            }
                            # {
                            # 'if': {
                            #         'filter_query': '{Chromosome} eq "chr2"',
                            #         #'filter_query': '{Direction} eq +',
                            #         #'column_id' :'Bulge Type'
                            #     },
                            #     #'border-left': '5px solid rgba(255, 26, 26, 0.9)',
                            #     'background-color':'rgba(255, 69, 0,0.15)'#'rgb(255, 102, 102)'

                            # },
                            # {
                            #     'if': {
                            #             'filter_query': '{Variant Unique} eq n',
                            #             'column_id' :'Bulge Type'
                            #         },
                            #         'border-left': '5px solid rgba(26, 26, 255, 0.9)',

                            # }

                    ]

        #Calculate sample count

        data_to_send=dff.iloc[
            page_current*page_size:(page_current+ 1)*page_size
        ].to_dict('records')
        if genome_type != 'ref':
            dict_sample_to_pop, dict_pop_to_superpop = associateSample.loadSampleAssociation(job_directory + 'sampleID.txt')[:2]
            for row in data_to_send:
                summarized_sample_cell = dict()
                for s in row['Samples'].split(','):
                    if s == 'n':
                        break
                    try:
                        summarized_sample_cell[dict_pop_to_superpop[dict_sample_to_pop[s]]] += 1
                    except:
                        summarized_sample_cell[dict_pop_to_superpop[dict_sample_to_pop[s]]] = 1
                if summarized_sample_cell:
                    row['Samples Summary'] = ', '.join([str(summarized_sample_cell[sp]) + ' ' + sp for sp in summarized_sample_cell])
                else:
                    row['Samples Summary'] = 'n'
        return data_to_send, cells_style

    @cache.memoize()
    def global_store_general(path_file_to_load):
        '''
        Caching dei file targets per una miglior performance di visualizzazione
        '''
        if 'scomposition' in path_file_to_load:
            rows_to_skip = 0
        else:
            rows_to_skip = 0        #Skip header
        if path_file_to_load is None:
            return ''
        if os.path.getsize(path_file_to_load) > 0:
            df = pd.read_csv( path_file_to_load , sep = '\t', header = None, skiprows = rows_to_skip)
        else:
            df = None
        #df.rename(columns = {"#Bulge type":'Bulge Type', "#Bulge_type":'Bulge Type', 'Bulge_Size':'Bulge Size'}, inplace = True)
        return df

    #Return the targets found for the selected sample
    def samplePage(job_id, hash):
        guide = hash[:hash.find('-Sample-')]
        sample = hash[hash.rfind('-') + 1:]
        if (not isdir(current_working_directory + 'Results/' + job_id)):
            return html.Div(dbc.Alert("The selected result does not exist", color = "danger"))

        with open(current_working_directory + 'Results/' + job_id + '/Params.txt') as p:
            all_params = p.read()
            genome_type_f = (next(s for s in all_params.split('\n') if 'Genome_selected' in s)).split('\t')[-1]
            ref_comp = (next(s for s in all_params.split('\n') if 'Ref_comp' in s)).split('\t')[-1]

        genome_type = 'ref'
        if '+' in genome_type_f:
            genome_type = 'var'
        if 'True' in ref_comp:
            genome_type = 'both'

        final_list = []
        final_list.append(
            #html.P('List of Targets found for the selected Sample - ' + sample + ' - and guide - ' + guide + ' -')
            html.H3('Selected Sample: ' + sample)
        )
        final_list.append(
            html.P(
                [
                    'List of Targets found for the selected sample.', #'The rows highlighted in red indicates that the target was found only in the genome with variants.',
                    html.Div(
                        [
                            html.P('Generating download link, Please wait...', id = 'download-link-sumbysample'),
                            dcc.Interval(interval = 5*1000, id = 'interval-sumbysample')
                        ]

                    )
                ]
            )
        )

        file_to_grep = '.samples.annotation.txt'
        sample_grep_result = current_working_directory + 'Results/'+ job_id + '/' + job_id + '.' + sample + '.' + guide + '.txt'
        put_header = 'head -1 ' + current_working_directory + 'Results/' + job_id + '/' + job_id + file_to_grep + ' > ' + sample_grep_result + ' ; '
        final_list.append(
            html.Div(job_id + '.' + sample + '.' + guide,style = {'display':'none'}, id = 'div-info-sumbysample-targets')
        )
        # print('qui sample before grep')
        # print('esiste sample?', str(os.path.exists(sample_grep_result)))

        if not os.path.exists(sample_grep_result):    #Example    job_id.HG001.guide.txt #NOTE HEADER NON SALVATO
            subprocess.call([put_header + 'LC_ALL=C fgrep ' + guide + ' ' + current_working_directory + 'Results/'+ job_id + '/' + job_id + file_to_grep + ' | LC_ALL=C fgrep ' + sample + ' > ' + sample_grep_result], shell = True)
            subprocess.Popen(['zip ' + sample_grep_result.replace('.txt','.zip') + ' ' + sample_grep_result],shell = True)

        cols = [{"name": i, "id": i, 'type':t, 'hideable':True} for i,t in zip(COL_BOTH, COL_BOTH_TYPE)]

        final_list.append(
            html.Div(
                dash_table.DataTable(
                    id='table-sample-target',
                    columns=cols,
                    #data = df.to_dict('records'),
                    virtualization = True,
                    fixed_rows={ 'headers': True, 'data': 0 },
                    #fixed_columns = {'headers': True, 'data':1},
                    style_cell={'width': '150px'},
                    page_current=0,
                    page_size=PAGE_SIZE,
                    page_action='custom',
                    sort_action='custom',
                    sort_mode='multi',
                    sort_by=[],
                    filter_action='custom',
                    filter_query='',
                    style_table={
                        'max-height': '600px'
                        #'overflowY': 'scroll',
                    },
                    style_data_conditional=[
                        # {
                        #     'if': {
                        #             'filter_query': '{Variant Unique} eq y',
                        #             #'column_id' :'{#Bulge type}',
                        #             #'column_id' :'{Total}'
                        #         },
                        #         #'border-left': '5px solid rgba(255, 26, 26, 0.9)',
                        #         'background-color':'rgba(255, 0, 0,0.15)'#'rgb(255, 102, 102)'

                        #     },
                        {
                            'if': {
                                    'filter_query': '{Variant Unique} eq F',
                                    #'filter_query': '{Direction} eq +',
                                    #'column_id' :'Bulge Type'
                                },
                                #'border-left': '5px solid rgba(255, 26, 26, 0.9)',
                                'background-color':'rgba(0, 0, 0,0.15)'#'rgb(255, 102, 102)'

                            }
                    ],
                    css= [{ 'selector': 'td.cell--selected, td.focused', 'rule': 'background-color: rgba(0, 0, 255,0.15) !important;' }, { 'selector': 'td.cell--selected *, td.focused *', 'rule': 'background-color: rgba(0, 0, 255,0.15) !important;'}],
                    # css= [{ 'selector': 'td.row--selected, td.focused', 'rule': 'background-color: rgba(0, 0, 255,0.15) !important;' }, { 'selector': 'td.row--selected *, td.focused *', 'rule': 'background-color: rgba(0, 0, 255,0.15) !important;'}],

                ),
                id = 'div-result-table',
            )
        )
        return html.Div(final_list, style = {'margin':'1%'})

    #Filter and sorting sample targets
    @app.callback(
        Output('table-sample-target', 'data'),
        [Input('table-sample-target', "page_current"),
         Input('table-sample-target', "page_size"),
         Input('table-sample-target', 'sort_by'),
         Input('table-sample-target', 'filter_query')],
        [State('url', 'search'),
         State('url', 'hash')]
    )
    def update_table_sample(page_current, page_size, sort_by, filter, search, hash):
        job_id = search.split('=')[-1]
        job_directory = current_working_directory + 'Results/' + job_id + '/'
        hash = hash.split('#')[1]
        guide = hash[:hash.find('-Sample-')]
        sample = hash[hash.rfind('-') + 1:]
        with open(current_working_directory + 'Results/' + job_id + '/Params.txt') as p:
            all_params = p.read()
            genome_type_f = (next(s for s in all_params.split('\n') if 'Genome_selected' in s)).split('\t')[-1]
            ref_comp = (next(s for s in all_params.split('\n') if 'Ref_comp' in s)).split('\t')[-1]

        genome_type = 'ref'
        if '+' in genome_type_f:
            genome_type = 'var'
        if 'True' in ref_comp:
            genome_type = 'both'

        filtering_expressions = filter.split(' && ')
        dff = global_store_general(current_working_directory + 'Results/'+ job_id + '/' + job_id + '.' + sample + '.' + guide + '.txt')
        if dff is None:
            raise PreventUpdate

        dff.rename(columns = COL_BOTH_RENAME , inplace = True)
        del dff['Correct Guide']         #NOTE Drop the Correct Guide column
        del dff['Variant Unique']
        for filter_part in filtering_expressions:
            col_name, operator, filter_value = split_filter_part(filter_part)

            if operator in ('eq', 'ne', 'lt', 'le', 'gt', 'ge'):
                # these operators match pandas series operator method names
                dff = dff.loc[getattr(dff[col_name], operator)(filter_value)]
            elif operator == 'contains':
                dff = dff.loc[dff[col_name].str.contains(filter_value)]
            elif operator == 'datestartswith':
                # this is a simplification of the front-end filtering logic,
                # only works with complete fields in standard format
                dff = dff.loc[dff[col_name].str.startswith(filter_value)]

        if len(sort_by):
            dff = dff.sort_values(
                ['Samples' if col['column_id'] == 'Samples Summary' else col['column_id'] for col in sort_by],
                ascending=[
                    col['direction'] == 'asc'
                    for col in sort_by
                ],
                inplace=False
            )

        #Calculate sample count
        dict_sample_to_pop, dict_pop_to_superpop = associateSample.loadSampleAssociation(job_directory + 'sampleID.txt')[:2]
        data_to_send=dff.iloc[
            page_current*page_size:(page_current+ 1)*page_size
        ].to_dict('records')
        for row in data_to_send:
            summarized_sample_cell = dict()
            for s in row['Samples'].split(','):
                if s == 'n':
                    break
                try:
                    summarized_sample_cell[dict_pop_to_superpop[dict_sample_to_pop[s]]] += 1
                except:
                    summarized_sample_cell[dict_pop_to_superpop[dict_sample_to_pop[s]]] = 1
            if summarized_sample_cell:
                row['Samples Summary'] = ', '.join([str(summarized_sample_cell[sp]) + ' ' + sp for sp in summarized_sample_cell])
            else:
                row['Samples Summary'] = 'n'
        return data_to_send

    #Return the targets for the selected cluster
    def clusterPage(job_id, hash):
        guide = hash[:hash.find('-Pos-')]
        chr_pos = hash[hash.find('-Pos-') + 5:]
        chromosome = chr_pos.split('-')[0]
        position = chr_pos.split('-')[1]
        if (not isdir(current_working_directory + 'Results/' + job_id)):
            return html.Div(dbc.Alert("The selected result does not exist", color = "danger"))
        with open(current_working_directory + 'Results/' + job_id + '/Params.txt') as p:
            all_params = p.read()
            genome_type_f = (next(s for s in all_params.split('\n') if 'Genome_selected' in s)).split('\t')[-1]
            ref_comp = (next(s for s in all_params.split('\n') if 'Ref_comp' in s)).split('\t')[-1]

        genome_type = 'ref'
        style_hide_reference = {'display':'none'}
        value_hide_reference = []
        if '+' in genome_type_f:
            genome_type = 'var'
        if 'True' in ref_comp:
            genome_type = 'both'
            style_hide_reference = {}
            value_hide_reference = ['hide-ref', 'hide-cluster']
        final_list = []
        final_list.append(
            html.H3('Selected Position: ' + chromosome + ' - ' + position)
        )

        if genome_type == 'ref':
            cols = [{"name": i, "id": i, 'type':t, 'hideable':True} for i,t in zip(COL_REF, COL_REF_TYPE)]
            file_to_grep = '.targets.cluster.txt'
        else:
            cols = [{"name": i, "id": i, 'type':t, 'hideable':True} for i,t in zip(COL_BOTH, COL_BOTH_TYPE)]
            file_to_grep = '.total.cluster.txt'
        # print('qui cluster before grep')

        cluster_grep_result = current_working_directory + 'Results/'+ job_id + '/' + job_id + '.' + chromosome + '_' + position + '.' + guide +'.txt'
        put_header = 'head -1 ' + current_working_directory + 'Results/' + job_id + '/' + job_id + file_to_grep + ' > ' + cluster_grep_result + ' ; '
        # print('esiste cluster?' , str(os.path.exists(cluster_grep_result)) )
        if not os.path.exists(cluster_grep_result):    #Example    job_id.chr3_100.guide.txt
            #Grep annotation for ref
            if genome_type == 'ref':#NOTE HEADER NON SALVATO
                get_annotation = subprocess.Popen(['LC_ALL=C fgrep ' + guide + ' ' + current_working_directory + 'Results/'+ job_id + '/' + job_id + '.Annotation.targets.txt' + ' |  awk \'$6==' + position + ' && $4==\"' + chromosome + '\"\''], shell = True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
                out, err = get_annotation.communicate()
                annotation_type = out.decode('UTF-8').strip().split('\t')[-1]
                subprocess.call([put_header + 'LC_ALL=C fgrep ' + guide + ' ' + current_working_directory + 'Results/'+ job_id + '/' + job_id + file_to_grep + ' | awk \'$6==' + position + ' && $4==\"' + chromosome + '\" {print $0\"\\t' + annotation_type + '\"}\' > ' + cluster_grep_result], shell = True)
            else:
                # print('qui cluster in grep')     #NOTE HEADER NON SALVATO
                subprocess.call([put_header + 'LC_ALL=C fgrep ' + guide + ' ' + current_working_directory + 'Results/'+ job_id + '/' + job_id + file_to_grep + ' | awk \'$6==' + position + ' && $4==\"' + chromosome + '\"\' > ' + cluster_grep_result], shell = True)  #NOTE top1 will have sample and annotation, other targets will have '.'-> 18/03 all samples and annotation are already writter for all targets
            subprocess.Popen(['zip ' + cluster_grep_result.replace('.txt','.zip') + ' ' + cluster_grep_result], shell = True)
        final_list.append(
            html.Div(job_id + '.' + chromosome + '_' + position + '.' + guide ,style = {'display':'none'}, id = 'div-info-sumbyposition-targets')
        )

        scomposition_file = current_working_directory + 'Results/'+ job_id + '/' + job_id + '.' + chromosome + '_' + position + '.' + guide +'.scomposition.txt'
        file_to_grep = '.samples.annotation.txt'

        iupac_scomposition_visibility = {'display':'none'}
        if genome_type != 'ref':
            iupac_scomposition_visibility = {}
            if not os.path.exists(scomposition_file):    #Example    job_id.chr_pos.guide.scomposition.txt
                subprocess.call(['LC_ALL=C fgrep ' + guide + ' ' + current_working_directory + 'Results/'+ job_id + '/' + job_id + file_to_grep + ' |  awk \'$6==' + position + ' && $4==\"' + chromosome + '\" && $13!=\"n\"\' > ' + scomposition_file], shell = True)

        final_list.append(html.P(
            [
                html.P('List of all the configurations for the target in the selected position.', style = iupac_scomposition_visibility),
                dcc.Checklist(
                    options = [{'label': 'Hide Reference Targets', 'value': 'hide-ref'}, {'label': 'Show only TOP1 Target', 'value': 'hide-cluster'}],
                    id='hide-reference-targets', value = value_hide_reference, style = style_hide_reference
                ),
                html.Div(
                    [
                        html.P('Generating download link, Please wait...', id = 'download-link-sumbyposition'),
                        dcc.Interval(interval = 5*1000, id = 'interval-sumbyposition')
                    ]

                )
            ]
        )
        )

        cols_for_scomposition = cols.copy()
        cols_for_scomposition.append({"name": 'Samples', "id": 'Samples', 'type':'text', 'hideable':True})
        final_list.append(
            html.Div(
                dash_table.DataTable(
                    id = 'table-scomposition-cluster',          #TABLE that represent scomposition of iupac of selected target, take rows from top_1.samples.txt
                    columns=cols_for_scomposition,
                    #data = df.to_dict('records'),
                    virtualization = True,
                    fixed_rows={ 'headers': True, 'data': 0 },
                    #fixed_columns = {'headers': True, 'data':1},
                    style_cell={'width': '150px'},
                    page_current=0,
                    page_size=PAGE_SIZE,
                    page_action='custom',
                    sort_action='custom',
                    sort_mode='multi',
                    sort_by=[],
                    filter_action='custom',
                    filter_query='',
                    style_table={
                        'max-height': '600px'
                        #'overflowY': 'scroll',
                    },
                    style_data_conditional=[
                        # {
                        #     'if': {
                        #             'filter_query': '{Variant Unique} eq y',
                        #             #'column_id' :'{#Bulge type}',
                        #             #'column_id' :'{Total}'
                        #         },
                        #         #'border-left': '5px solid rgba(255, 26, 26, 0.9)',
                        #         'background-color':'rgba(255, 0, 0,0.15)'#'rgb(255, 102, 102)'

                        #     },
                        {
                            'if': {
                                    'filter_query': '{Variant Unique} eq F',
                                    #'filter_query': '{Direction} eq +',
                                    #'column_id' :'Bulge Type'
                                },
                                #'border-left': '5px solid rgba(255, 26, 26, 0.9)',
                                'background-color':'rgba(0, 0, 0,0.15)'#'rgb(255, 102, 102)'

                            }
                    ],
                    css= [{ 'selector': 'td.cell--selected, td.focused', 'rule': 'background-color: rgba(0, 0, 255,0.15) !important;' }, { 'selector': 'td.cell--selected *, td.focused *', 'rule': 'background-color: rgba(0, 0, 255,0.15) !important;'}],

                ),
                style = iupac_scomposition_visibility
            )
        )

        final_list.append(html.Hr())

        #Cluster Table
        final_list.append(
            'List of Targets found for the selected position. Other possible configurations of the target are listed in the table above, along with the corresponding samples list.', # The rows highlighted in red indicates that the target was found only in the genome with variants.',
        )
        final_list.append(
            html.Div(
                dash_table.DataTable(
                    id='table-position-target',
                    columns=cols,
                    #data = df.to_dict('records'),
                    virtualization = True,
                    fixed_rows={ 'headers': True, 'data': 0 },
                    #fixed_columns = {'headers': True, 'data':1},
                    style_cell={'width': '150px'},
                    page_current=0,
                    page_size=PAGE_SIZE,
                    page_action='custom',
                    sort_action='custom',
                    sort_mode='multi',
                    sort_by=[],
                    filter_action='custom',
                    filter_query='',
                    style_table={
                        'max-height': '600px'
                        #'overflowY': 'scroll',
                    },
                    style_data_conditional=[
                        # {
                        #     'if': {
                        #             'filter_query': '{Variant Unique} eq y',
                        #             #'column_id' :'{#Bulge type}',
                        #             #'column_id' :'{Total}'
                        #         },
                        #         #'border-left': '5px solid rgba(255, 26, 26, 0.9)',
                        #         'background-color':'rgba(255, 0, 0,0.15)'#'rgb(255, 102, 102)'

                        #     },
                        {
                            'if': {
                                    'filter_query': '{Variant Unique} eq F',
                                    #'filter_query': '{Direction} eq +',
                                    #'column_id' :'Bulge Type'
                                },
                                #'border-left': '5px solid rgba(255, 26, 26, 0.9)',
                                'background-color':'rgba(0, 0, 0,0.15)'#'rgb(255, 102, 102)'

                            }
                    ],
                    css= [{ 'selector': 'td.cell--selected, td.focused', 'rule': 'background-color: rgba(0, 0, 255,0.15) !important;' }, { 'selector': 'td.cell--selected *, td.focused *', 'rule': 'background-color: rgba(0, 0, 255,0.15) !important;'}],

                ),
                id = 'div-result-table',
            )
        )
        # final_list.append(html.Div('', id ='target-to-highlight'))
        return html.Div(final_list, style = {'margin':'1%'})

    # #Save the first scomposition target from the second table, in order to highlight it in the first table
    # @app.callback(
    #     Output('target-to-highlight','children'),
    #     [Input('table-scomposition-cluster', 'data')],
    #     [State('target-to-highlight','children')]
    # )
    # def saveFirstScomposedTarget(data_scomp, current_target):
    #     if current_target != '' or current_target is None:
    #         raise PreventUpdate
    #     if data_scomp is None or not data_scomp:
    #         raise PreventUpdate
    #     return data_scomp[0]['DNA']

    # #update the Color of the top1 scomposed target in the first table
    # @app.callback(
    #     Output('table-position-target', 'style_data_conditional'),
    #     [Input('target-to-highlight', 'children')]
    # )
    # def highlightSummaryTarget(to_highlight):
    #     if to_highlight is None or to_highlight == '':
    #         raise PreventUpdate
    #     return [{'if': {'filter_query': '{DNA} eq ' + to_highlight}, 'font-weight':'bold'}]

    #Filter/sort cluster
    #Filter and sorting sample targets
    @app.callback(
        Output('table-position-target', 'data'),
        [Input('table-position-target', "page_current"),
         Input('table-position-target', "page_size"),
         Input('table-position-target', 'sort_by'),
         Input('table-position-target', 'filter_query'),
         Input('hide-reference-targets', 'value')],
        [State('url', 'search'),
         State('url', 'hash')]
    )
    def update_table_cluster(page_current, page_size, sort_by, filter, hide_reference, search, hash):
        job_id = search.split('=')[-1]
        job_directory = current_working_directory + 'Results/' + job_id + '/'
        hash = hash.split('#')[1]
        guide = hash[:hash.find('-Pos-')]
        chr_pos = hash[hash.find('-Pos-') + 5:]
        chromosome = chr_pos.split('-')[0]
        position = chr_pos.split('-')[1]

        with open(current_working_directory + 'Results/' + job_id + '/Params.txt') as p:
            all_params = p.read()
            genome_type_f = (next(s for s in all_params.split('\n') if 'Genome_selected' in s)).split('\t')[-1]
            ref_comp = (next(s for s in all_params.split('\n') if 'Ref_comp' in s)).split('\t')[-1]

        genome_type = 'ref'
        if '+' in genome_type_f:
            genome_type = 'var'
        if 'True' in ref_comp:
            genome_type = 'both'

        filtering_expressions = filter.split(' && ')
        dff = global_store_general(current_working_directory + 'Results/' + job_id + '/' + job_id + '.' + chromosome + '_' + position + '.' + guide +  '.txt')
        if dff is None:
            raise PreventUpdate

        if genome_type == 'ref':
            dff.rename(columns = COL_REF_RENAME, inplace = True)
        else:
            dff.rename(columns =COL_BOTH_RENAME , inplace = True)

        if genome_type != 'ref':
            # add_samples = [dff['Samples'][0]] * dff.shape[0]
            # check_minmms = dff['Min Mismatches']
            # if dff['Variant Unique'][0] != 'y' and dff['PAM Creation'][0] == 'n':
            #     for pos_minmms, minmms in enumerate(check_minmms):
            #         if minmms == '-':
            #             add_samples[pos_minmms] = 'n'
            # dff['Samples'] = add_samples
            del dff['Variant Unique']
            # dff.drop(dff.head(1).index, inplace=True)       #Remove first target, that is the top1 with no iupac (lowest mm of scomposed target) and is
                                                        #needed only for summary by guide, not the show target part
                                                        #NOTE 18/03 removed all the iupac char, the first line is needed to be shown
        # dff['Annotation Type'] = list(dff['Annotation Type'])[0]
        del dff['Correct Guide']

        if 'hide-ref' in hide_reference or genome_type == 'var':
            dff.drop( dff[(dff['Samples'] == 'n')].index, inplace = True)

        if 'hide-cluster' in hide_reference:
            dff = dff.head(1)

        for filter_part in filtering_expressions:
            col_name, operator, filter_value = split_filter_part(filter_part)

            if operator in ('eq', 'ne', 'lt', 'le', 'gt', 'ge'):
                # these operators match pandas series operator method names
                dff = dff.loc[getattr(dff[col_name], operator)(filter_value)]
            elif operator == 'contains':
                dff = dff.loc[dff[col_name].str.contains(filter_value)]
            elif operator == 'datestartswith':
                # this is a simplification of the front-end filtering logic,
                # only works with complete fields in standard format
                dff = dff.loc[dff[col_name].str.startswith(filter_value)]

        if len(sort_by):
            dff = dff.sort_values(
                ['Samples' if col['column_id'] == 'Samples Summary' else col['column_id'] for col in sort_by],
                ascending=[
                    col['direction'] == 'asc'
                    for col in sort_by
                ],
                inplace=False
            )

        #Calculate sample count

        data_to_send=dff.iloc[
            page_current*page_size:(page_current+ 1)*page_size
        ].to_dict('records')
        if genome_type != 'ref':
            dict_sample_to_pop, dict_pop_to_superpop = associateSample.loadSampleAssociation(job_directory + 'sampleID.txt')[:2]
            for row in data_to_send:
                summarized_sample_cell = dict()
                for s in row['Samples'].split(','):
                    if s == 'n':
                        break
                    try:
                        summarized_sample_cell[dict_pop_to_superpop[dict_sample_to_pop[s]]] += 1
                    except:
                        summarized_sample_cell[dict_pop_to_superpop[dict_sample_to_pop[s]]] = 1
                if summarized_sample_cell:
                    row['Samples Summary'] = ', '.join([str(summarized_sample_cell[sp]) + ' ' + sp for sp in summarized_sample_cell])
                else:
                    row['Samples Summary'] = 'n'
        return data_to_send

    #Filter/sort IUPAC decomposition table for cluster page
    @app.callback(
        Output('table-scomposition-cluster','data'),
        [Input('table-scomposition-cluster', "page_current"),
         Input('table-scomposition-cluster', "page_size"),
         Input('table-scomposition-cluster', 'sort_by'),
         Input('table-scomposition-cluster', 'filter_query')],
        [State('url', 'search'),
         State('url', 'hash')]
    )
    def update_iupac_scomposition_table_cluster(page_current, page_size, sort_by, filter, search, hash):
        job_id = search.split('=')[-1]
        job_directory = current_working_directory + 'Results/' + job_id + '/'
        hash = hash.split('#')[1]
        guide = hash[:hash.find('-Pos-')]
        chr_pos = hash[hash.find('-Pos-') + 5:]
        chromosome = chr_pos.split('-')[0]
        position = chr_pos.split('-')[1]

        with open(current_working_directory + 'Results/' + job_id + '/Params.txt') as p:
            all_params = p.read()
            genome_type_f = (next(s for s in all_params.split('\n') if 'Genome_selected' in s)).split('\t')[-1]
            ref_comp = (next(s for s in all_params.split('\n') if 'Ref_comp' in s)).split('\t')[-1]

        genome_type = 'ref'
        if '+' in genome_type_f:
            genome_type = 'var'
        if 'True' in ref_comp:
            genome_type = 'both'

        if genome_type == 'ref':
            raise PreventUpdate

        filtering_expressions = filter.split(' && ')
        dff = global_store_general(current_working_directory + 'Results/'+ job_id + '/' + job_id + '.' + chromosome + '_' + position + '.' + guide +'.scomposition.txt')
        if dff is None:
            raise PreventUpdate

        # #Grep annotation
        # file_to_grep = '.Annotation.targets.txt'
        # get_annotation = subprocess.Popen(['LC_ALL=C fgrep ' + guide + ' ' + current_working_directory + 'Results/'+ job_id + '/' + job_id + file_to_grep + ' |  awk \'$6==' + position + ' && $4==\"' + chromosome + '\"\''], shell = True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        # out, err = get_annotation.communicate()
        # annotation_type = out.decode('UTF-8').strip().split('\t')[-1]

        # if genome_type == 'var':
        #     dff.rename(columns = {0:'Bulge Type', 1:'crRNA', 2:'DNA', 3:'Chromosome', 4:'Position', 5:'Cluster Position', 6:'Direction',
        #     7:'Mismatches', 8:'Bulge Size', 9:'Total', 10:'Min Mismatches', 11:'Max Mismatches', 12:'Samples', 13:'Correct Guide', 14:'Annotation Type',15:'Top Subcluster'}, inplace = True)
        # else:
        dff.rename(columns = COL_BOTH_RENAME , inplace = True)
        # dff.drop(dff.columns[[-1,]], axis=1, inplace=True)         #NOTE Drop the Correct Guide column
        del dff['Correct Guide']
        #dff['Annotation Type'] = annotation_type
        del dff['Variant Unique']
        for filter_part in filtering_expressions:
            col_name, operator, filter_value = split_filter_part(filter_part)

            if operator in ('eq', 'ne', 'lt', 'le', 'gt', 'ge'):
                # these operators match pandas series operator method names
                dff = dff.loc[getattr(dff[col_name], operator)(filter_value)]
            elif operator == 'contains':
                dff = dff.loc[dff[col_name].str.contains(filter_value)]
            elif operator == 'datestartswith':
                # this is a simplification of the front-end filtering logic,
                # only works with complete fields in standard format
                dff = dff.loc[dff[col_name].str.startswith(filter_value)]

        if len(sort_by):
            dff = dff.sort_values(
                ['Samples' if col['column_id'] == 'Samples Summary' else col['column_id'] for col in sort_by],
                ascending=[
                    col['direction'] == 'asc'
                    for col in sort_by
                ],
                inplace=False
            )

        #Calculate sample count

        data_to_send=dff.iloc[
            page_current*page_size:(page_current+ 1)*page_size
        ].to_dict('records')
        if genome_type != 'ref':
            dict_sample_to_pop, dict_pop_to_superpop = associateSample.loadSampleAssociation(job_directory + 'sampleID.txt')[:2]
            for row in data_to_send:
                summarized_sample_cell = dict()
                for s in row['Samples'].split(','):
                    if s == 'n':
                        break     #If a target have n, it means it's REF, because either all have samples or the single target is REF
                    try:
                        summarized_sample_cell[dict_pop_to_superpop[dict_sample_to_pop[s]]] += 1
                    except:
                        summarized_sample_cell[dict_pop_to_superpop[dict_sample_to_pop[s]]] = 1
                if summarized_sample_cell:
                    row['Samples Summary'] = ', '.join([str(summarized_sample_cell[sp]) + ' ' + sp for sp in summarized_sample_cell])
                else:
                    row['Samples Summary'] = 'n'
        return data_to_send

    #Generate download link sumbyguide
    @app.callback(
        [Output('download-link-sumbyguide', 'children'),
        Output('interval-sumbyguide', 'disabled')],
        [Input('interval-sumbyguide','n_intervals')],
        [State('div-info-sumbyguide-targets','children'),
        State('url', 'search')]
    )
    def downloadLinkGuide(n, file_to_load, search): #file to load = job_id.RNA.1.0.guide
        if n is None:
            raise PreventUpdate
        job_id = search.split('=')[-1]
        file_to_load = file_to_load + '.zip'
        if os.path.exists(current_working_directory + 'Results/' + job_id + '/' + file_to_load):
            return html.A('Download zip', href=URL+'/data/' + job_id + '/' + file_to_load, target = '_blank' ), True

        return 'Generating download link, Please wait...', False

    # Generate download link sumbysample
    @app.callback(
        [Output('download-link-sumbysample', 'children'),
        Output('interval-sumbysample', 'disabled')],
        [Input('interval-sumbysample','n_intervals')],
        [State('div-info-sumbysample-targets','children'),
        State('url', 'search')]
    )
    def downloadLinkSample(n, file_to_load, search): #file to load = job_id.HG001.guide
        if n is None:
            raise PreventUpdate
        job_id = search.split('=')[-1]
        file_to_load = file_to_load + '.zip'
        if os.path.exists(current_working_directory + 'Results/' + job_id + '/' + file_to_load):
            return html.A('Download zip', href=URL+'/data/' + job_id + '/' + file_to_load, target = '_blank' ), True

        return 'Generating download link, Please wait...', False

    #Generate download link sumbyposition
    @app.callback(
        [Output('download-link-sumbyposition', 'children'),
        Output('interval-sumbyposition', 'disabled')],
        [Input('interval-sumbyposition','n_intervals')],
        [State('div-info-sumbyposition-targets','children'),
        State('url', 'search')]
    )
    def downloadLinkPosition(n, file_to_load, search): #file to load =
        if n is None:
            raise PreventUpdate
        job_id = search.split('=')[-1]
        file_to_load = file_to_load + '.zip'
        if os.path.exists(current_working_directory + 'Results/' + job_id + '/' + file_to_load):
            return html.A('Download zip', href=URL+'/data/' + job_id + '/' + file_to_load, target = '_blank' ), True

        return 'Generating download link, Please wait...', False

    ############################ Genome Database Page Callbacks ##########################

    @app.callback(
        Output('genomes-table', 'data'),
        [Input('genomes-table', "page_current"),
         Input('genomes-table', "page_size"),
         Input('genomes-table', 'sort_by'),
         Input('genomes-table', 'filter_query')])
    def updateGenomePageTable(page_current, page_size, sort_by, filter):
        filtering_expressions = filter.split(' && ')
        dff = get_genomes.get_genomes(current_working_directory)
        for filter_part in filtering_expressions:
            col_name, operator, filter_value = split_filter_part(filter_part)

            if operator in ('eq', 'ne', 'lt', 'le', 'gt', 'ge'):
                # these operators match pandas series operator method names
                dff = dff.loc[getattr(dff[col_name], operator)(filter_value)]
            elif operator == 'contains':
                dff = dff.loc[dff[col_name].str.contains(filter_value)]
            elif operator == 'datestartswith':
                # this is a simplification of the front-end filtering logic,
                # only works with complete fields in standard format
                dff = dff.loc[dff[col_name].str.startswith(filter_value)]

        if len(sort_by):
            dff = dff.sort_values(
                [col['column_id'] for col in sort_by],
                ascending=[
                    col['direction'] == 'asc'
                    for col in sort_by
                ],
                inplace=False
            )

        page = page_current
        size = page_size
        return dff.iloc[page * size: (page + 1) * size].to_dict('records')

    ############################ History Page ##########################

    def get_results():
        '''
        Get a dataframe of the Results directory
        '''
        results_dirs = [join(current_working_directory + '/Results/', f) for f in listdir(current_working_directory + '/Results/') if isdir(join(current_working_directory + '/Results/', f)) and isfile(current_working_directory + '/Results/' + f + '/Params.txt')]
        results_dirs.sort(key = os.path.getctime)   #Sorted older first
        results_dirs = [os.path.basename(f) for f in results_dirs]
        col = ['Job ID', 'Genome', 'PAM', 'Mismatches', 'DNA Bulges', 'RNA Bulges', 'Gecko Comparison', 'Reference Comparison', 'Date', 'Load']
        a = pd.DataFrame(columns = col)
        for job in results_dirs:
            if os.path.exists(current_working_directory + '/Results/' + job + '/Params.txt'):
                with open(current_working_directory  + '/Results/' + job + '/Params.txt') as p:
                    all_params = p.read()
                    mms = (next(s for s in all_params.split('\n') if 'Mismatches' in s)).split('\t')[-1]
                    genome_selected = (next(s for s in all_params.split('\n') if 'Genome_selected' in s)).split('\t')[-1]
                    genome_selected = genome_selected.replace('_',' ')
                    if os.path.exists(current_working_directory + '/Results/' + job + '/log.txt'):
                        with open(current_working_directory  + '/Results/' + job + '/log.txt') as lo:
                            all_log = lo.read()
                        job_start = (next(s for s in all_log.split('\n') if 'Job\tStart' in s)).split('\t')[-1]
                        try:
                            job_end = (next(s for s in all_log.split('\n') if 'Job\tDone' in s)).split('\t')[-1]
                        except:
                            link_load = URL + '/load?job=' + job
                        else:
                            link_load = URL + '/result?job=' + job
                    else:
                        job_start = 'n/a'
                        link_load = URL + '/load?job=' + job
                    dna = (next(s for s in all_params.split('\n') if 'DNA' in s)).split('\t')[-1]
                    rna = (next(s for s in all_params.split('\n') if 'RNA' in s)).split('\t')[-1]
                    pam = (next(s for s in all_params.split('\n') if 'Pam' in s)).split('\t')[-1]
                    gecko = (next(s for s in all_params.split('\n') if 'Gecko' in s)).split('\t')[-1]
                    if gecko == 'True':
                        gecko = 'Yes'
                    else:
                        gecko = 'No'
                    comparison = (next(s for s in all_params.split('\n') if 'Ref_comp' in s)).split('\t')[-1]
                    if comparison == 'True':
                        comparison = 'Yes'
                    else:
                        comparison = 'No'
                    if os.path.exists(current_working_directory + '/Results/' + job + '/guides.txt'):
                        with open(current_working_directory  + '/Results/' + job + '/guides.txt') as g:
                            n_guides = str(len(g.read().strip().split('\n')))
                    else:
                        n_guides ='n/a'

                    a = a.append({'Job ID':job, 'Genome':genome_selected, 'Mismatches':mms, 'DNA Bulges':dna,
                    'RNA Bulges':rna, 'PAM':pam,'Gecko Comparison':gecko,'Reference Comparison':comparison, 'Date':job_start, 'Load': link_load, 'Delete':''}, ignore_index = True)
        a = a.sort_values(['Mismatches','DNA Bulges','RNA Bulges'],ascending = [True, True, True])
        return a

    def generate_table_results(dataframe, page, max_rows = 10):
        '''
        Generate table for History page
        '''
        fl = []
        rows_remaining = len(dataframe) - (page - 1) * max_rows
        header = html.Thead(
                html.Tr([html.Th(col,style = {'vertical-align':'middle', 'text-align':'center'}) if col != 'Load' and col != 'Delete' else html.Th('',style = {'vertical-align':'middle', 'text-align':'center'})  for col in dataframe.columns])
            )
        body_history = []
        add_button = 0
        for i in range(min(rows_remaining, max_rows)):
            add_button += 1
            row_hist = []
            for col in dataframe.columns:
                if col == 'Load':
                    row_hist.append(
                            html.Td(html.A('Load', target = '_blank', href = dataframe.iloc[i + (page - 1)*max_rows][col]), style = {'vertical-align':'middle', 'text-align':'center'})
                    )
                elif col == 'Delete':
                    row_hist.append(
                        html.Td(html.Button('Delete', id = 'button-delete-history-'+str(i), **{'data-jobid':dataframe.iloc[i + (page - 1)*max_rows]['Job ID']}),style = {'vertical-align':'middle', 'text-align':'center'})
                    )
                else:
                    row_hist.append(
                            html.Td(dataframe.iloc[i + (page - 1)*max_rows][col], style = {'vertical-align':'middle', 'text-align':'center'})
                        )
            body_history.append(html.Tr(row_hist))
        fl.append(
            html.Table([
                header,
                html.Tbody(body_history)
            ], style = {'display':'inline-block'},)
        )

        for i in range(add_button, 10): #Add hidden buttons for callback removeJobId compatibility
            fl.append(html.Button(
                str(i), id = 'button-delete-history-'+str(i),**{'data-jobid':'None'}, style = {'display':'none'}
                ))
        return fl

    def historyPage():
        '''
        Create the History page
        '''
        results = get_results()
        final_list = []

        final_list.append(
            html.Div(
                [
                    html.H3('Results History'),
                    html.P('List of available results. Click on the \'Load\' link to open the corresponding result in a new page.')
                ]
            )
        )
        final_list.append(
                html.Div
                    (
                        [
                            dbc.Row(
                                [
                                    dbc.Col(html.Div(dcc.Dropdown(options = availableGenomes(), id = 'dropdown-genomes-history', placeholder = 'Select a Genome'))),
                                    dbc.Col(html.Div(dcc.Dropdown(options = availablePAM(), id = 'dropdown-pam-history', placeholder = 'Select a PAM'))),
                                    # dbc.Col(html.Div(dcc.Dropdown(options = av_mismatches, id = 'dropdown-mms-history', placeholder = 'Select a Mismatch value' ))),
                                    # dbc.Col(html.Div(dcc.Dropdown(options = av_bulges, id = 'dropdown-dna-history', placeholder = 'Select a DNA Bulge value' ))),
                                    # dbc.Col(html.Div(dcc.Dropdown(options = av_bulges, id = 'dropdown-rna-history', placeholder = 'Select a RNA Bulge value' ))),

                                    dbc.Col(html.Div(html.Button('Filter', id = 'button-filter-history')))
                                ]
                            ),
                        ],
                    )
            )
        final_list.append(html.Div('None,None',id = 'div-history-filter-query', style = {'display':'none'}))

        final_list.append(
                html.Div(
                    generate_table_results(results,1),
                    id = 'div-history-table',
                    style = {'text-align': 'center'}
                ),
        )
        final_list.append(
            html.Div(id = 'div-remove-jobid', style = {'display':'none'})
        )
        final_list.append(
            html.Div(
                [
                    html.Br(),
                    html.Button('Prev', id = 'prev-page-history'),
                    html.Button('Next', id = 'next-page-history')
                ],
                style = {'text-align': 'center'}
            )
        )
        max_page = len(results.index)
        max_page = math.floor(max_page / 10) + 1
        final_list.append(html.Div('1/' + str(max_page), id= 'div-current-page-history'))
        page = html.Div(final_list, style = {'margin':'1%'})
        return page

    #Callback to update the hidden div filter of history page
    @app.callback(
        Output('div-history-filter-query', 'children'),
        [Input('button-filter-history', 'n_clicks')],
        [State('dropdown-genomes-history', 'value'),
        State('dropdown-pam-history', 'value')]
    )
    def updateHistoryFilter(n, genome, pam):
        if n is None:
            raise PreventUpdate
        return str(genome) + ',' + str(pam)

    def supportFilterHistory(result_df, genome_f, pam_f):
        if genome_f is not None:
            result_df.drop(result_df[(result_df['Genome'] != genome_f)].index, inplace = True)
        if pam_f is not None:
            keep_values = []
            for index, row in result_df.iterrows():
                if row.PAM not in pam_f:
                    keep_values.append(index)
            result_df.drop(labels= keep_values, inplace = True)

        max_page = len(result_df.index)
        max_page = math.floor(max_page / 10) + 1
        return result_df, max_page

    #Remove Results and Apply Filtering
    @app.callback(
        [Output('div-history-table', 'children'),
        Output('div-current-page-history', 'children')],
        [Input('button-delete-history-0', 'n_clicks_timestamp'),
        Input('button-delete-history-1', 'n_clicks_timestamp'),
        Input('button-delete-history-2', 'n_clicks_timestamp'),
        Input('button-delete-history-3', 'n_clicks_timestamp'),
        Input('button-delete-history-4', 'n_clicks_timestamp'),
        Input('button-delete-history-5', 'n_clicks_timestamp'),
        Input('button-delete-history-6', 'n_clicks_timestamp'),
        Input('button-delete-history-7', 'n_clicks_timestamp'),
        Input('button-delete-history-8', 'n_clicks_timestamp'),
        Input('button-delete-history-9', 'n_clicks_timestamp'),
        Input('prev-page-history', 'n_clicks_timestamp'),
        Input('next-page-history', 'n_clicks_timestamp'),
        Input('div-history-filter-query', 'children')],
        [State('button-delete-history-0', 'data-jobid'),
        State('button-delete-history-1', 'data-jobid'),
        State('button-delete-history-2', 'data-jobid'),
        State('button-delete-history-3', 'data-jobid'),
        State('button-delete-history-4', 'data-jobid'),
        State('button-delete-history-5', 'data-jobid'),
        State('button-delete-history-6', 'data-jobid'),
        State('button-delete-history-7', 'data-jobid'),
        State('button-delete-history-8', 'data-jobid'),
        State('button-delete-history-9', 'data-jobid'),
        State('button-filter-history', 'n_clicks_timestamp'),
        State('url', 'search'),
        State('div-current-page-history', 'children')
        ]
    )
    def removeJobIDandFilter(n0, n1, n2, n3, n4, n5, n6, n7, n8, n9, nPrev, nNext, filter_q, jID0, jID1, jID2, jID3, jID4, jID5, jID6, jID7, jID8, jID9, n, search, current_page):
        #Get last pressed button
        if not n0:
            n0 = 0
        if not n1:
            n1 = 0
        if not n2:
            n2 = 0
        if not n3:
            n3 = 0
        if not n4:
            n4 = 0
        if not n5:
            n5 = 0
        if not n6:
            n6 = 0
        if not n7:
            n7 = 0
        if not n8:
            n8 = 0
        if not n9:
            n9 = 0
        if not nPrev:
            nPrev = 0
        if not nNext:
            nNext = 0
        if not n:
            n = 0
        btn_group = []
        btn_group.append(n0)
        btn_group.append(n1)
        btn_group.append(n2)
        btn_group.append(n3)
        btn_group.append(n4)
        btn_group.append(n5)
        btn_group.append(n6)
        btn_group.append(n7)
        btn_group.append(n8)
        btn_group.append(n9)
        btn_group.append(nPrev)
        btn_group.append(nNext)
        btn_group.append(n)

        genome_filter = filter_q.split(',')[0]
        pam_filter = filter_q.split(',')[1]
        if genome_filter == 'None':
            genome_filter = None
        if pam_filter == 'None':
            pam_filter = None
        current_page = current_page.split('/')[0]
        current_page = int(current_page)
        results = get_results()
        selectedID = ''

        if max(btn_group) == 0:
            selectedID = ''
        elif max(btn_group) == n0:
            selectedID = jID0
        elif max(btn_group) == n1:
            selectedID = jID1
        elif max(btn_group) == n2:
            selectedID = jID2
        elif max(btn_group) == n3:
            selectedID = jID3
        elif max(btn_group) == n4:
            selectedID = jID4
        elif max(btn_group) == n5:
            selectedID = jID5
        elif max(btn_group) == n6:
            selectedID = jID6
        elif max(btn_group) == n7:
            selectedID = jID7
        elif max(btn_group) == n8:
            selectedID = jID8
        elif max(btn_group) == n9:
            selectedID = jID9
        elif max(btn_group) == n:    #Filter Button selected, return the first page of the filtered table
            results, max_page = supportFilterHistory(results, genome_filter, pam_filter)
            return generate_table_results(results,1), '1/' + str(max_page)
        elif max(btn_group) == nNext:   #Next Button of the table
            current_page = current_page + 1
            results, max_page = supportFilterHistory(results, genome_filter, pam_filter)
            if ((current_page - 1) * 10) > len(results):
                current_page = current_page -1
                if current_page < 1:
                    current_page = 1
            return generate_table_results(results,current_page), str(current_page) + '/' + str(max_page)
        elif max(btn_group) == nPrev:   #Go to previous page
            current_page = current_page - 1
            if current_page < 1:
                current_page = 1
            results, max_page = supportFilterHistory(results, genome_filter, pam_filter)
            return generate_table_results(results,current_page), str(current_page) + '/' + str(max_page)

        if selectedID == '':
            raise PreventUpdate
        result_removed = Gmsg.deleteResultConfirm(selectedID)
        if result_removed:      #If the result was removed, update the table
            results, max_page = supportFilterHistory(get_results(), genome_filter, pam_filter)
            return generate_table_results(results,1), '1/' + str(max_page)
        else:
            raise PreventUpdate
        results, max_page = supportFilterHistory(get_results(), genome_filter, pam_filter)
        return generate_table_results(results,1), '1/' + str(max_page)

    ############################ GUI CALLBACKS #########################
    #Add a new genome
    @app.callback(
        Output("genome-job","value"),
        [Input("add-genome","n_clicks")],
    )
    def add_genome(nAdd):
        """
        Bottone per avviare la GUI in Tkinter per l'aggiunta di genomi offline
        """
        from GUI import ChooseFiles as cf
        if nAdd is None:
            raise PreventUpdate
        cf.startChooseFiles(current_working_directory,  app_location + 'GUI/' )
        return ''

    #Update an existing dictionary
    @app.callback(
        Output("dict-job","value"),
        [Input("update-dict","n_clicks")],
    )
    def update_dict(nUpd):
        """
        Bottone per avviare la GUI in Tkinter per l'aggiornamento di dizionari
        """
        from GUI import UpdateDict as ud
        if nUpd is None:
            raise PreventUpdate
        ud.startUpdateDict(current_working_directory)
        return ''

    #Open filedialog for choosing new annotation file
    @app.callback(
        [Output('label-new-annotation-selected', 'children'),
        Output('tooltip-label-new-annotation-selected', 'children')],
        [Input('button-choose-new-annotation', 'n_clicks')]
    )
    def fileDialogUpdateAnnotation(n):
        selected_file = openDialog(n, 'F')
        return 'Selected: ' + os.path.basename(selected_file), 'Full Path: ' + selected_file

    #Change annotation of selected Genome row
    @app.callback(
        Output('ann-job', 'children'),
        [Input('change-ann','n_clicks')],
        [ State('genomes-table', 'derived_virtual_data'),
         State('genomes-table', 'selected_rows'),
         State('tooltip-label-new-annotation-selected', 'children'),
         State('radioitems-new-annotation', 'value')]
        )
    def change_annotation(nChg, rows, selected_row, selected_new_ann, selected_type):
        if nChg is None:
            raise PreventUpdate
        if len(selected_row) == 0:
            return dbc.Alert("Warning! Please select a Genome!", is_open=True, duration=5000, color = 'warning' )
        selected_new_ann = selected_new_ann.split('Full Path: ')[-1]
        if not isfile(selected_new_ann):
            return dbc.Alert("Error! The selected file does not exist!", is_open=True, duration=7000, color = 'danger' )
        if selected_type is None:
            return dbc.Alert("Warning! Please select an option (Overwrite or Extend)!", is_open=True, duration=7000, color = 'warning' )
        row = pd.DataFrame(rows).iloc[selected_row,:]
        annotationFile = row["Annotation File"].iloc[0]

        if selected_type == 'replace':
            subprocess.run(['cp', selected_new_ann ,current_working_directory + 'annotations/' + annotationFile])
        else:
            with open (current_working_directory + 'annotations/' + annotationFile, 'a') as oldAnn:
                with open (selected_new_ann, 'r') as newAnn:
                    for line in newAnn:
                        oldAnn.write("\n"+line.strip())

        # from GUI import annotations as ann
        # ann.startChangeAnn(current_working_directory+"/annotations/"+str(annotationFile))

        #Return an alert if the job is completed
        return dbc.Alert(
                "Completed! The annotation file is updated!",
                id="alert-auto",
                is_open=True,
                duration=5000,
            )

    if __name__ == '__main__':
        #app.run_server(debug=True)
        app.run_server(host='0.0.0.0', debug=True, port=8050)
        #app.run_server(host='0.0.0.0',  port=8080) #NOTE to not reload the page when creating new images in graphical report
        cache.clear()       #delete cache when server is closed

        #BUG nel filtering se ho, in min mismatch etc, la stringa '-', che non è considerata numero
        #NOTE: l'ordinamento su Samples Summary o su Samples è fatto su stringhe, e non su numero di samples (potrebbe essere più utile)
        #BUG see https://github.com/plotly/dash/issues/1049; Location component is called twice, meaning that two grep can occure at once.

## Functions

`def add_genome(nAdd)`

  Bottone per avviare la GUI in Tkinter per l'aggiunta di genomi offline

  Expand source code

      @app.callback(
          Output("genome-job","value"),
          [Input("add-genome","n_clicks")],
      )
      def add_genome(nAdd):
          """
          Bottone per avviare la GUI in Tkinter per l'aggiunta di genomi offline
          """
          from GUI import ChooseFiles as cf
          if nAdd is None:
              raise PreventUpdate
          cf.startChooseFiles(current_working_directory,  app_location + 'GUI/' )
          return ''

`def availableGenomes()`

  Returns a list of dictionaries of the available genomes in the 'Genomes' directory.

  Returns

  - gen_dir (list of {'label': genome, 'value': genome}): list containing a series of dictionaries, one for each directory (genome) found in the 'Genomes' directory. Used as input parameter for the 'options' element of a Dash Droplist
  Expand source code

      def availableGenomes():
          '''
          Returns a list of dictionaries of the available genomes in the 'Genomes' directory.

          ***Returns***

          + **gen_dir** (*list* of {'label': genome, 'value': genome}): list containing a series of dictionaries, one for each directory (genome) found in
          the 'Genomes' directory. Used as input parameter for the 'options' element of a Dash Droplist
          '''
          onlydir = [f for f in listdir(current_working_directory + 'Genomes') if isdir(join(current_working_directory + 'Genomes', f))]
          onlydir = [x.replace('_', ' ') for x in onlydir]
          gen_dir = []
          for dir in onlydir:
              gen_dir.append({'label': dir, 'value' : dir})
          return gen_dir

`def availablePAM()`

  Returns a list of dictionaries of the available PAMs in the 'PAM' directory.

  Returns

  - pam_file (list of {'label': pam, 'value': pam}): list containing a series of dictionaries, one for each PAM file found in the 'PAM' directory. Used as input parameter for the 'options' element of a Dash Droplist
  Expand source code

      def availablePAM():
          '''
          Returns a list of dictionaries of the available PAMs in the 'PAM' directory.

          ***Returns***

          + **pam_file** (*list* of {'label': pam, 'value': pam}): list containing a series of dictionaries, one for each PAM file found in
              the 'PAM' directory. Used as input parameter for the 'options' element of a Dash Droplist
          '''
          onlyfile = [f for f in listdir(current_working_directory + 'pam') if isfile(join(current_working_directory + 'pam', f))]
          onlyfile = [x.replace('.txt', '') for x in onlyfile]            #removed .txt for better visualization
          pam_file = []
          for pam_name in onlyfile:
              if 'tempPAM' in pam_name:   #Skip the temp pam used for updating dictionaries
                  pass
              else:
                  pam_file.append({'label':pam_name, 'value':pam_name})
          return pam_file

`def changePage(href, path, search, hash_guide)`

  Change the page layout based on the URL.

  Args

  - [href] url (href): string containing the whole address, eg 'http://127.0.0.1:8080/result?job=QV99PN6XDL#CCATCGGTGGCCGTTTGCCCNNNnewX00'
  - [path] url (pathname): string containing the page, eg '/result'
  - [search] url (search): string containing the job ID, eg '?job=QV99PN6XDL'
  - [hash_guide] url (hash): string containing the view result query, eg '#CCATCGGTGGCCGTTTGCCCNNNnewX00'

  Returns

  - page-content (children): the layout of the page, based on the URL.
    - '/': returns the index page (main page)
    - '/load': returns the load page. Called after submitting a job
    - '/result': returns the result page. Based on the job ID in the search parameter, the user can also access to:
      - '#GUIDEnew': returns the table visualizing the targets based on Bulge and Mismatch values
      - '-Sample-': returns the table visualizing the targets for each sample
      - '-Pos-': returns the table visualizing the targets based on the selected chromosome position
    - '/user-guide': returns the help page
    - '/contacts': returns the contacts page
    - '/history': returns the history page. Available only offline
    - '/genomes': returns the genomes page. Available only offline
  - job-link (children): the link displayed to the user in the '/load' page, used to access the status of the job. By saving this URL the the user can monitor the status of the job and see the corresponding '/result' page
  Expand source code

      @app.callback(
          [Output('page-content', 'children'),
          Output('job-link', 'children')],
          [Input('url', 'href'), Input('url','pathname'), Input('url','search')],[State('url','hash')]
      )
      def changePage( href, path, search, hash_guide):
          '''
          Change the page layout based on the URL.

          ***Args***

          + [**href**] **url** (*href*): string containing the whole address, eg 'http://127.0.0.1:8080/result?job=QV99PN6XDL#CCATCGGTGGCCGTTTGCCCNNNnewX00'
          + [**path**] **url** (*pathname*): string containing the page, eg '/result'
          + [**search**] **url** (*search*): string containing the job ID, eg '?job=QV99PN6XDL'
          + [**hash_guide**] **url** (*hash*): string containing the view result query, eg '#CCATCGGTGGCCGTTTGCCCNNNnewX00'

          ***Returns***

          + **page-content** (*children*): the layout of the page, based on the URL.
              + '/': returns the index page (main page)
              + '/load': returns the load page. Called after submitting a job
              + '/result': returns the result page. Based on the job ID in the **search** parameter, the user can also access to:
                  + '#GUIDEnew': returns the table visualizing the targets based on Bulge and Mismatch values
                  + '-Sample-': returns the table visualizing the targets for each sample
                  + '-Pos-': returns the table visualizing the targets based on the selected chromosome position
              + '/user-guide': returns the help page
              + '/contacts': returns the contacts page
              + '/history': returns the history page. Available only offline
              + '/genomes': returns the genomes page. Available only offline
          + **job-link** (*children*): the link displayed to the user in the '/load' page, used to access the status of the job. By saving this URL the
          the user can monitor the status of the job and see the corresponding '/result' page
          '''

          if path == '/load':
              return load_page, URL + '/load' + search
          if path == '/result':
              job_id = search.split('=')[-1]
              if hash_guide is None or hash_guide == '':
                  return resultPage(job_id), URL + '/load' + search
              if 'new' in hash_guide:         #TODO cambiare nome alla pagina delle guide
                  return guidePagev3(job_id, hash_guide.split('#')[1]), URL + '/load' + search
              if '-Sample-' in hash_guide:
                  return samplePage(job_id, hash_guide.split('#')[1]), URL + '/load' + search
              if '-Pos-' in hash_guide:
                  return clusterPage(job_id, hash_guide.split('#')[1]), URL + '/load' + search
              return resultPage(job_id), URL + '/load' + search
          if path == '/test-page':
              return test_page(), URL + '/load' + search
          if path == '/test-page2':
              return test_page2, URL + '/load' + search
          if path == '/test-page3':
              return test_page3, URL + '/load' + search
          if path == '/user-guide':
              return about_page, URL + '/load' + search
          if path == '/contacts':
              return contacts_page, URL + '/load' + search
          if path == '/history':
              if ONLINE:
                  return indexPage(), ''
              return historyPage(), URL + '/load' + search
          if path == '/genomes':
              if ONLINE:
                  return indexPage(), ''
              genomes_page = html.Div(gen_page.genomesPage(current_working_directory), style = {'margin':'1%'})
              return genomes_page, URL + '/load' + search
          return indexPage(), ''

`def changeUrl(n, href, genome_selected, pam, text_guides, mms, dna, rna, gecko_opt, genome_ref_opt, adv_opts, dest_email, active_tab, text_sequence, len_guide_sequence)`

  Main function that generates the inputs for the Post/Process/submit_job.final.sh script and launches the search analysis.

  Args

  - [n] submit-job (n_clicks): this value comes from the output of the `checkInput()` function. If `1`, it means that the inputs are correct, and the function can proceed. If `None` it means that some inputs are missing, so a `raise PreventUpdate` is returned.
  - [href] url (href): string for the href part of the url. NOTE not used
  - [genome_selected] available-genome (value): string for the selected genome
  - [pam] available-pam (value): string for the selected PAM
  - [text_guides] text-guides (value): string for the input guides
  - [mms] mms (value): int for the mismatch selected
  - [dna] dna (value): int for the DNA bulge selected
  - [rna] rna (value): int for the RNA bbulge selected
  - [gecko_opt] checkbox-gecko (checked): bool for the GeCKO Checkbox
  - [genoma_ref_opt] checkbox-ref-comp (checked): bool for the reference genome comparison
  - [adv_opts] checklist-advanced (value): list of advanced options selected (NOTE only email)
  - [dest_email] example-email (value): string of the input email
  - [active_tab] tabs (active_tab): string of the ID of the active tab ('Guide' or 'Sequence')
  - [text_sequence] text-sequence (value): string of the input sequence
  - [len_guide_sequence] len-guide-sequence-ver (value): int of the selected guide len (available only if 'Sequence' tab is active)

  Returns

  - url (pathname): string '/load' to go to the Load Page
  - url (search): string '?job=' + the job ID, to check the status of the specific job

  Details

  - 1) (NOTE already done in `checkInput()`) The function checks the validity of the input parameters.
  - 2) In order to differentiante various analysis submissions, an ID is given to each analysis (Job ID), consisting of a string of 10 alphanumeric characters (A-Z 0-9). If a generated ID is already assigned, retry and compute another one. Every 7 iterations, add +1 to the length of the ID, up to a maximum of 20 chars. Then the JobID directory is created and inside is created a `queue.txt` file in order to implement a job queue
  - 3) The parameters used as input for the submit_job.final.sh script are created based on user input:
    - Email: the email address, link for the results and start date are written in `Genomes/JobID/email.txt`
    - PAM: get PAM len and direction (beginning or end), in order to write the correct files for pam and guides used in Crispritz.
    - Guides: if the 'Sequence' tab is selected, the input is processed in the `extract_seq` module, that returns a list of guides. Non valid characters are then eliminated, along with the exceeding guides (only 1000 guides are acceptable). The guides are then modified by adding N characters, following PAM type input (check Crispritz), and saved into file `Genomes/JobID/guides.txt`. The PAM is also saved in the `Genomes/JobID/pam.txt` file, following the Crispritz standard
    - Indexing: if the selected Genome-PAM has no index in `genome_library`, set a flag to calculate the index when submitting the job. The PAM used for indexing is `Genomes/JobID/pam_indexing.txt`
    - Params file: the informations about the job are saved into the `Params.txt` file
    - Annotation: based on the selected genome reference part, the annotation is chosen from the `annotations` directory
    - Sample: based on the enriched genome selected, the sampleID file is chosen from the `samplesID` directory
    - Dictionary: based on the enriched genome selected, the Dictionary for sample extraction is selected from the `dictionaries` directory
  - 4) If the input is equal to an already calculated analysis, then the function modify the job id to the one of the already existing analysis (even if it's completed/currently submitted/in queue), update the `email` file and the `log` file (in order to reset the 3 days availability on the server)
  - Launch the submit_job.final.sh script using `exeggutor`, to a max of 2 concurrent jobs, the others are put into a queue
  Expand source code

      @app.callback(
          [Output('url', 'pathname'),
          Output('url','search')],
          [Input('submit-job','n_clicks')],
          [State('url', 'href'),
          State('available-genome', 'value'),
          State('available-pam','value'),
          State('text-guides', 'value'),
          State('mms','value'),
          State('dna','value'),
          State('rna','value'),
          State('checkbox-gecko','checked'),
          State('checkbox-ref-comp', 'checked'),
          State('checklist-advanced', 'value'),
          State('example-email','value'),
          State('tabs','active_tab'),
          State('text-sequence','value'),
          State('len-guide-sequence-ver', 'value')]
      )
      def changeUrl(n, href, genome_selected, pam, text_guides, mms, dna, rna, gecko_opt, genome_ref_opt, adv_opts,dest_email, active_tab, text_sequence, len_guide_sequence):      #NOTE startJob
          '''
          Main function that generates the inputs for the Post/Process/submit_job.final.sh script and launches the search analysis.

          ***Args***

          + [**n**] **submit-job** (*n_clicks*): this value comes from the output of the `checkInput()` function. If `1`, it means that the inputs are
          correct, and the function can proceed. If `None` it means that some inputs are missing, so a `raise PreventUpdate` is returned.
          + [**href**] **url** (*href*): string for the href part of the url. NOTE not used
          + [**genome_selected**] **available-genome** (*value*): string for the selected genome
          + [**pam**] **available-pam** (*value*): string for the selected PAM
          + [**text_guides**] **text-guides** (*value*): string for the input guides
          + [**mms**] **mms** (*value*): int for the mismatch selected
          + [**dna**] **dna** (*value*): int for the DNA bulge selected
          + [**rna**] **rna** (*value*): int for the RNA bbulge selected
          + [**gecko_opt**] **checkbox-gecko** (*checked*): bool for the GeCKO Checkbox
          + [**genoma_ref_opt**] **checkbox-ref-comp** (*checked*): bool for the reference genome comparison
          + [**adv_opts**] **checklist-advanced** (*value*): list of advanced options selected (NOTE only email)
          + [**dest_email**] **example-email** (*value*): string of the input email
          + [**active_tab**] **tabs** (*active_tab*): string of the ID of the active tab ('Guide' or 'Sequence')
          + [**text_sequence**] **text-sequence** (*value*): string of the input sequence
          + [**len_guide_sequence**] **len-guide-sequence-ver** (*value*): int of the selected guide len (available only if 'Sequence' tab is active)

          ***Returns***

          + **url** (*pathname*): string '/load' to go to the Load Page
          + **url** (*search*): string '?job=' + the job ID, to check the status of the specific job

          ***Details***

          + 1) (NOTE already done in `checkInput()`) The function checks the validity of the input parameters.
          + 2) In order to differentiante various analysis submissions, an ID is given to each analysis (Job ID), consisting of a string of 10 alphanumeric
          characters (A-Z 0-9). If a generated ID is already assigned, retry and compute another one. Every 7 iterations, add +1 to the length of the
          ID, up to a maximum of 20 chars. Then the JobID directory is created and inside is created a `queue.txt` file in order to implement a job queue
          + 3) The parameters used as input for the submit_job.final.sh script are created based on user input:
              + Email: the email address, link for the results and start date are written in `Genomes/JobID/email.txt`
              + PAM: get PAM len and direction (beginning or end), in order to write the correct files for pam and guides used in Crispritz.
              + Guides: if the 'Sequence' tab is selected, the input is processed in the `extract_seq` module, that returns a list of guides. Non valid
              characters are then eliminated, along with the exceeding guides (only 1000 guides are acceptable). The guides are then modified by adding
              N characters, following PAM type input (check Crispritz), and saved into file `Genomes/JobID/guides.txt`. The PAM is also saved in the
              `Genomes/JobID/pam.txt` file, following the Crispritz standard
              + Indexing: if the selected Genome-PAM has no index in `genome_library`, set a flag to calculate the index when submitting the job. The PAM
              used for indexing is `Genomes/JobID/pam_indexing.txt`
              + Params file: the informations about the job are saved into the `Params.txt` file
              + Annotation: based on the selected genome reference part, the annotation is chosen from the `annotations` directory
              + Sample: based on the enriched genome selected, the sampleID file is chosen from the `samplesID` directory
              + Dictionary: based on the enriched genome selected, the Dictionary for sample extraction is selected from the `dictionaries` directory
          + 4) If the input is equal to an already calculated analysis, then the function modify the job id to the one of the already existing analysis
          (even if it's completed/currently submitted/in queue), update the `email` file and the `log` file (in order to reset the 3 days availability on the server)
          + Launch the submit_job.final.sh script using `exeggutor`, to a max of 2 concurrent jobs, the others are put into a queue
          '''
          if n is None:
              raise PreventUpdate

          # 1) Check input, else give simple input
          if genome_selected is None or genome_selected == '':
              genome_selected = 'hg38_ref'
          if pam is None or pam == '':
              pam = '20bp-NGG-SpCas9'
          if text_guides is None or text_guides == '':
              text_guides = 'GAGTCCGAGCAGAAGAAGAA\nCCATCGGTGGCCGTTTGCCC'
          else:
              text_guides = text_guides.strip()
              if ( not all(len(elem) == len(text_guides.split('\n')[0]) for elem in text_guides.split('\n'))):
                  text_guides = selectSameLenGuides(text_guides)
          if (len_guide_sequence is None or str(len_guide_sequence) == '') and ('sequence-tab' in active_tab):
              len_guide_sequence = 20
          if (text_sequence is None or text_sequence == '') and ('sequence-tab' in active_tab):
              text_sequence = '>sequence\nTACCCCAAACGCGGAGGCGCCTCGGGAAGGCGAGGTGGGCAAGTTCAATGCCAAGCGTGACGGGGGA'

          # 2) Get random string for job id
          n_it = 10
          len_id = 10
          for i in range(n_it):
              assigned_ids = [o for o in os.listdir(current_working_directory + 'Results/') if os.path.isdir(os.path.join(current_working_directory + 'Results/',o))]
              job_id = ''.join(random.choices(string.ascii_uppercase + string.digits, k = len_id))
              if job_id not in assigned_ids:
                  break
              if i > 7:
                  i = 0
                  len_id += 1
                  if len_id > 20:
                      break
          result_dir = current_working_directory + 'Results/' + job_id
          subprocess.run(['mkdir ' + result_dir], shell = True)
          #NOTE test command per queue
          subprocess.run(['touch ' + current_working_directory + 'Results/' + job_id + '/queue.txt'], shell = True)

          # 3) Set parameters
          generate_index_ref = False
          generate_index_enr = False
          search_index = True
          search = True
          annotation = True
          report =  True
          gecko_comp = False
          ref_comparison = False
          send_email = False
          if adv_opts is None:
              adv_opts = []
          if gecko_opt:
              gecko_comp = True
          if genome_ref_opt:
              ref_comparison = True
          if 'email' in adv_opts and dest_email is not None and len(dest_email.split('@')) > 1 and dest_email.split('@')[-1] != '':
              send_email = True
              with open(result_dir + '/email.txt', 'w') as e:
                  e.write(dest_email + '\n')
                  e.write(URL + '/load?job=' + job_id + '\n')
                  e.write(datetime.utcnow().strftime("%m/%d/%Y, %H:%M:%S") + '\n')
                  #e.write('Job done. Parameters: etc etc')
                  e.close()

          genome_selected = genome_selected.replace(' ', '_')
          genome_ref = genome_selected.split('+')[0]              # + char to separate ref and enr, eg Human_Genome_ref+Human_Genome_1000_genome_project
          if genome_ref == genome_selected:
              ref_comparison = False
          #NOTE Indexed genomes names are PAM + _ + bMax + _ + genome_selected

          pam_len = 0
          with open(current_working_directory + 'pam/' + pam + '.txt') as pam_file:
              pam_char = pam_file.readline()
              index_pam_value = pam_char.split(' ')[-1]
              if int(pam_char.split(' ')[-1]) < 0:
                  end_idx = int(pam_char.split(' ')[-1]) * (-1)
                  pam_char = pam_char.split(' ')[0][0 : end_idx]
                  pam_len = end_idx
                  pam_begin = True
              else:
                  end_idx = int(pam_char.split(' ')[-1])
                  pam_char = pam_char.split(' ')[0][end_idx * (-1):]
                  pam_len = end_idx
                  pam_begin = False

          if 'sequence-tab' in active_tab:
              #Extract sequence and create the guides
              guides = []
              for name_and_seq in text_sequence.split('>'):
                  if '' == name_and_seq:
                      continue
                  name = name_and_seq[:name_and_seq.find('\n')]
                  seq = name_and_seq[name_and_seq.find('\n'):]
                  seq = seq.strip().split()
                  seq = ''.join(seq)
                  # name, seq = name_and_seq.strip().split('\n')
                  if 'chr' in seq:
                      extracted_seq = extract_seq.extractSequence(name, seq, genome_ref.replace(' ', '_'))
                  else:
                      extracted_seq = seq.strip()

                  guides.extend(convert_pam.getGuides(extracted_seq, pam_char, len_guide_sequence, pam_begin))
                  text_guides = '\n'.join(guides).strip()

          text_guides = text_guides.upper()
          for g in text_guides.split('\n'):
              for c in g:
                  if c not in VALID_CHARS:
                      text_guides = text_guides.replace(c,'')
          if len(text_guides.split('\n')) > 1000:
              text_guides = '\n'.join(text_guides.split('\n')[:1000]).strip()
          len_guides = len(text_guides.split('\n')[0])
          #Adjust guides by adding Ns to make compatible with Crispritz
          if (pam_begin):
              pam_to_file = pam_char + ('N' * len_guides) + ' ' + index_pam_value
              pam_to_indexing = pam_char + ('N' * 25) + ' ' + index_pam_value
          else:
              pam_to_file = ('N' * len_guides) + pam_char + ' ' + index_pam_value
              pam_to_indexing = ('N' * 25) + pam_char + ' ' + index_pam_value

          save_pam_file = open(result_dir + '/pam.txt', 'w')
          save_pam_file.write(pam_to_file)
          save_pam_file.close()
          pam = result_dir + '/pam.txt'

          guides_file = result_dir + '/guides.txt'
          if text_guides is not None and text_guides != '':
              save_guides_file = open(result_dir + '/guides.txt', 'w')
              if (pam_begin):
                  text_guides = 'N' * pam_len + text_guides.replace('\n', '\n' + 'N' * pam_len)
              else:
                  text_guides = text_guides.replace('\n', 'N' * pam_len + '\n') + 'N' * pam_len
              save_guides_file.write(text_guides)
              save_guides_file.close()

          if (int(dna) == 0 and int(rna) == 0):
              search_index = False
          max_bulges = rna
          if (int(dna) > int(rna)):
              max_bulges = dna

          if (search_index):
              search = False

          #Check if index exists, otherwise set generate_index to true
          genome_indices_created =  [f for f in listdir(current_working_directory + 'genome_library') if isdir(join(current_working_directory + 'genome_library', f))]
          genome_idx = ''
          genome_idx_ref = ''
          for gidx in genome_indices_created:
              if pam_char in gidx and genome_selected in gidx:
                  if int(gidx.split('_')[1]) >= int(max_bulges):
                      if '+' in gidx:
                          genome_idx = gidx
                      genome_idx_ref = gidx.split('+')[0]

          if genome_idx == '':
              if int(max_bulges) > 0:
                  generate_index_enr = True
                  with open(result_dir + '/pam_indexing.txt', 'w+') as pam_id_file:
                      pam_id_file.write(pam_to_indexing)
              genome_idx = pam_char + '_' + str(max_bulges) + '_' + genome_selected
          if genome_idx_ref == '':
              if int(max_bulges) > 0:
                  generate_index_ref = True
                  with open(result_dir + '/pam_indexing.txt', 'w+') as pam_id_file:
                      pam_id_file.write(pam_to_indexing)
              genome_idx_ref = pam_char + '_' + str(max_bulges) + '_' + genome_selected.split('+')[0]

          if genome_ref == genome_selected:
              generate_index_enr = False
          # genome_idx = pam_char + '_' + '2' + '_' + genome_selected
          # genome_idx_ref = genome_idx.split('+')[0]

          #Create Params.txt file
          with open(result_dir + '/Params.txt', 'w') as p:
              p.write('Genome_selected\t' + genome_selected + '\n')
              p.write('Genome_ref\t' + genome_ref + '\n')
              if search_index:
                  p.write('Genome_idx\t' + genome_idx + '\n')
              else:
                  p.write('Genome_idx\t' + 'None\n')
              p.write('Pam\t' + pam_char + '\n')
              p.write('Max_bulges\t' + str(max_bulges) + '\n')
              p.write('Mismatches\t' + str(mms) + '\n')
              p.write('DNA\t' + str(dna) + '\n')
              p.write('RNA\t' + str(rna) + '\n')
              p.write('Gecko\t' + str(gecko_comp) + '\n')
              p.write('Ref_comp\t' + str(ref_comparison) + '\n')
              p.close()

          # 4) Check if input parameters (mms, bulges, pam, guides, genome) are the same as a previous search
          all_result_dirs = [f for f in listdir(current_working_directory + 'Results') if isdir(join(current_working_directory + 'Results', f))]
          all_result_dirs.remove(job_id)
          #all_result_dirs.remove('test')
          for check_param_dir in all_result_dirs:
              if os.path.exists(current_working_directory + 'Results/' + check_param_dir + '/Params.txt'):
                  #if os.path.exists(current_working_directory + 'Results/' + check_param_dir + '/log.txt'):
                      #with open(current_working_directory + 'Results/' + check_param_dir + '/log.txt') as log:
                          #if ('Job\tDone' in log.read()):
                  if (filecmp.cmp(current_working_directory + 'Results/' + check_param_dir + '/Params.txt', result_dir + '/Params.txt' )):
                          guides1 = open(current_working_directory + 'Results/' + check_param_dir + '/guides.txt').read().split('\n')
                          guides2 = open(current_working_directory + 'Results/' + job_id + '/guides.txt').read().split('\n')
                          if (collections.Counter(guides1) == collections.Counter(guides2)):
                              if os.path.exists(current_working_directory + 'Results/' + check_param_dir + '/log.txt'):
                                  adj_date = False
                                  with open(current_working_directory + 'Results/' + check_param_dir + '/log.txt') as log:
                                      log_content = log.read().strip()
                                      if ('Job\tDone' in log_content):
                                          adj_date = True
                                          log_content = log_content.split('\n')
                                          new_date = subprocess.Popen(['echo $(date)'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell = True)
                                          out, err = new_date.communicate()
                                          rewrite = '\n'.join(log_content[:-1]) + '\nJob\tDone\t' + out.decode('UTF-8').strip()
                                  if adj_date:
                                      with open(current_working_directory + 'Results/' + check_param_dir + '/log.txt' ,'w+') as log:
                                          log.write(rewrite)
                                          #Send mail
                                      if send_email:
                                          with open(current_working_directory + 'Results/' + job_id + '/email.txt' ,'w+') as e:
                                              e.write(dest_email + '\n')
                                              e.write(URL + '/load?job=' + check_param_dir + '\n')
                                              e.write(datetime.utcnow().strftime("%m/%d/%Y, %H:%M:%S") + '\n')
                                          #Send mail with file in job_id dir with link to job already done, note that job_id directory will be deleted
                                          subprocess.call(['python ' + app_main_directory + 'send_mail.py ' + current_working_directory + 'Results/' + job_id ], shell = True)

                                  elif send_email:
                                      #Job is not finished, add this user email to email.txt and when job is done send to both
                                      if os.path.exists(current_working_directory + 'Results/' + check_param_dir + '/email.txt'):
                                          with open(current_working_directory + 'Results/' + check_param_dir + '/email.txt' ,'a+') as e:
                                              e.write('--OTHEREMAIL--')
                                              e.write(dest_email + '\n')
                                              e.write(URL + '/load?job=' + check_param_dir + '\n')
                                              e.write(datetime.utcnow().strftime("%m/%d/%Y, %H:%M:%S") + '\n')
                                      else:
                                          with open(current_working_directory + 'Results/' + check_param_dir + '/email.txt' ,'w+') as e:
                                              e.write(dest_email + '\n')
                                              e.write(URL + '/load?job=' + check_param_dir + '\n')
                                              e.write(datetime.utcnow().strftime("%m/%d/%Y, %H:%M:%S") + '\n')

                                  subprocess.call(['rm -r ' + current_working_directory + 'Results/' + job_id], shell = True)
                                  return '/load','?job=' + check_param_dir
                              else:
                                  #We may have entered a jobdir that was in queue
                                  if os.path.exists(current_working_directory + 'Results/' + check_param_dir + '/queue.txt'):
                                      if send_email:
                                          if os.path.exists(current_working_directory + 'Results/' + check_param_dir + '/email.txt'):
                                              with open(current_working_directory + 'Results/' + check_param_dir + '/email.txt' ,'a+') as e:
                                                  e.write('--OTHEREMAIL--')
                                                  e.write(dest_email + '\n')
                                                  e.write(URL + '/load?job=' + check_param_dir + '\n')
                                                  e.write(datetime.utcnow().strftime("%m/%d/%Y, %H:%M:%S") + '\n')
                                          else:
                                              with open(current_working_directory + 'Results/' + check_param_dir + '/email.txt' ,'w+') as e:
                                                  e.write(dest_email + '\n')
                                                  e.write(URL + '/load?job=' + check_param_dir + '\n')
                                                  e.write(datetime.utcnow().strftime("%m/%d/%Y, %H:%M:%S") + '\n')
                                      return '/load','?job=' + check_param_dir

          #Annotation
          if (not search and not search_index):
              annotation = False

          #Generate report
          if (not search and not search_index):
              report = False

          genome_type = 'ref'     #Indicates if search is 'ref', 'var' or 'both'
          if '+' in genome_selected:
              genome_type = 'var'
          if ref_comparison:
              genome_type = 'both'

          #Get annotation file, already with the absolute path. TODO currently only one annotation per genome
          #Also get dictionary and sample dictionary files
          annotation_file = [f for f in listdir(current_working_directory + 'annotations/') if isfile(join(current_working_directory + 'annotations/', f)) and f.startswith(genome_ref)][0]

          if genome_type == 'ref':
              sample_list = None
              dictionary_directory = None
          else:
              sample_list = current_working_directory + 'samplesID/samples_' + genome_selected + '.txt'
              dictionary_directory = current_working_directory + 'dictionaries/dictionary_' + genome_selected

          command = app_main_directory + 'PostProcess/./submit_job.final.sh ' + current_working_directory + 'Results/' + job_id + ' ' + current_working_directory +  'Genomes/' + genome_selected + ' ' + current_working_directory + 'Genomes/' + genome_ref + ' ' + current_working_directory + 'genome_library/' + genome_idx + (
              ' ' + pam + ' ' + guides_file + ' ' + str(mms) + ' ' + str(dna) + ' ' + str(rna) + ' ' + str(search_index) + ' ' + str(search) + ' ' + str(annotation) + (
                  ' ' + str(report) + ' ' + str(gecko_comp) + ' ' + str(ref_comparison) + ' ' + current_working_directory +  'genome_library/' + genome_idx_ref + ' ' + str(send_email) + ' '  + current_working_directory +  'annotations/' + annotation_file +
                  ' ' + genome_type + ' ' + app_main_directory + ' ' + str(dictionary_directory) + ' ' + str(sample_list) + ' ' + str(generate_index_ref) + ' ' + str(generate_index_enr) + ' ' + current_working_directory))

          exeggutor.submit(subprocess.run, command, shell=True)
          return '/load','?job=' + job_id

`def change_annotation(nChg, rows, selected_row, selected_new_ann, selected_type)`
  Expand source code

      @app.callback(
          Output('ann-job', 'children'),
          [Input('change-ann','n_clicks')],
          [ State('genomes-table', 'derived_virtual_data'),
           State('genomes-table', 'selected_rows'),
           State('tooltip-label-new-annotation-selected', 'children'),
           State('radioitems-new-annotation', 'value')]
          )
      def change_annotation(nChg, rows, selected_row, selected_new_ann, selected_type):
          if nChg is None:
              raise PreventUpdate
          if len(selected_row) == 0:
              return dbc.Alert("Warning! Please select a Genome!", is_open=True, duration=5000, color = 'warning' )
          selected_new_ann = selected_new_ann.split('Full Path: ')[-1]
          if not isfile(selected_new_ann):
              return dbc.Alert("Error! The selected file does not exist!", is_open=True, duration=7000, color = 'danger' )
          if selected_type is None:
              return dbc.Alert("Warning! Please select an option (Overwrite or Extend)!", is_open=True, duration=7000, color = 'warning' )
          row = pd.DataFrame(rows).iloc[selected_row,:]
          annotationFile = row["Annotation File"].iloc[0]

          if selected_type == 'replace':
              subprocess.run(['cp', selected_new_ann ,current_working_directory + 'annotations/' + annotationFile])
          else:
              with open (current_working_directory + 'annotations/' + annotationFile, 'a') as oldAnn:
                  with open (selected_new_ann, 'r') as newAnn:
                      for line in newAnn:
                          oldAnn.write("\n"+line.strip())

          # from GUI import annotations as ann
          # ann.startChangeAnn(current_working_directory+"/annotations/"+str(annotationFile))

          #Return an alert if the job is completed
          return dbc.Alert(
                  "Completed! The annotation file is updated!",
                  id="alert-auto",
                  is_open=True,
                  duration=5000,
              )

`def checkEmailValidity(val)`

  Checks email validity (i.e. an '@' is present) and changes the border to green or red accordingly.

  Args

  - [val] example-email (value): string of the email

  Returns

  - example-email (style): dictionary of the style for the Input email: change border to red (if no '@' in val) or to green
  Expand source code

      @app.callback(
          Output('example-email', 'style'),
          [Input('example-email', 'value')]
      )
      def checkEmailValidity(val):
          '''
          Checks email validity (i.e. an '@' is present) and changes the border to green or red accordingly.

          ***Args***

          + [**val**] **example-email** (*value*): string of the email

          ***Returns***

          + **example-email** (*style*): dictionary of the style for the Input email: change border to red (if no '@' in **val**) or to green
          '''
          if val is None:
              raise PreventUpdate

          if '@' in val:
              return {'border':'1px solid #94f033', 'outline':'0'}
          return {'border':'1px solid red'}

`def checkInput(n, n_close, genome_selected, pam, text_guides, mms, dna, rna, len_guide_seq, active_tab, is_open)`

  Checks the presence and correctness of the input fields, changing their border to red if it's missing and displaying a Modal element with a list of the missing inputs. The callback is triggered when the user clicks on the 'Submit' button or when the modal is closed (by the 'Close' button or by clicking on-screen when the Modal element is open)

  Args

  - [n] check-job (n_clicks): button that starts the job after checking the correctness of the input
  - [n_close] close (n_clicks): button that closes the opened modal
  - [genome_selected] available-genome (value): string of the selected genome from the Dropdown. `None` if not selected, '' if selected and then deleted
  - [pam] available-pam (value): string of the selected PAM from the Dropdown. `None` if not selected, '' if selected and then deleted
  - [text_guides] text-guides (value): string of the input guides from the Textarea. `None` if not selected, '' if selected and then deleted
  - [mms] mms (value): int of the selected mismatch value. `None` if not selected, '' if selected and then deleted
  - [dna] dna (value): int of the selected DNA bulge value. `None` if not selected, '' if selected and then deleted
  - [rna] rna (value): int of the selected RNA bulge value. `None` if not selected, '' if selected and then deleted
  - [len_guide_seq] len-guide-sequence-ver (value): int value of the length of the guides (available when 'Sequence' tab is active). `None` if not selected, '' if selected and then deleted
  - [active_tab] tabs (active_tab): string of the ID of the active tab ('Guide' or 'Sequence')
  - [is_open] modal (is_open): True if the modal is displayed, false otherwise

  Returns

  - submit-job (n_clicks): reset the click counter (`None`) if some inputs are missing, else put to `1` in order to trigger the `changeUrl()` function and proceed with the job
  - modal (is_open): `True` if some inputs are missing, `False` otherwise
  - available-genome (className): string containing the name of the css class for the red-border ('missing-input'), indicating a missing input. `None` if input is ok
  - available-pam (className): string containing the name of the css class for the red-border ('missing-input'), indicating a missing input. `None` if input is ok
  - text-guides (style): dictionary for the style element (NOTE not updated if input is missing)
  - mms (className): string containing the name of the css class for the red-border ('missing-input'), indicating a missing input. `None` if input is ok
  - dna (className): string containing the name of the css class for the red-border ('missing-input'), indicating a missing input. `None` if input is ok
  - rna (className): string containing the name of the css class for the red-border ('missing-input'), indicating a missing input. `None` if input is ok
  - len-guide-sequence-ver (className): string containing the name of the css class for the red-border ('missing-input'), indicating a missing input. `None` if input is ok
  - warning-list (children): html.Div for the Modal component, listing the missing inputs
  Expand source code

      @app.callback(
          [Output('submit-job', 'n_clicks'),
          Output('modal', 'is_open'),
          Output('available-genome', 'className'),
          Output('available-pam', 'className'),
          Output('text-guides', 'style'),
          Output('mms', 'className'),
          Output('dna', 'className'),
          Output('rna', 'className'),
          Output('len-guide-sequence-ver', 'className'),
          Output('warning-list', 'children')],
          [Input('check-job','n_clicks'),
          Input('close','n_clicks')],
          [State('available-genome', 'value'),
          State('available-pam','value'),
          State('text-guides', 'value'),
          State('mms','value'),
          State('dna','value'),
          State('rna','value'),
          State('len-guide-sequence-ver','value'),
          State('tabs','active_tab'),
          State("modal", "is_open")]
      )
      def checkInput(n, n_close, genome_selected, pam, text_guides, mms, dna, rna, len_guide_seq, active_tab ,is_open):
          '''
          Checks the presence and correctness of the input fields, changing their border to red if it's missing and displaying a Modal element with
          a list of the missing inputs. The callback is triggered when the user clicks on the 'Submit' button or when the modal is closed
          (by the 'Close' button or by clicking on-screen when the Modal element is open)

          ***Args***

          + [**n**] **check-job** (*n_clicks*): button that starts the job after checking the correctness of the input
          + [**n_close**] **close** (*n_clicks*): button that closes the opened modal
          + [**genome_selected**] **available-genome** (*value*): string of the selected genome from the Dropdown. `None` if not selected, '' if selected
          and then deleted
          + [**pam**] **available-pam** (*value*): string of the selected PAM from the Dropdown. `None` if not selected, '' if selected
          and then deleted
          + [**text_guides**] **text-guides** (*value*): string of the input guides from the Textarea. `None` if not selected, '' if selected
          and then deleted
          + [**mms**] **mms** (*value*): int of the selected mismatch value. `None` if not selected, '' if selected
          and then deleted
          + [**dna**] **dna** (*value*): int of the selected DNA bulge value. `None` if not selected, '' if selected
          and then deleted
          + [**rna**] **rna** (*value*): int of the selected RNA bulge value. `None` if not selected, '' if selected
          and then deleted
          + [**len_guide_seq**] **len-guide-sequence-ver** (*value*): int value of the length of the guides (available when 'Sequence' tab is active). `None` if not selected, '' if selected
          and then deleted
          + [**active_tab**] **tabs** (*active_tab*): string of the ID of the active tab ('Guide' or 'Sequence')
          + [**is_open**] **modal** (*is_open*): True if the modal is displayed, false otherwise

          ***Returns***

          + **submit-job** (*n_clicks*): reset the click counter (`None`) if some inputs are missing, else put to `1` in order to trigger the `changeUrl()`
          function and proceed with the job
          + **modal** (*is_open*): `True` if some inputs are missing, `False` otherwise
          + **available-genome** (*className*): string containing the name of the css class for the red-border ('missing-input'), indicating a missing input. `None` if
          input is ok
          + **available-pam** (*className*): string containing the name of the css class for the red-border ('missing-input'), indicating a missing input. `None` if
          input is ok
          + **text-guides** (*style*): dictionary for the style element (NOTE not updated if input is missing)
          + **mms** (*className*): string containing the name of the css class for the red-border ('missing-input'), indicating a missing input. `None` if
          input is ok
          + **dna** (*className*): string containing the name of the css class for the red-border ('missing-input'), indicating a missing input. `None` if
          input is ok
          + **rna** (*className*): string containing the name of the css class for the red-border ('missing-input'), indicating a missing input. `None` if
          input is ok
          + **len-guide-sequence-ver** (*className*): string containing the name of the css class for the red-border ('missing-input'), indicating a missing input. `None` if
          input is ok
          + **warning-list** (*children*): html.Div for the Modal component, listing the missing inputs
          '''
          if n is None:
              raise PreventUpdate
          if is_open is None:
              is_open = False

          classname_red = 'missing-input'
          genome_update = None
          pam_update = None
          text_update = {'width':'450px', 'height':'160px'}
          mms_update = None
          dna_update = None
          rna_update = None
          len_guide_update = None
          update_style = False
          miss_input_list = []

          if genome_selected is None or genome_selected == '':
              genome_update = classname_red
              update_style = True
              miss_input_list.append('Genome')
          if pam is None or pam == '':
              pam_update = classname_red
              update_style = True
              miss_input_list.append('PAM')
          # if text_guides is None or text_guides == '':
              # text_update = {'width':'450px', 'height':'160px','border': '1px solid red'}
              # update_style = True
              # miss_input_list.append('crRNA sequence(s)')
          if mms is None or str(mms) == '':
              mms_update = classname_red
              update_style = True
              miss_input_list.append('Allowed Mismatches')
          if dna is None or str(dna) == '':
              dna_update = classname_red
              update_style = True
              miss_input_list.append('Bulge DNA size')
          if rna is None or str(rna) == '':
              rna_update = classname_red
              update_style = True
              miss_input_list.append('Bulge RNA size')
          if (len_guide_seq is None or str(len_guide_seq) == '') and ('sequence-tab' in active_tab):
              len_guide_update = classname_red
              update_style = True
              miss_input_list.append('crRNA length')
          miss_input = html.Div(
              [
                  html.P('The following inputs are missing:'),
                  html.Ul([html.Li(x) for x in miss_input_list]),
                  html.P('Please fill in the values before submitting the job')
              ]
          )

          if not update_style:
              return 1, False, genome_update, pam_update, text_update, mms_update, dna_update, rna_update, len_guide_update, miss_input
          return None, not is_open, genome_update, pam_update, text_update, mms_update, dna_update, rna_update, len_guide_update, miss_input

`def clusterPage(job_id, hash)`
  Expand source code

      def clusterPage(job_id, hash):
          guide = hash[:hash.find('-Pos-')]
          chr_pos = hash[hash.find('-Pos-') + 5:]
          chromosome = chr_pos.split('-')[0]
          position = chr_pos.split('-')[1]
          if (not isdir(current_working_directory + 'Results/' + job_id)):
              return html.Div(dbc.Alert("The selected result does not exist", color = "danger"))
          with open(current_working_directory + 'Results/' + job_id + '/Params.txt') as p:
              all_params = p.read()
              genome_type_f = (next(s for s in all_params.split('\n') if 'Genome_selected' in s)).split('\t')[-1]
              ref_comp = (next(s for s in all_params.split('\n') if 'Ref_comp' in s)).split('\t')[-1]

          genome_type = 'ref'
          style_hide_reference = {'display':'none'}
          value_hide_reference = []
          if '+' in genome_type_f:
              genome_type = 'var'
          if 'True' in ref_comp:
              genome_type = 'both'
              style_hide_reference = {}
              value_hide_reference = ['hide-ref', 'hide-cluster']
          final_list = []
          final_list.append(
              html.H3('Selected Position: ' + chromosome + ' - ' + position)
          )

          if genome_type == 'ref':
              cols = [{"name": i, "id": i, 'type':t, 'hideable':True} for i,t in zip(COL_REF, COL_REF_TYPE)]
              file_to_grep = '.targets.cluster.txt'
          else:
              cols = [{"name": i, "id": i, 'type':t, 'hideable':True} for i,t in zip(COL_BOTH, COL_BOTH_TYPE)]
              file_to_grep = '.total.cluster.txt'
          # print('qui cluster before grep')

          cluster_grep_result = current_working_directory + 'Results/'+ job_id + '/' + job_id + '.' + chromosome + '_' + position + '.' + guide +'.txt'
          put_header = 'head -1 ' + current_working_directory + 'Results/' + job_id + '/' + job_id + file_to_grep + ' > ' + cluster_grep_result + ' ; '
          # print('esiste cluster?' , str(os.path.exists(cluster_grep_result)) )
          if not os.path.exists(cluster_grep_result):    #Example    job_id.chr3_100.guide.txt
              #Grep annotation for ref
              if genome_type == 'ref':#NOTE HEADER NON SALVATO
                  get_annotation = subprocess.Popen(['LC_ALL=C fgrep ' + guide + ' ' + current_working_directory + 'Results/'+ job_id + '/' + job_id + '.Annotation.targets.txt' + ' |  awk \'$6==' + position + ' && $4==\"' + chromosome + '\"\''], shell = True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
                  out, err = get_annotation.communicate()
                  annotation_type = out.decode('UTF-8').strip().split('\t')[-1]
                  subprocess.call([put_header + 'LC_ALL=C fgrep ' + guide + ' ' + current_working_directory + 'Results/'+ job_id + '/' + job_id + file_to_grep + ' | awk \'$6==' + position + ' && $4==\"' + chromosome + '\" {print $0\"\\t' + annotation_type + '\"}\' > ' + cluster_grep_result], shell = True)
              else:
                  # print('qui cluster in grep')     #NOTE HEADER NON SALVATO
                  subprocess.call([put_header + 'LC_ALL=C fgrep ' + guide + ' ' + current_working_directory + 'Results/'+ job_id + '/' + job_id + file_to_grep + ' | awk \'$6==' + position + ' && $4==\"' + chromosome + '\"\' > ' + cluster_grep_result], shell = True)  #NOTE top1 will have sample and annotation, other targets will have '.'-> 18/03 all samples and annotation are already writter for all targets
              subprocess.Popen(['zip ' + cluster_grep_result.replace('.txt','.zip') + ' ' + cluster_grep_result], shell = True)
          final_list.append(
              html.Div(job_id + '.' + chromosome + '_' + position + '.' + guide ,style = {'display':'none'}, id = 'div-info-sumbyposition-targets')
          )

          scomposition_file = current_working_directory + 'Results/'+ job_id + '/' + job_id + '.' + chromosome + '_' + position + '.' + guide +'.scomposition.txt'
          file_to_grep = '.samples.annotation.txt'

          iupac_scomposition_visibility = {'display':'none'}
          if genome_type != 'ref':
              iupac_scomposition_visibility = {}
              if not os.path.exists(scomposition_file):    #Example    job_id.chr_pos.guide.scomposition.txt
                  subprocess.call(['LC_ALL=C fgrep ' + guide + ' ' + current_working_directory + 'Results/'+ job_id + '/' + job_id + file_to_grep + ' |  awk \'$6==' + position + ' && $4==\"' + chromosome + '\" && $13!=\"n\"\' > ' + scomposition_file], shell = True)

          final_list.append(html.P(
              [
                  html.P('List of all the configurations for the target in the selected position.', style = iupac_scomposition_visibility),
                  dcc.Checklist(
                      options = [{'label': 'Hide Reference Targets', 'value': 'hide-ref'}, {'label': 'Show only TOP1 Target', 'value': 'hide-cluster'}],
                      id='hide-reference-targets', value = value_hide_reference, style = style_hide_reference
                  ),
                  html.Div(
                      [
                          html.P('Generating download link, Please wait...', id = 'download-link-sumbyposition'),
                          dcc.Interval(interval = 5*1000, id = 'interval-sumbyposition')
                      ]

                  )
              ]
          )
          )

          cols_for_scomposition = cols.copy()
          cols_for_scomposition.append({"name": 'Samples', "id": 'Samples', 'type':'text', 'hideable':True})
          final_list.append(
              html.Div(
                  dash_table.DataTable(
                      id = 'table-scomposition-cluster',          #TABLE that represent scomposition of iupac of selected target, take rows from top_1.samples.txt
                      columns=cols_for_scomposition,
                      #data = df.to_dict('records'),
                      virtualization = True,
                      fixed_rows={ 'headers': True, 'data': 0 },
                      #fixed_columns = {'headers': True, 'data':1},
                      style_cell={'width': '150px'},
                      page_current=0,
                      page_size=PAGE_SIZE,
                      page_action='custom',
                      sort_action='custom',
                      sort_mode='multi',
                      sort_by=[],
                      filter_action='custom',
                      filter_query='',
                      style_table={
                          'max-height': '600px'
                          #'overflowY': 'scroll',
                      },
                      style_data_conditional=[
                          # {
                          #     'if': {
                          #             'filter_query': '{Variant Unique} eq y',
                          #             #'column_id' :'{#Bulge type}',
                          #             #'column_id' :'{Total}'
                          #         },
                          #         #'border-left': '5px solid rgba(255, 26, 26, 0.9)',
                          #         'background-color':'rgba(255, 0, 0,0.15)'#'rgb(255, 102, 102)'

                          #     },
                          {
                              'if': {
                                      'filter_query': '{Variant Unique} eq F',
                                      #'filter_query': '{Direction} eq +',
                                      #'column_id' :'Bulge Type'
                                  },
                                  #'border-left': '5px solid rgba(255, 26, 26, 0.9)',
                                  'background-color':'rgba(0, 0, 0,0.15)'#'rgb(255, 102, 102)'

                              }
                      ],
                      css= [{ 'selector': 'td.cell--selected, td.focused', 'rule': 'background-color: rgba(0, 0, 255,0.15) !important;' }, { 'selector': 'td.cell--selected *, td.focused *', 'rule': 'background-color: rgba(0, 0, 255,0.15) !important;'}],

                  ),
                  style = iupac_scomposition_visibility
              )
          )

          final_list.append(html.Hr())

          #Cluster Table
          final_list.append(
              'List of Targets found for the selected position. Other possible configurations of the target are listed in the table above, along with the corresponding samples list.', # The rows highlighted in red indicates that the target was found only in the genome with variants.',
          )
          final_list.append(
              html.Div(
                  dash_table.DataTable(
                      id='table-position-target',
                      columns=cols,
                      #data = df.to_dict('records'),
                      virtualization = True,
                      fixed_rows={ 'headers': True, 'data': 0 },
                      #fixed_columns = {'headers': True, 'data':1},
                      style_cell={'width': '150px'},
                      page_current=0,
                      page_size=PAGE_SIZE,
                      page_action='custom',
                      sort_action='custom',
                      sort_mode='multi',
                      sort_by=[],
                      filter_action='custom',
                      filter_query='',
                      style_table={
                          'max-height': '600px'
                          #'overflowY': 'scroll',
                      },
                      style_data_conditional=[
                          # {
                          #     'if': {
                          #             'filter_query': '{Variant Unique} eq y',
                          #             #'column_id' :'{#Bulge type}',
                          #             #'column_id' :'{Total}'
                          #         },
                          #         #'border-left': '5px solid rgba(255, 26, 26, 0.9)',
                          #         'background-color':'rgba(255, 0, 0,0.15)'#'rgb(255, 102, 102)'

                          #     },
                          {
                              'if': {
                                      'filter_query': '{Variant Unique} eq F',
                                      #'filter_query': '{Direction} eq +',
                                      #'column_id' :'Bulge Type'
                                  },
                                  #'border-left': '5px solid rgba(255, 26, 26, 0.9)',
                                  'background-color':'rgba(0, 0, 0,0.15)'#'rgb(255, 102, 102)'

                              }
                      ],
                      css= [{ 'selector': 'td.cell--selected, td.focused', 'rule': 'background-color: rgba(0, 0, 255,0.15) !important;' }, { 'selector': 'td.cell--selected *, td.focused *', 'rule': 'background-color: rgba(0, 0, 255,0.15) !important;'}],

                  ),
                  id = 'div-result-table',
              )
          )
          # final_list.append(html.Div('', id ='target-to-highlight'))
          return html.Div(final_list, style = {'margin':'1%'})

`def colorSelectedRow(sel_cel, all_guides)`

  Update the background color of the row of the main table when the user clicks on a cell. Also change to bold the 'Reference' and 'Enriched' labels.

  Args

  - [sel_cel] general-profile-table (selected_cells): list contaning the selected row of the main guide table
  - [all_guides] general-profile-table (data): list of all the rows that are currenty displayed in the main guide table

  Returns

  - general-profile-table (style_data_conditional): dictionary of the style for the main table
  Expand source code

      @app.callback(
          Output('general-profile-table', 'style_data_conditional'),
          [Input('general-profile-table', 'selected_cells')],
          [State('general-profile-table', 'data')]
      )
      def colorSelectedRow(sel_cel, all_guides):
          '''
          Update the background color of the row of the main table when the user clicks on a cell. Also change to bold the 'Reference' and 'Enriched'
          labels.

          ***Args***

          + [**sel_cel**] **general-profile-table** (*selected_cells*): list contaning the selected row of the main guide table
          + [**all_guides**] **general-profile-table** (*data*): list of all the rows that are currenty displayed in the main guide table

          ***Returns***

          + **general-profile-table** (*style_data_conditional*): dictionary of the style for the main table
          '''
          if sel_cel is None or not sel_cel or not all_guides:
              raise PreventUpdate
          guide = all_guides[int(sel_cel[0]['row'])]['Guide']
          #Change background color of all the rows that have the 'Guide' column equal to the selected guide (guides are unique)
          return [
              {
                  'if': {
                          'filter_query': '{Guide} eq "' + guide + '"'
                      },
                      'background-color':'rgba(0, 0, 255,0.15)'
                  },
                  {
                  'if': {
                          'column_id' :'Genome'
                      },
                      'font-weight':'bold',
                      'textAlign': 'center'
                  }
          ]

`def downloadLinkGuide(n, file_to_load, search)`
  Expand source code

      @app.callback(
          [Output('download-link-sumbyguide', 'children'),
          Output('interval-sumbyguide', 'disabled')],
          [Input('interval-sumbyguide','n_intervals')],
          [State('div-info-sumbyguide-targets','children'),
          State('url', 'search')]
      )
      def downloadLinkGuide(n, file_to_load, search): #file to load = job_id.RNA.1.0.guide
          if n is None:
              raise PreventUpdate
          job_id = search.split('=')[-1]
          file_to_load = file_to_load + '.zip'
          if os.path.exists(current_working_directory + 'Results/' + job_id + '/' + file_to_load):
              return html.A('Download zip', href=URL+'/data/' + job_id + '/' + file_to_load, target = '_blank' ), True

          return 'Generating download link, Please wait...', False

`def downloadLinkPosition(n, file_to_load, search)`
  Expand source code

      @app.callback(
          [Output('download-link-sumbyposition', 'children'),
          Output('interval-sumbyposition', 'disabled')],
          [Input('interval-sumbyposition','n_intervals')],
          [State('div-info-sumbyposition-targets','children'),
          State('url', 'search')]
      )
      def downloadLinkPosition(n, file_to_load, search): #file to load =
          if n is None:
              raise PreventUpdate
          job_id = search.split('=')[-1]
          file_to_load = file_to_load + '.zip'
          if os.path.exists(current_working_directory + 'Results/' + job_id + '/' + file_to_load):
              return html.A('Download zip', href=URL+'/data/' + job_id + '/' + file_to_load, target = '_blank' ), True

          return 'Generating download link, Please wait...', False

`def downloadLinkSample(n, file_to_load, search)`
  Expand source code

      @app.callback(
          [Output('download-link-sumbysample', 'children'),
          Output('interval-sumbysample', 'disabled')],
          [Input('interval-sumbysample','n_intervals')],
          [State('div-info-sumbysample-targets','children'),
          State('url', 'search')]
      )
      def downloadLinkSample(n, file_to_load, search): #file to load = job_id.HG001.guide
          if n is None:
              raise PreventUpdate
          job_id = search.split('=')[-1]
          file_to_load = file_to_load + '.zip'
          if os.path.exists(current_working_directory + 'Results/' + job_id + '/' + file_to_load):
              return html.A('Download zip', href=URL+'/data/' + job_id + '/' + file_to_load, target = '_blank' ), True

          return 'Generating download link, Please wait...', False

`def fileDialogAnnotation(n)`
  Expand source code

      @app.callback(
          Output('selected-annotationfile', 'children'),
          [Input('button-select-annotation','n_clicks')]
      )
      def fileDialogAnnotation(n):
          return 'Selected: ' + openDialog(n, 'F','annotations')

`def fileDialogPam(n)`
  Expand source code

      @app.callback(
          Output('selected-pamfile', 'children'),
          [Input('button-select-pam','n_clicks')]
      )
      def fileDialogPam(n):
          return 'Selected: ' + openDialog(n, 'F')

`def fileDialogRefGenome(n)`
  Expand source code

      @app.callback(
          Output('selected-referencegenome', 'children'),
          [Input('button-select-refgenome','n_clicks')]
      )
      def fileDialogRefGenome(n):
          return 'Selected: ' + openDialog(n, 'D','Genomes')

`def fileDialogSamplesID(n)`
  Expand source code

      @app.callback(
      Output('selected-sampleIDfile', 'children'),
      [Input('button-select-sampleID','n_clicks')]
      )
      def fileDialogSamplesID(n):
          return 'Selected: ' + openDialog(n, 'F','samplesID')

`def fileDialogUpdateAnnotation(n)`
  Expand source code

      @app.callback(
          [Output('label-new-annotation-selected', 'children'),
          Output('tooltip-label-new-annotation-selected', 'children')],
          [Input('button-choose-new-annotation', 'n_clicks')]
      )
      def fileDialogUpdateAnnotation(n):
          selected_file = openDialog(n, 'F')
          return 'Selected: ' + os.path.basename(selected_file), 'Full Path: ' + selected_file

`def fileDialogVCF(n)`
  Expand source code

      @app.callback(
          Output('selected-vcf', 'children'),
          [Input('button-select-vcf','n_clicks')]
      )
      def fileDialogVCF(n):
          return 'Selected: ' + openDialog(n, 'D')

`def filterPositionTable(nPrev, nNext, filter_q, n, search, sel_cel, all_guides, current_page, mms_bulge)`

  Filtering/changing page of the table in the Summary by Position tab.

  Args + [nPrev] prev-page-position (n_clicks_timestamp): int timestamp of last click on Previous Page button + [nNext] next-page-position (n_clicks_timestamp): int timestamp of last click on Next Page button + [filter_q] div-position-filter-query (children): string of the filter query + [n] button-filter-position (n_clicks_timestamp): int timestamp of the Apply Filter button + [search] url (search): string containing the job ID, eg '?job=QV99PN6XDL' + [sel_cel] general-profile-table (selected_cells): list contaning the selected row of the main guide table + [all_guides] general-profile-table (data): list of all the rows that are currenty displayed in the main guide table + [current_page] div-current-page-table-samples (children): string containing the current page of the table (eg '1/15') + [mms_bulge] div-mms-bulge-position (children): string containing the value of the max mismatch and bulge value (eg. '6-2')

  Returns

  - div-table-position (children): html.Table with filtering applied and/or next/previous page of data
  - div-current-page-table-position (children): string of the currently displayed page
  Expand source code

      @app.callback(
          [Output('div-table-position', 'children'),
          Output('div-current-page-table-position', 'children')],
          [Input('prev-page-position','n_clicks_timestamp'),
          Input('next-page-position', 'n_clicks_timestamp'),
          Input('div-position-filter-query', 'children')],
          [State('button-filter-position', 'n_clicks_timestamp'),
          State('url', 'search'),
          State('general-profile-table', 'selected_cells'),
          State('general-profile-table', 'data'),
          State('div-current-page-table-position', 'children'),
          State('div-mms-bulges-position', 'children')]
      )
      def filterPositionTable(nPrev, nNext, filter_q, n, search, sel_cel, all_guides, current_page, mms_bulge):
          '''
          Filtering/changing page of the table in the Summary by Position tab.

          ***Args***
          + [**nPrev**] **prev-page-position** (*n_clicks_timestamp*): int timestamp of last click on Previous Page button
          + [**nNext**] **next-page-position** (*n_clicks_timestamp*): int timestamp of last click on Next Page button
          + [**filter_q**] **div-position-filter-query** (*children*): string of the filter query
          + [**n**] **button-filter-position** (*n_clicks_timestamp*): int timestamp of the Apply Filter button
          + [**search**] **url** (*search*): string containing the job ID, eg '?job=QV99PN6XDL'
          + [**sel_cel**] **general-profile-table** (*selected_cells*): list contaning the selected row of the main guide table
          + [**all_guides**] **general-profile-table** (*data*): list of all the rows that are currenty displayed in the main guide table
          + [**current_page**] **div-current-page-table-samples** (*children*): string containing the current page of the table (eg '1/15')
          + [**mms_bulge**] **div-mms-bulge-position** (*children*): string containing the value of the max mismatch and bulge value (eg. '6-2')

          ***Returns***

          + **div-table-position** (*children*): html.Table with filtering applied and/or next/previous page of data
          + **div-current-page-table-position** (*children*): string of the currently displayed page
          '''

          if sel_cel is None:
              raise PreventUpdate
          if nPrev is None and nNext is None and n is None:
              raise PreventUpdate

          if nPrev is None:
              nPrev = 0
          if nNext is None:
              nNext = 0
          if n is None:
              n = 0

          filter_q = filter_q.split(',')
          chr = filter_q[0]
          if chr == 'None':
              chr = None
          pos_begin = filter_q[1]
          if pos_begin == 'None':
              pos_begin = None
          pos_end = filter_q[2]
          if pos_end == 'None':
              pos_end = None

          current_page = current_page.split('/')[0]
          current_page = int(current_page)
          mms = int(mms_bulge.split('-')[0])
          max_bulges = int(mms_bulge.split('-')[1])
          btn_position_section = []
          btn_position_section.append(n)
          btn_position_section.append(nPrev)
          btn_position_section.append(nNext)
          job_id = search.split('=')[-1]
          job_directory = current_working_directory + 'Results/' + job_id + '/'
          guide = all_guides[int(sel_cel[0]['row'])]['Guide']
          if max(btn_position_section) == n:              #Last button pressed is filtering, return the first page of the filtered table
              if pos_begin is None or pos_begin == '':
                  pos_begin = 0
              if pos_end == '':
                  pos_end = None
              if pos_end:
                  if int(pos_end) < int(pos_begin):
                      pos_end = None
              df = pd.read_csv(job_directory + job_id + '.summary_by_position.' + guide +'.txt', sep = '\t')

              df.rename(columns = {'#Chromosome':'Chromosome'}, inplace = True)
              more_info_col = []
              for i in range(df.shape[0]):
                  more_info_col.append('Show Targets')
              df[''] = more_info_col
              if chr is None or chr == '':
                  max_page = len(df.index)
                  max_page = math.floor(max_page / 10) + 1
                  return generate_table_position(df, 'table-position', 1, mms, max_bulges,guide, job_id ), '1/' + str(max_page)
              if pos_end is None:
                  df.drop(df[(df['Chromosome'] != chr) | ((df['Chromosome'] == chr) & (df['Position'] < int(pos_begin)) )].index , inplace = True)
              else:
                  df.drop(df[(df['Chromosome'] != chr) | ((df['Chromosome'] == chr) & (df['Position'] < int(pos_begin)) | (df['Position'] > int(pos_end)))].index , inplace = True)
              max_page = len(df.index)
              max_page = math.floor(max_page / 10) + 1
              return generate_table_position(df, 'table-position', 1, mms, max_bulges,guide, job_id ), '1/'+ str(max_page)
          else:

              if max(btn_position_section) == nNext:
                  current_page = current_page + 1
                  if chr:
                      if pos_begin is None or pos_begin == '':
                          pos_begin = 0
                      if pos_end == '':
                          pos_end = None
                      if pos_end:
                          if int(pos_end) < int(pos_begin):
                              pos_end = None
                      df = pd.read_csv(job_directory + job_id + '.summary_by_position.' + guide +'.txt', sep = '\t')
                  else:
                      df = pd.read_csv(job_directory + job_id + '.summary_by_position.' + guide +'.txt', sep = '\t')#, nrows = current_page * 10)
                  df.rename(columns = {'#Chromosome':'Chromosome'}, inplace = True)
                  more_info_col = []
                  for i in range(df.shape[0]):
                      more_info_col.append('Show Targets')
                  df[''] = more_info_col
                  if chr:
                      if pos_end is None:
                          df.drop(df[(df['Chromosome'] != chr) | ((df['Chromosome'] == chr) & (df['Position'] < int(pos_begin)) )].index , inplace = True)
                      else:
                          df.drop(df[(df['Chromosome'] != chr) | ((df['Chromosome'] == chr) & (df['Position'] < int(pos_begin)) | (df['Position'] > int(pos_end)))].index , inplace = True)
                  if ((current_page - 1) * 10) > len(df):
                      current_page = current_page -1
                      if current_page < 1:
                          current_page = 1
                  max_page = len(df.index)
                  max_page = math.floor(max_page / 10) + 1
                  return generate_table_position(df, 'table-position', current_page, mms, max_bulges,guide, job_id ), str(current_page) + '/' + str(max_page)
              else:                       #Go to previous page
                  current_page = current_page - 1
                  if current_page < 1:
                      current_page = 1

                  if chr:
                      if pos_begin is None or pos_begin == '':
                          pos_begin = 0
                      if pos_end == '':
                          pos_end = None
                      if pos_end:
                          if int(pos_end) < int(pos_begin):
                              pos_end = None
                      df = pd.read_csv(job_directory + job_id + '.summary_by_position.' + guide +'.txt', sep = '\t')
                  else:
                      df = pd.read_csv(job_directory + job_id + '.summary_by_position.' + guide +'.txt', sep = '\t')#, nrows = current_page * 10)
                  df.rename(columns = {'#Chromosome':'Chromosome'}, inplace = True)
                  more_info_col = []
                  for i in range(df.shape[0]):
                      more_info_col.append('Show Targets')
                  df[''] = more_info_col
                  if chr:
                      if pos_end is None:
                          df.drop(df[(df['Chromosome'] != chr) | ((df['Chromosome'] == chr) & (df['Position'] < int(pos_begin)) )].index , inplace = True)
                      else:
                          df.drop(df[(df['Chromosome'] != chr) | ((df['Chromosome'] == chr) & (df['Position'] < int(pos_begin)) | (df['Position'] > int(pos_end)))].index , inplace = True)

                  max_page = len(df.index)
                  max_page = math.floor(max_page / 10) + 1
                  return generate_table_position(df, 'table-position', current_page, mms, max_bulges,guide, job_id ), str(current_page) + '/' + str(max_page)

`def filterSampleTable(nPrev, nNext, filter_q, n, search, sel_cel, all_guides, current_page)`

  Filtering/changing page of the table in the Summary by Sample tab.

  Args + [nPrev] prev-page-sample (n_clicks_timestamp): int timestamp of last click on Previous Page button + [nNext] next-page-sample (n_clicks_timestamp): int timestamp of last click on Next Page button + [filter_q] div-sample-filter-query (children): string of the filter query + [n] button-filter-population-sample (n_clicks_timestamp): int timestamp of the Apply Filter button + [search] url (search): string containing the job ID, eg '?job=QV99PN6XDL' + [sel_cel] general-profile-table (selected_cells): list contaning the selected row of the main guide table + [all_guides] general-profile-table (data): list of all the rows that are currenty displayed in the main guide table + [current_page] div-current-page-table-samples (children): string containing the current page of the table (eg '1/15')

  Returns

  - div-table-samples (children): html.Table with filtering applied and/or next/previous page of data
  - div-current-page-table-samples (children): string of the currently displayed page
  Expand source code

      @app.callback(
          [Output('div-table-samples', 'children'),
          Output('div-current-page-table-samples', 'children')],
          [Input('prev-page-sample', 'n_clicks_timestamp'),
          Input('next-page-sample', 'n_clicks_timestamp'),
          Input('div-sample-filter-query', 'children')],
          [State('button-filter-population-sample', 'n_clicks_timestamp'),
          State('url', 'search'),
          State('general-profile-table', 'selected_cells'),
          State('general-profile-table', 'data'),
          State('div-current-page-table-samples', 'children')]
      )
      def filterSampleTable( nPrev, nNext, filter_q, n, search, sel_cel, all_guides, current_page):
          '''
          Filtering/changing page of the table in the Summary by Sample tab.

          ***Args***
          + [**nPrev**] **prev-page-sample** (*n_clicks_timestamp*): int timestamp of last click on Previous Page button
          + [**nNext**] **next-page-sample** (*n_clicks_timestamp*): int timestamp of last click on Next Page button
          + [**filter_q**] **div-sample-filter-query** (*children*): string of the filter query
          + [**n**] **button-filter-population-sample** (*n_clicks_timestamp*): int timestamp of the Apply Filter button
          + [**search**] **url** (*search*): string containing the job ID, eg '?job=QV99PN6XDL'
          + [**sel_cel**] **general-profile-table** (*selected_cells*): list contaning the selected row of the main guide table
          + [**all_guides**] **general-profile-table** (*data*): list of all the rows that are currenty displayed in the main guide table
          + [**current_page**] **div-current-page-table-samples** (*children*): string containing the current page of the table (eg '1/15')

          ***Returns***

          + **div-table-samples** (*children*): html.Table with filtering applied and/or next/previous page of data
          + **div-current-page-table-samples** (*children*): string of the currently displayed page
          '''
          if sel_cel is None:
              raise PreventUpdate
          if nPrev is None and nNext is None and n is None:
              raise PreventUpdate

          if nPrev is None:
              nPrev = 0
          if nNext is None:
              nNext = 0
          if n is None:
              n = 0

          sup_pop = filter_q.split(',')[0]
          pop = filter_q.split(',')[1]
          samp = filter_q.split(',')[2]
          if sup_pop == 'None':
              sup_pop = None
          if pop == 'None':
              pop = None
          if samp == 'None' or samp == 'NONE':
              samp = None
          current_page = current_page.split('/')[0]
          current_page = int(current_page)
          btn_sample_section = []
          btn_sample_section.append(n)
          btn_sample_section.append(nPrev)
          btn_sample_section.append(nNext)
          job_id = search.split('=')[-1]
          job_directory = current_working_directory + 'Results/' + job_id + '/'
          population_1000gp = associateSample.loadSampleAssociation(job_directory + 'sampleID.txt')[2]
          with open(current_working_directory + 'Results/' + job_id + '/Params.txt') as p:
              all_params = p.read()
              genome_type_f = (next(s for s in all_params.split('\n') if 'Genome_selected' in s)).split('\t')[-1]
              ref_comp = (next(s for s in all_params.split('\n') if 'Ref_comp' in s)).split('\t')[-1]

          genome_type = 'ref'
          if '+' in genome_type_f:
              genome_type = 'var'
          if 'True' in ref_comp:
              genome_type = 'both'

          guide = all_guides[int(sel_cel[0]['row'])]['Guide']
          if genome_type == 'both':
              col_names_sample = ['Sample', 'Gender', 'Population', 'Super Population',  'Targets in Reference', 'Targets in Enriched', 'Targets in Population', 'Targets in Super Population', 'PAM Creation', 'Class']
          else:
              col_names_sample = ['Sample', 'Gender', 'Population', 'Super Population',  'Targets in Reference', 'Targets in Enriched', 'Targets in Population', 'Targets in Super Population', 'PAM Creation', 'Class']
          if max(btn_sample_section) == n:              #Last button pressed is filtering, return the first page of the filtered table
              if genome_type == 'both':
                  df = pd.read_csv(job_directory + job_id + '.summary_by_samples.' + guide + '.txt', sep = '\t', names = col_names_sample, skiprows = 1)
                  df = df.sort_values('Targets in Enriched', ascending = False)
                  df.drop(['Targets in Reference'], axis = 1, inplace = True)
              else:
                  df = pd.read_csv(job_directory + job_id + '.summary_by_samples.' + guide + '.txt', sep = '\t', names = col_names_sample, skiprows = 1)
                  df = df.sort_values('Targets in Reference', ascending = False)
                  df.drop(['Targets in Reference'], axis = 1, inplace = True)
                  df.drop(['Class'], axis = 1, inplace = True)
              more_info_col = []
              for i in range(df.shape[0]):
                  more_info_col.append('Show Targets')
              df[''] = more_info_col
              if (sup_pop is None or sup_pop == '') and (pop is None or pop == '') and (samp is None or samp == ''):   #No filter value selected
                  max_page = len(df.index)
                  max_page = math.floor(max_page / 10) + 1
                  return generate_table_samples(df, 'table-samples', 1, guide, job_id ), '1/' + str(max_page)
              if samp is None or samp == '':
                  if pop is None or pop == '':
                      df.drop(df[(~(df['Population'].isin(population_1000gp[sup_pop])))].index , inplace = True)
                  else:
                      df.drop(df[(df['Population'] != pop)].index , inplace = True)
              else:
                  df.drop(df[(df['Sample'] != samp)].index , inplace = True)
              max_page = len(df.index)
              max_page = math.floor(max_page / 10) + 1
              return generate_table_samples(df, 'table-samples', 1, guide, job_id ), '1/' + str(max_page)
          else:
              if max(btn_sample_section) == nNext:
                  current_page = current_page + 1
                  if genome_type == 'both':
                      df = pd.read_csv(job_directory + job_id + '.summary_by_samples.' + guide + '.txt', sep = '\t', names = col_names_sample, skiprows = 1)
                      df = df.sort_values('Targets in Enriched', ascending = False)
                      df.drop(['Targets in Reference'], axis = 1, inplace = True)
                  else:
                      df = pd.read_csv(job_directory + job_id + '.summary_by_samples.' + guide + '.txt', sep = '\t', names = col_names_sample, skiprows = 1)
                      df = df.sort_values('Targets in Reference', ascending = False)
                      df.drop(['Targets in Reference'], axis = 1, inplace = True)
                      df.drop(['Class'], axis = 1, inplace = True)
                  more_info_col = []
                  for i in range(df.shape[0]):
                      more_info_col.append('Show Targets')
                  df[''] = more_info_col
                  #Active filter
                  if pop or sup_pop or samp:
                      if samp is None or samp == '':
                          if pop is None or pop == '':
                              df.drop(df[(~(df['Population'].isin(population_1000gp[sup_pop])))].index , inplace = True)
                          else:
                              df.drop(df[(df['Population'] != pop)].index , inplace = True)
                      else:
                          df.drop(df[(df['Sample'] != samp)].index , inplace = True)

                  if ((current_page - 1) * 10) > len(df):
                      current_page = current_page -1
                      if current_page < 1:
                          current_page = 1
                  max_page = len(df.index)
                  max_page = math.floor(max_page / 10) + 1
                  return generate_table_samples(df, 'table-samples', current_page, guide, job_id ), str(current_page) + '/' + str(max_page)

              else:   #Go to previous page
                  current_page = current_page - 1
                  if current_page < 1:
                      current_page = 1
                  if genome_type == 'both':
                      df = pd.read_csv(job_directory + job_id + '.summary_by_samples.' + guide + '.txt', sep = '\t', names = col_names_sample, skiprows = 1)
                      df = df.sort_values('Targets in Enriched', ascending = False)
                      df.drop(['Targets in Reference'], axis = 1, inplace = True)
                  else:
                      df = pd.read_csv(job_directory + job_id + '.summary_by_samples.' + guide + '.txt', sep = '\t', names = col_names_sample, skiprows = 1)
                      df = df.sort_values('Targets in Reference', ascending = False)
                      df.drop(['Targets in Reference'], axis = 1, inplace = True)
                      df.drop(['Class'], axis = 1, inplace = True)
                  more_info_col = []
                  for i in range(df.shape[0]):
                      more_info_col.append('Show Targets')
                  df[''] = more_info_col
                  if pop or sup_pop or samp:
                      if samp is None or samp == '':
                          if pop is None or pop == '':
                              df.drop(df[(~(df['Population'].isin(population_1000gp[sup_pop])))].index , inplace = True)
                          else:
                              df.drop(df[(df['Population'] != pop)].index , inplace = True)
                      else:
                          df.drop(df[(df['Sample'] != samp)].index , inplace = True)
                  max_page = len(df.index)
                  max_page = math.floor(max_page / 10) + 1
                  return generate_table_samples(df, 'table-samples', current_page, guide, job_id ), str(current_page) + '/' + str(max_page)
          raise PreventUpdate

`def generate_table(dataframe, id_table, genome_type, guide='', job_id='', max_rows=2600)`

  Generates an html.Table for the Summary by Guide section.

  Args

  - dataframe: dataframe containing the data to display
  - id_table: string that identifies the ID of the new generated html.Table
  - genome_type: string containing the type of the search ('ref', 'var' or 'both')
  - guide: string of the selected guide
  - job_id: string of the current jobID
  - max_rows: int of the max number of rows of the table

  Returns

  - html.Table() containing data for the Summary by Guide section
  Expand source code

      def generate_table(dataframe, id_table, genome_type, guide='', job_id='', max_rows=2600):
          '''
          Generates an html.Table for the Summary by Guide section.

          ***Args***

          + **dataframe**: dataframe containing the data to display
          + **id_table**: string that identifies the ID of the new generated html.Table
          + **genome_type**: string containing the type of the search ('ref', 'var' or 'both')
          + **guide**: string of the selected guide
          + **job_id**: string of the current jobID
          + **max_rows**: int of the max number of rows of the table

          ***Returns***

          + html.Table() containing data for the Summary by Guide section
          '''
          if genome_type == 'both':
              header = [html.Tr([
                  html.Th('Bulge Type', rowSpan = '2', style = {'vertical-align':'middle', 'text-align':'center'}),
                  html.Th('Mismatches', rowSpan = '2', style = {'vertical-align':'middle', 'text-align':'center'}),
                  html.Th('Bulge Size', rowSpan = '2', style = {'vertical-align':'middle', 'text-align':'center'}),
                  html.Th('Targets found in Genome', colSpan = str(3), style = {'vertical-align':'middle', 'text-align':'center'}),
                  html.Th('PAM Creation', rowSpan = '2', style = {'vertical-align':'middle', 'text-align':'center'}),
                  html.Th('', rowSpan = '2'),
                  ])
              ]
          # 'Bulge Type' 'Mismatches' 'Bulge Size' 'Targets in Reference' 'Targets in Enriched' 'Combined' 'PAM Creation' ''

              header.append(html.Tr([html.Th(x, style = {'vertical-align':'middle', 'text-align':'center'}) for x in ['Reference', 'Enriched','Combined']]))
          else:
              header = [html.Tr([html.Th(col, style = {'vertical-align':'middle', 'text-align':'center'}) for col in dataframe.columns])]
          return html.Table(
              # Header
              # [html.Tr([html.Th(col) if col != 'Targets in Enriched' else html.Th(html.Abbr(col, title = 'Counting of targets that are generated by the insertion of a IUPAC nucleotide of a sample',
              # style = {'text-decoration':'underline'})) for col in dataframe.columns])] +
              # [html.Tr([html.Th(col, rowSpan = 2) if 'Targets' not in col or 'Combined' not in col else html.Th('Targets') for col in dataframe.columns])] +
              header  +
              # Body
              [html.Tr([
                  html.Td(html.A(dataframe.iloc[i][col],  href = 'result?job=' + job_id + '#' + guide +'new' + dataframe.iloc[i]['Bulge Type'] + str(dataframe.iloc[i]['Bulge Size']) + str(dataframe.iloc[i]['Mismatches']) , target = '_blank' ), style = {'vertical-align':'middle', 'text-align':'center'}) if col == '' else  html.Td(dataframe.iloc[i][col],style = {'vertical-align':'middle', 'text-align':'center'}) for col in dataframe.columns
              ]) for i in range(min(len(dataframe), max_rows))],
              style = {'display':'inline-block'},
              id = id_table
          )

`def generate_table_position(dataframe, id_table, page, mms, bulges, guide='', job_id='', max_rows=10)`

  Generates an html.Table for the Summary by Position section.

  Args

  - dataframe: dataframe containing the data to display
  - id_table: string that identifies the ID of the new generated html.Table
  - page: int containing the current page of the table
  - mms: int value of the max allowed mismatched
  - bulges: int value of the max allowed bulges
  - guide: string of the selected guide
  - job_id: string of the current jobID
  - max_rows: int of the max number of rows of the table

  Returns

  - html.Table() containing data for the Summary by Position section
  Expand source code

      def generate_table_position(dataframe, id_table, page, mms, bulges, guide = '', job_id = '', max_rows = 10): #NOTE v2 della tabella posizioni       #TODO modifica layout righe per allinearle
          '''
          Generates an html.Table for the Summary by Position section.

          ***Args***

          + **dataframe**: dataframe containing the data to display
          + **id_table**: string that identifies the ID of the new generated html.Table
          + **page**: int containing the current page of the table
          + **mms**: int value of the max allowed mismatched
          + **bulges**: int value of the max allowed bulges
          + **guide**: string of the selected guide
          + **job_id**: string of the current jobID
          + **max_rows**: int of the max number of rows of the table

          ***Returns***

          + html.Table() containing data for the Summary by Position section
          '''
          rows_remaining = len(dataframe) - (page - 1) * max_rows
          header = [html.Tr([
              html.Th('Chromosome', rowSpan = '2', style = {'vertical-align':'middle', 'text-align':'center'}),
              html.Th('Position', rowSpan = '2', style = {'vertical-align':'middle', 'text-align':'center'}),
              html.Th('Best Target', rowSpan = '2', style = {'vertical-align':'middle', 'text-align':'center'}),
              html.Th('Min Mismatch', rowSpan = '2', style = {'vertical-align':'middle', 'text-align':'center'}),
              html.Th('Min Bulge', rowSpan = '2', style = {'vertical-align':'middle', 'text-align':'center'}),
              html.Th('Bulge', rowSpan = '2', style = {'vertical-align':'middle', 'text-align':'center'}),
              html.Th('Targets in Cluster by Mismatch Value', colSpan = str(mms +1), style = {'vertical-align':'middle', 'text-align':'center'}),
              html.Th('', rowSpan = '2'),
              ])
          ]
          mms_header = []
          for mm in range (mms +1):
              mms_header.append(html.Th(str(mm) + ' MM', style = {'vertical-align':'middle', 'text-align':'center'}))
          header.append(html.Tr(mms_header))

          data = []
          for i in range(min(rows_remaining, max_rows)):
              first_cells = [
                  html.Td(dataframe.iloc[i + (page - 1)*max_rows]['Chromosome'], rowSpan = str(bulges +1),  style = {'vertical-align':'middle', 'text-align':'center'}),
                  html.Td(dataframe.iloc[i + (page - 1)*max_rows]['Position'], rowSpan = str(bulges +1),  style = {'vertical-align':'middle', 'text-align':'center'}),
                  html.Td(dataframe.iloc[i + (page - 1)*max_rows]['Best Target'], rowSpan = str(bulges+1),  style = {'vertical-align':'middle', 'text-align':'center'}),
                  html.Td(dataframe.iloc[i + (page - 1)*max_rows]['Min Mismatch'], rowSpan = str(bulges+1),  style = {'vertical-align':'middle', 'text-align':'center'}),
                  html.Td(dataframe.iloc[i + (page - 1)*max_rows]['Min Bulge'], rowSpan = str(bulges+1),  style = {'vertical-align':'middle', 'text-align':'center'}),
                  html.Th('0 Bulge', style = {'vertical-align':'middle', 'text-align':'center', 'padding-left':'0'})
              ]

              mm_cells = [html.Td(dataframe.iloc[i + (page - 1)*max_rows][col], style = {'vertical-align':'middle', 'text-align':'center'}) for col in dataframe.columns[5:5+mms+1]]
              data.append(html.Tr(first_cells + mm_cells + [html.Td(
                      html.A('Show Targets',  href = 'result?job=' + job_id + '#' + guide +'-Pos-' + str(dataframe.iloc[i + (page - 1)*max_rows]['Chromosome']) + '-' +  str(dataframe.iloc[i + (page - 1)*max_rows]['Position']) , target = '_blank'),
                      rowSpan = str(bulges+1), style = {'vertical-align':'middle', 'text-align':'center'})
                  ]))
              for b in range (bulges):
                  data.append(
                      html.Tr(
                          [html.Th(str(b +1) + ' Bulge', style = {'vertical-align':'middle', 'text-align':'center'} )]
                          +
                          [html.Td(dataframe.iloc[i + (page - 1)*max_rows][col]) for col in dataframe.columns[5 + (b +1) *(mms +1) : 5 + (b +1) *(mms+1) + mms +1]]
                      )
                  )

          return html.Table(header + data, style = {'display':'inline-block'}, id = id_table)

`def generate_table_results(dataframe, page, max_rows=10)`

  Generate table for History page

  Expand source code

      def generate_table_results(dataframe, page, max_rows = 10):
          '''
          Generate table for History page
          '''
          fl = []
          rows_remaining = len(dataframe) - (page - 1) * max_rows
          header = html.Thead(
                  html.Tr([html.Th(col,style = {'vertical-align':'middle', 'text-align':'center'}) if col != 'Load' and col != 'Delete' else html.Th('',style = {'vertical-align':'middle', 'text-align':'center'})  for col in dataframe.columns])
              )
          body_history = []
          add_button = 0
          for i in range(min(rows_remaining, max_rows)):
              add_button += 1
              row_hist = []
              for col in dataframe.columns:
                  if col == 'Load':
                      row_hist.append(
                              html.Td(html.A('Load', target = '_blank', href = dataframe.iloc[i + (page - 1)*max_rows][col]), style = {'vertical-align':'middle', 'text-align':'center'})
                      )
                  elif col == 'Delete':
                      row_hist.append(
                          html.Td(html.Button('Delete', id = 'button-delete-history-'+str(i), **{'data-jobid':dataframe.iloc[i + (page - 1)*max_rows]['Job ID']}),style = {'vertical-align':'middle', 'text-align':'center'})
                      )
                  else:
                      row_hist.append(
                              html.Td(dataframe.iloc[i + (page - 1)*max_rows][col], style = {'vertical-align':'middle', 'text-align':'center'})
                          )
              body_history.append(html.Tr(row_hist))
          fl.append(
              html.Table([
                  header,
                  html.Tbody(body_history)
              ], style = {'display':'inline-block'},)
          )

          for i in range(add_button, 10): #Add hidden buttons for callback removeJobId compatibility
              fl.append(html.Button(
                  str(i), id = 'button-delete-history-'+str(i),**{'data-jobid':'None'}, style = {'display':'none'}
                  ))
          return fl

`def generate_table_samples(dataframe, id_table, page, guide='', job_id='', max_rows=10)`

  Generates an html.Table for the Summary by Sample section.

  Args

  - dataframe: dataframe containing the data to display
  - id_table: string that identifies the ID of the new generated html.Table
  - page: int containing the current page of the table
  - guide: string of the selected guide
  - job_id: string of the current jobID
  - max_rows: int of the max number of rows of the table

  Returns

  - html.Table() containing data for the Summary by Sample section
  Expand source code

      def generate_table_samples(dataframe, id_table, page ,guide='', job_id='', max_rows=10):
          '''
          Generates an html.Table for the Summary by Sample section.

          ***Args***

          + **dataframe**: dataframe containing the data to display
          + **id_table**: string that identifies the ID of the new generated html.Table
          + **page**: int containing the current page of the table
          + **guide**: string of the selected guide
          + **job_id**: string of the current jobID
          + **max_rows**: int of the max number of rows of the table

          ***Returns***

          + html.Table() containing data for the Summary by Sample section
          '''
          rows_remaining = len(dataframe) - (page - 1) * max_rows
          return html.Table(
              # Header
              # [html.Tr([html.Th(col) if col != 'Targets in Enriched'  else html.Th(html.Abbr(col, title = 'Counting of targets that are generated by the insertion of a IUPAC nucleotide of a sample',
              # style = {'text-decoration':'underline'})) for col in dataframe.columns]) ] +
              [html.Tr([html.Th(col) for col in dataframe.columns]) ] +
              # Body
              [html.Tr([
                  html.Td(html.A(dataframe.iloc[i + (page - 1)*max_rows][col],  href = 'result?job=' + job_id + '#' + guide +'-Sample-' + dataframe.iloc[i  + (page - 1)*max_rows]['Sample'] , target = '_blank' )) if col == '' else  html.Td(dataframe.iloc[i + (page - 1)*max_rows][col]) for col in dataframe.columns
              ]) for i in range(min(rows_remaining, max_rows))],
              style = {'display':'inline-block'},
              id = id_table
          )

`def get_results()`

  Get a dataframe of the Results directory

  Expand source code

      def get_results():
          '''
          Get a dataframe of the Results directory
          '''
          results_dirs = [join(current_working_directory + '/Results/', f) for f in listdir(current_working_directory + '/Results/') if isdir(join(current_working_directory + '/Results/', f)) and isfile(current_working_directory + '/Results/' + f + '/Params.txt')]
          results_dirs.sort(key = os.path.getctime)   #Sorted older first
          results_dirs = [os.path.basename(f) for f in results_dirs]
          col = ['Job ID', 'Genome', 'PAM', 'Mismatches', 'DNA Bulges', 'RNA Bulges', 'Gecko Comparison', 'Reference Comparison', 'Date', 'Load']
          a = pd.DataFrame(columns = col)
          for job in results_dirs:
              if os.path.exists(current_working_directory + '/Results/' + job + '/Params.txt'):
                  with open(current_working_directory  + '/Results/' + job + '/Params.txt') as p:
                      all_params = p.read()
                      mms = (next(s for s in all_params.split('\n') if 'Mismatches' in s)).split('\t')[-1]
                      genome_selected = (next(s for s in all_params.split('\n') if 'Genome_selected' in s)).split('\t')[-1]
                      genome_selected = genome_selected.replace('_',' ')
                      if os.path.exists(current_working_directory + '/Results/' + job + '/log.txt'):
                          with open(current_working_directory  + '/Results/' + job + '/log.txt') as lo:
                              all_log = lo.read()
                          job_start = (next(s for s in all_log.split('\n') if 'Job\tStart' in s)).split('\t')[-1]
                          try:
                              job_end = (next(s for s in all_log.split('\n') if 'Job\tDone' in s)).split('\t')[-1]
                          except:
                              link_load = URL + '/load?job=' + job
                          else:
                              link_load = URL + '/result?job=' + job
                      else:
                          job_start = 'n/a'
                          link_load = URL + '/load?job=' + job
                      dna = (next(s for s in all_params.split('\n') if 'DNA' in s)).split('\t')[-1]
                      rna = (next(s for s in all_params.split('\n') if 'RNA' in s)).split('\t')[-1]
                      pam = (next(s for s in all_params.split('\n') if 'Pam' in s)).split('\t')[-1]
                      gecko = (next(s for s in all_params.split('\n') if 'Gecko' in s)).split('\t')[-1]
                      if gecko == 'True':
                          gecko = 'Yes'
                      else:
                          gecko = 'No'
                      comparison = (next(s for s in all_params.split('\n') if 'Ref_comp' in s)).split('\t')[-1]
                      if comparison == 'True':
                          comparison = 'Yes'
                      else:
                          comparison = 'No'
                      if os.path.exists(current_working_directory + '/Results/' + job + '/guides.txt'):
                          with open(current_working_directory  + '/Results/' + job + '/guides.txt') as g:
                              n_guides = str(len(g.read().strip().split('\n')))
                      else:
                          n_guides ='n/a'

                      a = a.append({'Job ID':job, 'Genome':genome_selected, 'Mismatches':mms, 'DNA Bulges':dna,
                      'RNA Bulges':rna, 'PAM':pam,'Gecko Comparison':gecko,'Reference Comparison':comparison, 'Date':job_start, 'Load': link_load, 'Delete':''}, ignore_index = True)
          a = a.sort_values(['Mismatches','DNA Bulges','RNA Bulges'],ascending = [True, True, True])
          return a

`def global_store(value)`

  Caching of files, mainly the ones for the 'Show Targets' tables, to improve loading speed. NOTE that if two files have the same name, then the generated cache will be the same, so a regular cleaning of the 'Cache' directory is mandatory.

  Args

  - value: string of the job ID to load

  Returns

  - df: dataframe for the 'Show Targets' table
  Expand source code

      @cache.memoize()
      def global_store(value):
          '''
          Caching of files, mainly the ones for the 'Show Targets' tables, to improve loading speed. NOTE that if two files have the same name, then the
          generated cache will be the same, so a regular cleaning of the 'Cache' directory is mandatory.

          ***Args***

          + **value**: string of the job ID to load

          ***Returns***

          + **df**: dataframe for the 'Show Targets' table
          '''
          if value is None:
              return ''
          target = [f for f in listdir(current_working_directory + 'Results/' + value) if isfile(join(current_working_directory + 'Results/'+value, f)) and f.endswith('scores.txt') ]
          if not target:
              target = [f for f in listdir(current_working_directory + 'Results/' + value) if isfile(join(current_working_directory + 'Results/'+value, f)) and f.endswith('targets.txt') ]

          df = pd.read_csv(current_working_directory + 'Results/' +value + '/' + target[0], sep = '\t')
          df.rename(columns = {"#Bulge type":'BulgeType', '#Bulge_type':'BulgeType','Bulge Size': 'BulgeSize', 'Bulge_Size': 'BulgeSize', 'Doench 2016':'Doench2016','Doench_2016':'Doench2016'}, inplace = True)
          return df

`def global_store_general(path_file_to_load)`

  Caching dei file targets per una miglior performance di visualizzazione

  Expand source code

      @cache.memoize()
      def global_store_general(path_file_to_load):
          '''
          Caching dei file targets per una miglior performance di visualizzazione
          '''
          if 'scomposition' in path_file_to_load:
              rows_to_skip = 0
          else:
              rows_to_skip = 0        #Skip header
          if path_file_to_load is None:
              return ''
          if os.path.getsize(path_file_to_load) > 0:
              df = pd.read_csv( path_file_to_load , sep = '\t', header = None, skiprows = rows_to_skip)
          else:
              df = None
          #df.rename(columns = {"#Bulge type":'Bulge Type', "#Bulge_type":'Bulge Type', 'Bulge_Size':'Bulge Size'}, inplace = True)
          return df

`def global_store_subset(value, bulge_t, bulge_s, mms, guide)`

  Caching of files for the subset table in the 'Show Targets' link of the Summary by Guide tabe, to improve loading speed. NOTE that if two files have the same name, then the generated cache will be the same, so a regular cleaning of the 'Cache' directory is mandatory.

  Args

  - value: string of the job ID to load
  - bulge_t: string of the type of bulge selected
  - bulge_s: string of the number of bulges selected
  - mms: string of the number of mismatches selected
  - guide: string of the selected guide

  Returns

  - df: dataframe for the subset table of the 'Show Targets' table of the Summary by Guide
  Expand source code

      @cache.memoize()
      def global_store_subset(value, bulge_t, bulge_s, mms, guide):
          '''
          Caching of files for the subset table in the 'Show Targets' link of the Summary by Guide tabe, to improve loading speed. NOTE that if two
          files have the same name, then the generated cache will be the same, so a regular cleaning of the 'Cache' directory is mandatory.

          ***Args***

          + **value**: string of the job ID to load
          + **bulge_t**: string of the type of bulge selected
          + **bulge_s**: string of the number of bulges selected
          + **mms**: string of the number of mismatches selected
          + **guide**: string of the selected guide

          ***Returns***

          + **df**: dataframe for the subset table of the 'Show Targets' table of the Summary by Guide
          '''
          if value is None:
              return ''
          #Skiprows = 1 to skip header of file
          df = pd.read_csv( current_working_directory + 'Results/' + value + '/' + value + '.' + bulge_t + '.' + bulge_s + '.' + mms + '.' + guide +'.txt', sep = '\t', header = None) #, skiprows = 1)
          return df

`def guidePagev3(job_id, hash)`

  Creates the layout for the 'Show Targets' page of the Summary by Guide tab.

  Args

  - job_id: string of the jobID
  - hash: string containing the informations about the selected 'Show Targets' row, eg '#CCATCGGTGGCCGTTTGCCCNNNnewDNA10'

  Returns

  - list of Dash Components containing the layout of the page
  Expand source code

      def guidePagev3(job_id, hash):
          '''
          Creates the layout for the 'Show Targets' page of the Summary by Guide tab.

          ***Args***

          + **job_id**: string of the jobID
          + **hash**: string containing the informations about the selected 'Show Targets' row, eg '#CCATCGGTGGCCGTTTGCCCNNNnewDNA10'

          ***Returns***

          + list of Dash Components containing the layout of the page
          '''
          guide = hash[:hash.find('new')]
          mms = hash[-1:]
          bulge_s = hash[-2:-1]
          if 'DNA' in hash:
              bulge_t = 'DNA'
          elif 'RNA' in hash:
              bulge_t = 'RNA'
          else:
              bulge_t = 'X'
          add_header = ' - Mismatches ' + str(mms)
          if bulge_t != 'X':
              add_header += ' - ' + str(bulge_t) + ' ' + str(bulge_s)
          value = job_id
          if (not isdir(current_working_directory + 'Results/' + job_id)):
              return html.Div(dbc.Alert("The selected result does not exist", color = "danger"))
          with open(current_working_directory + 'Results/' + value + '/Params.txt') as p:
              all_params = p.read()
              genome_type_f = (next(s for s in all_params.split('\n') if 'Genome_selected' in s)).split('\t')[-1]
              ref_comp = (next(s for s in all_params.split('\n') if 'Ref_comp' in s)).split('\t')[-1]

          genome_type = 'ref'
          style_hide_reference = {'display':'none'}
          value_hide_reference = []
          if '+' in genome_type_f:
              genome_type = 'var'
          if 'True' in ref_comp:
              genome_type = 'both'
              style_hide_reference = {}
              value_hide_reference = ['hide-ref']
          final_list = []
          final_list.append(html.H3('Selected Guide: ' + guide + add_header))
          final_list.append(
              html.P(
                  [
                      'List of Targets found for the selected guide. Select a row to view other possible configurations of the target, along with the corresponding samples list.', # 'Select a row to view the target IUPAC character scomposition. The rows highlighted in red indicates that the target was found only in the genome with variants.',
                      dcc.Checklist(options = [{'label': 'Hide Reference Targets', 'value': 'hide-ref'}], id='hide-reference-targets', value = value_hide_reference, style = style_hide_reference),
                      html.Div(
                          [
                              html.P('Generating download link, Please wait...', id = 'download-link-sumbyguide'),
                              dcc.Interval(interval = 5*1000, id = 'interval-sumbyguide')
                          ]
                      )
                  ]
              )
          )
          if genome_type == 'ref':
              cols = [{"name": i, "id": i, 'type':t, 'hideable':True} for i,t in zip(COL_REF, COL_REF_TYPE)]
              file_to_grep = '.Annotation.targets.txt'#'.top_1.txt'
          else:
              cols = [{"name": i, "id": i, 'type':t, 'hideable':True} for i,t in zip(COL_BOTH, COL_BOTH_TYPE)]
              file_to_grep = '.samples.annotation.txt'

          job_directory = current_working_directory + 'Results/' + job_id + '/'
          guide_grep_result = job_directory + job_id + '.' + bulge_t + '.' + bulge_s + '.' + mms + '.' + guide + '.txt'
          put_header = 'head -1 ' + job_directory + job_id + file_to_grep + ' > ' + guide_grep_result + ' ; '
          final_list.append(
              html.Div(job_id + '.' + bulge_t + '.' + bulge_s + '.' + mms + '.' + guide,style = {'display':'none'}, id = 'div-info-sumbyguide-targets')
          )

          if not os.path.exists(guide_grep_result):    #Example    job_id.X.0.4.guide.txt #NOTE HEADER NON SALVATO
              subprocess.call([put_header + 'LC_ALL=C fgrep ' + guide + ' ' + job_directory + job_id + file_to_grep + ' | LC_ALL=C fgrep ' + bulge_t + ' | awk \'$8==' + mms + ' && $9==' + bulge_s + '\'> ' + guide_grep_result], shell = True)
              subprocess.Popen(['zip ' + guide_grep_result.replace('.txt','.zip') + ' ' + guide_grep_result], shell = True)
          global_store_subset(job_id, bulge_t, bulge_s, mms,guide)

          final_list.append(
              html.Div(
                  dash_table.DataTable(
                      id='table-subset-target',
                      columns=cols,
                      virtualization = True,
                      fixed_rows={ 'headers': True, 'data': 0 },
                      style_cell={'width': '150px'},
                      page_current=0,
                      page_size=PAGE_SIZE,
                      page_action='custom',
                      sort_action='custom',
                      sort_mode='multi',
                      sort_by=[],
                      filter_action='custom',
                      filter_query='',
                      style_table={
                          'max-height': '600px'
                          #'overflowY': 'scroll',
                      },
                      style_cell_conditional=[
                          {
                              'if': {'column_id': 'Samples'},
                              'textAlign': 'left'
                          }
                      ],
                      css= [{ 'selector': 'td.cell--selected, td.focused', 'rule': 'background-color: rgba(0, 0, 255,0.15) !important;' }, { 'selector': 'td.cell--selected *, td.focused *', 'rule': 'background-color: rgba(0, 0, 255,0.15) !important;'}],
                      style_data_conditional = [
                          {
                              'if': {
                                      'filter_query': '{Variant Unique} eq F',
                                  },
                                  'background-color':'rgba(0, 0, 0,0.15)'
                              },
                      ]
                  ),
                  id = 'div-result-table',
              )
          )
          final_list.append(html.Br())
          final_list.append(
              html.Div(
                  id = 'div-second-table-subset-targets'
              )
          )

          return html.Div(final_list, style = {'margin':'1%'})

`def historyPage()`

  Create the History page

  Expand source code

      def historyPage():
          '''
          Create the History page
          '''
          results = get_results()
          final_list = []

          final_list.append(
              html.Div(
                  [
                      html.H3('Results History'),
                      html.P('List of available results. Click on the \'Load\' link to open the corresponding result in a new page.')
                  ]
              )
          )
          final_list.append(
                  html.Div
                      (
                          [
                              dbc.Row(
                                  [
                                      dbc.Col(html.Div(dcc.Dropdown(options = availableGenomes(), id = 'dropdown-genomes-history', placeholder = 'Select a Genome'))),
                                      dbc.Col(html.Div(dcc.Dropdown(options = availablePAM(), id = 'dropdown-pam-history', placeholder = 'Select a PAM'))),
                                      # dbc.Col(html.Div(dcc.Dropdown(options = av_mismatches, id = 'dropdown-mms-history', placeholder = 'Select a Mismatch value' ))),
                                      # dbc.Col(html.Div(dcc.Dropdown(options = av_bulges, id = 'dropdown-dna-history', placeholder = 'Select a DNA Bulge value' ))),
                                      # dbc.Col(html.Div(dcc.Dropdown(options = av_bulges, id = 'dropdown-rna-history', placeholder = 'Select a RNA Bulge value' ))),

                                      dbc.Col(html.Div(html.Button('Filter', id = 'button-filter-history')))
                                  ]
                              ),
                          ],
                      )
              )
          final_list.append(html.Div('None,None',id = 'div-history-filter-query', style = {'display':'none'}))

          final_list.append(
                  html.Div(
                      generate_table_results(results,1),
                      id = 'div-history-table',
                      style = {'text-align': 'center'}
                  ),
          )
          final_list.append(
              html.Div(id = 'div-remove-jobid', style = {'display':'none'})
          )
          final_list.append(
              html.Div(
                  [
                      html.Br(),
                      html.Button('Prev', id = 'prev-page-history'),
                      html.Button('Next', id = 'next-page-history')
                  ],
                  style = {'text-align': 'center'}
              )
          )
          max_page = len(results.index)
          max_page = math.floor(max_page / 10) + 1
          final_list.append(html.Div('1/' + str(max_page), id= 'div-current-page-history'))
          page = html.Div(final_list, style = {'margin':'1%'})
          return page

`def inExample(nI, nR)`

  Inserts an example value in all fields to show the user an example result, or reset the input fields. NOT available in OFFLINE version.

  Args

  - [nI] example-parameters (n_clicks_timestamp): button that inserts the example values
  - [nR] remove-parameters (n_clicks_timestamp): button that removes the values and resets all the fields

  Returns

  - available-genome (value): 'hg38 ref+hg38 1000genomeproject' as input example, or '' to reset the value
  - available-pam (value): '20bp-NGG-SpCas9' as input example, or '' to reset the value
  - text-guides (value): 'GAGTCCGAGCAGAAGAAGAA\nCCATCGGTGGCCGTTTGCCC' as input example, or '' to reset the value
  - mms (value): '4' as input example, or '' to reset the value
  - dna (value): '1' as input example, or '' to reset the value
  - rna (value): '1' as input example, or '' to reset the value
  - len-guide-sequence-ver (value): '20' as input example, or '' to reset the value
  - text-sequence (value): '>sequence\nTACCCCAAACGCGGAGGCGCCTCGGGAAGGCGAGGTGGGCAAGTTCAATGCCAAGCGTGACGGGGGA' as input example, or '' to reset the value
  Expand source code

      @app.callback(
          [Output('available-genome', 'value'),
           Output('available-pam', 'value'),
           Output('text-guides', 'value'),
           Output('mms', 'value'),
           Output('dna', 'value'),
           Output('rna', 'value'),
           Output('len-guide-sequence-ver', 'value'),
           Output('text-sequence', 'value')],
           [Input('example-parameters', 'n_clicks_timestamp'),
           Input('remove-parameters', 'n_clicks_timestamp')]
      )
      def inExample(nI, nR):
          '''
          Inserts an example value in all fields to show the user an example result, or reset the input fields. NOT available in OFFLINE version.

          ***Args***

          + [**nI**] **example-parameters** (*n_clicks_timestamp*): button that inserts the example values
          + [**nR**] **remove-parameters** (*n_clicks_timestamp*): button that removes the values and resets all the fields

          ***Returns***

          + **available-genome** (*value*): 'hg38 ref+hg38 1000genomeproject' as input example, or '' to reset the value
          + **available-pam** (*value*): '20bp-NGG-SpCas9' as input example, or '' to reset the value
          + **text-guides** (*value*): 'GAGTCCGAGCAGAAGAAGAA\\nCCATCGGTGGCCGTTTGCCC' as input example, or '' to reset the value
          + **mms** (*value*): '4' as input example, or '' to reset the value
          + **dna** (*value*): '1' as input example, or '' to reset the value
          + **rna** (*value*): '1' as input example, or '' to reset the value
          + **len-guide-sequence-ver** (*value*): '20' as input example, or '' to reset the value
          + **text-sequence** (*value*): '>sequence\\nTACCCCAAACGCGGAGGCGCCTCGGGAAGGCGAGGTGGGCAAGTTCAATGCCAAGCGTGACGGGGGA' as input example,
          or '' to reset the value
          '''

          if (nI is None) and (nR is None):
              raise PreventUpdate

          if nI is None:
              nI = 0

          if nR is None:
              nR = 0

          if nI > 0:
              if nI > nR:
                  return 'hg38 ref+hg38 1000genomeproject', '20bp-NGG-SpCas9', 'GAGTCCGAGCAGAAGAAGAA\nCCATCGGTGGCCGTTTGCCC', '4', '1', '1', '20', '>sequence\nTACCCCAAACGCGGAGGCGCCTCGGGAAGGCGAGGTGGGCAAGTTCAATGCCAAGCGTGACGGGGGA'

          if nR > 0:
              if nR > nI:
                  return '', '', '', '', '', '', '', ''

`def indexPage()`

  Creates the layout of the main page ('/'). The creation of the main page is put under a function in order to reload the genome and pam dropdown if a new genome is added, simply by reloading the page.

  Returns

  - index_page (list): list of html, dcc and dbc components for the layout.
  Expand source code

      def indexPage():
          '''
          Creates the layout of the main page ('/'). The creation of the main page is put under a function in order to reload the genome and pam dropdown
          if a new genome is added, simply by reloading the page.

          ***Returns***

          + **index_page** (*list*): list of html, dcc and dbc components for the layout.
          '''
          final_list = []
          final_list.extend([
              html.Div([
                  'CRISPRme  performs  predictive analysis and result assessment on population and individual specific CRISPR/Cas experiments.' +
                  ' CRISPRme enumerates on- and off-target accounting simultaneously for  substitutions, DNA/RNA bulges and common genetic variants from the 1000 genomes project.'+
                  ' CRISPRme is based on CRISPRitz [1] a software tool for population target analyses.'
              + ' CRISPRme is devoted to individual specific on- and off-target analyses.'
              ]),
              ])

          final_list.append(
              html.Div(
                  [
                      # html.P(['Download the offline version here: ', html.A('InfOmics/CRISPRitz', href = 'https://github.com/InfOmics/CRISPRitz', target="_blank"), ' or ', html.A('Pinellolab/CRISPRitz', href = 'https://github.com/pinellolab/CRISPRitz', target="_blank") ])
                      html.Br()
                  ]
              )
          )
          checklist_div = html.Div(
              [
                  dbc.FormGroup(
                      [
                          dbc.Checkbox(
                              id="checkbox-gecko", className="form-check-input", checked = True
                          ),
                          dbc.Label(
                              #html.P(['Activate Gecko ', html.Abbr('comparison', title ='The results of your test guides will be compared with results obtained from a previous computed analysis on gecko library')]) ,
                              html.P('Compare your results with the GeCKO v2 library'),
                              html_for="checkbox-gecko",
                              className="form-check-label",
                          ),
                          dbc.Checkbox(
                              id="checkbox-ref-comp", className="form-check-input"
                          ),
                          dbc.Label(
                              html.P('Compare your results with the corresponding reference genome'),
                              html_for="checkbox-ref-comp",
                              className="form-check-label",
                          )

                      ],
                      check = True
                  )
              ],
              id = 'checklist-test-div'
          )

          modal = html.Div(
              [
                  dbc.Modal(
                      [
                          dbc.ModalHeader("WARNING! Missing inputs"),
                          dbc.ModalBody('The following inputs are missing, please select values before submitting the job', id = 'warning-list'),
                          dbc.ModalFooter(
                              dbc.Button("Close", id="close" , className="modal-button")
                          ),
                      ],
                      id="modal",
                      centered=True
                  ),
              ]
          )

          tab_guides_content = html.Div(
              [
                  html.P([
                      'Insert crRNA sequence(s), one per line.',
                      html.P('Sequences must have the same length and be provided without the PAM sequence', id = 'testP') ,
                  ],
                  style = {'word-wrap': 'break-word'}),

                  dcc.Textarea(id = 'text-guides', placeholder = 'GAGTCCGAGCAGAAGAAGAA\nCCATCGGTGGCCGTTTGCCC', style = {'width':'450px', 'height':'160px', 'font-family':'monospace', 'font-size':'large'}),
                  dbc.FormText('Note: a maximum number of 1000 sequences can be provided', color = 'secondary')
              ],
              style = {'width':'450px'} #NOTE same as text-area
          )
          tab_sequence_content = html.Div(
              [
                  html.P(['Search crRNAs by inserting one or more genomic sequences.', html.P('Chromosome ranges can also be supplied')],
                  style = {'word-wrap': 'break-word'}),

                  dcc.Textarea(id = 'text-sequence', placeholder = '>sequence 1\nAAGTCCCAGGACTTCAGAAGagctgtgagaccttggc\n>sequence2\nchr1:11,130,540-11,130,751', style = {'width':'450px', 'height':'160px', 'font-family':'monospace', 'font-size':'large'}),
                  dbc.FormText('Note: a maximum number of 1000 characters can be provided', color = 'secondary')
              ],
              style = {'width':'450px'} #NOTE same as text-area
          )
          final_list.append(
              html.Div(
                  html.Div(
                      [
                          modal,
                          html.Div(
                              [
                                  html.Div([
                                      html.H3('STEP 1', style = {'margin-top':'0'}),
                                      html.Div([
                                          html.P([html.Button(html.P("Load example", style={'color':'rgb(46,140,187)','text-decoration-line': 'underline', 'font-size':'initial'}), id='example-parameters',
                                                      style={ 'border': 'None', 'text-transform': 'capitalize','height':'12','font-weight': '500', 'padding': '0 0px','textcolor':'blu'}), ' - ',
                                          #html.Br(),
                                          html.Button(html.P(children="Reset", style={'color':'rgb(46,140,187)','text-decoration-line': 'underline','font-size':'initial'}), id='remove-parameters',
                                                      style={'border': 'None', 'text-transform': 'capitalize','height':'12','font-weight': '500', 'padding': '0 0px'})])
                                      ], style = {'display':DISPLAY_ONLINE})
                                  ], className = 'flex-div-insert-delete-example'),

                                  html.P(html.P('Select a genome') ),
                                  html.Div(
                                      dcc.Dropdown(options = availableGenomes(), clearable = False, id = "available-genome",) #style = {'width':'75%'})
                                  ),
                                  dbc.FormText('Note: Genomes enriched with variants are indicated with a \'+\' symbol', color='secondary'),

                                  html.Div(
                                      [
                                          html.Div(
                                              [
                                                  html.P(html.P('Select PAM')),
                                                  html.Div(
                                                      dcc.Dropdown(options = availablePAM(), clearable = False, id = 'available-pam')
                                                  )
                                              ],
                                              style = {'flex':'0 0 100%', 'margin-top': '10%'}
                                          )
                                      ],
                                      id = 'div-pam',
                                      className = 'flex-div-pam'
                                  ),
                                  html.Br(),
                                  html.Button("Add New Genome", id = 'add-genome', style = {'margin-right':'5px', 'display':DISPLAY_OFFLINE}),
                                  html.Div('', id = 'genome-job', style = {'display':'none'}),
                                  html.Button("Update dictionary", id = 'update-dict', style = {'margin-left':'5px', 'display':DISPLAY_OFFLINE}),
                                  html.Div('', id = 'dict-job', style = {'display':'none'}),
                                  html.Div(
                                      [
                                          html.Ul(
                                              [html.Li(
                                                  [html.A('Contact us', href = URL + '/contacts', target="_blank"),' to request new genomes availability in the dropdown list'],
                                                  style = {'margin-top':'5%', 'display': DISPLAY_ONLINE}
                                              ),
                                              ],
                                              style = {'list-style':'inside'}
                                          ),
                                      ],
                                      style = {'height':'50%'}
                                  ),
                              ],
                              id = 'step1',
                              style = {'flex':'0 0 30%', 'tex-align':'center'}
                          ),
                          html.Div(style = {'border-right':'solid 1px white'}),
                          html.Div(
                              [
                                  html.H3('STEP 2', style = {'margin-top':'0'}),
                                  html.Div(
                                      [
                                          html.Div(
                                              [   html.P('Select the input type'),
                                                  dbc.Tabs(
                                                      [
                                                          dbc.Tab(tab_guides_content, label='Guides', tab_id= 'guide-tab'),
                                                          dbc.Tab(tab_sequence_content, label='Sequence', tab_id = 'sequence-tab')
                                                      ],
                                                      active_tab='guide-tab',
                                                      id = 'tabs'
                                                  )
                                              ],
                                              id = 'div-guides'
                                          ),
                                          html.Div(
                                              [
                                                  html.P('Allowed mismatches'),
                                                  dcc.Dropdown(options = av_mismatches, clearable = False, id = 'mms', style = {'width':'60px'}),
                                                  html.P('Bulge DNA size'),
                                                  dcc.Dropdown(options = av_bulges, clearable = False, id = 'dna', style = {'width':'60px'}),
                                                  html.P('Bulge RNA size'),
                                                  dcc.Dropdown(options = av_bulges, clearable = False, id = 'rna', style = {'width':'60px'}),
                                                  dbc.Fade(
                                                      [
                                                          html.P('crRNA length (without PAM)'),
                                                          dcc.Dropdown(options = av_guide_sequence, clearable = False, id = 'len-guide-sequence-ver', style = {'width':'60px'})
                                                      ],
                                                      id = 'fade-len-guide', is_in= False, appear= False
                                                  )
                                              ]
                                          )
                                      ],
                                      className = 'flex-step2'
                                  )

                              ],
                              id = 'step2',
                              style = {'flex':'0 0 40%'}

                          ),
                          html.Div(style = {'border-right':'solid 1px white'}),
                          html.Div(
                              [
                                  html.H3('Advanced Options', style = {'margin-top':'0px'}),
                                  checklist_div,
                                  dcc.Checklist(
                                      options = [
                                          {'label':'Notify me by email','value':'email', 'disabled':False}
                                      ],
                                      id = 'checklist-advanced',
                                      style = {'display': DISPLAY_ONLINE}
                                  ),
                                  dbc.Fade(
                                      [
                                          dbc.FormGroup(
                                              [
                                                  dbc.Label("Email", html_for="example-email"),
                                                  dbc.Input(type="email", id="example-email", placeholder="Enter email", className='exampleEmail')
                                              ]
                                          )
                                      ],
                                      id = 'fade', is_in= False, appear= False,
                                  ),
                                  html.Div(
                                      [
                                          html.Button('Submit', id = 'check-job', style = {'background-color':'skyblue'}),
                                          html.Button('', id = 'submit-job', style = {'display':'none'})
                                      ],
                                      style = {'display':'inline-block', 'margin':'0 auto'}   #style="height:55px; width:150px"
                                  )
                              ],
                              id = 'step3',
                              style = {'tex-align':'center'},
                              className = 'flex-step3'
                          )
                      ],
                      id = 'div-steps',
                      style = {'margin':'1%'},
                      className = 'flex-div-steps'
                  ),
                  style = {'background-color':'rgba(154, 208, 150, 0.39)', 'border-radius': '10px', 'border':'1px solid black'},
                  id = 'steps-background'
              )
          )
          final_list.append(html.Br())
          final_list.append(html.P('[1] Cancellieri, Samuele, et al. \"Crispritz: rapid, high-throughput, and variant-aware in silico off-target site identification for crispr genome editing.\" Bioinformatics (2019).'))
          final_list.append(
              html.P(['Download CRISPRitz here: ', html.A('InfOmics/CRISPRitz', href = 'https://github.com/InfOmics/CRISPRitz', target="_blank"), ' or ', html.A('Pinellolab/CRISPRitz', href = 'https://github.com/pinellolab/CRISPRitz', target="_blank") ])
          )
          index_page = html.Div(final_list, style = {'margin':'1%'})
          return index_page

`def loadDistributionPopulations(sel_cel, all_guides, job_id)`

  Load the barplot images from the 'assets' directory.

  Args

  - [sel_cel] general-profile-table (selected_cells): list contaning the selected row of the main guide table
  - [all_guides] general-profile-table (data): list of all the rows that are currenty displayed in the main guide table
  - [search] url (search): string containing the job ID, eg '?job=QV99PN6XDL'

  Returns

  - content-collapse-population (children): list of Dash Components with the images of the Populations Distribution
  Expand source code

      @app.callback(
          Output('content-collapse-population', 'children'),
          [Input('general-profile-table', 'selected_cells')],
          [State('general-profile-table', 'data'),
          State('url','search')]
      )
      def loadDistributionPopulations(sel_cel, all_guides, job_id):
          '''
          Load the barplot images from the 'assets' directory.

          ***Args***

          + [**sel_cel**] **general-profile-table** (*selected_cells*): list contaning the selected row of the main guide table
          + [**all_guides**] **general-profile-table** (*data*): list of all the rows that are currenty displayed in the main guide table
          + [**search**] **url** (*search*): string containing the job ID, eg '?job=QV99PN6XDL'

          ***Returns***

          + **content-collapse-population** (*children*): list of Dash Components with the images of the Populations Distribution
          '''
          if sel_cel is None or not sel_cel or not all_guides:
              raise PreventUpdate
          guide = all_guides[int(sel_cel[0]['row'])]['Guide']
          job_id = job_id.split('=')[-1]

          with open(current_working_directory + 'Results/' + job_id + '/Params.txt') as p:
              all_params = p.read()
              mms = int((next(s for s in all_params.split('\n') if 'Mismatches' in s)).split('\t')[-1])
              max_bulges = int((next(s for s in all_params.split('\n') if 'Max_bulges' in s)).split('\t')[-1])

          distributions = [dbc.Row(html.P('On- and Off-Targets distributions in the Reference and Enriched Genome. For the Enriched Genome, the targets are divided into 5 SuperPopulations (EAS, EUR, AFR, AMR, SAS).', style = {'margin-left':'0.75rem'}))]

          for i in range(math.ceil((mms + max_bulges + 1) / BARPLOT_LEN)):
              all_images = []
              for mm in range (i * BARPLOT_LEN, (i + 1) * BARPLOT_LEN ):
                  if mm < (mms + max_bulges + 1):
                      try:
                          all_images.append(
                              dbc.Col(
                                  [
                                      html.A(
                                          html.Img(
                                              src = 'data:image/png;base64,{}'.format(base64.b64encode(open(current_working_directory + 'Results/' + job_id + '/populations_distribution_' + guide + '_' + str(mm) + 'total.png', 'rb').read()).decode()),
                                              id = 'distribution-population' + str(mm), width="100%", height="auto"
                                          ),
                                          target="_blank",
                                          href = 'assets/Img/' + job_id + '/' + 'populations_distribution_' + guide + '_' + str(mm) + 'total.png'
                                      ),
                                      html.Div(html.P('Distribution ' + str(mm) + ' Mismatches + Bulges ', style = {'display':'inline-block'} ),style = {'text-align':'center'})
                                  ]
                              )
                          )
                      except:
                          all_images.append(
                              dbc.Col(
                                  [
                                      html.Div(html.P('No Targets found with ' + str(mm) + ' Mismatches + Bulges' , style = {'display':'inline-block'}), style = {'text-align':'center'}),
                                      # html.Div(html.P('Distribution ' + str(mm) + ' Mismatches + Bulges ', style = {'display':'inline-block'} ),style = {'text-align':'center'})
                                  ],
                                  align = 'center'
                              )
                          )
                  else:
                      all_images.append(dbc.Col(html.P('')))

              distributions.append(
                  html.Div(
                      [
                          dbc.Row(
                              all_images
                          )
                      ]
                  )
              )
          return distributions

`def loadFullSubsetTable(active_cel, data, cols, search, style_data, sel_cell)`
  Expand source code

      @app.callback(
          [Output('div-second-table-subset-targets', 'children'),
          Output('table-subset-target', 'style_data_conditional')],
          [Input('table-subset-target', 'active_cell')],
          [State('table-subset-target', 'data'),
          State('table-subset-target', 'columns'),
          State('url', 'search'),
          State('table-subset-target', 'style_data_conditional'),
          State('table-subset-target', 'selected_cells')]
      )
      def loadFullSubsetTable(active_cel, data, cols, search, style_data, sel_cell):
          #NOTE tabella secondaria della scomposizione ora non serve, non cancello il codice ma uso PreventUpdate per non azionare la funzione
          if False:
              raise PreventUpdate
          if active_cel is  None:
              raise PreventUpdate
          fl = []
          job_id = search.split('=')[-1]
          with open(current_working_directory + 'Results/' + job_id + '/Params.txt') as p:
              all_params = p.read()
              genome_type_f = (next(s for s in all_params.split('\n') if 'Genome_selected' in s)).split('\t')[-1]
              ref_comp = (next(s for s in all_params.split('\n') if 'Ref_comp' in s)).split('\t')[-1]

          genome_type = 'ref'
          if '+' in genome_type_f:
              genome_type = 'var'
          if 'True' in ref_comp:
              genome_type = 'both'

          if genome_type == 'ref':
              raise PreventUpdate
          #fl.append(html.Br())
          fl.append(html.Hr())
          #Table for IUPAC scomposition
          #fl.append(html.Br())
          fl.append('List of all the configurations for the selected target.')
          fl.append(html.Br())
          cols.append({"name": 'Samples', "id": 'Samples', 'type':'text', 'hideable':True})

          fl.append(
              html.Div(
                  dash_table.DataTable(
                      id='second-table-subset-targets',
                      columns=cols,
                      virtualization = True,
                      fixed_rows={ 'headers': True, 'data': 0 },
                      style_cell={'width': '150px'},
                      page_current=0,
                      page_size=PAGE_SIZE,
                      page_action='custom',
                      sort_action='custom',
                      sort_mode='multi',
                      sort_by=[],
                      filter_action='custom',
                      filter_query='',
                      style_table={
                          'max-height': '600px'
                      },
                      style_cell_conditional=[
                          {
                              'if': {'column_id': 'Samples'},
                              'textAlign': 'left'
                          }
                      ],
                      css= [{ 'selector': 'td.cell--selected, td.focused', 'rule': 'background-color: rgba(0, 0, 255,0.15) !important;' }, { 'selector': 'td.cell--selected *, td.focused *', 'rule': 'background-color: rgba(0, 0, 255,0.15) !important;'}],
                      style_data_conditional = [
                              # {
                              # 'if': {
                              #         'filter_query': '{Variant Unique} eq y',
                              #     },
                              #     #'border-left': '5px solid rgba(255, 26, 26, 0.9)',
                              #     'background-color':'rgba(255, 0, 0,0.15)'#'rgb(255, 102, 102)'

                              # },
                              {
                              'if': {
                                      'filter_query': '{Variant Unique} eq F',
                                      #'filter_query': '{Direction} eq +',
                                      #'column_id' :'Bulge Type'
                                  },
                                  #'border-left': '5px solid rgba(255, 26, 26, 0.9)',
                                  'background-color':'rgba(0, 0, 0,0.15)'#'rgb(255, 102, 102)'
                              }
                      ]
                  )
              )
          )

          pos_cluster = data[int(sel_cell[0]['row'])]['Cluster Position']
          chrom = data[int(sel_cell[0]['row'])]['Chromosome']
          cells_style = [
                             style_data[0],
                              {
                                  'if': {
                                          'filter_query': '{Cluster Position} eq "' + str(pos_cluster) + '"',
                                          ##'filter_query': '{Chromosome} eq "' + str(chrom) + '"',
                                          #'column_id' :'{#Bulge type}',
                                          #'column_id' :'{Total}'
                                      },
                                      #'border-left': '5px solid rgba(255, 26, 26, 0.9)',
                                      'background-color':'rgba(0, 0, 255,0.15)'#'rgb(255, 102, 102)'

                              }

                              # {
                              # 'if': {
                              #         'filter_query': '{Chromosome} eq "chr2"',
                              #         #'filter_query': '{Direction} eq +',
                              #         #'column_id' :'Bulge Type'
                              #     },
                              #     #'border-left': '5px solid rgba(255, 26, 26, 0.9)',
                              #     'background-color':'rgba(255, 69, 0,0.15)'#'rgb(255, 102, 102)'

                              # },
                              # {
                              #     'if': {
                              #             'filter_query': '{Variant Unique} eq n',
                              #             'column_id' :'Bulge Type'
                              #         },
                              #         'border-left': '5px solid rgba(26, 26, 255, 0.9)',

                              # }

                      ]
          return  fl, cells_style

`def openDialog(n, type_ask, start_dir='./')`
  Expand source code

      def openDialog(n, type_ask, start_dir = './'):
          if n is None:
              raise PreventUpdate
          root = tk.Tk()
          root.withdraw()
          if type_ask == 'D':
              selected = filedialog.askdirectory(initialdir = current_working_directory + start_dir)
          else:
              selected = filedialog.askopenfilename(initialdir = current_working_directory + start_dir)
          root.destroy()
          if selected == '' or selected == '()':
              selected = 'None'
          return str(selected)

`def openResultDirectory(n, search, guide)`

  Opens a new tab where the final list of off-targets is shown (NOT available when ONLINE mode is selected)

  Args

  - [n] button-open-result-directory (n_cliks): number of times the button was pressed
  - [search] url (search): string containing the job ID, eg '?job=QV99PN6XDL'
  - [guide] div-open-result-directory (children): string of the currently selected guide

  Returns

  - Opens a new tab showing the off-target list of jobID.targets.GUIDE.txt final file
  Expand source code

      @app.callback(
          Output('div-open-result-directory', 'children'),
          [Input('button-open-result-directory', 'n_clicks')],
          [State('url', 'search'),
          State('div-open-result-directory', 'children')]
      )
      def openResultDirectory(n, search, guide):
          '''
          Opens a new tab where the final list of off-targets is shown (NOT available when ONLINE mode is selected)

          ***Args***

          + [**n**] **button-open-result-directory** (*n_cliks*): number of times the button was pressed
          + [**search**] **url** (*search*): string containing the job ID, eg '?job=QV99PN6XDL'
          + [**guide**] **div-open-result-directory** (*children*): string of the currently selected guide

          ***Returns***

          + Opens a new tab showing the off-target list of jobID.targets.GUIDE.txt final file
          '''
          if n is None or ONLINE:
              raise PreventUpdate
          job_id = search.split('=')[-1]  #TODO decidere se aprire tutta la cartella, solo il file, e nel secondo caso creare copia submit job che non rimuova .targets.GUIDE.txt
          wb.open_new_tab(current_working_directory + 'Results/' + job_id + '/' + job_id + '.targets.' + guide + '.txt')
          raise PreventUpdate

`def parse_contents(contents)`

  Read the uploaded file and converts into bit

  Expand source code

      def parse_contents(contents):
          '''
          Read the uploaded file and converts into bit
          '''
          content_type, content_string = contents.split(',')
          decoded = base64.b64decode(content_string)
          return decoded

`def refreshSearch(n, dir_name)`

  The Interval component of the '/load' page calls this function every 3 seconds. The function checks the status of the submitted job by means of the log and output file created and updated by the submit_job.final.sh script, and notifies the user about the current status ('To Do', 'Done' etc.) When the job is completed, a link directing to the '/result' page is shown. If the job doesn't exists or is in the queue, a notification will appear.

  Args

  - [n] load-page-check (n_intervals): element of the Interval component that shows the number of time it has been fired
  - [dir_name] url (search): string containing the job ID, eg '?job=QV99PN6XDL'

  Returns

  - view-results (style): dictionary for the style element of the link that redirects to the '/result' page. If the job is completed then the {'visibility':'visible'} is returned, else {'visibility':'visible'}
  - search-status (children): html.P() that contains the status of the search phase. Can be:
    - To Do, red
    - Done, green
    - Currently ongoing, orange
    - Not Available, red
  - generate-report-status (children): html.P() that contains the status of the generate report. Can be as above
  - post-process-status (children): html.P() that contains the status of the post process phase. Can be as above
  - view-results (href): link that redirects to the '/result' page
  - no-directory-error (children): Alert component that notify the user that the result is not present in the server, or is in the queue
  Expand source code

      @app.callback(
          [Output('view-results', 'style'),
          Output('search-status', 'children'),
          Output('generate-report-status', 'children'),
          Output('post-process-status', 'children'),
          Output('view-results','href'),
          Output('no-directory-error', 'children')],
          [Input('load-page-check', 'n_intervals')],
          [State('url', 'search')]
      )
      def refreshSearch(n, dir_name):
          '''
          The Interval component of the '/load' page calls this function every 3 seconds. The function checks the status of the submitted job by means
          of the log and output file created and updated by the submit_job.final.sh script, and notifies the user about the current status ('To Do', 'Done' etc.)
          When the job is completed, a link directing to the '/result' page is shown. If the job doesn't exists or is in the queue,
          a notification will appear.

          ***Args***

          + [**n**] **load-page-check** (*n_intervals*): element of the Interval component that shows the number of time it has been fired
          + [**dir_name**] **url** (*search*): string containing the job ID, eg '?job=QV99PN6XDL'

          ***Returns***

          + **view-results** (*style*): dictionary for the style element of the link that redirects to the '/result' page. If the job is completed
          then the {'visibility':'visible'} is returned, else {'visibility':'visible'}
          + **search-status** (*children*): html.P() that contains the status of the search phase. Can be:
              + To Do, red
              + Done, green
              + Currently ongoing, orange
              + Not Available, red
          + **generate-report-status** (*children*): html.P() that contains the status of the generate report. Can be as above
          + **post-process-status** (*children*): html.P() that contains the status of the post process phase. Can be as above
          + **view-results** (*href*): link that redirects to the '/result' page
          + **no-directory-error** (*children*): Alert component that notify the user that the result is not present in the server, or is in the queue
          '''

          if n is None:
              raise PreventUpdate

          onlydir = [f for f in listdir(current_working_directory + 'Results') if isdir(join(current_working_directory + 'Results', f))]
          current_job_dir = current_working_directory + 'Results/' +  dir_name.split('=')[-1] + '/'
          if dir_name.split('=')[-1] in onlydir:
              onlyfile = [f for f in listdir(current_job_dir) if isfile(join(current_job_dir, f))]
              if os.path.exists(current_job_dir + 'guides.txt'):
                  with open(current_job_dir + 'guides.txt') as guides:
                      n_guides = len(guides.read().strip().split('\n'))
              else:
                  n_guides = -1
              if 'log.txt' in onlyfile:
                  with open(current_job_dir + 'log.txt') as log:
                      all_done = 0

                      search_status = html.P('To do', style = {'color':'red'})
                      report_status = html.P('To do', style = {'color':'red'})
                      post_process_status = html.P('To do', style = {'color':'red'})
                      current_log = log.read()

                      if ('Search-index\tDone' in current_log and 'Search\tDone' in current_log):
                          search_status = html.P('Done', style = {'color':'green'})
                          all_done = all_done + 1
                      elif os.path.exists(current_job_dir + 'output.txt'):                #Extract % of search done from output.txt
                          with open(current_job_dir + 'output.txt', 'r') as output_status:
                              line = output_status.read().strip()
                              if 'Indexing_Reference' in line:
                                  search_status = html.P('Indexing Reference Genome...' + ' ' + 'Step [1/2]', style = {'color':'orange'})
                              if 'Indexing_Enriched' in line:
                                  search_status = html.P('Indexing Enriched Genome...' + ' ' + 'Step [2/2]', style = {'color':'orange'})
                              if 'Search_output' in line:
                                  if 'both' in line:
                                      last_percent = line.rfind('%')
                                      if last_percent > 0:
                                          last_percent = line[line[:last_percent].rfind(' '): last_percent]
                                          search_status_message = last_percent + '%'
                                      else:
                                          search_status_message = 'Searching...'

                                      steps = 'Step [1/2]'
                                      if 'Search_output_ref' in line:
                                          steps = 'Step [2/2]'

                                  else:
                                      last_percent = line.rfind('%')
                                      if last_percent > 0:
                                          last_percent = line[line[:last_percent].rfind(' '): last_percent]
                                          search_status_message = last_percent + '%'
                                      else:
                                          search_status_message = 'Searching...'
                                      steps = ''
                                  search_status = html.P(search_status_message + ' ' + steps, style = {'color':'orange'})

                      if ('Report\tDone' in current_log):
                          report_status = html.P('Done', style = {'color':'green'})
                          all_done = all_done + 1
                      elif os.path.exists(current_job_dir + 'output.txt'):                #Extract % of search done
                          with open(current_job_dir + 'output.txt', 'r') as output_status:
                              line = output_status.read().strip()
                              if 'Generate_report' in line:
                                  if n_guides < 0:
                                      report_status = html.P('Generating Report...', style = {'color':'orange'})
                                  else:
                                      status_message = round((len(line.split('\n')) - 1) / n_guides, 2)
                                      report_status = html.P(str(status_message * 100) + '%', style = {'color':'orange'})
                      if ('PostProcess\tDone' in current_log):
                          post_process_status = html.P('Done', style = {'color':'green'})
                          all_done = all_done + 1
                      elif os.path.exists(current_job_dir + 'output.txt'):                #Extract % of search done
                          with open(current_job_dir + 'output.txt', 'r') as output_status:
                              line = output_status.read().strip()
                              if 'PostProcess_output' in line:
                                  line = line.split('\n')
                                  if line[-1] == 'PostProcess_output':
                                      post_process_status = html.P('Processing data...', style = {'color':'orange'})
                                  else:
                                      if 'Annotating...' in line:
                                          last_percent = line[-1].rfind('%')
                                          if last_percent > 0:
                                              last_percent = line[line[:last_percent].rfind(' '): last_percent]
                                              status_message = last_percent + '%'
                                          else:
                                              status_message = 'Annotating...'
                                      else:
                                          status_message = line[-1]
                                      post_process_status = html.P(status_message, style = {'color':'orange'})
                      if all_done == 3 or 'Job\tDone' in current_log:
                          return {'visibility':'visible'},  search_status, report_status, post_process_status ,'/result?job=' + dir_name.split('=')[-1], ''
                      else:
                          return {'visibility':'hidden'},  search_status, report_status, post_process_status,'', ''
              elif 'queue.txt' in onlyfile:
                  return {'visibility':'hidden'},  html.P('To do', style = {'color':'red'}), html.P('To do', style = {'color':'red'}), html.P('To do', style = {'color':'red'}),'', dbc.Alert("Job submitted. Current status: in queue", color = "info")
          return {'visibility':'hidden'},  html.P('Not available', style = {'color':'red'}), html.P('Not available', style = {'color':'red'}), html.P('Not available', style = {'color':'red'}) ,'', dbc.Alert("The selected result does not exist", color = "danger")

`def removeJobIDandFilter(n0, n1, n2, n3, n4, n5, n6, n7, n8, n9, nPrev, nNext, filter_q, jID0, jID1, jID2, jID3, jID4, jID5, jID6, jID7, jID8, jID9, n, search, current_page)`
  Expand source code

      @app.callback(
          [Output('div-history-table', 'children'),
          Output('div-current-page-history', 'children')],
          [Input('button-delete-history-0', 'n_clicks_timestamp'),
          Input('button-delete-history-1', 'n_clicks_timestamp'),
          Input('button-delete-history-2', 'n_clicks_timestamp'),
          Input('button-delete-history-3', 'n_clicks_timestamp'),
          Input('button-delete-history-4', 'n_clicks_timestamp'),
          Input('button-delete-history-5', 'n_clicks_timestamp'),
          Input('button-delete-history-6', 'n_clicks_timestamp'),
          Input('button-delete-history-7', 'n_clicks_timestamp'),
          Input('button-delete-history-8', 'n_clicks_timestamp'),
          Input('button-delete-history-9', 'n_clicks_timestamp'),
          Input('prev-page-history', 'n_clicks_timestamp'),
          Input('next-page-history', 'n_clicks_timestamp'),
          Input('div-history-filter-query', 'children')],
          [State('button-delete-history-0', 'data-jobid'),
          State('button-delete-history-1', 'data-jobid'),
          State('button-delete-history-2', 'data-jobid'),
          State('button-delete-history-3', 'data-jobid'),
          State('button-delete-history-4', 'data-jobid'),
          State('button-delete-history-5', 'data-jobid'),
          State('button-delete-history-6', 'data-jobid'),
          State('button-delete-history-7', 'data-jobid'),
          State('button-delete-history-8', 'data-jobid'),
          State('button-delete-history-9', 'data-jobid'),
          State('button-filter-history', 'n_clicks_timestamp'),
          State('url', 'search'),
          State('div-current-page-history', 'children')
          ]
      )
      def removeJobIDandFilter(n0, n1, n2, n3, n4, n5, n6, n7, n8, n9, nPrev, nNext, filter_q, jID0, jID1, jID2, jID3, jID4, jID5, jID6, jID7, jID8, jID9, n, search, current_page):
          #Get last pressed button
          if not n0:
              n0 = 0
          if not n1:
              n1 = 0
          if not n2:
              n2 = 0
          if not n3:
              n3 = 0
          if not n4:
              n4 = 0
          if not n5:
              n5 = 0
          if not n6:
              n6 = 0
          if not n7:
              n7 = 0
          if not n8:
              n8 = 0
          if not n9:
              n9 = 0
          if not nPrev:
              nPrev = 0
          if not nNext:
              nNext = 0
          if not n:
              n = 0
          btn_group = []
          btn_group.append(n0)
          btn_group.append(n1)
          btn_group.append(n2)
          btn_group.append(n3)
          btn_group.append(n4)
          btn_group.append(n5)
          btn_group.append(n6)
          btn_group.append(n7)
          btn_group.append(n8)
          btn_group.append(n9)
          btn_group.append(nPrev)
          btn_group.append(nNext)
          btn_group.append(n)

          genome_filter = filter_q.split(',')[0]
          pam_filter = filter_q.split(',')[1]
          if genome_filter == 'None':
              genome_filter = None
          if pam_filter == 'None':
              pam_filter = None
          current_page = current_page.split('/')[0]
          current_page = int(current_page)
          results = get_results()
          selectedID = ''

          if max(btn_group) == 0:
              selectedID = ''
          elif max(btn_group) == n0:
              selectedID = jID0
          elif max(btn_group) == n1:
              selectedID = jID1
          elif max(btn_group) == n2:
              selectedID = jID2
          elif max(btn_group) == n3:
              selectedID = jID3
          elif max(btn_group) == n4:
              selectedID = jID4
          elif max(btn_group) == n5:
              selectedID = jID5
          elif max(btn_group) == n6:
              selectedID = jID6
          elif max(btn_group) == n7:
              selectedID = jID7
          elif max(btn_group) == n8:
              selectedID = jID8
          elif max(btn_group) == n9:
              selectedID = jID9
          elif max(btn_group) == n:    #Filter Button selected, return the first page of the filtered table
              results, max_page = supportFilterHistory(results, genome_filter, pam_filter)
              return generate_table_results(results,1), '1/' + str(max_page)
          elif max(btn_group) == nNext:   #Next Button of the table
              current_page = current_page + 1
              results, max_page = supportFilterHistory(results, genome_filter, pam_filter)
              if ((current_page - 1) * 10) > len(results):
                  current_page = current_page -1
                  if current_page < 1:
                      current_page = 1
              return generate_table_results(results,current_page), str(current_page) + '/' + str(max_page)
          elif max(btn_group) == nPrev:   #Go to previous page
              current_page = current_page - 1
              if current_page < 1:
                  current_page = 1
              results, max_page = supportFilterHistory(results, genome_filter, pam_filter)
              return generate_table_results(results,current_page), str(current_page) + '/' + str(max_page)

          if selectedID == '':
              raise PreventUpdate
          result_removed = Gmsg.deleteResultConfirm(selectedID)
          if result_removed:      #If the result was removed, update the table
              results, max_page = supportFilterHistory(get_results(), genome_filter, pam_filter)
              return generate_table_results(results,1), '1/' + str(max_page)
          else:
              raise PreventUpdate
          results, max_page = supportFilterHistory(get_results(), genome_filter, pam_filter)
          return generate_table_results(results,1), '1/' + str(max_page)

`def resetTab(current_tab, is_in)`

  Manages the fading of the Dropdown for the guide length when the tab 'Sequence' is active.

  Args

  - [current_tab] tabs (active_tab): string of the ID of the current active tab
  - [is_in] fade-len-guide (is_in): True if the Dropdown guide length element is displayed, False otherwise

  Returns

  - fade-len-guide (is_in): True in order to show the Dropdown guide length element, False to hide it
  Expand source code

      @app.callback(
          Output('fade-len-guide', 'is_in'),
          [Input('tabs', 'active_tab')],
          [State('fade-len-guide', 'is_in')]
      )
      def resetTab(current_tab, is_in):
          '''
          Manages the fading of the Dropdown for the guide length when the tab 'Sequence' is active.

          ***Args***

          + [**current_tab**] **tabs** (*active_tab*): string of the ID of the current active tab
          + [**is_in**] **fade-len-guide** (*is_in*): True if the Dropdown guide length element is displayed, False otherwise

          ***Returns***

          + **fade-len-guide** (*is_in*): True in order to show the Dropdown guide length element, False to hide it
          '''
          if current_tab is None:
              raise PreventUpdate

          if current_tab == 'guide-tab':
              return False
          return True

`def resultPage(job_id)`

  The function creates the layout for the Result Page, with the general summary table at the top, the popup Population histogram section, the Tabs for the three different summaries and report (Guide, Sample, Position, Graphical Sumamary).

  Args

  - job_id: string containing the jobbID of the current job

  Returns

  - result_page: list of Dash Components for the layout of the page
  Expand source code

      def resultPage(job_id):
          '''
          The function creates the layout for the Result Page, with the general summary table at the top, the popup Population histogram section, the
          Tabs for the three different summaries and report (Guide, Sample, Position, Graphical Sumamary).

          ***Args***

          + **job_id**: string containing the jobbID of the current job

          ***Returns***

          + **result_page**: list of Dash Components for the layout of the page
          '''
          value = job_id
          job_directory = current_working_directory + 'Results/' + job_id + '/'
          if (not isdir(job_directory)):
              return html.Div(dbc.Alert("The selected result does not exist", color = "danger"))

          #Load informations from the Params file
          with open(current_working_directory + 'Results/' + value + '/Params.txt') as p:
              all_params = p.read()
              mms = (next(s for s in all_params.split('\n') if 'Mismatches' in s)).split('\t')[-1]
              bulge_dna = (next(s for s in all_params.split('\n') if 'DNA' in s)).split('\t')[-1]
              bulge_rna = (next(s for s in all_params.split('\n') if 'RNA' in s)).split('\t')[-1]
              genome_type_f = (next(s for s in all_params.split('\n') if 'Genome_selected' in s)).split('\t')[-1]
              ref_comp = (next(s for s in all_params.split('\n') if 'Ref_comp' in s)).split('\t')[-1]
              max_bulges = (next(s for s in all_params.split('\n') if 'Max_bulges' in s)).split('\t')[-1]

          genome_name = genome_type_f
          genome_type = 'ref'
          if '+' in genome_type_f:
              genome_type = 'var'
              genome_name = genome_name.split('_')[0] + ' Variants'
          else:
              genome_name = genome_name.split('_')[0] + ' Reference'
          if 'True' in ref_comp:
              genome_type = 'both'
          mms = int(mms[0])

          #load acfd for each guide
          with open(current_working_directory + 'Results/' + value + '/acfd.txt') as a:
              all_scores = a.read().strip().split('\n')

          list_error_guides = []
          if os.path.exists(current_working_directory + 'Results/' + value + '/guides_error.txt'):
              with open(current_working_directory + 'Results/' + value + '/guides_error.txt') as error_g:
                  for e_g in error_g:
                      list_error_guides.append(e_g.strip())

          #Columns for the main table
          col_targetfor = '('
          for i in range(1, mms + int(max_bulges)):
              col_targetfor = col_targetfor + str(i) + '-'
          col_targetfor = col_targetfor + str(mms + int(max_bulges))
          col_targetfor = col_targetfor + ' Mismatches + Bulges)'

          columns_profile_table = [
              {'name':['', 'Guide'], 'id':'Guide', 'type':'text'},
              {'name':['', 'CFD'], 'id':'CFD', 'type':'text'},
              {'name':['', 'Doench 2016'], 'id':'Doench 2016', 'type':'text'},    #Doench, only for REF or VAR
              {'name':['Doench 2016', 'Reference'], 'id':'Reference', 'type':'text'},        #REF Doench, only for Both
              {'name':['Doench 2016', 'Enriched'], 'id':'Enriched', 'type':'text'},          #VAR Doench, only for Both
              {'name':['', 'On-Targets Reference'], 'id':'On-Targets Reference', 'type':'text'},
              {'name':['', 'On-Targets Enriched'], 'id':'On-Targets Enriched', 'type':'text'},
              {'name':['', 'Samples in Class 0 - 0+ - 1 - 1+'], 'id':'Samples in Class 0 - 0+ - 1 - 1+', 'type':'text'},
              {'name':['', 'Genome'], 'id':'Genome', 'type':'text'},
              {'name':['Off-targets for Mismatch (MM) + Bulge (B) Value', 'Total'], 'id':'Total', 'type':'text'}
              ]    #Column of headers . Remove the entries accordingly when checking type of genome

          for i in range (1, mms + int(max_bulges) + 1):
              columns_profile_table.append({'name':['Off-targets for Mismatch (MM) + Bulge (B) Value', str(i) + ' MM + B'], 'id': str(i) + ' MM + B', 'type':'text'})
          remove_indices = set()

          if 'NO SCORES' in all_scores:
              # remove_indices.update([1,2,3,4])    #Remove CFD and Doench header
              remove_indices.update('CFD', 'Doench 2016', 'Reference', 'Enriched')

          if genome_type == 'ref':
              # remove_indices.update([3,4,6,7])
              remove_indices.update(['Reference', 'Enriched', 'On-Targets Enriched', 'Samples in Class 0 - 0+ - 1 - 1+' ])
          elif genome_type == 'both':
              # remove_indices.update([6])
              remove_indices.update(['Doench 2016','On-Targets Enriched'])
          else:
              # remove_indices.update([3,4,5,7])
              remove_indices.update(['Reference', 'Enriched', 'On-Targets Reference', 'Samples in Class 0 - 0+ - 1 - 1+'])

          #Remove headers not used in selected search result
          columns_profile_table = [i for j, i in enumerate(columns_profile_table) if columns_profile_table[j]['id'] not in remove_indices]

          final_list = []
          if list_error_guides:   #If some guides were not processed due to too many targets
              final_list.append(
                  dbc.Alert(
                      [
                          'Warning: Some guides have too many targets! ',
                          html.A("Click here", href= URL + "/data/" + job_id + '/guides_error.txt', className="alert-link"),
                          ' to view them'
                      ], color='warning')
              )
          final_list.append(
              html.H3('Result Summary - ' + genome_name + ' - Mismatches ' + str(mms) + ' - DNA ' + bulge_dna + ' - RNA ' + bulge_rna)
          )

          add_to_description = html.P(
              'General summary for the given guides. For each guide, the number of Off-Targets found for each Mismatch + Bulge value is shown.'
          )
          if genome_type == 'both':
              add_to_description = html.P(
                  [
                      'General summary for the given guides. For each guide, the number of ',
                      html.Span(
                          "Samples for each Class is provided",
                          id="tooltip-sample-class",
                          style={"textDecoration": "underline", "cursor": "pointer"}
                      ),
                      ', along with the number of Off-Targets found for each Mismatch + Bulge value, for both Reference and Enriched Genomes.',
                      dbc.Tooltip(
                          [
                              html.Div([html.P([html.B('Class 0:'), ' Samples that does not have any On-Targets']),
                              html.P([html.B('Class 0+:'), ' Samples that have a subset of the Reference Genome On-Targets']),
                              html.P([html.B('Class 1:'), ' Samples that have the same On-Targets as the Reference Genome']),
                              html.P([html.B('Class 1+:'), ' Samples that creates at least a new On-Target, that is not present in the Reference Genome'])],
                              style = {'display':'inline-block'})
                          ],
                          target="tooltip-sample-class", style = {'font-size': '12px'}
                      )
                  ]
              )
          final_list.append(add_to_description)
          final_list.append(
              html.Div(
                  dash_table.DataTable(
                      id = 'general-profile-table',
                      columns = columns_profile_table,
                      merge_duplicate_headers=True,
                      #fixed_rows={ 'headers': True, 'data': 0 },
                      selected_cells = [{'row':0, 'column':0}],
                      css= [{ 'selector': 'td.cell--selected, td.focused', 'rule': 'background-color: rgba(0, 0, 255,0.15) !important;' }, { 'selector': 'td.cell--selected *, td.focused *', 'rule': 'background-color: rgba(0, 0, 255,0.15) !important;'}],
                      page_current= 0,
                      page_size= PAGE_SIZE,
                      page_action='custom',
                      #virtualization = True,
                      filter_action='custom',
                      filter_query='',

                      sort_action='custom',
                      sort_mode='multi',
                      sort_by=[],
                      style_table={
                          'max-height': '260px',
                          'overflowY': 'scroll',
                      },
                      style_data={
                          'whiteSpace': 'pre',
                          'height': 'auto',
                          'font-size':'1.30rem'
                      },
                      style_data_conditional = [
                          {
                              'if': {
                                      'column_id' :'Genome'
                                  },
                                  'font-weight':'bold',
                                  'textAlign': 'center'

                          }
                      ]
                  )
                  ,id = 'div-general-profile-table')
          )

          final_list.append(html.Br())

          if genome_type == 'ref':
              final_list.append(
              dcc.Tabs(id="tabs-reports", value='tab-summary-by-guide', children=[
                  dcc.Tab(label='Summary by Guide', value='tab-summary-by-guide'),
                  dcc.Tab(label='Summary by Position', value='tab-summary-by-position'),
                  dcc.Tab(label='Graphical Summary', value='tab-summary-graphical'),
              ])
          )
          else:
              #Barplot for population distributions
              final_list.append(
                  html.Div(
                      [
                          dbc.Row(
                              [
                                  dbc.Col(html.Button("Show/Hide Target Distribution in SuperPopulations", id="btn-collapse-populations")),
                              ]
                          ),
                          dbc.Collapse(
                              dbc.Card(dbc.CardBody(
                                  html.Div(id = 'content-collapse-population')
                              )),
                              id="collapse-populations",
                          ),
                      ]
                  )
              )
              final_list.append(html.Br())
              final_list.append(
                  dcc.Tabs(id="tabs-reports", value='tab-summary-by-guide', children=[
                      dcc.Tab(label='Summary by Guide', value='tab-summary-by-guide'),
                      dcc.Tab(label='Summary by Sample', value='tab-summary-by-sample'),
                      dcc.Tab(label='Summary by Position', value='tab-summary-by-position'),
                      dcc.Tab(label='Graphical Summary', value='tab-summary-graphical'),
                  ])
              )
          final_list.append(html.Div(id = 'div-tab-content'))

          final_list.append(html.Div(genome_type, style = {'display':'none'}, id = 'div-genome-type'))
          result_page = html.Div(final_list, style = {'margin':'1%'})
          return result_page

`def samplePage(job_id, hash)`
  Expand source code

      def samplePage(job_id, hash):
          guide = hash[:hash.find('-Sample-')]
          sample = hash[hash.rfind('-') + 1:]
          if (not isdir(current_working_directory + 'Results/' + job_id)):
              return html.Div(dbc.Alert("The selected result does not exist", color = "danger"))

          with open(current_working_directory + 'Results/' + job_id + '/Params.txt') as p:
              all_params = p.read()
              genome_type_f = (next(s for s in all_params.split('\n') if 'Genome_selected' in s)).split('\t')[-1]
              ref_comp = (next(s for s in all_params.split('\n') if 'Ref_comp' in s)).split('\t')[-1]

          genome_type = 'ref'
          if '+' in genome_type_f:
              genome_type = 'var'
          if 'True' in ref_comp:
              genome_type = 'both'

          final_list = []
          final_list.append(
              #html.P('List of Targets found for the selected Sample - ' + sample + ' - and guide - ' + guide + ' -')
              html.H3('Selected Sample: ' + sample)
          )
          final_list.append(
              html.P(
                  [
                      'List of Targets found for the selected sample.', #'The rows highlighted in red indicates that the target was found only in the genome with variants.',
                      html.Div(
                          [
                              html.P('Generating download link, Please wait...', id = 'download-link-sumbysample'),
                              dcc.Interval(interval = 5*1000, id = 'interval-sumbysample')
                          ]

                      )
                  ]
              )
          )

          file_to_grep = '.samples.annotation.txt'
          sample_grep_result = current_working_directory + 'Results/'+ job_id + '/' + job_id + '.' + sample + '.' + guide + '.txt'
          put_header = 'head -1 ' + current_working_directory + 'Results/' + job_id + '/' + job_id + file_to_grep + ' > ' + sample_grep_result + ' ; '
          final_list.append(
              html.Div(job_id + '.' + sample + '.' + guide,style = {'display':'none'}, id = 'div-info-sumbysample-targets')
          )
          # print('qui sample before grep')
          # print('esiste sample?', str(os.path.exists(sample_grep_result)))

          if not os.path.exists(sample_grep_result):    #Example    job_id.HG001.guide.txt #NOTE HEADER NON SALVATO
              subprocess.call([put_header + 'LC_ALL=C fgrep ' + guide + ' ' + current_working_directory + 'Results/'+ job_id + '/' + job_id + file_to_grep + ' | LC_ALL=C fgrep ' + sample + ' > ' + sample_grep_result], shell = True)
              subprocess.Popen(['zip ' + sample_grep_result.replace('.txt','.zip') + ' ' + sample_grep_result],shell = True)

          cols = [{"name": i, "id": i, 'type':t, 'hideable':True} for i,t in zip(COL_BOTH, COL_BOTH_TYPE)]

          final_list.append(
              html.Div(
                  dash_table.DataTable(
                      id='table-sample-target',
                      columns=cols,
                      #data = df.to_dict('records'),
                      virtualization = True,
                      fixed_rows={ 'headers': True, 'data': 0 },
                      #fixed_columns = {'headers': True, 'data':1},
                      style_cell={'width': '150px'},
                      page_current=0,
                      page_size=PAGE_SIZE,
                      page_action='custom',
                      sort_action='custom',
                      sort_mode='multi',
                      sort_by=[],
                      filter_action='custom',
                      filter_query='',
                      style_table={
                          'max-height': '600px'
                          #'overflowY': 'scroll',
                      },
                      style_data_conditional=[
                          # {
                          #     'if': {
                          #             'filter_query': '{Variant Unique} eq y',
                          #             #'column_id' :'{#Bulge type}',
                          #             #'column_id' :'{Total}'
                          #         },
                          #         #'border-left': '5px solid rgba(255, 26, 26, 0.9)',
                          #         'background-color':'rgba(255, 0, 0,0.15)'#'rgb(255, 102, 102)'

                          #     },
                          {
                              'if': {
                                      'filter_query': '{Variant Unique} eq F',
                                      #'filter_query': '{Direction} eq +',
                                      #'column_id' :'Bulge Type'
                                  },
                                  #'border-left': '5px solid rgba(255, 26, 26, 0.9)',
                                  'background-color':'rgba(0, 0, 0,0.15)'#'rgb(255, 102, 102)'

                              }
                      ],
                      css= [{ 'selector': 'td.cell--selected, td.focused', 'rule': 'background-color: rgba(0, 0, 255,0.15) !important;' }, { 'selector': 'td.cell--selected *, td.focused *', 'rule': 'background-color: rgba(0, 0, 255,0.15) !important;'}],
                      # css= [{ 'selector': 'td.row--selected, td.focused', 'rule': 'background-color: rgba(0, 0, 255,0.15) !important;' }, { 'selector': 'td.row--selected *, td.focused *', 'rule': 'background-color: rgba(0, 0, 255,0.15) !important;'}],

                  ),
                  id = 'div-result-table',
              )
          )
          return html.Div(final_list, style = {'margin':'1%'})

`def selectSameLenGuides(list_guides)`

  If the user provide guides with different lengths, the function select the first guide, calculate its length, and discards all the guides with different lengths.

  Args:

  - list_guides: list of the input guides

  Returns

  - same_len_guides: list of guides with the same length
  Expand source code

      def selectSameLenGuides(list_guides):
          '''
          If the user provide guides with different lengths, the function select the first guide, calculate its length, and discards all the guides with
          different lengths.

          ***Args***:

          + **list_guides**: list of the input guides

          ***Returns***

          + **same_len_guides**: list of guides with the same length
          '''
          selected_length = len(list_guides.split('\n')[0])
          same_len_guides_list = []
          for guide in list_guides.split('\n'):
              if len(guide) == selected_length:
                  same_len_guides_list.append(guide)
          same_len_guides = '\n'.join(same_len_guides_list).strip()
          return same_len_guides

`def split_filter_part(filter_part)`

  Split the input filter, used for Dash DataTables. See https://dash.plotly.com/datatable/filtering

  Expand source code

      def split_filter_part(filter_part):
          '''
          Split the input filter, used for Dash DataTables. See https://dash.plotly.com/datatable/filtering
          '''
          for operator_type in operators:
              for operator in operator_type:
                  if operator in filter_part:
                      name_part, value_part = filter_part.split(operator, 1)
                      name = name_part[name_part.find('{') + 1: name_part.rfind('}')]

                      value_part = value_part.strip()
                      v0 = value_part[0]
                      if (v0 == value_part[-1] and v0 in ("'", '"', '`')):
                          value = value_part[1: -1].replace('\\' + v0, v0)
                      else:
                          try:
                              value = float(value_part)
                          except ValueError:
                              value = value_part

                      # word operators need spaces after them in the filter string,
                      # but we don't want these later
                      return name, operator_type[0].strip(), value

          return [None] * 3

`def startAddNewGenome(n)`
  Expand source code

      @app.callback(
          Output('div-targetoutput','children'),
          [Input('button-add-new-genome', 'n_clicks')]
      )
      def startAddNewGenome(n):
          if n is None:
              raise PreventUpdate
          #TODO: prende gli state dei valori selezionati dall'utente e controlla se sono stati tutti selezionati, altrimenti fa comparire un
          #avviso che manca qualcosa (cfr. funzione checkInput, ma basta solo avere l'elenco dei campi da compilare)
          #...
          #...

          #input corretti
          #Faccio un subprocess.Popen facendo partire lo script per generare il nuovo genoma
          #Lo script.sh crea un file nella cartella Genomes chiamato 'status_creation_nomegenoma.txt' dove nomegenoma è ref o enr in base alla
          #selezione dell'utente
          #In questo script faccio echo dei vari step in cui sono in questo momento con l'analisi, in modo poi da leggere questo file e con un interval
          #dire all'utente a che punto siamo
          print('Button clicked')
          subprocess.Popen([app_main_directory + 'PostProcess/aggiunta_genoma_test_script.sh', current_working_directory])
          return ''

`def suggestComparison(value)`

  Update to Checked the Option 'Compare your results with the corresponding reference genome' if an Enriched Genome is selected ('+' is in the name of the genome)

  Args

  - [value] available-genome (value): the name of the genome selected by the user in the Dropdown

  Returns

  - checkbox-ref-comp [checked]: True if an Enriched genome is selected
  Expand source code

      @app.callback(
          Output('checkbox-ref-comp', 'checked'),
          [Input('available-genome', 'value')]
      )
      def suggestComparison(value):
          '''
          Update to Checked the Option 'Compare your results with the corresponding reference genome' if an Enriched Genome is selected ('+' is in the
          name of the genome)

          ***Args***

          + [**value**] **available-genome** (*value*): the name of the genome selected by the user in the Dropdown

          ***Returns***

          + **checkbox-ref-comp** [*checked*]: True if an Enriched genome is selected
          '''
          if value is None:
              raise PreventUpdate
          if '+' in value:
              return True
          raise PreventUpdate

`def supportFilterHistory(result_df, genome_f, pam_f)`
  Expand source code

      def supportFilterHistory(result_df, genome_f, pam_f):
          if genome_f is not None:
              result_df.drop(result_df[(result_df['Genome'] != genome_f)].index, inplace = True)
          if pam_f is not None:
              keep_values = []
              for index, row in result_df.iterrows():
                  if row.PAM not in pam_f:
                      keep_values.append(index)
              result_df.drop(labels= keep_values, inplace = True)

          max_page = len(result_df.index)
          max_page = math.floor(max_page / 10) + 1
          return result_df, max_page

`def test_page()`
  Expand source code

      def test_page():
          final_list = []
          final_list.append(html.Div(id='test-div-for-button'))
          final_list.append(
              html.H3('Genomes')
          )
          final_list.append(
              html.P('Select one of the two available Tabs and fill in the field to add a new Genome or to update and existing dictionary.')
          )

          #Check if an already processing add new genome is currently active. If yes, set the button to be disabled
          already_processing = False
          status_label = 'Status: No Job Submitted'
          status_value = 0
          style_disabled = {}
          if isfile(current_working_directory + 'Genomes/' + 'aggiunta_nuovo_genoma.txt' ):
              already_processing = True
              style_disabled = {'background-color':'darkgrey'}
              with open(current_working_directory + 'Genomes/' + 'aggiunta_nuovo_genoma.txt') as info_status:
                  status_general = next(info_status).strip().split(' ') #Get current step number [0] and step name [1] on accessing page for first time
                  status_label = 'Status: ' + status_general[1]
                  status_value = int(status_general[0]) * 25

          new_genome_content = html.Div(
              [
                  html.Br(),
                  html.P('1) Select a Reference Genome'),
                  html.Br(),
                  dbc.Row(
                      [
                          dbc.Col(
                              [
                                  dbc.Row(
                                      [
                                          dbc.Col(html.Button('Select Reference Genome Directory', id = 'button-select-refgenome')),
                                          dbc.Col(html.P('Selected: None', id = 'selected-referencegenome'))
                                      ]
                                  )
                              ]
                          ),
                          dbc.Col(
                              [
                                  dbc.Row(
                                      [
                                          dbc.Col(html.Button('Select PAM File', id = 'button-select-pam')),
                                          dbc.Col(html.P('Selected: None', id = 'selected-pamfile'))
                                      ]
                                  )
                              ]
                          ),

                      ]
                  ),
                  html.Br(),
                  dbc.Row(
                      [
                          dbc.Col(
                              [
                                  dbc.Row(
                                      [
                                          dbc.Col(html.Button('Select Annotation File', id = 'button-select-annotation')),
                                          dbc.Col(html.P('Selected: None', id = 'selected-annotationfile'))
                                      ]
                                  ),

                              ]
                          ),
                          dbc.Col(
                                  dbc.Row(
                                      [
                                          dbc.Col(dcc.Input(placeholder = 'Select number of max Bulges', type = 'number', min = 0, id = 'input-max-bulges'))
                                      ]
                                  )
                              )

                      ]
                  ),
                  html.Hr(),
                  html.P('2) (Optional) Enrich the previously selected Reference Genome with samples informations'),
                  html.Br(),
                  dbc.Row(
                      [
                          dbc.Col(
                              [
                                  dbc.Row(
                                      [
                                          dbc.Col(html.Button('Select VCFs Directory', id = 'button-select-vcf')),
                                          dbc.Col(html.P('Selected: None', id = 'selected-vcf'))
                                      ]
                                  )
                              ]
                          ),
                          dbc.Col(
                              [
                                  dbc.Row(
                                      [
                                          dbc.Col(html.Button('Select Samples ID File', id = 'button-select-sampleID')),
                                          dbc.Col(html.P('Selected: None', id = 'selected-sampleIDfile'))
                                      ]
                                  ),

                              ]
                          )
                      ]
                  ),
                  html.Br(),
                  dbc.Row(
                      [
                          dbc.Col(
                              dbc.Row(
                                  [
                                      dbc.Col(html.P('Enriched Genome Name')),
                                      dbc.Col(dcc.Input(placeholder = 'Example: 1000genomeproject', id = 'input-enriched-name'))
                                  ]
                              )
                          )
                      ]
                  ),
                  html.Hr(),
                  dbc.Row(
                      [
                          dbc.Col(
                              html.Button('Start add new genome', id = 'button-add-new-genome', disabled = already_processing, style = style_disabled)
                          ),
                          dbc.Col(
                              [
                                  dbc.Row(
                                      dbc.Col(
                                          html.P(status_label, id = 'status-add-new-genome')
                                      )
                                  ),
                                  dbc.Row(
                                      dbc.Col(
                                          html.Div(
                                              [
                                                  dbc.Progress(value = status_value, id = 'progress-add-new-genome'),
                                                  dcc.Interval(interval = 30*1000,id='interval-add-new-genome')
                                              ]
                                          )
                                      )
                                  )
                              ]
                          )
                      ]
                  )
              ]
          )

          update_dictionary_content = html.Div() #TODO finire con layout simile a quello per aggiungere nuovo genoma

          final_list.append(
              dbc.Tabs(
                  [
                      dbc.Tab(new_genome_content, label='Add New Genome', tab_id= 'add-genome-tab'),
                      dbc.Tab(update_dictionary_content, label='Update Dictionary', tab_id = 'update-dictionary')
                  ],
                  active_tab='add-genome-tab',
                  id = 'tabs-new-genome-or-dictionary'
              )
          )

          final_list.append(html.Div(id = 'div-targetoutput', style = {'display':'none'}))
          return html.Div(final_list, style = {'margin':'1%'})

`def toggleCollapseDistributionPopulations(n, is_open)`

  Open/Close the section containing the Population Distributions barplots.

  Args

  - [n] btn-collapse-populations (n_clicks): int for the number of times the button was clicked
  - [is_open] collapse-populations (is_open): bool for the status of the component (True: is open, False: is closed)

  Returns

  - collapse-populations (is_open): True to show, False to hide
  Expand source code

      @app.callback(
          Output("collapse-populations", "is_open"),
          [Input("btn-collapse-populations", "n_clicks")],
          [State("collapse-populations", "is_open")],
      )
      def toggleCollapseDistributionPopulations(n, is_open):
          '''
          Open/Close the section containing the Population Distributions barplots.

          ***Args***

          + [**n**] **btn-collapse-populations** (*n_clicks*): int for the number of times the button was clicked
          + [**is_open**] **collapse-populations** (*is_open*): bool for the status of the component (True: is open, False: is closed)

          ***Returns***

          + **collapse-populations** (*is_open*): True to show, False to hide
          '''
          if n:
              return not is_open
          return is_open

`def toggle_fade(selected_options, is_in)`

  Manages the fading of the InputBox for the email when the option 'Notify me by email' is selected/deselected.

  Args

  - [selected_options] checklist-advanced (value): list of IDs of the options checked
  - [is_in] fade (is_in): True if the Input email element is displayed, False otherwise

  Returns

  - fade (is_in): True in order to show the Input email element, False to hide it
  Expand source code

      @app.callback(
          Output("fade", "is_in"),
          [Input("checklist-advanced", "value")],
          [State("fade", "is_in")],
      )
      def toggle_fade(selected_options, is_in):
          '''
          Manages the fading of the InputBox for the email when the option 'Notify me by email' is selected/deselected.

          ***Args***

          + [**selected_options**] **checklist-advanced** (*value*): list of IDs of the options checked
          + [**is_in**] **fade** (*is_in*): True if the Input email element is displayed, False otherwise

          ***Returns***

          + **fade** (*is_in*): True in order to show the Input email element, False to hide it
          '''
          if  selected_options is None:
              return False
          if 'email' in selected_options:
              return True
          return False

`def updateContentTab(value, sel_cel, all_guides, search, genome_type)`

  Load the layout based on the selected Tab in the '/result' page

  Args

  - [value] tabs-reports (value): string representing the ID of the active Tab
  - [sel_cel] general-profile-table (selected_cells): list contaning the selected row of the main guide table
  - [all_guides] general-profile-table (data): list of all the rows that are currenty displayed in the main guide table
  - [search] url (search): string containing the job ID, eg '?job=QV99PN6XDL'
  - [genome_type] div-genome-type (children): string containing the type of the search ('ref', 'var' or 'both')

  Returns

  - div-tab-content (children): return the layout based on the selected Tab:
    - 'tab-summary-by-guide': show the general table of the targets divided by Bulge and Mismatch values
    - 'tab-summary-by-sample': show the general table of the targets divided by sample
    - 'tab-summary-by-position': show the general table of the targets divided by chromosome and position
    - 'tab-summary-graphical': show the graphical report. A total of 10 Buttons, one for each mms value, are created, but only some of them are set to be visible (the number of mms selected by the user)
  Expand source code

      @app.callback(
          Output('div-tab-content', 'children'),
          [Input('tabs-reports', 'value'),
          Input('general-profile-table','selected_cells')],
          [State('general-profile-table', 'data'),
          State('url', 'search'),
          State('div-genome-type', 'children')]
      )
      def updateContentTab(value, sel_cel, all_guides, search, genome_type):
          '''
          Load the layout based on the selected Tab in the '/result' page

          ***Args***

          + [**value**] **tabs-reports** (*value*): string representing the ID of the active Tab
          + [**sel_cel**] **general-profile-table** (*selected_cells*): list contaning the selected row of the main guide table
          + [**all_guides**] **general-profile-table** (*data*): list of all the rows that are currenty displayed in the main guide table
          + [**search**] **url** (*search*): string containing the job ID, eg '?job=QV99PN6XDL'
          + [**genome_type**] **div-genome-type** (*children*): string containing the type of the search ('ref', 'var' or 'both')

          ***Returns***

          + **div-tab-content** (*children*): return the layout based on the selected Tab:
              + 'tab-summary-by-guide': show the general table of the targets divided by Bulge and Mismatch values
              + 'tab-summary-by-sample': show the general table of the targets divided by sample
              + 'tab-summary-by-position': show the general table of the targets divided by chromosome and position
              + 'tab-summary-graphical': show the graphical report. A total of 10 Buttons, one for each mms value, are created, but only some of them
              are set to be visible (the number of mms selected by the user)
          '''

          if value is None or sel_cel is None or not sel_cel or not all_guides:
              raise PreventUpdate

          guide = all_guides[int(sel_cel[0]['row'])]['Guide']
          job_id = search.split('=')[-1]
          job_directory = current_working_directory + 'Results/' + job_id + '/'

          with open(current_working_directory + 'Results/' + job_id + '/Params.txt') as p:
              all_params = p.read()
              mms = (next(s for s in all_params.split('\n') if 'Mismatches' in s)).split('\t')[-1]
              genome_selected = (next(s for s in all_params.split('\n') if 'Genome_selected' in s)).split('\t')[-1]
              max_bulges = (next(s for s in all_params.split('\n') if 'Max_bulges' in s)).split('\t')[-1]

          fl = []
          fl.append(html.Br())
          fl.append(html.H5('Focus on: ' + guide))

          if value == 'tab-summary-by-guide': #BUG se cambio guida selezionata due volte mi cambia il mms mettendo a 0, provare con un div nascosto
              #Show Summary by Guide table
              fl.append(
                  html.P(
                      ['Summary table counting the number of targets found in the Enriched Genome for each combination of Bulge Type, Bulge Size and Mismatch. Select \'Show Targets\' to view the corresponding list of targets. ',
                      # html.A('Click here', href = URL + '/data/' + job_id + '/' + job_id + '.targets.' + guide + '.zip' ,target = '_blank', id = 'download-full-list' ), ' to download the full list of targets.'
                      ]
                      )
              )
              fl.append(
                  html.Div(
                      html.Button(html.A('Download Full list of targets', href = URL + '/data/' + job_id + '/' + job_id + '.targets.' + guide + '.zip' ,target = '_blank', style = {'text-decoration':'none', 'color':'black'} )),
                      style = {'display':DISPLAY_ONLINE}
                  )
              )
              fl.append(
                  html.Div(
                      html.Button('Open Result Directory', id = 'button-open-result-directory'),
                      style = {'display':DISPLAY_OFFLINE}
                  )
              )
              fl.append(html.Div(guide,id = 'div-open-result-directory', style = {'display':'none'}))
              fl.append(html.Br())
              df = pd.read_pickle(job_directory + job_id + '.summary_by_guide.' + guide + '.txt')
              if genome_type == 'both':
                  df.drop( df[(df['Bulge Size'] == 0) & ((df['Bulge Type'] == 'DNA') | ((df['Bulge Type'] == 'RNA'))) | ((df['Targets in Reference'] == 0) & (df['Targets in Enriched'] == 0))  ].index, inplace = True)
              elif genome_type == 'var':
                  df.drop( df[(df['Bulge Size'] == 0) & ((df['Bulge Type'] == 'DNA') | ((df['Bulge Type'] == 'RNA'))) | ((df['Targets in Enriched'] == 0))  ].index, inplace = True)
                  del df['Targets in Reference']
              else:
                  df.drop( df[(df['Bulge Size'] == 0) & ((df['Bulge Type'] == 'DNA') | ((df['Bulge Type'] == 'RNA'))) | ((df['Targets in Reference'] == 0))  ].index, inplace = True)
              more_info_col = []
              total_col = []
              for i in range(df.shape[0]):
                  more_info_col.append('Show Targets')
                  total_col.append(df['Bulge Size'])

              #Swap pam creation and Combined column
              if genome_type == 'both':   #TODO fixare per var e ref
                  df['Combined'] = df['Targets in Reference'] + df['Targets in Enriched']
                  #['Guide' 'Bulge Type' 'Bulge Size' 'Mismatches' 'Targets in Reference', 'Targets in Enriched' 'PAM Creation' 'Combined']
                  df = df[['Guide' , 'Bulge Type', 'Mismatches', 'Bulge Size' , 'Targets in Reference', 'Targets in Enriched', 'Combined', 'PAM Creation']]
              df[''] = more_info_col
              df['Total'] = df['Bulge Size'] + df['Mismatches']
              if genome_type == 'both' and genome_type == 'var':
                  df = df.sort_values(['Total', 'Targets in Enriched'], ascending = [True, False])
              else:
                  df = df.sort_values('Total', ascending = True)
              del df['Total']
              del df['Guide']
              fl.append(html.Div(
                      generate_table(df, 'table-summary-by-guide', genome_type, guide, job_id ), style = {'text-align': 'center'}
                  )
              )
              return fl
          elif value == 'tab-summary-by-sample':
              #Show Summary by Sample table
              fl.append(
                  html.P('Summary table counting the number of targets found in the Enriched Genome for each sample. Filter the table by selecting the Population or Superpopulation desired from the dropdowns.')
              )
              if genome_type == 'both':
                  col_names_sample = ['Sample', 'Gender', 'Population', 'Super Population',  'Targets in Reference', 'Targets in Enriched', 'Targets in Population', 'Targets in Super Population', 'PAM Creation', 'Class']
                  df = pd.read_csv(job_directory + job_id + '.summary_by_samples.' + guide + '.txt', sep = '\t', names = col_names_sample, skiprows = 1)
                  df = df.sort_values('Targets in Enriched', ascending = False)
                  df.drop(['Targets in Reference'], axis = 1, inplace = True)
              else:
                  col_names_sample = ['Sample', 'Gender', 'Population', 'Super Population',  'Targets in Reference', 'Targets in Enriched', 'Targets in Population', 'Targets in Super Population', 'PAM Creation', 'Class']
                  df = pd.read_csv(job_directory + job_id + '.summary_by_samples.' + guide + '.txt', sep = '\t', names = col_names_sample, skiprows = 1)
                  df = df.sort_values('Targets in Enriched', ascending = False)
                  df.drop(['Targets in Reference'], axis = 1, inplace = True)
                  df.drop(['Class'], axis = 1, inplace = True)
              more_info_col = []
              for i in range(df.shape[0]):
                  more_info_col.append('Show Targets')
              df[''] = more_info_col

              population_1000gp = associateSample.loadSampleAssociation(job_directory + 'sampleID.txt')[2]
              super_populations = [{'label':i, 'value':i} for i in population_1000gp.keys()]
              populations = []
              for k in population_1000gp.keys():
                  for i in population_1000gp[k]:
                      populations.append({'label':i, 'value':i})
              fl.append(
                  html.Div
                      (
                          [
                              dbc.Row(
                                  [
                                      dbc.Col(html.Div(dcc.Dropdown(options = super_populations, id = 'dropdown-superpopulation-sample', placeholder = 'Select a Super Population'))),
                                      dbc.Col(html.Div(dcc.Dropdown(options = populations, id = 'dropdown-population-sample', placeholder = 'Select a Population'))),
                                      #dbc.Col(html.Div(dcc.Dropdown( id = 'dropdown-sample', placeholder = 'Select a Sample'))),
                                      dbc.Col(html.Div(dcc.Input(id = 'input-sample', placeholder = 'Select a Sample' ))),
                                      dbc.Col(html.Div(html.Button('Filter', id = 'button-filter-population-sample')))
                                  ]
                              ),
                          ],
                          style = {'width':'50%'}
                      )
              )
              fl.append(html.Div('None,None,None',id = 'div-sample-filter-query', style = {'display':'none'})) #Folr keep current filter:  Superpop,Pop
              fl.append(html.Div(
                      generate_table_samples(df, 'table-samples', 1, guide, job_id ), style = {'text-align': 'center'}, id = 'div-table-samples'
                  )
              )
              fl.append(
                  html.Div(
                      [
                          html.Button('Prev', id = 'prev-page-sample'),
                          html.Button('Next', id = 'next-page-sample')
                      ],
                      style = {'text-align': 'center'}
                  )
              )
              max_page = len(df.index)
              max_page = math.floor(max_page / 10) + 1
              fl.append(html.Div('1/' + str(max_page), id= 'div-current-page-table-samples'))
              return fl
          elif value == 'tab-summary-by-position':
              #Show Summary by position table
              fl.append(
                  html.P('Summary table containing all the targets found in a specific position of the genome. For each position, the enriched target with the lowest Mismatch + Bulge count is shown (if no target was found in the Enriched Genome, the correspondig reference one is shown), along with his Mismatch and Bulge Size values.' +
                  ' The subtable \'Targets in Cluster by Mismatch Value\' represents the number of targets found in that position for a particular Mismatch-Bulge Size pair.')
              )

              fl.append(
                  html.P('Filter the table by selecting the chromosome of interest and writing the start and/or end position of the region to view.')
              )
              #Dropdown chromosomes
              try:
                  onlyfile = [f for f in listdir(current_working_directory + 'Genomes/' + genome_selected) if (isfile(join(current_working_directory + 'Genomes/' + genome_selected, f)) and (f.endswith('.fa') or f.endswith('.fasta')))]
              except:
                  onlyfile = ['chr' + str(i) + '.fa' for i in range(1,23)]
                  onlyfile.append('chrX.fa')
                  onlyfile.append('chrY.fa')                                              #NOTE in case no chr in GENOMES/ i put 22 chr + X Y M
                  onlyfile.append('chrM.fa')
              onlyfile = [x[:x.rfind('.')] for x in onlyfile]            #removed .fa for better visualization
              chr_file = []
              chr_file_unset = []
              for chr_name in onlyfile:
                  chr_name = chr_name.replace('.enriched', '')
                  if '_' in chr_name:
                      chr_file_unset.append(chr_name)
                  else:
                      chr_file.append(chr_name)
              #Show first 'normal' chromosomes (chr1) then the others (chr1_KI270706v1_random)
              chr_file.sort(key = lambda s: [int(t) if t.isdigit() else t.lower() for t in re.split(r'(\d+)', s)])
              chr_file_unset.sort(key = lambda s: [int(t) if t.isdigit() else t.lower() for t in re.split(r'(\d+)', s)])
              chr_file += chr_file_unset
              chr_file = [{'label': chr_name, 'value' : chr_name} for chr_name in chr_file]

              # Colonne tabella: chr, pos, target migliore, min mm, min bulges, num target per ogni categoria di mm e bulge, show targets; ordine per total, poi mm e poi bulge
              start_time = time.time()
              df = pd.read_csv( job_directory + job_id + '.summary_by_position.' + guide +'.txt', sep = '\t')
              df.rename(columns = {'#Chromosome':'Chromosome'}, inplace = True)
              more_info_col = []
              for i in range(df.shape[0]):
                  more_info_col.append('Show Targets')
              df[''] = more_info_col
              fl.append(
                  html.Div
                      (
                          [
                              dbc.Row(
                                  [
                                      dbc.Col(html.Div(dcc.Dropdown(options = chr_file, id = 'dropdown-chr-table-position', placeholder = 'Select a chromosome'))),
                                      dbc.Col(html.Div(dcc.Input(placeholder = 'Start Position', id = 'input-position-start'))),
                                      dbc.Col(html.Div(dcc.Input(placeholder = 'End Position', id = 'input-position-end'))),
                                      dbc.Col(html.Div(html.Button('Filter', id = 'button-filter-position')))
                                  ]
                              ),
                          ],
                          style = {'width':'50%'}
                      )
              )
              fl.append(html.Div('None,None,None',id = 'div-position-filter-query', style = {'display':'none'})) #Folr keep current filter:  chr,pos_start,pos_end
              start_time = time.time()
              fl.append(html.Div(
                      generate_table_position(df, 'table-position', 1 , int(mms), int(max_bulges), guide, job_id ), style = {'text-align': 'center'}, id = 'div-table-position'
                  )
              )
              fl.append(
                  html.Div(
                      [
                          html.Button('Prev', id = 'prev-page-position'),
                          html.Button('Next', id = 'next-page-position')
                      ],
                      style = {'text-align': 'center'}
                  )
              )
              max_page = len(df.index)
              max_page = math.floor(max_page / 10) + 1
              fl.append(html.Div('1/' + str(max_page), id= 'div-current-page-table-position'))
              fl.append(html.Div(mms + '-' + max_bulges, id = 'div-mms-bulges-position', style = {'display':'none'}))
              return fl
          else:
              #Show Report images
              samp_style = {}
              if genome_type == 'ref':
                  samp_style = {'display':'none'}

              fl.append(html.Br())

              fl_buttons = []
              for i in range (10):
                  if (i <= int(mms)): #TODO change into (i <= (int(mms) + int(max_bulges)))
                      fl_buttons.append(
                          html.Button(str(i) + ' mm',id = 'btn' + str(i)),
                      )
                  else:
                      fl_buttons.append(
                          html.Button(str(i) + ' mm',id = 'btn' + str(i), style = {'display':'none'}), #Hide buttons for mms non selected
                      )

              fl.append(html.Br())

              radar_img = 'summary_single_guide_' + guide + '_' + str(0) + 'mm.png'

              barplot_img = 'summary_histogram_' + guide + '_' + str(0) + 'mm.png'
              try:            #NOTE serve per non generare errori se il barplot non è stato fatto
                  barplot_src = 'data:image/png;base64,{}'.format(base64.b64encode(open(current_working_directory + 'Results/' + job_id + '/' + barplot_img, 'rb').read()).decode())
              except:
                  barplot_src = ''
              try:
                  barplot_href = 'assets/Img/' + job_id + '/' + barplot_img
              except:
                  barplot_href = ''

              try:
                  radar_src = 'data:image/png;base64,{}'.format(base64.b64encode(open(current_working_directory + 'Results/' + job_id + '/' + radar_img, 'rb').read()).decode())
              except:
                  radar_src = ''
              try:
                  radar_href = 'assets/Img/' + job_id + '/' + radar_img
              except:
                  radar_href = ''

              if genome_type != 'ref':
                  population_1000gp = associateSample.loadSampleAssociation(job_directory + 'sampleID.txt')[2]

                  super_populations = [{'label':i, 'value':i} for i in population_1000gp.keys()]
                  populations = []
                  for k in population_1000gp.keys():
                      for i in population_1000gp[k]:
                          populations.append({'label':i, 'value':i})
              else:
                  super_populations = []
                  populations = []

              fl.append(
                  html.Div(
                      [   dbc.Row(
                              [
                                  dbc.Col(
                                      [
                                          html.P('Select Mismatch Value'),
                                          dbc.Row(
                                              html.Div(fl_buttons),
                                          )
                                      ]
                                  ),
                                  dbc.Col(
                                      [
                                          html.P('Select Individual Data', style = samp_style ),
                                          dbc.Row([
                                          dbc.Col(html.Div(dcc.Dropdown(options = super_populations, id = 'dropdown-superpopulation-sample', placeholder = 'Select a Super Population', style = samp_style))),
                                          dbc.Col(html.Div(dcc.Dropdown(options = populations, id = 'dropdown-population-sample', placeholder = 'Select a Population', style = samp_style))),
                                          dbc.Col(html.Div(dcc.Dropdown( id = 'dropdown-sample', placeholder = 'Select a Sample', style = samp_style))),
                                          ])
                                      ]
                                  )
                              ]
                          ),
                      ]
                  )
              )
              fl.append(html.Hr())
              fl.append(
                      html.Div(
                          [
                              dbc.Row(
                                  [
                                      dbc.Col([    #Guide part
                                          html.Div(
                                              [

                                                  dbc.Row(html.Br()),
                                                  dbc.Row(
                                                      [
                                                          dbc.Col(
                                                              html.A(
                                                                  html.Img(src = radar_src, id = 'barplot-img-guide', width="100%", height="auto"),
                                                                  target="_blank",
                                                                  href = radar_href
                                                              ),
                                                              width = 10
                                                          )
                                                      ]
                                                  ),
                                                  dbc.Row(html.Br()),
                                                  dbc.Row(
                                                      [
                                                          dbc.Col(
                                                              html.A(
                                                                  html.Img(src = barplot_src,id = 'radar-img-guide', width="100%", height="auto"),
                                                                  target="_blank",
                                                                  href = barplot_href
                                                              ),
                                                              width = 10
                                                          )
                                                      ]
                                                  )

                                              ],
                                              id = 'div-guide-image'
                                          )
                                      ]),
                                      dbc.Col([    #Sample part
                                          html.Div(
                                              [

                                              ],
                                              id = 'div-sample-image'
                                          )
                                      ])
                                  ]
                              )
                          ]

                      )
                  )

              fl.append(html.Br())
              fl.append(html.Br())

              #TODO codice per l'integrazione del CFD graph. When .CFDGraph.txt will be integrated, remove the try/except

              cfd_path = job_directory + job_id +'.CFDGraph.txt'
              if not isfile(cfd_path): #No file found
                  return fl

              fl.extend(
                  CFDGraph.CFDGraph(cfd_path)
              )

              return fl
          # guide = all_guides[int(sel_cel[0]['row'])]['State']
              # return guide + value
          raise PreventUpdate

`def updateGenomePageTable(page_current, page_size, sort_by, filter)`
  Expand source code

      @app.callback(
          Output('genomes-table', 'data'),
          [Input('genomes-table', "page_current"),
           Input('genomes-table', "page_size"),
           Input('genomes-table', 'sort_by'),
           Input('genomes-table', 'filter_query')])
      def updateGenomePageTable(page_current, page_size, sort_by, filter):
          filtering_expressions = filter.split(' && ')
          dff = get_genomes.get_genomes(current_working_directory)
          for filter_part in filtering_expressions:
              col_name, operator, filter_value = split_filter_part(filter_part)

              if operator in ('eq', 'ne', 'lt', 'le', 'gt', 'ge'):
                  # these operators match pandas series operator method names
                  dff = dff.loc[getattr(dff[col_name], operator)(filter_value)]
              elif operator == 'contains':
                  dff = dff.loc[dff[col_name].str.contains(filter_value)]
              elif operator == 'datestartswith':
                  # this is a simplification of the front-end filtering logic,
                  # only works with complete fields in standard format
                  dff = dff.loc[dff[col_name].str.startswith(filter_value)]

          if len(sort_by):
              dff = dff.sort_values(
                  [col['column_id'] for col in sort_by],
                  ascending=[
                      col['direction'] == 'asc'
                      for col in sort_by
                  ],
                  inplace=False
              )

          page = page_current
          size = page_size
          return dff.iloc[page * size: (page + 1) * size].to_dict('records')

`def updateHistoryFilter(n, genome, pam)`
  Expand source code

      @app.callback(
          Output('div-history-filter-query', 'children'),
          [Input('button-filter-history', 'n_clicks')],
          [State('dropdown-genomes-history', 'value'),
          State('dropdown-pam-history', 'value')]
      )
      def updateHistoryFilter(n, genome, pam):
          if n is None:
              raise PreventUpdate
          return str(genome) + ',' + str(pam)

`def updateImagesTabs(n0, n1, n2, n3, n4, n5, n6, n7, n8, n9, superpopulation, population, sample, sel_cel, search, all_guides)`

  Loads the images on the Graphical Report based on the selected mismatch value and/or sample, population, superpopulation value.

  Args

  - [n_] btn_ (n_clicks_timestam): string of the timestamp of the last time the button was pressed.
  - [superpopulation] dropdown-superpopulation-sample (value): string of the selected superpopulation
  - [population] dropdown-population-sample (value): string of the selected population
  - [sample] dropdown-sample (value): string of the selected sample
  - [sel_cel] general-profile-table (selected_cells): list contaning the selected row of the main guide table
  - [search] url (search): string containing the job ID, eg '?job=QV99PN6XDL'
  - [all_guides] general-profile-table (data): list of all the rows that are currenty displayed in the main guide table

  Returns

  - div-guide-image (children): returns barplot and radarchart of the selected guide
  - div-sample-image (children): returns radarchart specific for sample/pop/superpop of the selected guide

  Details

  - The function sort all the timestamps of the buttons (can be up to 10 buttons, one for each mms value) and select the one that was clicked last. Since it is not possible in the current Dash version to create callbacks of components that are not available in the app.layout, but the buttons are created dynamically (we do not know how many mms the user selects), we create 10 buttons and show only the ones corresponding to the number of selected mms, the others are hidden using `{'display':'none'}`. In this way we can create this callback even when we do not know how may mms the user will set.
  - If the image for a specific sample/pop/superpop is not available, the function calls `crispritz.py generate-report` in order to create the image
  - The corresponding image is then loaded from the `assets/Img/jobID` directory, in order to be also available to be opened in a new tab (by clicking on it)
  Expand source code

      @app.callback(
          [Output('div-guide-image', 'children'),
          Output('div-sample-image', 'children')],
          [Input('btn0', 'n_clicks_timestamp'),
          Input('btn1', 'n_clicks_timestamp'),
          Input('btn2', 'n_clicks_timestamp'),
          Input('btn3', 'n_clicks_timestamp'),
          Input('btn4', 'n_clicks_timestamp'),
          Input('btn5', 'n_clicks_timestamp'),
          Input('btn6', 'n_clicks_timestamp'),
          Input('btn7', 'n_clicks_timestamp'),
          Input('btn8', 'n_clicks_timestamp'),
          Input('btn9', 'n_clicks_timestamp'),
          Input('dropdown-superpopulation-sample', 'value'),
          Input('dropdown-population-sample', 'value'),
          Input('dropdown-sample', 'value'),
          Input('general-profile-table', 'selected_cells')],
          [State('url', 'search'),
          State('general-profile-table', 'data')]
      )
      def updateImagesTabs(n0, n1, n2, n3, n4, n5, n6, n7, n8, n9, superpopulation, population, sample, sel_cel, search, all_guides):
          '''
          Loads the images on the Graphical Report based on the selected mismatch value and/or sample, population, superpopulation value.

          ***Args***

          + [**n_**] **btn_** (*n_clicks_timestam*): string of the timestamp of the last time the button was pressed.
          + [**superpopulation**] **dropdown-superpopulation-sample** (*value*): string of the selected superpopulation
          + [**population**] **dropdown-population-sample** (*value*): string of the selected population
          + [**sample**] **dropdown-sample** (*value*): string of the selected sample
          + [**sel_cel**] **general-profile-table** (*selected_cells*): list contaning the selected row of the main guide table
          + [**search**] **url** (*search*): string containing the job ID, eg '?job=QV99PN6XDL'
          + [**all_guides**] **general-profile-table** (*data*): list of all the rows that are currenty displayed in the main guide table

          ***Returns***

          + **div-guide-image** (*children*): returns barplot and radarchart of the selected guide
          + **div-sample-image** (*children*): returns radarchart specific for sample/pop/superpop of the selected guide

          ***Details***

          + The function sort all the timestamps of the buttons (can be up to 10 buttons, one for each mms value) and select the one that was clicked last.
          Since it is not possible in the current Dash version to create callbacks of components that are not available in the app.layout, but the buttons
          are created dynamically (we do not know how many mms the user selects), we create 10 buttons and show only the ones corresponding to the number
          of selected mms, the others are hidden using `{'display':'none'}`. In this way we can create this callback even when we do not know how may mms
          the user will set.
          + If the image for a specific sample/pop/superpop is not available, the function calls `crispritz.py generate-report` in order to create the image
          + The corresponding image is then loaded from the `assets/Img/jobID` directory, in order to be also available to be opened in a new tab (by
          clicking on it)
          '''

          if sel_cel is None :
              raise PreventUpdate
          job_id = search.split('=')[-1]
          job_directory = current_working_directory + 'Results/' + job_id + '/'
          guide = all_guides[int(sel_cel[0]['row'])]['Guide']

          guide_images = []
          sample_images = []
          with open(current_working_directory + 'Results/' + job_id + '/Params.txt') as p:
              all_params = p.read()
              gecko_comp = (next(s for s in all_params.split('\n') if 'Gecko' in s)).split('\t')[-1]

          gecko_string = ''
          if gecko_comp == 'True':
              gecko_string = '-gecko'

          #Initialize to 0 Buttons not pressed
          if not n0:
              n0 = 0
          if not n1:
              n1 = 0
          if not n2:
              n2 = 0
          if not n3:
              n3 = 0
          if not n4:
              n4 = 0
          if not n5:
              n5 = 0
          if not n6:
              n6 = 0
          if not n7:
              n7 = 0
          if not n8:
              n8 = 0
          if not n9:
              n9 = 0
          btn_group = []
          btn_group.append(n0)
          btn_group.append(n1)
          btn_group.append(n2)
          btn_group.append(n3)
          btn_group.append(n4)
          btn_group.append(n5)
          btn_group.append(n6)
          btn_group.append(n7)
          btn_group.append(n8)
          btn_group.append(n9)

          #Check last pressed button
          if max(btn_group) == n0:
              mm_show = 0
          if max(btn_group) == n1:
              mm_show = 1
          if max(btn_group) == n2:
              mm_show = 2
          if max(btn_group) == n3:
              mm_show = 3
          if max(btn_group) == n4:
              mm_show = 4
          if max(btn_group) == n5:
              mm_show = 5
          if max(btn_group) == n6:
              mm_show = 6
          if max(btn_group) == n7:
              mm_show = 7
          if max(btn_group) == n8:
              mm_show = 8
          if max(btn_group) == n9:
              mm_show = 9
          if max(btn_group) == 0:
              mm_show = 0
          radar_img = 'summary_single_guide_' + guide + '_' + str(mm_show) + 'mm.png'

          barplot_img = 'summary_histogram_' + guide + '_' + str(mm_show) + 'mm.png'
          try:            #NOTE if the image is not available, show nothing
              barplot_src = 'data:image/png;base64,{}'.format(base64.b64encode(open(current_working_directory + 'Results/' + job_id + '/' + barplot_img, 'rb').read()).decode())
          except:
              barplot_src = ''
          try:
              barplot_href = 'assets/Img/' + job_id + '/' + barplot_img
          except:
              barplot_href = ''

          try:
              radar_src = 'data:image/png;base64,{}'.format(base64.b64encode(open(current_working_directory + 'Results/' + job_id + '/' + radar_img, 'rb').read()).decode())
          except:
              radar_src = ''
          try:
              radar_href = 'assets/Img/' + job_id + '/' + radar_img
          except:
              radar_href = ''

          guide_images.extend([

              dbc.Row(html.Br()),
              dbc.Row(
                  [
                      dbc.Col(
                          html.A(
                              html.Img(src = radar_src, id = 'radar-img-guide', width="100%", height="auto"),
                              target="_blank",
                              href = radar_href
                          ),
                          width = 10
                      )
                  ]
              ),
              dbc.Row(html.Br()),
              dbc.Row(
                  [
                      dbc.Col(
                          html.A(
                              html.Img(src = barplot_src,id = 'barplot-img-guide', width="100%", height="auto"),
                              target="_blank",
                              href = barplot_href
                          ),
                          width = 10
                      )
                  ]
              )
          ])
          class_images = [(sample, 'Samples'), (population, 'Population'), (superpopulation, 'Superpopulation')]

          #If the user selects a sample/pop/superpop not already processed, call crispritz generate-report and create the image
          for c in class_images:
              if c[0]:
                  try:
                      first_img_source = 'data:image/png;base64,{}'.format(base64.b64encode(open(job_directory + 'summary_single_guide_' + c[0] +'_' + guide + '_' + str(mm_show) + 'mm.png', 'rb').read()).decode())
                  except:
                      #create image from annotation file of samples
                      subprocess.call(['cd '+job_directory +';'+
                      ' crispritz.py generate-report ' + guide + ' -mm ' + str(mm_show) + ' -annotation ' + job_id + '.sample_annotation.' + guide +
                      '.' + c[1].lower() + '.txt '+ gecko_string +' -ws -sample ' + c[0]], shell = True)

                      copy_img = subprocess.Popen(['cp ' + job_directory + 'summary_single_guide_' + c[0] +'_' + guide + '_' + str(mm_show) + 'mm.png assets/Img/' + job_id], shell = True)
                      copy_img.wait() #BUG se metto il copia link, mi si ricarica la pagina, forse il problema non c'è se uso gnicorn
                      first_img_source = 'data:image/png;base64,{}'.format(base64.b64encode(open(job_directory + 'summary_single_guide_' + c[0] +'_' + guide + '_' + str(mm_show) + 'mm.png', 'rb').read()).decode())
                  try:
                      first_img_href = 'assets/Img/' + job_id + '/' + 'summary_single_guide_' + c[0] +'_' + guide + '_' + str(mm_show) + 'mm.png'
                  except:
                      first_img_href = ''
                  sample_images.append(dbc.Row(html.Br()))

                  sample_images.append(
                      dbc.Row(
                      [
                          dbc.Col(
                              html.A(
                                  html.Img(src = first_img_source, width="100%", height="auto"),
                                  target="_blank",
                                  href = first_img_href
                              ),
                              width = 10
                          ),
                      ],
                      no_gutters=True
                  ),
                  )
          return guide_images, sample_images

`def updatePopulationDrop(superpop, search)`

  Update the Dropdown containing the Population list based on the selected Superpopulation.

  Args

  - [superpop] dropdown-superpopulation-sample (value): string of the selected Superpopulation
  - [search] url (search): string containing the job ID, eg '?job=QV99PN6XDL'

  Returns

  - dropdown-population-sample (options): list of dictionaries for the option element of the Population dropdown
  - dropdown-population-sample (value): currently selected value (reinitialized to be empty)
  Expand source code

      @app.callback(
          [Output('dropdown-population-sample', 'options'),
          Output('dropdown-population-sample', 'value')],
          [Input('dropdown-superpopulation-sample', 'value')],
          [State('url', 'search')]
      )
      def updatePopulationDrop(superpop, search):
          '''
          Update the Dropdown containing the Population list based on the selected Superpopulation.

          ***Args***

          + [**superpop**] **dropdown-superpopulation-sample** (*value*): string of the selected Superpopulation
          + [**search**] **url** (*search*): string containing the job ID, eg '?job=QV99PN6XDL'

          ***Returns***

          + **dropdown-population-sample** (*options*): list of dictionaries for the option element of the Population dropdown
          + **dropdown-population-sample** (*value*): currently selected value (reinitialized to be empty)
          '''
          if superpop is None or superpop == '':
              raise PreventUpdate
          job_id = search.split('=')[-1]
          job_directory = current_working_directory + 'Results/' + job_id + '/'
          population_1000gp = associateSample.loadSampleAssociation(job_directory + 'sampleID.txt')[2]
          return [{'label':i, 'value':i} for i in population_1000gp[superpop]], None

`def updatePositionFilter(n, chr, pos_start, pos_end)`

  Update the filter (chr,start,end) for the filtering of the Summary by Position table

  Args

  - [n] button-filter-population-sample (n_clicks): int for the number of times the button was clicked
  - [chr] dropdown-chr-table-position (value): string containing the selected chromosome
  - [pos_start] input-position-start (value): string containing the start position
  - [pos_end] input-position-end (value): string containing the end position

  Returns

  - div-position-filter-query (children): string in the format of 'chr,start,end', or 'None' if a parameter was not provided (eg 'chr4,1010,None')
  Expand source code

      @app.callback(
          Output('div-position-filter-query', 'children'),
          [Input('button-filter-position', 'n_clicks')],
          [State('dropdown-chr-table-position', 'value'),
          State('input-position-start', 'value'),
          State('input-position-end', 'value')]
      )
      def updatePositionFilter(n, chr, pos_start, pos_end):
          '''
          Update the filter (chr,start,end) for the filtering of the Summary by Position table

          ***Args***

          + [**n**] **button-filter-population-sample** (*n_clicks*): int for the number of times the button was clicked
          + [**chr**] **dropdown-chr-table-position** (*value*): string containing the selected chromosome
          + [**pos_start**] **input-position-start** (*value*): string containing the start position
          + [**pos_end**] **input-position-end** (*value*): string containing the end position

          ***Returns***

          + **div-position-filter-query** (**children**): string in the format of 'chr,start,end', or 'None' if a parameter was not provided (eg
          'chr4,1010,None')
          '''
          if n is None:
              raise PreventUpdate
          if pos_start == '':
              pos_start = 'None'
          if pos_end == '':
              pos_end = 'None'
          return str(chr) + ',' + str(pos_start) + ',' + str(pos_end)

`def updateSampleDrop(pop, search)`

  Update the Dropdown containing the Sample list based on the selected Population.

  Args

  - [pop] dropdown-population-sample (value): string of the selected Population
  - [search] url (search): string containing the job ID, eg '?job=QV99PN6XDL'

  Returns

  - dropdown-sample (options): list of dictionaries for the option element of the Sample dropdown
  - dropdown-sample (value): currently selected value (reinitialized to be empty)
  Expand source code

      @app.callback(
          [Output('dropdown-sample','options'),
          Output('dropdown-sample','value')],
          [Input('dropdown-population-sample', 'value')],
          [State('url', 'search')]
      )
      def updateSampleDrop(pop, search):
          '''
          Update the Dropdown containing the Sample list based on the selected Population.

          ***Args***

          + [**pop**] **dropdown-population-sample** (*value*): string of the selected Population
          + [**search**] **url** (*search*): string containing the job ID, eg '?job=QV99PN6XDL'

          ***Returns***

          + **dropdown-sample** (*options*): list of dictionaries for the option element of the Sample dropdown
          + **dropdown-sample** (*value*): currently selected value (reinitialized to be empty)
          '''
          if pop is None or pop == '':
              return [], None
          job_id = search.split('=')[-1]
          job_directory = current_working_directory + 'Results/' + job_id + '/'
          dict_pop = associateSample.loadSampleAssociation(job_directory + 'sampleID.txt')[3]
          return [{'label': sam, 'value' : sam} for sam in dict_pop[pop]], None

`def updateSampleFilter(n, superpopulation, population, sample)`

  Update the filter (superpop,pop,sample) for the filtering of the Summary by Sample table

  Args

  - [n] button-filter-population-sample (n_clicks): int for the number of times the button was clicked
  - [superpopulation] dropdown-superpopulation-sample (value): string containing the selected Superpopulation
  - [population] dropdown-population-sample (value): string containing the selected Population
  - [sample] input-sample (value): string containing the Sample, given in input by the user

  Returns

  - div-simple-filter-query (children): string in the format of 'superpop,pop,sample', or 'None' if a parameter was not provided (eg 'EU,TSI,None')
  Expand source code

      @app.callback(
          Output('div-sample-filter-query', 'children'),
          [Input('button-filter-population-sample', 'n_clicks')],
          [State('dropdown-superpopulation-sample', 'value'),
          State('dropdown-population-sample', 'value'),
          State('input-sample', 'value')]
      )
      def updateSampleFilter(n, superpopulation, population, sample):
          '''
          Update the filter (superpop,pop,sample) for the filtering of the Summary by Sample table

          ***Args***

          + [**n**] **button-filter-population-sample** (*n_clicks*): int for the number of times the button was clicked
          + [**superpopulation**] **dropdown-superpopulation-sample** (*value*): string containing the selected Superpopulation
          + [**population**] **dropdown-population-sample** (*value*): string containing the selected Population
          + [**sample**] **input-sample** (*value*): string containing the Sample, given in input by the user

          ***Returns***

          + **div-simple-filter-query** (**children**): string in the format of 'superpop,pop,sample', or 'None' if a parameter was not provided (eg
          'EU,TSI,None')
          '''
          if n is None:
              raise PreventUpdate
          return str(superpopulation) + ',' + str(population) + ',' + str(sample).replace(' ','').upper()

`def updateStatusCreateNewGenome(n, n_button)`
  Expand source code

      @app.callback(
          [Output('progress-add-new-genome', 'value'),
          Output('status-add-new-genome','children'),
          Output('button-add-new-genome', 'disabled'),
          Output('button-add-new-genome', 'style')],
          [Input('interval-add-new-genome','n_intervals'),
          Input('button-add-new-genome','n_clicks')]
      )
      def updateStatusCreateNewGenome(n, n_button):
          if n is None and n_button is None:
              raise PreventUpdate

          context = dash.callback_context.triggered[0]['prop_id'].split('.')[0] #id of input that triggered callback
          if context == 'button-add-new-genome':
              return 0, 'Status: Copy Genome', True,  {'background-color':'darkgrey'}
          #TODO questa funzione legge il file creato dalla funzione startAddNewGenome e in base allo step in cui siamo (Copia file, indicizzazione, enrichment etc)
          #ritorna in output una valore della barra (eg +25 per ogni step fatto) e lo step a cui siamo (eg Status: Enrichment Genome)

          #controlla se esiste il file aggiunta nuovo genoma, e aggiorna la barra e mette il bottone a Disabled = True
          if isfile(current_working_directory + 'Genomes/aggiunta_nuovo_genoma.txt'):
              with open(current_working_directory + 'Genomes/aggiunta_nuovo_genoma.txt') as info_status:
                  a = next(info_status).strip().split(' ') #Get current step number [0] and step name [1]
                  if 'Done' == a[1]:
                      subprocess.run(['rm', current_working_directory + 'Genomes/' + 'aggiunta_nuovo_genoma.txt'])
                      return 100, 'Status: Done', False, {}
              return str(int(a[0]) * 25), 'Status: ' + a[1], True,  {'background-color':'darkgrey'}
          return 0 , 'Status: No Job Submitted', False, {}

`def update_dict(nUpd)`

  Bottone per avviare la GUI in Tkinter per l'aggiornamento di dizionari

  Expand source code

      @app.callback(
          Output("dict-job","value"),
          [Input("update-dict","n_clicks")],
      )
      def update_dict(nUpd):
          """
          Bottone per avviare la GUI in Tkinter per l'aggiornamento di dizionari
          """
          from GUI import UpdateDict as ud
          if nUpd is None:
              raise PreventUpdate
          ud.startUpdateDict(current_working_directory)
          return ''

`def update_iupac_scomposition_table_cluster(page_current, page_size, sort_by, filter, search, hash)`
  Expand source code

      @app.callback(
          Output('table-scomposition-cluster','data'),
          [Input('table-scomposition-cluster', "page_current"),
           Input('table-scomposition-cluster', "page_size"),
           Input('table-scomposition-cluster', 'sort_by'),
           Input('table-scomposition-cluster', 'filter_query')],
          [State('url', 'search'),
           State('url', 'hash')]
      )
      def update_iupac_scomposition_table_cluster(page_current, page_size, sort_by, filter, search, hash):
          job_id = search.split('=')[-1]
          job_directory = current_working_directory + 'Results/' + job_id + '/'
          hash = hash.split('#')[1]
          guide = hash[:hash.find('-Pos-')]
          chr_pos = hash[hash.find('-Pos-') + 5:]
          chromosome = chr_pos.split('-')[0]
          position = chr_pos.split('-')[1]

          with open(current_working_directory + 'Results/' + job_id + '/Params.txt') as p:
              all_params = p.read()
              genome_type_f = (next(s for s in all_params.split('\n') if 'Genome_selected' in s)).split('\t')[-1]
              ref_comp = (next(s for s in all_params.split('\n') if 'Ref_comp' in s)).split('\t')[-1]

          genome_type = 'ref'
          if '+' in genome_type_f:
              genome_type = 'var'
          if 'True' in ref_comp:
              genome_type = 'both'

          if genome_type == 'ref':
              raise PreventUpdate

          filtering_expressions = filter.split(' && ')
          dff = global_store_general(current_working_directory + 'Results/'+ job_id + '/' + job_id + '.' + chromosome + '_' + position + '.' + guide +'.scomposition.txt')
          if dff is None:
              raise PreventUpdate

          # #Grep annotation
          # file_to_grep = '.Annotation.targets.txt'
          # get_annotation = subprocess.Popen(['LC_ALL=C fgrep ' + guide + ' ' + current_working_directory + 'Results/'+ job_id + '/' + job_id + file_to_grep + ' |  awk \'$6==' + position + ' && $4==\"' + chromosome + '\"\''], shell = True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
          # out, err = get_annotation.communicate()
          # annotation_type = out.decode('UTF-8').strip().split('\t')[-1]

          # if genome_type == 'var':
          #     dff.rename(columns = {0:'Bulge Type', 1:'crRNA', 2:'DNA', 3:'Chromosome', 4:'Position', 5:'Cluster Position', 6:'Direction',
          #     7:'Mismatches', 8:'Bulge Size', 9:'Total', 10:'Min Mismatches', 11:'Max Mismatches', 12:'Samples', 13:'Correct Guide', 14:'Annotation Type',15:'Top Subcluster'}, inplace = True)
          # else:
          dff.rename(columns = COL_BOTH_RENAME , inplace = True)
          # dff.drop(dff.columns[[-1,]], axis=1, inplace=True)         #NOTE Drop the Correct Guide column
          del dff['Correct Guide']
          #dff['Annotation Type'] = annotation_type
          del dff['Variant Unique']
          for filter_part in filtering_expressions:
              col_name, operator, filter_value = split_filter_part(filter_part)

              if operator in ('eq', 'ne', 'lt', 'le', 'gt', 'ge'):
                  # these operators match pandas series operator method names
                  dff = dff.loc[getattr(dff[col_name], operator)(filter_value)]
              elif operator == 'contains':
                  dff = dff.loc[dff[col_name].str.contains(filter_value)]
              elif operator == 'datestartswith':
                  # this is a simplification of the front-end filtering logic,
                  # only works with complete fields in standard format
                  dff = dff.loc[dff[col_name].str.startswith(filter_value)]

          if len(sort_by):
              dff = dff.sort_values(
                  ['Samples' if col['column_id'] == 'Samples Summary' else col['column_id'] for col in sort_by],
                  ascending=[
                      col['direction'] == 'asc'
                      for col in sort_by
                  ],
                  inplace=False
              )

          #Calculate sample count

          data_to_send=dff.iloc[
              page_current*page_size:(page_current+ 1)*page_size
          ].to_dict('records')
          if genome_type != 'ref':
              dict_sample_to_pop, dict_pop_to_superpop = associateSample.loadSampleAssociation(job_directory + 'sampleID.txt')[:2]
              for row in data_to_send:
                  summarized_sample_cell = dict()
                  for s in row['Samples'].split(','):
                      if s == 'n':
                          break     #If a target have n, it means it's REF, because either all have samples or the single target is REF
                      try:
                          summarized_sample_cell[dict_pop_to_superpop[dict_sample_to_pop[s]]] += 1
                      except:
                          summarized_sample_cell[dict_pop_to_superpop[dict_sample_to_pop[s]]] = 1
                  if summarized_sample_cell:
                      row['Samples Summary'] = ', '.join([str(summarized_sample_cell[sp]) + ' ' + sp for sp in summarized_sample_cell])
                  else:
                      row['Samples Summary'] = 'n'
          return data_to_send

`def update_table(page_current, page_size, sort_by, filter, search, hash_guide)`

  La funzione ritorna uno split dei risultati in base ad un filtering o a un sort da parte dell'utente. Inoltre aggiorna i risultati visualizzati quando il bottone next page / prev page è cliccato. (Codice preso dalla pagina dash datatable sul sorting con python) Inoltre carica i file targets, o scores se presente, e lo trasforma in un dataframe, cambiando il nome delle colonne per farle corrispondere all'id delle colonne della tabella nella pagina. Se non ci sono targets ritorna un avviso di errore

  Expand source code

      @app.callback(
          Output('result-table', 'data'),
          [Input('result-table', "page_current"),
           Input('result-table', "page_size"),
           Input('result-table', "sort_by"),
           Input('result-table', 'filter_query')],
           [State('url', 'search'),
           State('url', 'hash')]
      )
      def update_table(page_current, page_size, sort_by, filter, search, hash_guide):
          '''
          La funzione ritorna uno split dei risultati in base ad un filtering o a un sort da parte dell'utente. Inoltre aggiorna i risultati
          visualizzati quando il bottone next page / prev page è cliccato. (Codice preso dalla pagina dash datatable sul sorting con python)
          Inoltre carica i file targets, o scores se presente, e lo trasforma in un dataframe, cambiando il nome delle colonne per farle corrispondere
          all'id delle colonne della tabella nella pagina.
          Se non ci sono targets ritorna un avviso di errore
          '''
          job_id = search.split('=')[-1]
          job_directory = current_working_directory + 'Results/' + job_id + '/'
          guide = hash_guide.split('#')[1]
          value = job_id
          if search is None:
              raise PreventUpdate

          filtering_expressions = filter.split(' && ')
          #filtering_expressions.append(['{crRNA} = ' + guide])
          df = global_store(value)
          dff = df[df['crRNA'] == guide]

          sort_by.insert(0, {'column_id' : 'Mismatches', 'direction': 'asc'})
          sort_by.insert(1, {'column_id' : 'BulgeSize', 'direction': 'asc'})
          #sort_by.insert(2, {'column_id': 'CFD', 'direction':'desc'})
          for filter_part in filtering_expressions:
              col_name, operator, filter_value = split_filter_part(filter_part)

              if operator in ('eq', 'ne', 'lt', 'le', 'gt', 'ge'):
                  # these operators match pandas series operator method names
                  dff = dff.loc[getattr(dff[col_name], operator)(filter_value)].sort_values([col['column_id'] for col in sort_by],
                  ascending=[
                      col['direction'] == 'asc'
                      for col in sort_by
                  ],
                  inplace=False)
              elif operator == 'contains':
                  dff = dff.loc[dff[col_name].str.contains(filter_value)]
              elif operator == 'datestartswith':
                  # this is a simplification of the front-end filtering logic,
                  # only works with complete fields in standard format
                  dff = dff.loc[dff[col_name].str.startswith(filter_value)]

          #NOTE sort_by: [{'column_id': 'BulgeType', 'direction': 'asc'}, {'column_id': 'crRNA', 'direction': 'asc'}]
          #sort_by.insert(0, {'column_id' : 'Mismatches', 'direction': 'asc'})
          #sort_by.insert(0, {'column_id' : 'BulgeSize', 'direction': 'asc'})
          if len(sort_by):
              dff = dff.sort_values(
                  ['Samples' if col['column_id'] == 'Samples Summary' else col['column_id'] for col in sort_by],
                  ascending=[
                      col['direction'] == 'asc'
                      for col in sort_by
                  ],
                  inplace=False
              )

          #Check if results are not 0
          warning_no_res = ''
          with open(job_directory + job_id + '.targets.txt') as t:
              no_result = False
              t.readline()
              last_line = t.readline()
              if (last_line == '' or last_line == '\n'):
                  no_result = True

          if (no_result):
              warning_no_res = dbc.Alert("No results were found with the given parameters", color = "warning")

          return dff.iloc[
              page_current*page_size:(page_current+ 1)*page_size
          ].to_dict('records')

`def update_table_cluster(page_current, page_size, sort_by, filter, hide_reference, search, hash)`
  Expand source code

      @app.callback(
          Output('table-position-target', 'data'),
          [Input('table-position-target', "page_current"),
           Input('table-position-target', "page_size"),
           Input('table-position-target', 'sort_by'),
           Input('table-position-target', 'filter_query'),
           Input('hide-reference-targets', 'value')],
          [State('url', 'search'),
           State('url', 'hash')]
      )
      def update_table_cluster(page_current, page_size, sort_by, filter, hide_reference, search, hash):
          job_id = search.split('=')[-1]
          job_directory = current_working_directory + 'Results/' + job_id + '/'
          hash = hash.split('#')[1]
          guide = hash[:hash.find('-Pos-')]
          chr_pos = hash[hash.find('-Pos-') + 5:]
          chromosome = chr_pos.split('-')[0]
          position = chr_pos.split('-')[1]

          with open(current_working_directory + 'Results/' + job_id + '/Params.txt') as p:
              all_params = p.read()
              genome_type_f = (next(s for s in all_params.split('\n') if 'Genome_selected' in s)).split('\t')[-1]
              ref_comp = (next(s for s in all_params.split('\n') if 'Ref_comp' in s)).split('\t')[-1]

          genome_type = 'ref'
          if '+' in genome_type_f:
              genome_type = 'var'
          if 'True' in ref_comp:
              genome_type = 'both'

          filtering_expressions = filter.split(' && ')
          dff = global_store_general(current_working_directory + 'Results/' + job_id + '/' + job_id + '.' + chromosome + '_' + position + '.' + guide +  '.txt')
          if dff is None:
              raise PreventUpdate

          if genome_type == 'ref':
              dff.rename(columns = COL_REF_RENAME, inplace = True)
          else:
              dff.rename(columns =COL_BOTH_RENAME , inplace = True)

          if genome_type != 'ref':
              # add_samples = [dff['Samples'][0]] * dff.shape[0]
              # check_minmms = dff['Min Mismatches']
              # if dff['Variant Unique'][0] != 'y' and dff['PAM Creation'][0] == 'n':
              #     for pos_minmms, minmms in enumerate(check_minmms):
              #         if minmms == '-':
              #             add_samples[pos_minmms] = 'n'
              # dff['Samples'] = add_samples
              del dff['Variant Unique']
              # dff.drop(dff.head(1).index, inplace=True)       #Remove first target, that is the top1 with no iupac (lowest mm of scomposed target) and is
                                                          #needed only for summary by guide, not the show target part
                                                          #NOTE 18/03 removed all the iupac char, the first line is needed to be shown
          # dff['Annotation Type'] = list(dff['Annotation Type'])[0]
          del dff['Correct Guide']

          if 'hide-ref' in hide_reference or genome_type == 'var':
              dff.drop( dff[(dff['Samples'] == 'n')].index, inplace = True)

          if 'hide-cluster' in hide_reference:
              dff = dff.head(1)

          for filter_part in filtering_expressions:
              col_name, operator, filter_value = split_filter_part(filter_part)

              if operator in ('eq', 'ne', 'lt', 'le', 'gt', 'ge'):
                  # these operators match pandas series operator method names
                  dff = dff.loc[getattr(dff[col_name], operator)(filter_value)]
              elif operator == 'contains':
                  dff = dff.loc[dff[col_name].str.contains(filter_value)]
              elif operator == 'datestartswith':
                  # this is a simplification of the front-end filtering logic,
                  # only works with complete fields in standard format
                  dff = dff.loc[dff[col_name].str.startswith(filter_value)]

          if len(sort_by):
              dff = dff.sort_values(
                  ['Samples' if col['column_id'] == 'Samples Summary' else col['column_id'] for col in sort_by],
                  ascending=[
                      col['direction'] == 'asc'
                      for col in sort_by
                  ],
                  inplace=False
              )

          #Calculate sample count

          data_to_send=dff.iloc[
              page_current*page_size:(page_current+ 1)*page_size
          ].to_dict('records')
          if genome_type != 'ref':
              dict_sample_to_pop, dict_pop_to_superpop = associateSample.loadSampleAssociation(job_directory + 'sampleID.txt')[:2]
              for row in data_to_send:
                  summarized_sample_cell = dict()
                  for s in row['Samples'].split(','):
                      if s == 'n':
                          break
                      try:
                          summarized_sample_cell[dict_pop_to_superpop[dict_sample_to_pop[s]]] += 1
                      except:
                          summarized_sample_cell[dict_pop_to_superpop[dict_sample_to_pop[s]]] = 1
                  if summarized_sample_cell:
                      row['Samples Summary'] = ', '.join([str(summarized_sample_cell[sp]) + ' ' + sp for sp in summarized_sample_cell])
                  else:
                      row['Samples Summary'] = 'n'
          return data_to_send

`def update_table_general_profile(page_current, page_size, sort_by, filter, search)`

  The function loads the data to be shown into the table.

  Args

  - [page_current] general-profile-table (page_current): int of the current page to show
  - [page_size] general-profile-table (page_size): int of the maximum number of row for each page
  - [sort_by] general-profile-table (sort_by): list dictionaries of column IDs and sorting direction
  - [filter] general-profile-table (filter_query): list dictionaries for the data filtering
  - [search] url (search): string containing the job ID, eg '?job=QV99PN6XDL'

  Returns

  - general-profile-table (data): dictionary of the data to be shown
  - general-profile-table (selected_cells): dictionary containing the selected cell, default at first row
  Expand source code

      @app.callback(
          [Output('general-profile-table', 'data'),
          Output('general-profile-table', 'selected_cells')],
          [Input('general-profile-table', "page_current"),
           Input('general-profile-table', "page_size"),
           Input('general-profile-table', 'sort_by'),
           Input('general-profile-table', 'filter_query')],
          [State('url', 'search')]
      )
      def update_table_general_profile(page_current, page_size, sort_by, filter, search):
          '''
          The function loads the data to be shown into the table.

          ***Args***

          + [**page_current**] **general-profile-table** (*page_current*): int of the current page to show
          + [**page_size**] **general-profile-table** (*page_size*): int of the maximum number of row for each page
          + [**sort_by**] **general-profile-table** (*sort_by*): list dictionaries of column IDs and sorting direction
          + [**filter**] **general-profile-table** (*filter_query*): list dictionaries for the data filtering
          + [**search**] **url** (*search*): string containing the job ID, eg '?job=QV99PN6XDL'

          ***Returns***

          + **general-profile-table** (*data*): dictionary of the data to be shown
          + **general-profile-table** (*selected_cells*): dictionary containing the selected cell, default at first row
          '''
          job_id = search.split('=')[-1]

          with open(current_working_directory + 'Results/' + job_id + '/Params.txt') as p:
              all_params = p.read()
              genome_type_f = (next(s for s in all_params.split('\n') if 'Genome_selected' in s)).split('\t')[-1]
              ref_comp = (next(s for s in all_params.split('\n') if 'Ref_comp' in s)).split('\t')[-1]
              mms = int((next(s for s in all_params.split('\n') if 'Mismatches' in s)).split('\t')[-1])
              max_bulges = int((next(s for s in all_params.split('\n') if 'Max_bulges' in s)).split('\t')[-1])

          genome_type = 'ref'
          if '+' in genome_type_f:
              genome_type = 'var'
          if 'True' in ref_comp:
              genome_type = 'both'

          filtering_expressions = filter.split(' && ')

          #Get error guides
          list_error_guides = []
          if os.path.exists(current_working_directory + 'Results/' + job_id + '/guides_error.txt'):
              with open(current_working_directory + 'Results/' + job_id + '/guides_error.txt') as error_g:
                  for e_g in error_g:
                      list_error_guides.append(e_g.strip())

          #Get guide from guide.txt
          with open(current_working_directory + 'Results/' + job_id + '/guides.txt') as g:
              guides = g.read().strip().split('\n')
              guides.sort()

          #load acfd for each guide
          with open(current_working_directory + 'Results/' + job_id + '/acfd.txt') as a:
              all_scores = a.read().strip().split('\n')

          #Load scores
          if 'NO SCORES' not in all_scores:
              all_scores.sort()
              acfd = [float(a.split('\t')[1]) for a in all_scores if a.split('\t')[0] not in list_error_guides]
              doench = [int(a.split('\t')[2]) for a in all_scores if a.split('\t')[0] not in list_error_guides]
              if genome_type == 'both':
                  doench_enr = [int(a.split('\t')[3]) for a in all_scores if a.split('\t')[0] not in list_error_guides]
              acfd  = [int(round((100/(100 + x))*100)) for x in acfd]

          #Get target counting from summary by guide
          column_on_target = []
          column_off_target_ref = []
          column_sample_class = []
          column_total = []
          if genome_type == 'ref' or genome_type == 'var':
              for g in guides:
                  one_to_n_mms = []
                  df_profile = pd.read_pickle(current_working_directory + 'Results/' + job_id + '/' + job_id + '.summary_by_guide.' + g + '.txt')
                  on_t_ref = int(df_profile[(df_profile.Mismatches == 0) & (df_profile['Bulge Type'] == 'X')].iloc[0]['Targets in Reference'])
                  try: #For VAR, read enriched values
                      on_t_enr = int(df_profile[(df_profile.Mismatches == 0) & (df_profile['Bulge Type'] == 'X')].iloc[0]['Targets in Enriched'])
                      column_on_target.append(str(on_t_enr))
                  except: #For REF, read only reference values
                      column_on_target.append(str(on_t_ref))

                  for i in range (1, mms + 1 + int(max_bulges)):         #For column Targets for 1-2 Total (Mismatches + Bulges), sum values for row with same total
                      try:    #For VAR
                          one_to_n_mms.append(sum(df_profile[((df_profile['Mismatches'] + df_profile['Bulge Size']) == i)]['Targets in Enriched'].to_list()))
                      except: #For REF
                          one_to_n_mms.append(sum(df_profile[((df_profile['Mismatches'] + df_profile['Bulge Size']) == i)]['Targets in Reference'].to_list()))
                  column_total.append(int(sum(one_to_n_mms)))
                  column_off_target_ref.append([int(x) for x in one_to_n_mms])  #[[1, 5, 10], [3, 7, 20]]       each internal list is the number of off-target for a guide, divided by value (from 1 to mm+max_bulge)
          else:
              #NOTE  USO IL CONTEGGIO PRESO DA jobid.general_target_count.txt
              with open(current_working_directory + 'Results/' + job_id + '/' + job_id + '.general_target_count.txt') as general_count:
                  header_general = next(general_count) #skip header
                  general_count_content = general_count.read().strip().split('\n')
                  general_count_content.sort(key = lambda x : x[0])
              guides = []
              column_on_target_old = dict()      #will contain GUIDE -> 3(1 - 2), then later, in column_on_target, only the 1 will be kept
              for tmp in general_count_content:
                  tmp = tmp.split('\t')   #[GUIDE, total_onT (onT_ref - onTvar), total_offT_ref ( - - - ), total_offT_var ( - - -)]
                  guides.append(tmp[0])
                  column_on_target_old[tmp[0]] = tmp[1]      #tmp[1] =  3(1 - 2)
                  a = tmp[2].replace('(','').replace(')','').replace('-','').replace('  ',' ').strip().split(' ') # ottengo in a[0] il total, da a[1:] il numero di targets per 1 2 3 ... Total del REF
                  b = tmp[3].replace('(','').replace(')','').replace('-','').replace('  ',' ').strip().split(' ') # ottengo in a[0] il total, da a[1:] il numero di targets per 1 2 3 ... Total del VAR
                  column_total.append(a[0] + '\n' + b[0] + '\n' + str(int(a[0]) + int(b[0])))
                  column_off_target_ref.append(
                      [a[x] + '\n' + b[x] + '\n' + str(int(a[x]) + int(b[x])) for x in range(len(a))][1:]     #[1:] to skip total value
                  )   # in the end i obtain [ ['1\n3\n5', '12\n21\n54'] , ['0\n3\n4', '12\n25\n34']], one list per guide, inside each list string for each total value

              with open(current_working_directory + 'Results/' + job_id + '/' + job_id + '.SampleClasses.txt') as samp_classes:
                  header_classes = next(samp_classes).strip().split('\t')[1:]     #List of Guides
                  for line in samp_classes:
                      if 'Total for Class' in line:       #Last line
                          value_classes = line.strip().split('\t')[1:]           #List of values of classes for each guide
              #NOTE just for changing '-' to ' - '
              for pos_vc, vc in enumerate(value_classes):
                  value_classes[pos_vc] = ' - '.join(vc.split('-'))

              dict_classes = dict(zip(header_classes, value_classes))
              column_on_target = []
              for g in guides:
                  column_sample_class.append(dict_classes[g])
                  column_on_target.append(column_on_target_old[g].split('(')[-1].split('-')[0])

          data_guides = dict()
          data_guides['Guide'] = guides
          if 'NO SCORES' not in all_scores:
              data_guides['CFD'] = acfd
              if genome_type == 'both':
                  data_guides['Reference'] = doench
                  data_guides['Enriched'] = doench_enr
              else:
                  data_guides['Doench 2016'] = doench

          if genome_type == 'ref' or genome_type == 'both':
              data_guides['On-Targets Reference'] = column_on_target
              data_guides['Genome'] = 'REFERENCE'     #NOTE if genome_type == 'both', it will be updated
          else:
              data_guides['On-Targets Enriched'] = column_on_target
              data_guides['Genome'] = 'ENRICHED'

          if genome_type == 'both':
              data_guides['Samples in Class 0 - 0+ - 1 - 1+'] = column_sample_class
              data_guides['Genome'] = 'REFERENCE\nENRICHED\nCOMBINED'
          data_guides['Total'] = column_total
          for i in range (1, mms + int(max_bulges) + 1):  #add count target for each total value
              data_guides[str(i) + ' MM + B'] = [str(x[i-1]) for x in column_off_target_ref]      #NOTE i-1 since i starts from 1

          dff = pd.DataFrame(data_guides)

          if 'NO SCORES' not in all_scores:
              try:
                  dff = dff.sort_values(['CFD', 'Doench 2016'], ascending = [False, False])
              except: #for BOTH
                  dff = dff.sort_values(['CFD', 'Enriched'], ascending = [False, False])
          else:
              try:
                  dff = dff.sort_values('On-Targets Reference', ascending = True)
              except:
                  dff = dff.sort_values('On-Targets Enriched', ascending = True)

          for filter_part in filtering_expressions:
              col_name, operator, filter_value = split_filter_part(filter_part)

              if operator in ('eq', 'ne', 'lt', 'le', 'gt', 'ge'):
                  # these operators match pandas series operator method names
                  dff = dff.loc[getattr(dff[col_name], operator)(filter_value)]
              elif operator == 'contains':
                  dff = dff.loc[dff[col_name].str.contains(filter_value)]
              elif operator == 'datestartswith':
                  # this is a simplification of the front-end filtering logic,
                  # only works with complete fields in standard format
                  dff = dff.loc[dff[col_name].str.startswith(filter_value)]

          if len(sort_by):
              dff = dff.sort_values(
                  ['Samples' if col['column_id'] == 'Samples Summary' else col['column_id'] for col in sort_by],
                  ascending=[
                      col['direction'] == 'asc'
                      for col in sort_by
                  ],
                  inplace=False
              )

          data_to_send=dff.iloc[
              page_current*page_size:(page_current+ 1)*page_size
          ].to_dict('records')
          return data_to_send, [{'row':0, 'column':0}]

`def update_table_sample(page_current, page_size, sort_by, filter, search, hash)`
  Expand source code

      @app.callback(
          Output('table-sample-target', 'data'),
          [Input('table-sample-target', "page_current"),
           Input('table-sample-target', "page_size"),
           Input('table-sample-target', 'sort_by'),
           Input('table-sample-target', 'filter_query')],
          [State('url', 'search'),
           State('url', 'hash')]
      )
      def update_table_sample(page_current, page_size, sort_by, filter, search, hash):
          job_id = search.split('=')[-1]
          job_directory = current_working_directory + 'Results/' + job_id + '/'
          hash = hash.split('#')[1]
          guide = hash[:hash.find('-Sample-')]
          sample = hash[hash.rfind('-') + 1:]
          with open(current_working_directory + 'Results/' + job_id + '/Params.txt') as p:
              all_params = p.read()
              genome_type_f = (next(s for s in all_params.split('\n') if 'Genome_selected' in s)).split('\t')[-1]
              ref_comp = (next(s for s in all_params.split('\n') if 'Ref_comp' in s)).split('\t')[-1]

          genome_type = 'ref'
          if '+' in genome_type_f:
              genome_type = 'var'
          if 'True' in ref_comp:
              genome_type = 'both'

          filtering_expressions = filter.split(' && ')
          dff = global_store_general(current_working_directory + 'Results/'+ job_id + '/' + job_id + '.' + sample + '.' + guide + '.txt')
          if dff is None:
              raise PreventUpdate

          dff.rename(columns = COL_BOTH_RENAME , inplace = True)
          del dff['Correct Guide']         #NOTE Drop the Correct Guide column
          del dff['Variant Unique']
          for filter_part in filtering_expressions:
              col_name, operator, filter_value = split_filter_part(filter_part)

              if operator in ('eq', 'ne', 'lt', 'le', 'gt', 'ge'):
                  # these operators match pandas series operator method names
                  dff = dff.loc[getattr(dff[col_name], operator)(filter_value)]
              elif operator == 'contains':
                  dff = dff.loc[dff[col_name].str.contains(filter_value)]
              elif operator == 'datestartswith':
                  # this is a simplification of the front-end filtering logic,
                  # only works with complete fields in standard format
                  dff = dff.loc[dff[col_name].str.startswith(filter_value)]

          if len(sort_by):
              dff = dff.sort_values(
                  ['Samples' if col['column_id'] == 'Samples Summary' else col['column_id'] for col in sort_by],
                  ascending=[
                      col['direction'] == 'asc'
                      for col in sort_by
                  ],
                  inplace=False
              )

          #Calculate sample count
          dict_sample_to_pop, dict_pop_to_superpop = associateSample.loadSampleAssociation(job_directory + 'sampleID.txt')[:2]
          data_to_send=dff.iloc[
              page_current*page_size:(page_current+ 1)*page_size
          ].to_dict('records')
          for row in data_to_send:
              summarized_sample_cell = dict()
              for s in row['Samples'].split(','):
                  if s == 'n':
                      break
                  try:
                      summarized_sample_cell[dict_pop_to_superpop[dict_sample_to_pop[s]]] += 1
                  except:
                      summarized_sample_cell[dict_pop_to_superpop[dict_sample_to_pop[s]]] = 1
              if summarized_sample_cell:
                  row['Samples Summary'] = ', '.join([str(summarized_sample_cell[sp]) + ' ' + sp for sp in summarized_sample_cell])
              else:
                  row['Samples Summary'] = 'n'
          return data_to_send

`def update_table_subset(page_current, page_size, sort_by, filter, hide_reference, search, hash_guide)`

  Function that returns the data to be shown in the 'Show Targets' table of the Summary by Guide.

  Args

  - [page_current] table-subset-target (page_current): int of the current page to show
  - [page_size] table-subset-target (page_size): int of the maximum number of row for each page
  - [sort_by] table-subset-target (sort_by): list dictionaries of column IDs and sorting direction
  - [filter] table-subset-target (filter_query): list dictionaries for the data filtering
  - [hide_reference] hide-reference-targets (value): if the value is not None, then the Reference targets are removed from the shown data
  - [search] url (search): string containing the job ID, eg '?job=QV99PN6XDL'
  - [hash_guide] url (hash): string containing the informations about the selected 'Show Targets' row, eg '#CCATCGGTGGCCGTTTGCCCNNNnewDNA10'

  Returns

  - table-subset-target (data): dictionary of the data to be shown
  Expand source code

      @app.callback(
          Output('table-subset-target', 'data'),
          [Input('table-subset-target', "page_current"),
           Input('table-subset-target', "page_size"),
           Input('table-subset-target', "sort_by"),
           Input('table-subset-target', 'filter_query'),
           Input('hide-reference-targets', 'value')],
           [State('url', 'search'),
           State('url', 'hash')]
      )
      def update_table_subset(page_current, page_size, sort_by, filter, hide_reference, search, hash_guide):
          '''
          Function that returns the data to be shown in the 'Show Targets' table of the Summary by Guide.

          ***Args***

          + [**page_current**] **table-subset-target** (*page_current*): int of the current page to show
          + [**page_size**] **table-subset-target** (*page_size*): int of the maximum number of row for each page
          + [**sort_by**] **table-subset-target** (*sort_by*): list dictionaries of column IDs and sorting direction
          + [**filter**] **table-subset-target** (*filter_query*): list dictionaries for the data filtering
          + [**hide_reference**] **hide-reference-targets** (*value*): if the value is not None, then the Reference targets are removed from the shown data
          + [**search**] **url** (*search*): string containing the job ID, eg '?job=QV99PN6XDL'
          + [**hash_guide**] **url** (*hash*): string containing the informations about the selected 'Show Targets' row, eg '#CCATCGGTGGCCGTTTGCCCNNNnewDNA10'

          ***Returns***

          + **table-subset-target** (*data*): dictionary of the data to be shown

          '''
          job_id = search.split('=')[-1]
          job_directory = current_working_directory + 'Results/' + job_id + '/'
          with open(current_working_directory + 'Results/' + job_id + '/Params.txt') as p:
              all_params = p.read()
              genome_type_f = (next(s for s in all_params.split('\n') if 'Genome_selected' in s)).split('\t')[-1]
              ref_comp = (next(s for s in all_params.split('\n') if 'Ref_comp' in s)).split('\t')[-1]

          genome_type = 'ref'
          if '+' in genome_type_f:
              genome_type = 'var'
          if 'True' in ref_comp:
              genome_type = 'both'
          value = job_id
          if search is None:
              raise PreventUpdate
          filtering_expressions = filter.split(' && ')
          #filtering_expressions.append(['{crRNA} = ' + guide])
          guide = hash_guide[1:hash_guide.find('new')]
          mms = hash_guide[-1:]
          bulge_s = hash_guide[-2:-1]
          if 'DNA' in hash_guide:
              bulge_t = 'DNA'
          elif 'RNA' in hash_guide:
              bulge_t = 'RNA'
          else:
              bulge_t = 'X'
          df = global_store_subset(value, bulge_t, bulge_s, mms, guide)
          dff = df
          if genome_type == 'ref':
              dff.rename(columns = COL_REF_RENAME, inplace = True)
          else:
              dff.rename(columns = COL_BOTH_RENAME , inplace = True)

          if 'hide-ref' in hide_reference or genome_type == 'var':
              dff.drop( df[(df['Samples'] == 'n')].index, inplace = True)

          try:    #For VAR and BOTH
              del dff['Variant Unique']
          except: #For REF
              pass

          for filter_part in filtering_expressions:
              col_name, operator, filter_value = split_filter_part(filter_part)
              if col_name == 'Samples Summary':
                  col_name = 'Samples'
              if operator in ('eq', 'ne', 'lt', 'le', 'gt', 'ge'):
                  # these operators match pandas series operator method names
                  dff = dff.loc[getattr(dff[col_name], operator)(filter_value)]
              elif operator == 'contains':
                  dff = dff.loc[dff[col_name].str.contains(filter_value)]
              elif operator == 'datestartswith':
                  # this is a simplification of the front-end filtering logic,
                  # only works with complete fields in standard format
                  dff = dff.loc[dff[col_name].str.startswith(filter_value)]

          if len(sort_by):
              dff = dff.sort_values(
                  ['Samples' if col['column_id'] == 'Samples Summary' else col['column_id'] for col in sort_by],
                  ascending=[
                      col['direction'] == 'asc'
                      for col in sort_by
                  ],
                  inplace=False
              )

          cells_style = [

                              # {
                              # 'if': {
                              #         'filter_query': '{Variant Unique} eq y',
                              #         #'filter_query': '{Direction} eq +',
                              #         #'column_id' :'Bulge Type'
                              #     },
                              #     #'border-left': '5px solid rgba(255, 26, 26, 0.9)',
                              #     'background-color':'rgba(255, 0, 0,0.15)'#'rgb(255, 102, 102)'

                              # },
                              {
                                  'if': {
                                          'filter_query': '{Cluster Position} eq "' + guide + '"',
                                          #'column_id' :'{#Bulge type}',
                                          #'column_id' :'{Total}'
                                      },
                                      #'border-left': '5px solid rgba(255, 26, 26, 0.9)',
                                      'background-color':'rgba(0, 0, 255,0.15)'#'rgb(255, 102, 102)'

                              },

                              # {
                              # 'if': {
                              #         'filter_query': '{Chromosome} eq "chr2"',
                              #         #'filter_query': '{Direction} eq +',
                              #         #'column_id' :'Bulge Type'
                              #     },
                              #     #'border-left': '5px solid rgba(255, 26, 26, 0.9)',
                              #     'background-color':'rgba(255, 69, 0,0.15)'#'rgb(255, 102, 102)'

                              # },
                              # {
                              #     'if': {
                              #             'filter_query': '{Variant Unique} eq n',
                              #             'column_id' :'Bulge Type'
                              #         },
                              #         'border-left': '5px solid rgba(26, 26, 255, 0.9)',

                              # }

                      ]

          #Calculate sample count

          data_to_send=dff.iloc[
              page_current*page_size:(page_current+ 1)*page_size
          ].to_dict('records')
          if genome_type != 'ref':
              dict_sample_to_pop, dict_pop_to_superpop = associateSample.loadSampleAssociation(job_directory + 'sampleID.txt')[:2]
              for row in data_to_send:
                  summarized_sample_cell = dict()
                  for s in row['Samples'].split(','):
                      if s == 'n':
                          break
                      try:
                          summarized_sample_cell[dict_pop_to_superpop[dict_sample_to_pop[s]]] += 1
                      except:
                          summarized_sample_cell[dict_pop_to_superpop[dict_sample_to_pop[s]]] = 1
                  if summarized_sample_cell:
                      row['Samples Summary'] = ', '.join([str(summarized_sample_cell[sp]) + ' ' + sp for sp in summarized_sample_cell])
                  else:
                      row['Samples Summary'] = 'n'
          return data_to_send#, cells_style + style_data_table

`def update_table_subsetSecondTable(page_current, page_size, sort_by, filter, search, hash_guide, active_cel, data)`
  Expand source code

      @app.callback(
          [Output('second-table-subset-targets', 'data'),                         #Table showing iupac scomposition
          Output('second-table-subset-targets', 'style_data_conditional')],
          [Input('second-table-subset-targets', "page_current"),
           Input('second-table-subset-targets', "page_size"),
           Input('second-table-subset-targets', "sort_by"),
           Input('second-table-subset-targets', 'filter_query')],
           [State('url', 'search'),
           State('url', 'hash'),
           State('table-subset-target', 'active_cell'),
          State('table-subset-target', 'data')]
      )
      def update_table_subsetSecondTable(page_current, page_size, sort_by, filter, search, hash_guide, active_cel, data):
          #NOTE tabella secondaria della scomposizione ora non serve, non cancello il codice ma uso PreventUpdate per non azionare la funzione
          if False:
              raise PreventUpdate
          if active_cel is None:
              raise PreventUpdate
          job_id = search.split('=')[-1]
          job_directory = current_working_directory + 'Results/' + job_id + '/'
          with open(current_working_directory + 'Results/' + job_id + '/Params.txt') as p:
              all_params = p.read()
              genome_type_f = (next(s for s in all_params.split('\n') if 'Genome_selected' in s)).split('\t')[-1]
              ref_comp = (next(s for s in all_params.split('\n') if 'Ref_comp' in s)).split('\t')[-1]

          guide = hash_guide[1:hash_guide.find('new')]
          genome_type = 'ref'
          if '+' in genome_type_f:
              genome_type = 'var'
          if 'True' in ref_comp:
              genome_type = 'both'
          if search is None:
              raise PreventUpdate

          if genome_type == 'ref':
              raise PreventUpdate

          filtering_expressions = filter.split(' && ')
          bulge_t =  data[active_cel['row']]['Bulge Type']
          bulge_s = str(data[active_cel['row']]['Bulge Size'])
          mms = str(data[active_cel['row']]['Mismatches'])
          chrom = str(data[active_cel['row']]['Chromosome'])
          pos = str(data[active_cel['row']]['Cluster Position'])
          # annotation_type = str(data[active_cel['row']]['Annotation Type'])

          scomposition_file = job_directory + job_id + '.' + bulge_t + '.' + bulge_s + '.' + mms + '.' + guide + '.' + chrom + '.' + pos + '.scomposition.txt'
          file_to_grep = '.samples.annotation.txt'

          if not os.path.exists(scomposition_file):    #Example    job_id.X.0.4.GUIDE.chrom.position.scomposition.txt
              # subprocess.call(['LC_ALL=C fgrep ' + guide + ' ' + current_working_directory + 'Results/'+ job_id + '/' + job_id + file_to_grep + ' |  awk \'$6==' + pos + ' && $4==\"' + chrom + '\" && $8==' + mms + ' && $9==' + bulge_s +'\' > ' + scomposition_file], shell = True)
              subprocess.call(['LC_ALL=C fgrep ' + guide + ' ' + current_working_directory + 'Results/'+ job_id + '/' + job_id + file_to_grep + ' |  awk \'$6==' + pos + ' && $4==\"' + chrom + '\" && $9==' + bulge_s + ' && $13!=\"n\"' +'\' > ' + scomposition_file], shell = True)

          if os.path.getsize(scomposition_file) > 0:          #Check if result grep has at least 1 result
              df = pd.read_csv(scomposition_file, header = None, sep = '\t')
              # df['Annotation Type'] = annotation_type
          else:
              raise PreventUpdate

          df.rename(columns = COL_BOTH_RENAME , inplace = True)
          df.drop(df[(~( df['Cluster Position'] == int(data[active_cel['row']]['Cluster Position']))) | (~( df['Chromosome'] == data[active_cel['row']]['Chromosome']))].index, inplace = True)
          dff = df

          for filter_part in filtering_expressions:
              col_name, operator, filter_value = split_filter_part(filter_part)

              if operator in ('eq', 'ne', 'lt', 'le', 'gt', 'ge'):
                  # these operators match pandas series operator method names
                  dff = dff.loc[getattr(dff[col_name], operator)(filter_value)]
              elif operator == 'contains':
                  dff = dff.loc[dff[col_name].str.contains(filter_value)]
              elif operator == 'datestartswith':
                  # this is a simplification of the front-end filtering logic,
                  # only works with complete fields in standard format
                  dff = dff.loc[dff[col_name].str.startswith(filter_value)]

          if len(sort_by):
              dff = dff.sort_values(
                  ['Samples' if col['column_id'] == 'Samples Summary' else col['column_id'] for col in sort_by],
                  ascending=[
                      col['direction'] == 'asc'
                      for col in sort_by
                  ],
                  inplace=False
              )

          cells_style = [
                              # {
                              # 'if': {
                              #         'filter_query': '{Variant Unique} eq y',
                              #         #'filter_query': '{Direction} eq +',
                              #         #'column_id' :'Bulge Type'
                              #     },
                              #     #'border-left': '5px solid rgba(255, 26, 26, 0.9)',
                              #     'background-color':'rgba(255, 0, 0,0.15)'#'rgb(255, 102, 102)'

                              # },
                              {
                              'if': {
                                      'filter_query': '{Variant Unique} eq F',
                                      #'filter_query': '{Direction} eq +',
                                      #'column_id' :'Bulge Type'
                                  },
                                  #'border-left': '5px solid rgba(255, 26, 26, 0.9)',
                                  'background-color':'rgba(0, 0, 0,0.15)'#'rgb(255, 102, 102)'
                              }
                              # {
                              # 'if': {
                              #         'filter_query': '{Chromosome} eq "chr2"',
                              #         #'filter_query': '{Direction} eq +',
                              #         #'column_id' :'Bulge Type'
                              #     },
                              #     #'border-left': '5px solid rgba(255, 26, 26, 0.9)',
                              #     'background-color':'rgba(255, 69, 0,0.15)'#'rgb(255, 102, 102)'

                              # },
                              # {
                              #     'if': {
                              #             'filter_query': '{Variant Unique} eq n',
                              #             'column_id' :'Bulge Type'
                              #         },
                              #         'border-left': '5px solid rgba(26, 26, 255, 0.9)',

                              # }

                      ]

          #Calculate sample count

          data_to_send=dff.iloc[
              page_current*page_size:(page_current+ 1)*page_size
          ].to_dict('records')
          if genome_type != 'ref':
              dict_sample_to_pop, dict_pop_to_superpop = associateSample.loadSampleAssociation(job_directory + 'sampleID.txt')[:2]
              for row in data_to_send:
                  summarized_sample_cell = dict()
                  for s in row['Samples'].split(','):
                      if s == 'n':
                          break
                      try:
                          summarized_sample_cell[dict_pop_to_superpop[dict_sample_to_pop[s]]] += 1
                      except:
                          summarized_sample_cell[dict_pop_to_superpop[dict_sample_to_pop[s]]] = 1
                  if summarized_sample_cell:
                      row['Samples Summary'] = ', '.join([str(summarized_sample_cell[sp]) + ' ' + sp for sp in summarized_sample_cell])
                  else:
                      row['Samples Summary'] = 'n'
          return data_to_send, cells_style