From 2f67d2d7072136ea7bbc6538ae314122374979da Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 30 Aug 2023 20:42:53 +0800 Subject: [PATCH 1/7] Fix text mining session input and fix bug upon refresh --- app.py | 8 +++---- callbacks/text_mining/callbacks.py | 37 +++++++++++++----------------- 2 files changed, 20 insertions(+), 25 deletions(-) diff --git a/app.py b/app.py index e11798ee..3e3d85ff 100644 --- a/app.py +++ b/app.py @@ -208,16 +208,16 @@ id='coexpression-is-submitted', storage_type='session' ), + + # ============================== + # Regulatory Feature Enrichment + # ============================== dcc.Store( id='tfbs-saved-input', storage_type='session' ), - # ============================== - # Regulatory Feature Enrichment - # ============================== - dcc.Store( id='tfbs-submitted-input', storage_type='session' diff --git a/callbacks/text_mining/callbacks.py b/callbacks/text_mining/callbacks.py index 2c91134a..ed452677 100644 --- a/callbacks/text_mining/callbacks.py +++ b/callbacks/text_mining/callbacks.py @@ -5,11 +5,6 @@ from .util import * from ..lift_over import util as lift_over_util - -Text_mining_input = namedtuple( - 'Text_mining_input', ['text_mining_query']) - - def init_callback(app): # to display user input interval in the top nav @@ -52,6 +47,9 @@ def get_input_homepage_session_state(query): return query @app.callback( + Output('text-mining-input-error', 'style'), + Output('text-mining-input-error', 'children'), + Output('text-mining-is-submitted', 'data', allow_duplicate=True), Output('text-mining-query-submitted-input', 'data', allow_duplicate=True), @@ -62,30 +60,27 @@ def get_input_homepage_session_state(query): ) def submit_text_mining_input(text_mining_submitted_n_clicks, homepage_is_submitted, text_mining_query): if homepage_is_submitted and text_mining_submitted_n_clicks >= 1: - submitted_input = Text_mining_input( - text_mining_query)._asdict() - - return True, submitted_input + is_there_error, message = is_error(text_mining_query) + + if not is_there_error: + return {'display': 'none'}, message, True, text_mining_query + else: + return {'display': 'block'}, message, False, None raise PreventUpdate + @app.callback( Output('text-mining-results-container', 'style'), - Output('text-mining-input-error', 'style'), - Output('text-mining-input-error', 'children'), - - State('text-mining-query', 'value'), Input('text-mining-is-submitted', 'data') ) - def display_text_mining_output(text_mining_query, text_mining_is_submitted): - is_there_error, message = is_error(text_mining_query) + def display_coexpression_output(text_mining_is_submitted): if text_mining_is_submitted: - if not is_there_error: - return {'display': 'block'}, {'display': 'none'}, message - else: - return {'display': 'none'}, {'display': 'block'}, message + return {'display': 'block'} + + else: + return {'display': 'none'} - return {'display': 'none'}, {'display': 'none'}, message @app.callback( Output('text-mining-result-table', 'data'), @@ -98,7 +93,7 @@ def display_text_mining_output(text_mining_query, text_mining_is_submitted): ) def display_text_mining_results(text_mining_is_submitted, homepage_submitted, text_mining_query_submitted_input): if homepage_submitted and text_mining_is_submitted: - query_string = text_mining_query_submitted_input['text_mining_query'] + query_string = text_mining_query_submitted_input is_there_error, _ = is_error(query_string) if not is_there_error: From ee6d71713bec2c5bda86e61e254ba16033d26c96 Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 30 Aug 2023 21:36:04 +0800 Subject: [PATCH 2/7] Fix genomic interval display bug --- callbacks/homepage/callbacks.py | 52 ++++++++++++++++++++++++++++++++- pages/homepage.py | 3 +- 2 files changed, 53 insertions(+), 2 deletions(-) diff --git a/callbacks/homepage/callbacks.py b/callbacks/homepage/callbacks.py index f4e8d09c..e47d1870 100644 --- a/callbacks/homepage/callbacks.py +++ b/callbacks/homepage/callbacks.py @@ -116,7 +116,8 @@ def get_nipponbare_gene_ids(nb_intervals_str, homepage_is_submitted): return genes_from_Nb[1], genes_from_Nb[0].to_dict('records') raise PreventUpdate - + + """ @app.callback( Output('homepage-genomic-intervals-saved-input', 'data', allow_duplicate=True), @@ -141,6 +142,55 @@ def set_input_fields(genomic_intervals, example_genomic_interval_n_clicks, *_): return get_example_genomic_interval(ctx.triggered_id['description']) raise PreventUpdate + """ + """ + @app.callback( + Output('homepage-genomic-intervals-saved-input', + 'data', allow_duplicate=True), + Input({'type': 'example-genomic-interval', + 'description': ALL}, 'n_clicks'), + prevent_initial_call=True + ) + def set_input_fields(example_genomic_interval_n_clicks): + if ctx.triggered_id and not all(val == 0 for val in example_genomic_interval_n_clicks): + return get_example_genomic_interval(ctx.triggered_id['description']) + + raise PreventUpdate + + """ + @app.callback( + Output('homepage-genomic-intervals-saved-input', + 'data', allow_duplicate=True), + Input({'type': 'example-genomic-interval', + 'description': ALL}, 'n_clicks'), + prevent_initial_call=True + ) + def set_input_fields_with_preset_input(example_genomic_interval_n_clicks): + if ctx.triggered_id and not all(val == 0 for val in example_genomic_interval_n_clicks): + return get_example_genomic_interval(ctx.triggered_id['description']) + + raise PreventUpdate + + @app.callback( + Output('homepage-genomic-intervals-saved-input', + 'data', allow_duplicate=True), + Input('homepage-genomic-intervals', 'value'), + prevent_initial_call=True + ) + def set_input_fields(genomic_intervals): + return genomic_intervals + + @app.callback( + Output('homepage-genomic-intervals-saved-input', + 'data', allow_duplicate=True), + Input('homepage-reset', 'n_clicks'), + prevent_initial_call=True + ) + def clear_input_fields(reset_n_clicks): + if reset_n_clicks >= 1: + return None + + raise PreventUpdate @app.callback( Output('homepage-results-container', 'style'), diff --git a/pages/homepage.py b/pages/homepage.py index 995c8fa6..f79b306d 100644 --- a/pages/homepage.py +++ b/pages/homepage.py @@ -114,7 +114,8 @@ dbc.Input( id='homepage-genomic-intervals', type='text', - value='' + value='', + debounce=True ), html.Div([ From d089cae5e2ab30a81edb37329209a51f6081d61d Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 30 Aug 2023 21:41:51 +0800 Subject: [PATCH 3/7] Allow enter key to submit genomic interval --- callbacks/homepage/callbacks.py | 5 +++-- pages/homepage.py | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/callbacks/homepage/callbacks.py b/callbacks/homepage/callbacks.py index e47d1870..c250f22d 100644 --- a/callbacks/homepage/callbacks.py +++ b/callbacks/homepage/callbacks.py @@ -58,6 +58,7 @@ def display_specific_analysis_page(nav_className, analysis_nav_id, analysis_layo State('homepage-genomic-intervals', 'value'), Input('homepage-submit', 'n_clicks'), + Input('homepage-genomic-intervals', 'n_submit'), State('session-container', 'children'), Input('homepage-reset', 'n_clicks'), @@ -65,7 +66,7 @@ def display_specific_analysis_page(nav_className, analysis_nav_id, analysis_layo prevent_initial_call=True ) - def parse_input(nb_intervals_str, n_clicks, dccStore_children, *_): + def parse_input(nb_intervals_str, n_clicks, n_submit, dccStore_children, *_): if 'homepage-clear-cache' == ctx.triggered_id: clear_cache_folder() @@ -75,7 +76,7 @@ def parse_input(nb_intervals_str, n_clicks, dccStore_children, *_): return dccStore_children, None, {'display': 'none'}, False, '' - if 'homepage-submit' == ctx.triggered_id and n_clicks >= 1: + if n_submit >= 1 or ('homepage-submit' == ctx.triggered_id and n_clicks >= 1): if nb_intervals_str: intervals = lift_over_util.get_genomic_intervals_from_input( nb_intervals_str) diff --git a/pages/homepage.py b/pages/homepage.py index f79b306d..d70ef634 100644 --- a/pages/homepage.py +++ b/pages/homepage.py @@ -115,7 +115,8 @@ id='homepage-genomic-intervals', type='text', value='', - debounce=True + debounce=True, + n_submit=0 ), html.Div([ From c853632ded125c78f58bd3c4dbdc798bac461f9b Mon Sep 17 00:00:00 2001 From: memgonzales Date: Thu, 31 Aug 2023 07:54:34 +0800 Subject: [PATCH 4/7] Do not link to any URL if no entry in cell --- callbacks/lift_over/callbacks.py | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/callbacks/lift_over/callbacks.py b/callbacks/lift_over/callbacks.py index 873fea35..dad5627a 100644 --- a/callbacks/lift_over/callbacks.py +++ b/callbacks/lift_over/callbacks.py @@ -308,7 +308,8 @@ def display_gene_tables(nb_intervals_str, active_tab, filter_rice_variants, othe if active_tab == get_tab_id('All Genes'): all_genes_raw = get_all_genes(other_refs, nb_intervals) - all_genes_raw['OGI'] = get_rgi_orthogroup_link( + mask = (all_genes_raw['OGI'] != NULL_PLACEHOLDER) + all_genes_raw.loc[mask, 'OGI'] = get_rgi_orthogroup_link( all_genes_raw, 'OGI') if 'Nipponbare' in all_genes_raw.columns: mask = (all_genes_raw['Nipponbare'] != NULL_PLACEHOLDER) @@ -336,13 +337,16 @@ def display_gene_tables(nb_intervals_str, active_tab, filter_rice_variants, othe mask = (common_genes_raw['OGI'] != NULL_PLACEHOLDER) common_genes_raw.loc[mask, 'OGI'] = get_rgi_orthogroup_link( common_genes_raw, 'OGI') + if 'Nipponbare' in common_genes_raw.columns: - common_genes_raw['Nipponbare'] = get_rgi_genecard_link( + mask = (common_genes_raw['Nipponbare'] != NULL_PLACEHOLDER) + common_genes_raw.loc[mask, 'Nipponbare'] = get_rgi_genecard_link( common_genes_raw, 'Nipponbare') for cultivar in other_ref_genomes: if cultivar in common_genes_raw.columns: - common_genes_raw[cultivar] = get_rgi_genecard_link( + mask = (common_genes_raw[cultivar] != NULL_PLACEHOLDER) + common_genes_raw.loc[mask, cultivar] = get_rgi_genecard_link( common_genes_raw, cultivar) common_genes = common_genes_raw.to_dict('records') @@ -357,9 +361,12 @@ def display_gene_tables(nb_intervals_str, active_tab, filter_rice_variants, othe nb_intervals)[0].drop( ['Chromosome', 'Start', 'End', 'Strand'], axis=1) - genes_from_Nb_raw['OGI'] = get_rgi_orthogroup_link( + mask = (genes_from_Nb_raw['OGI'] != NULL_PLACEHOLDER) + genes_from_Nb_raw.loc[mask, 'OGI'] = get_rgi_orthogroup_link( genes_from_Nb_raw, 'OGI') - genes_from_Nb_raw['Name'] = get_rgi_genecard_link( + + mask = (genes_from_Nb_raw['Name'] != NULL_PLACEHOLDER) + genes_from_Nb_raw.loc[mask, 'Name'] = get_rgi_genecard_link( genes_from_Nb_raw, 'Name') genes_from_Nb = genes_from_Nb_raw.to_dict('records') @@ -376,9 +383,12 @@ def display_gene_tables(nb_intervals_str, active_tab, filter_rice_variants, othe genes_from_other_ref_raw = get_unique_genes_in_other_ref( other_ref, nb_intervals) - genes_from_other_ref_raw['OGI'] = get_rgi_orthogroup_link( + mask = (genes_from_other_ref_raw['OGI'] != NULL_PLACEHOLDER) + genes_from_other_ref_raw.loc[mask, 'OGI'] = get_rgi_orthogroup_link( genes_from_other_ref_raw, 'OGI') - genes_from_other_ref_raw['Name'] = get_rgi_genecard_link( + + mask = (genes_from_other_ref_raw['Name'] != NULL_PLACEHOLDER) + genes_from_other_ref_raw.loc[mask, 'Name'] = get_rgi_genecard_link( genes_from_other_ref_raw, 'Name') genes_from_other_ref = genes_from_other_ref_raw.to_dict( From 9b25ffb55fc2981f0677aeaa11dac4d0ce2823ea Mon Sep 17 00:00:00 2001 From: memgonzales Date: Thu, 31 Aug 2023 08:06:41 +0800 Subject: [PATCH 5/7] Use scipy for Benjamini-Hochberg in coexpression --- callbacks/coexpression/util.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/callbacks/coexpression/util.py b/callbacks/coexpression/util.py index d8f189b6..109d035e 100644 --- a/callbacks/coexpression/util.py +++ b/callbacks/coexpression/util.py @@ -7,8 +7,7 @@ import pandas as pd import networkx as nx -from scipy.stats import fisher_exact -import statsmodels.stats.multitest as sm +from scipy.stats import fisher_exact, false_discovery_control from collections import namedtuple @@ -219,8 +218,7 @@ def do_module_enrichment_analysis(implicated_gene_ids, genomic_intervals, addl_g # Add 1 since user-facing module number is one-based p_values_indices.append(idx + 1) - _, adj_p_values, _, _ = sm.multipletests( - p_values, method='fdr_bh') + adj_p_values = false_discovery_control(p_values, method='bh') significant_adj_p_values = [(p_values_indices[idx], adj_p_value) for idx, adj_p_value in enumerate( adj_p_values) if adj_p_value < const.P_VALUE_CUTOFF] significant_adj_p_values.sort(key=lambda x: x[1]) From 96cdd0db117e5254ed4c994ec543d0e0201f4283 Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 31 Aug 2023 16:50:56 +0800 Subject: [PATCH 6/7] Revise clearing of dcc store data code --- app.py | 14 ++++---- callbacks/homepage/callbacks.py | 59 +++------------------------------ callbacks/homepage/util.py | 8 +++-- 3 files changed, 17 insertions(+), 64 deletions(-) diff --git a/app.py b/app.py index 3e3d85ff..8948a39c 100644 --- a/app.py +++ b/app.py @@ -66,13 +66,6 @@ dash.page_container, - # Do NOT place inside session-container. - # Otherwise, the input field for genomic interval will be cleared when submitted. - dcc.Store( - id='homepage-genomic-intervals-saved-input', - storage_type='session' - ), - # Session storage html.Div( id='session-container', @@ -85,6 +78,11 @@ storage_type='session' ), + dcc.Store( + id='homepage-genomic-intervals-saved-input', + storage_type='session' + ), + dcc.Store( id='homepage-genomic-intervals-submitted-input', storage_type='session' @@ -95,6 +93,8 @@ storage_type='session' ), + + # ========== # Lift-over # ========== diff --git a/callbacks/homepage/callbacks.py b/callbacks/homepage/callbacks.py index c250f22d..8ac2e9da 100644 --- a/callbacks/homepage/callbacks.py +++ b/callbacks/homepage/callbacks.py @@ -72,7 +72,7 @@ def parse_input(nb_intervals_str, n_clicks, n_submit, dccStore_children, *_): if 'homepage-reset' == ctx.triggered_id: # clear data for items in dcc.Store found in session-container - dccStore_children = get_cleared_dccStore_data(dccStore_children) + dccStore_children = get_cleared_dccStore_data_excluding_some_data(dccStore_children) return dccStore_children, None, {'display': 'none'}, False, '' @@ -86,8 +86,8 @@ def parse_input(nb_intervals_str, n_clicks, n_submit, dccStore_children, *_): {'display': 'block'}, False, nb_intervals_str else: # clear data for items in dcc.Store found in session-container - dccStore_children = get_cleared_dccStore_data( - dccStore_children) + dccStore_children = get_cleared_dccStore_data_excluding_some_data( + dccStore_children, 'homepage-genomic-intervals-saved-input') browse_loci_util.write_igv_tracks_to_file(nb_intervals_str) @@ -117,48 +117,7 @@ def get_nipponbare_gene_ids(nb_intervals_str, homepage_is_submitted): return genes_from_Nb[1], genes_from_Nb[0].to_dict('records') raise PreventUpdate - - """ - @app.callback( - Output('homepage-genomic-intervals-saved-input', - 'data', allow_duplicate=True), - State('homepage-genomic-intervals', 'value'), - Input({'type': 'example-genomic-interval', - 'description': ALL}, 'n_clicks'), - Input('homepage-reset', 'n_clicks'), - prevent_initial_call=True - ) - def set_input_fields(genomic_intervals, example_genomic_interval_n_clicks, *_): - if all(val == 0 for val in example_genomic_interval_n_clicks): - return '' - - if ctx.triggered_id: - if 'homepage-reset' == ctx.triggered_id: - return None - - if 'homepage-genomic-intervals' == ctx.triggered_id: - return genomic_intervals - - return get_example_genomic_interval(ctx.triggered_id['description']) - - raise PreventUpdate - """ - """ - @app.callback( - Output('homepage-genomic-intervals-saved-input', - 'data', allow_duplicate=True), - Input({'type': 'example-genomic-interval', - 'description': ALL}, 'n_clicks'), - prevent_initial_call=True - ) - def set_input_fields(example_genomic_interval_n_clicks): - if ctx.triggered_id and not all(val == 0 for val in example_genomic_interval_n_clicks): - return get_example_genomic_interval(ctx.triggered_id['description']) - - raise PreventUpdate - - """ @app.callback( Output('homepage-genomic-intervals-saved-input', 'data', allow_duplicate=True), @@ -172,6 +131,7 @@ def set_input_fields_with_preset_input(example_genomic_interval_n_clicks): raise PreventUpdate + @app.callback( Output('homepage-genomic-intervals-saved-input', 'data', allow_duplicate=True), @@ -181,17 +141,6 @@ def set_input_fields_with_preset_input(example_genomic_interval_n_clicks): def set_input_fields(genomic_intervals): return genomic_intervals - @app.callback( - Output('homepage-genomic-intervals-saved-input', - 'data', allow_duplicate=True), - Input('homepage-reset', 'n_clicks'), - prevent_initial_call=True - ) - def clear_input_fields(reset_n_clicks): - if reset_n_clicks >= 1: - return None - - raise PreventUpdate @app.callback( Output('homepage-results-container', 'style'), diff --git a/callbacks/homepage/util.py b/callbacks/homepage/util.py index 6920db7f..2a77fe94 100644 --- a/callbacks/homepage/util.py +++ b/callbacks/homepage/util.py @@ -14,9 +14,13 @@ def clear_cache_folder(): if os.path.exists(const.TEMP): shutil.rmtree(const.TEMP, ignore_errors=True) -def get_cleared_dccStore_data(dccStore_children): +def get_cleared_dccStore_data_excluding_some_data(dccStore_children, *arg): for i in range(len(dccStore_children)): - dccStore_children[i]['props']['data'] = '' + dccStore_ID = dccStore_children[i]['props']['id'] + + if not dccStore_ID in arg: + dccStore_children[i]['props']['data'] = '' + return dccStore_children def get_example_genomic_interval(description): From 21c05d2717c96378392fb15f4cf20445b94345aa Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 31 Aug 2023 17:39:53 +0800 Subject: [PATCH 7/7] Fix text mining input session --- callbacks/text_mining/callbacks.py | 23 +++++++++++++++-------- pages/analysis/text_mining.py | 10 +++++++--- 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/callbacks/text_mining/callbacks.py b/callbacks/text_mining/callbacks.py index ed452677..8943bc86 100644 --- a/callbacks/text_mining/callbacks.py +++ b/callbacks/text_mining/callbacks.py @@ -25,19 +25,25 @@ def display_input(nb_intervals_str, homepage_is_submitted, *_): @app.callback( Output('text-mining-query-saved-input', 'data', allow_duplicate=True), - State('text-mining-query', 'value'), Input({'type': 'example-text-mining', 'description': ALL}, 'n_clicks'), prevent_initial_call=True ) - def set_input_fields(query_string, *_): - if ctx.triggered_id: - if 'text-mining-query' == ctx.triggered_id: - return query_string - + def set_input_fields_with_preset_input(example_text_mining_n_clicks): + if ctx.triggered_id and not all(val == 0 for val in example_text_mining_n_clicks): return ctx.triggered_id['description'] raise PreventUpdate + + + @app.callback( + Output('text-mining-query-saved-input', 'data', allow_duplicate=True), + Input('text-mining-query', 'value'), + prevent_initial_call=True + ) + def set_input_fields(query_string): + return query_string + @app.callback( Output('text-mining-query', 'value'), @@ -54,12 +60,13 @@ def get_input_homepage_session_state(query): Output('text-mining-query-submitted-input', 'data', allow_duplicate=True), Input('text-mining-submit', 'n_clicks'), + Input('text-mining-query', 'n_submit'), State('homepage-is-submitted', 'data'), State('text-mining-query', 'value'), prevent_initial_call=True ) - def submit_text_mining_input(text_mining_submitted_n_clicks, homepage_is_submitted, text_mining_query): - if homepage_is_submitted and text_mining_submitted_n_clicks >= 1: + def submit_text_mining_input(text_mining_submitted_n_clicks, text_mining_query_n_submit, homepage_is_submitted, text_mining_query): + if homepage_is_submitted and (text_mining_submitted_n_clicks >= 1 or text_mining_query_n_submit >= 1): is_there_error, message = is_error(text_mining_query) if not is_there_error: diff --git a/pages/analysis/text_mining.py b/pages/analysis/text_mining.py index aeb80056..3bbd801e 100644 --- a/pages/analysis/text_mining.py +++ b/pages/analysis/text_mining.py @@ -37,19 +37,23 @@ dbc.Input( id='text-mining-query', type='text', - value='' + value='', + debounce=True, + n_submit=0 ), html.Div([html.Span('Examples:', className='pe-3'), html.Span('pre-harvest sprouting', id={'type': 'example-text-mining', 'description': 'pre-harvest sprouting'}, - className='sample-genomic-interval'), + className='sample-genomic-interval', + n_clicks=0), html.Span(',', className='sample-genomic-interval'), html.Span('anaerobic germination', id={'type': 'example-text-mining', 'description': 'anaerobic germination'}, - className='sample-genomic-interval ms-3')], + className='sample-genomic-interval ms-3', + n_clicks=0)], className='pt-3'), html.Br(),