diff options
| author | Pjotr Prins | 2026-03-27 11:12:54 +0100 |
|---|---|---|
| committer | Pjotr Prins | 2026-03-27 11:12:54 +0100 |
| commit | d91a3a5876200f011085a739ab82315f0d8eff70 (patch) | |
| tree | f2ba1ef08a0decf88216a741b210a544b935bcb4 | |
| parent | c70a27ae64463eac43f0e4421e9d8cce9d4200ba (diff) | |
| parent | 8065e026dc1b410e167eacbb7545c07abdf3b543 (diff) | |
| download | genecup-d91a3a5876200f011085a739ab82315f0d8eff70.tar.gz | |
Merge branch 'tux02-master'
| -rw-r--r-- | .gitignore | 3 | ||||
| -rw-r--r-- | README.md (renamed from Readme.md) | 72 | ||||
| -rw-r--r-- | VERSION | 1 | ||||
| -rwxr-xr-x | server.py | 103 | ||||
| -rw-r--r-- | static/style.css | 37 | ||||
| -rw-r--r-- | templates/about.html | 36 | ||||
| -rw-r--r-- | templates/index.html | 13 | ||||
| -rw-r--r-- | templates/layout.html | 28 |
8 files changed, 195 insertions, 98 deletions
diff --git a/.gitignore b/.gitignore index 6bb15bb..f1044ec 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ -.key +minipubmed/ instance settings.sh -minipubmed diff --git a/Readme.md b/README.md index 42ac577..ce7d2e3 100644 --- a/Readme.md +++ b/README.md @@ -5,13 +5,12 @@ URL: [https://genecup.org](https://genecup.org) GeneCup automatically extracts information from PubMed and NHGRI-EBI GWAS catalog on the relationship of any gene with a custom list of keywords hierarchically organized into an ontology. The users create an ontology by identifying categories of concepts and a list of keywords for each concept. As an example, we created an ontology for drug addiction related concepts over 300 of these keywords are organized into six categories: - -- names of abused drugs, e.g., opioids -- terms describing addiction, e.g., relapse -- key brain regions implicated in addiction, e.g., ventral striatum -- neurotrasmission, e.g., dopaminergic -- synaptic plasticity, e.g., long term potentiation -- intracellular signaling, e.g., phosphorylation +* names of abused drugs, e.g., opioids +* terms describing addiction, e.g., relapse +* key brain regions implicated in addiction, e.g., ventral striatum +* neurotrasmission, e.g., dopaminergic +* synaptic plasticity, e.g., long term potentiation +* intracellular signaling, e.g., phosphorylation Live searches are conducted through PubMed to get relevant PMIDs, which are then used to retrieve the abstracts from a local archive. The relationships are presented as an interactive cytoscape graph. The nodes can be moved around to better reveal the connections. Clicking on the links will bring up the corresponding sentences in a new browser window. Stress related sentences for addiction keywords are further classified into either systemic or cellular stress using a convolutional neural network. @@ -23,9 +22,23 @@ Live searches are conducted through PubMed to get relevant PMIDs, which are then 3. sort the genes based on the number of abstracts with useful sentences. 4. generate the final list, include symbol, alias, and name -## Install local mirror of PubMed +## Dependencies + +* [local copy of PubMed](https://dataguide.nlm.nih.gov/edirect/archive.html) +* python == 3.8 +* see requirements.txt and guix.scm for list of packages and versions + +## Deploy with GNU Guix + +The main genecup.org service is deployed deterministically (and self contained) using GNU Guix. + +See also https://issues.genenetwork.org/topics/deploy/genecup. -- Following the instruction provided by NCBI: https://www.nlm.nih.gov/dataguide/edirect/archive.html +## Development + +The source code and data are in a git repository: https://git.genenetwork.org/genecup/ + +Unpack minipubmed and punkt (see below). And run, for example ## Mini PubMed for testing @@ -34,18 +47,47 @@ For testing or code development, it is useful to have a small collection of PubM 1. install [edirect](https://dataguide.nlm.nih.gov/edirect/install.html) (make sure you refresh your shell after install so the PATH is updated) 2. unpack the minipubmed.tgz file 3. test the installation by running: - ``` cd minipubmed -cat pmid.list |fetch-pubmed -path PubMed/Archive/ >test.xml +cat pmid.list |fetch-PubMed -path PubMed/Archive/ >test.xml ``` - You should see 2473 abstracts in the test.xml file. -# Run the server +## NLTK tokens -You can use the [guix.scm](./guix.scm) container to run genecup: +You also need to fetch punkt.zip from https://www.nltk.org/nltk_data/ ```sh -GeneCup$ guix shell -L . -C -N -F genecup-gemini coreutils edirect -- env EDIRECT_PUBMED_MASTER=./minipubmed GEMINI_API_KEY="AIza****" ./server.py --port 4201 +cd minipubmed +mkdir tokenizers +cd tokenizers +wget https://raw.githubusercontent.com/nltk/nltk_data/gh-pages/packages/tokenizers/punkt.zip +unzip punkt.zip +``` + +## Support + +E-mail [Pjotr Prins](https://thebird.nl) or [Hao Chen](https://www.uthsc.edu/neuroscience-institute/about/faculty/chen.php). + +## License + +GeneCup source code is published under the liberal free software MIT licence (aka expat license) + +## Cite + +[GeneCup: mining PubMed and GWAS catalog for gene-keyword relationships](https://academic.oup.com/g3journal/article/12/5/jkac059/6548160) by +Gunturkun MH, Flashner E, Wang T, Mulligan MK, Williams RW, Prins P, and Chen H. + +G3 (Bethesda). 2022 May 6;12(5):jkac059. doi: 10.1093/g3journal/jkac059. PMID: 35285473; PMCID: PMC9073678. + +``` +@article{GeneCup, + pmid = {35285473}, + author = {Gunturkun, M. H. and Flashner, E. and Wang, T. and Mulligan, M. K. and Williams, R. W. and Prins, P. and Chen, H.}, + title = {{GeneCup: mining PubMed and GWAS catalog for gene-keyword relationships}}, + journal = {G3 (Bethesda)}, + year = {2022}, + doi = {10.1093/g3journal/jkac059}, + url = {http://www.ncbi.nlm.nih.gov/pubmed/35285473} +} ``` diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..3a48e0d --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +1.8.4-pre2 diff --git a/server.py b/server.py index 60b5909..f457e17 100755 --- a/server.py +++ b/server.py @@ -59,7 +59,14 @@ except FileNotFoundError: except Exception as e: print(f"Error loading genecup_synthesis_prompt.txt: {e}. LLM prompts will be affected.") +VERSION=None +def version(): + global VERSION + if VERSION is None: + with open("VERSION", 'r') as file: + VERSION = file.read() + return VERSION app=Flask(__name__) @@ -256,7 +263,7 @@ def root(): onto_cont=open("addiction.onto","r").read() dict_onto=ast.literal_eval(onto_cont) - return render_template('index.html',onto_len_dir=onto_len_dir, onto_list=onto_list, ontol = 'addiction', dict_onto = dict_onto) + return render_template('index.html',onto_len_dir=onto_len_dir, onto_list=onto_list, ontol = 'addiction', dict_onto = dict_onto,version=version()) @app.route("/login", methods=["POST", "GET"]) @@ -283,8 +290,8 @@ def login(): onto_list = session['onto_list'] else: flash("Invalid username or password!", "inval") - return render_template('signup.html') - return render_template('index.html',onto_len_dir=onto_len_dir, onto_list=onto_list, ontol = 'addiction', dict_onto = dict_onto) + return render_template('signup.html',version=version()) + return render_template('index.html',onto_len_dir=onto_len_dir, onto_list=onto_list, ontol = 'addiction', dict_onto = dict_onto,version=version()) @app.route("/signup", methods=["POST", "GET"]) @@ -302,7 +309,7 @@ def signup(): if (found_user and (bcrypt.checkpw(password.encode('utf8'), found_user.password)==False)): flash("Already registered, but wrong password!", "inval") - return render_template('signup.html',onto_len_dir=onto_len_dir, onto_list=onto_list, ontol = 'addiction', dict_onto = dict_onto) + return render_template('signup.html',onto_len_dir=onto_len_dir, onto_list=onto_list, ontol = 'addiction', dict_onto = dict_onto,version=version()) session['email'] = email session['hashed_email'] = hashlib.md5(session['email'] .encode('utf-8')).hexdigest() @@ -328,12 +335,12 @@ def signup(): os.makedirs(session['user_folder']+"/ontology/") flash("Login Succesful!") - return render_template('index.html',onto_len_dir=onto_len_dir, onto_list=onto_list, ontol = 'addiction', dict_onto = dict_onto) + return render_template('index.html',onto_len_dir=onto_len_dir, onto_list=onto_list, ontol = 'addiction', dict_onto = dict_onto,version=version()) else: if 'email' in session: flash("Already Logged In!") - return render_template('index.html',onto_len_dir=onto_len_dir, onto_list=onto_list, ontol = 'addiction', dict_onto = dict_onto) - return render_template('signup.html',onto_len_dir=onto_len_dir, onto_list=onto_list, ontol = 'addiction', dict_onto = dict_onto) + return render_template('index.html',onto_len_dir=onto_len_dir, onto_list=onto_list, ontol = 'addiction', dict_onto = dict_onto,version=version()) + return render_template('signup.html',onto_len_dir=onto_len_dir, onto_list=onto_list, ontol = 'addiction', dict_onto = dict_onto,version=version()) @app.route("/signin", methods=["POST", "GET"]) @@ -357,11 +364,11 @@ def signin(): onto_len_dir = session['onto_len_dir'] onto_list = session['onto_list'] dict_onto=ast.literal_eval(onto_cont) - return render_template('index.html', onto_len_dir=onto_len_dir, onto_list=onto_list, ontol = 'addiction', dict_onto = dict_onto) + return render_template('index.html', onto_len_dir=onto_len_dir, onto_list=onto_list, ontol = 'addiction', dict_onto = dict_onto,version=version()) else: flash("Invalid username or password!", "inval") - return render_template('signup.html') - return render_template('signin.html') + return render_template('signup.html',version=version()) + return render_template('signin.html',version=version()) # change password @app.route("/<nm_passwd>", methods=["POST", "GET"]) @@ -387,7 +394,7 @@ def profile(nm_passwd): onto_list = '' onto_cont=open("addiction.onto","r").read() dict_onto=ast.literal_eval(onto_cont) - return render_template('index.html', onto_len_dir=onto_len_dir, onto_list=onto_list, ontol = 'addiction', dict_onto = dict_onto) + return render_template('index.html', onto_len_dir=onto_len_dir, onto_list=onto_list, ontol = 'addiction', dict_onto = dict_onto,version=version()) # remove reserved characters from the hashed passwords reserved = (";", "/", "?", ":", "@", "=", "&", ".") def replace_reserved(fullstring): @@ -397,7 +404,7 @@ def profile(nm_passwd): replaced_passwd = replace_reserved(str(found_user.password)) if replaced_passwd == user_passwd: - return render_template("/passwd_change.html", name=user_name) + return render_template("/passwd_change.html", name=user_name,version=version()) else: return "This url does not exist" else: @@ -422,12 +429,12 @@ def logout(): flash(f"You have been logged out, {user1}", "inval") # Used f-string for clarity session.pop('email', None) session.clear() - return render_template('index.html',onto_len_dir=onto_len_dir, onto_list=onto_list, ontol = 'addiction', dict_onto = dict_onto) + return render_template('index.html',onto_len_dir=onto_len_dir, onto_list=onto_list, ontol = 'addiction', dict_onto = dict_onto,version=version()) @app.route("/about") def about(): - return render_template('about.html') + return render_template('about.html',version=version()) # Ontology selection @@ -449,7 +456,7 @@ def index_ontology(): dict_onto=ast.literal_eval(onto_cont) onto_len_dir = session['onto_len_dir'] onto_list = session['onto_list'] - return render_template('index.html',onto_len_dir=onto_len_dir, onto_list=onto_list, ontol = session['namecat'], dict_onto=dict_onto ) + return render_template('index.html',onto_len_dir=onto_len_dir, onto_list=onto_list, ontol = session['namecat'], dict_onto=dict_onto ,version=version()) @app.route("/ontology", methods=["POST", "GET"]) @@ -697,7 +704,7 @@ def ontology(): else: onto_len_dir=0 onto_list='' - return render_template('ontology.html',dict_onto=dict_onto, namecat=name_to_html, onto_len_dir=onto_len_dir, onto_list=onto_list) + return render_template('ontology.html',dict_onto=dict_onto, namecat=name_to_html, onto_len_dir=onto_len_dir, onto_list=onto_list,no_footer=True,version=version()) @app.route("/ontoarchive") @@ -711,7 +718,7 @@ def ontoarchive(): onto_list = '' onto_cont=open("addiction.onto","r").read() dict_onto=ast.literal_eval(onto_cont) - return render_template('index.html',onto_len_dir=onto_len_dir, onto_list=onto_list, ontol = 'addiction', dict_onto = dict_onto) + return render_template('index.html',onto_len_dir=onto_len_dir, onto_list=onto_list, ontol = 'addiction', dict_onto = dict_onto,version=version()) else: session['user_folder'] = datadir+"/user/"+str(session['hashed_email']) else: @@ -720,7 +727,7 @@ def ontoarchive(): onto_list = '' onto_cont=open("addiction.onto","r").read() dict_onto=ast.literal_eval(onto_cont) - return render_template('index.html', onto_len_dir=onto_len_dir, onto_list=onto_list, ontol = 'addiction', dict_onto = dict_onto) + return render_template('index.html', onto_len_dir=onto_len_dir, onto_list=onto_list, ontol = 'addiction', dict_onto = dict_onto,version=version()) session_id=session['id'] def sorted_alphanumeric(data): @@ -746,7 +753,7 @@ def ontoarchive(): session['onto_len_dir'] = onto_len_dir session['onto_list'] = onto_list message3="<ul><li> Click on the Date/Time to view archived results. <li>The Date/Time are based on US Central time zone.</ul> " - return render_template('ontoarchive.html', onto_len_dir=onto_len_dir, onto_list = onto_list, onto_folder_list=onto_folder_list, onto_directory_list=onto_directory_list, session_id=session_id, message3=message3) + return render_template('ontoarchive.html', onto_len_dir=onto_len_dir, onto_list = onto_list, onto_folder_list=onto_folder_list, onto_directory_list=onto_directory_list, session_id=session_id, message3=message3,version=version()) # Remove an ontology folder @@ -762,7 +769,7 @@ def removeonto(): onto_list = '' onto_cont=open("addiction.onto","r").read() dict_onto=ast.literal_eval(onto_cont) - return render_template('index.html', onto_len_dir=onto_len_dir, onto_list=onto_list, ontol = 'addiction', dict_onto = dict_onto) + return render_template('index.html', onto_len_dir=onto_len_dir, onto_list=onto_list, ontol = 'addiction', dict_onto = dict_onto,version=version()) @app.route('/progress') @@ -792,7 +799,7 @@ def progress(): onto_cont=open("addiction.onto","r").read() dict_onto=ast.literal_eval(onto_cont) message="<span class='text-danger'>Up to 200 terms can be searched at a time</span>" - return render_template('index.html' ,onto_len_dir=onto_len_dir, onto_list=onto_list, ontol = 'addiction', dict_onto = dict_onto, message=message) + return render_template('index.html' ,onto_len_dir=onto_len_dir, onto_list=onto_list, ontol = 'addiction', dict_onto = dict_onto, message=message,version=version()) if len(genes)==0: if ('email' in session): @@ -804,7 +811,7 @@ def progress(): onto_cont=open("addiction.onto","r").read() dict_onto=ast.literal_eval(onto_cont) message="<span class='text-danger'>Please enter a search term </span>" - return render_template('index.html',onto_len_dir=onto_len_dir, onto_list=onto_list, ontol = 'addiction', dict_onto = dict_onto, message=message) + return render_template('index.html',onto_len_dir=onto_len_dir, onto_list=onto_list, ontol = 'addiction', dict_onto = dict_onto, message=message,version=version()) tf_path=tempfile.gettempdir() genes_for_folder_name ="" @@ -892,7 +899,7 @@ def progress(): genes_session += str(gen) + "_" genes_session = genes_session[:-1] session['query']=genes - return render_template('progress.html', url_in="search", url_out="cytoscape/?rnd="+rnd+"&genequery="+genes_session) + return render_template('progress.html', url_in="search", url_out="cytoscape/?rnd="+rnd+"&genequery="+genes_session,version=version()) @app.route("/search") @@ -1159,7 +1166,7 @@ def tableview(): onto_list = '' onto_cont=open("addiction.onto","r").read() dict_onto=ast.literal_eval(onto_cont) - return render_template('index.html', onto_len_dir=onto_len_dir, onto_list=onto_list, ontol = 'addiction', dict_onto = dict_onto) + return render_template('index.html', onto_len_dir=onto_len_dir, onto_list=onto_list, ontol = 'addiction', dict_onto = dict_onto,version=version()) jedges ='' nodata_temp = 1 # Default to no data @@ -1194,7 +1201,7 @@ def tableview(): onto_list = '' onto_cont=open("addiction.onto","r").read() dict_onto=ast.literal_eval(onto_cont) - return render_template('index.html', onto_len_dir=onto_len_dir, onto_list=onto_list, ontol = 'addiction', dict_onto = dict_onto) + return render_template('index.html', onto_len_dir=onto_len_dir, onto_list=onto_list, ontol = 'addiction', dict_onto = dict_onto,version=version()) jedges ='' nodata_temp = 1 # Default to no data @@ -1227,7 +1234,7 @@ def tableview(): num_gene = gene_name.count(',')+1 message3="<ul><li> <font color=\"#E74C3C\">Click on the abstract count to read sentences linking the keyword and the gene</font> <li> Click on a keyword to see the terms included in the search. <li>View the results in <a href='\\cytoscape/?rnd={}&genequery={}'\ ><b> a graph.</b></a> </ul> Links will be preserved when the table is copy-n-pasted into a spreadsheet.".format(rnd_url,genes_url) - return render_template('tableview.html', genes_session_tmp = genes_session_tmp, nodata_temp=nodata_temp, num_gene=num_gene, jedges=jedges, jnodes=jnodes,gene_name=gene_name, message3=message3, rnd_url=rnd_url, genes_url=genes_url) + return render_template('tableview.html', genes_session_tmp = genes_session_tmp, nodata_temp=nodata_temp, num_gene=num_gene, jedges=jedges, jnodes=jnodes,gene_name=gene_name, message3=message3, rnd_url=rnd_url, genes_url=genes_url,no_footer=True,version=version()) # Table for the zero abstract counts @@ -1250,7 +1257,7 @@ def tableview0(): onto_list = '' onto_cont=open("addiction.onto","r").read() dict_onto=ast.literal_eval(onto_cont) - return render_template('index.html', onto_len_dir=onto_len_dir, onto_list=onto_list, ontol = 'addiction', dict_onto = dict_onto) + return render_template('index.html', onto_len_dir=onto_len_dir, onto_list=onto_list, ontol = 'addiction', dict_onto = dict_onto,version=version()) jedges ='' nodata_temp = 1 # Default to no data @@ -1282,7 +1289,7 @@ def tableview0(): onto_list = '' onto_cont=open("addiction.onto","r").read() dict_onto=ast.literal_eval(onto_cont) - return render_template('index.html', onto_len_dir=onto_len_dir, onto_list=onto_list, ontol = 'addiction', dict_onto = dict_onto) + return render_template('index.html', onto_len_dir=onto_len_dir, onto_list=onto_list, ontol = 'addiction', dict_onto = dict_onto,version=version()) jedges ='' nodata_temp = 1 # Default to no data @@ -1316,7 +1323,7 @@ def tableview0(): gene_name = gene_name+added num_gene = gene_name.count(',')+1 message4="<b> Notes: </b><li> These are the keywords that have <b>zero</b> abstract counts. <li>View all the results in <a href='\\cytoscape/?rnd={}&genequery={}'><b> a graph.</b></a> ".format(rnd_url,genes_url) - return render_template('tableview0.html',nodata_temp=nodata_temp, num_gene=num_gene, jedges=jedges, jnodes=jnodes,gene_name=gene_name, message4=message4) + return render_template('tableview0.html',nodata_temp=nodata_temp, num_gene=num_gene, jedges=jedges, jnodes=jnodes,gene_name=gene_name, message4=message4,version=version()) @app.route("/userarchive") @@ -1329,7 +1336,7 @@ def userarchive(): if ('email' in session): if os.path.exists(datadir+"/user/"+str(session['hashed_email'])) == False: flash("Search history doesn't exist!") - return render_template('index.html', onto_len_dir=onto_len_dir, onto_list=onto_list, ontol = 'addiction', dict_onto = dict_onto) + return render_template('index.html', onto_len_dir=onto_len_dir, onto_list=onto_list, ontol = 'addiction', dict_onto = dict_onto,version=version()) else: session['user_folder'] = datadir+"/user/"+str(session['hashed_email']) else: @@ -1339,7 +1346,7 @@ def userarchive(): onto_list = '' onto_cont=open("addiction.onto","r").read() dict_onto=ast.literal_eval(onto_cont) - return render_template('index.html', onto_len_dir=onto_len_dir, onto_list=onto_list, ontol = 'addiction', dict_onto = dict_onto) + return render_template('index.html', onto_len_dir=onto_len_dir, onto_list=onto_list, ontol = 'addiction', dict_onto = dict_onto,version=version()) session_id=session['id'] def sorted_alphanumeric(data): @@ -1377,7 +1384,7 @@ def userarchive(): len_dir = len(directory_list) message3="<ul><li> Click on the Date/Time to view archived results. <li>The Date/Time are based on US Central time zone.</ul> " - return render_template('userarchive.html', len_dir=len_dir, gene_list = gene_list, onto_list = onto_list_archive, folder_list=folder_list, directory_list=directory_list, session_id=session_id, message3=message3) + return render_template('userarchive.html', len_dir=len_dir, gene_list = gene_list, onto_list = onto_list_archive, folder_list=folder_list, directory_list=directory_list, session_id=session_id, message3=message3,version=version()) # Remove the search directory @@ -1393,7 +1400,7 @@ def remove(): onto_list = '' onto_cont=open("addiction.onto","r").read() dict_onto=ast.literal_eval(onto_cont) - return render_template('index.html', onto_len_dir=onto_len_dir, onto_list=onto_list, ontol = 'addiction', dict_onto = dict_onto) + return render_template('index.html', onto_len_dir=onto_len_dir, onto_list=onto_list, ontol = 'addiction', dict_onto = dict_onto,version=version()) @app.route('/date', methods=['GET', 'POST']) @@ -1475,10 +1482,10 @@ def date(): onto_list_session = '' # Renamed to avoid conflict onto_cont=open("addiction.onto","r").read() dict_onto=ast.literal_eval(onto_cont) - return render_template('index.html', onto_len_dir=onto_len_dir, onto_list=onto_list_session, ontol = 'addiction', dict_onto = dict_onto) + return render_template('index.html', onto_len_dir=onto_len_dir, onto_list=onto_list_session, ontol = 'addiction', dict_onto = dict_onto,version=version()) message3="<ul><li> <font color=\"#E74C3C\">Click on the abstract count to read sentences linking the keyword and the gene</font> <li> Click on a keyword to see the terms included in the search. <li>View the results in <a href='\\cytoscape/?rnd={}&genequery={}'\ ><b> a graph.</b></a> </ul> Links will be preserved when the table is copy-n-pasted into a spreadsheet.".format(select_date,genes_session_str) - return render_template('tableview.html',nodata_temp=nodata_temp, num_gene=num_gene,genes_session_tmp = genes_session_tmp, rnd_url=select_date ,jedges=jedges, jnodes=jnodes,gene_name=gene_name, genes_url=genes_session_str, message3=message3) + return render_template('tableview.html',nodata_temp=nodata_temp, num_gene=num_gene,genes_session_tmp = genes_session_tmp, rnd_url=select_date ,jedges=jedges, jnodes=jnodes,gene_name=gene_name, genes_url=genes_session_str, message3=message3,no_footer=True,version=version()) @app.route('/cytoscape/') def cytoscape(): @@ -1504,7 +1511,7 @@ def cytoscape(): onto_list_session = '' # Renamed onto_cont=open("addiction.onto","r").read() dict_onto=ast.literal_eval(onto_cont) - return render_template('index.html', onto_len_dir=onto_len_dir, onto_list=onto_list_session, ontol = 'addiction', dict_onto = dict_onto) + return render_template('index.html', onto_len_dir=onto_len_dir, onto_list=onto_list_session, ontol = 'addiction', dict_onto = dict_onto,version=version()) try: with open(rnd_url_path+"_0link","r") as z: @@ -1524,7 +1531,7 @@ def cytoscape(): onto_list_session = '' # Renamed onto_cont=open("addiction.onto","r").read() dict_onto=ast.literal_eval(onto_cont) - return render_template('index.html', onto_len_dir=onto_len_dir, onto_list=onto_list_session, ontol = 'addiction', dict_onto = dict_onto) + return render_template('index.html', onto_len_dir=onto_len_dir, onto_list=onto_list_session, ontol = 'addiction', dict_onto = dict_onto,version=version()) try: with open(rnd_url_path+"_0link","r") as z: @@ -1535,7 +1542,7 @@ def cytoscape(): if (len(zeroLink.strip())>0): # Check if zeroLink has content after stripping whitespace message2+="<span style=\"color:darkred;\">No result was found for these genes: " + zeroLink + "</span>" - return render_template('cytoscape.html', elements=elements, message2=message2) + return render_template('cytoscape.html', elements=elements, message2=message2,version=version()) @app.route("/sentences") @@ -1558,7 +1565,7 @@ def sentences(): matching_sents = get_sentences_from_file(tf_name, gene0, cat0) if not matching_sents: # It's possible the file was found but no sentences matched the criteria. - return render_template('sentences.html', sentences=f"<p>No sentences found for {gene0} and {cat0}.</p>") + return render_template('sentences.html', sentences=f"<p>No sentences found for {gene0} and {cat0}.</p>",no_footer=True,version=version()) all_stress_sentences = [] num_abstract = len(matching_sents) @@ -1643,7 +1650,7 @@ Here are the sentences to classify: out= out1+ out2 + "<ol>" + out3 # K.clear_session() # Removed - return render_template('sentences.html', sentences=out+"</ol><p>") + return render_template('sentences.html', sentences=out+"</ol><p>",no_footer=True,version=version()) # Show the cytoscape graph for one gene from the top gene list @@ -1659,7 +1666,7 @@ def showTopGene(): print(f"Warning: searchArchived did not return expected data for {query}") message2="<li><strong>"+query + "</strong> is one of the top addiction genes. <li> An archived search is shown. Click on the blue circle to update the results and include keywords for brain region and gene function. <strong> The update may take a long time to finish.</strong> " - return render_template("cytoscape.html", elements=nodesEdges, message="Top addiction genes", message2=message2) + return render_template("cytoscape.html", elements=nodesEdges, message="Top addiction genes", message2=message2,version=version()) ''' @app.route("/shownode") @@ -1695,7 +1702,7 @@ def shownode(): if not out: # If node not found or details are empty out = f"<p>Details for node '{node.upper()}' not found in the current ontology.</p>" - return render_template('sentences.html', sentences=out+"<p>") + return render_template('sentences.html', sentences=out+"<p>",no_footer=True,version=version()) ''' @app.route("/shownode") def shownode(): @@ -1711,7 +1718,7 @@ def shownode(): for ky in dictionary.keys(): if node in dictionary[ky].keys(): out="<p>"+node.upper()+"<hr><li>"+ next(iter(dictionary[ky][node])).replace("|", "<li>") - return render_template('sentences.html', sentences=out+"<p>") + return render_template('sentences.html', sentences=out+"<p>",no_footer=True,version=version()) @@ -1873,11 +1880,11 @@ def synonyms(): prompt_string = GENECUP_PROMPT_TEMPLATE.replace("{{gene}}", node) prompt_string += formatted_sentences - return render_template('genenames.html', case=case, gene=node.upper(), synonym_list=synonym_list, synonym_list_str=synonym_list_str, prompt=prompt_string) + return render_template('genenames.html', case=case, gene=node.upper(), synonym_list=synonym_list, synonym_list_str=synonym_list_str, prompt=prompt_string,version=version()) except KeyError: case = 2 - return render_template('genenames.html', gene=node, case=case) + return render_template('genenames.html', gene=node, case=case,version=version()) except Exception as e: print(f"An unexpected error occurred in /synonyms for node {node}: {e}") return f"An error occurred while processing your request for {node}.", 500 @@ -1886,7 +1893,7 @@ def synonyms(): @app.route("/startGeneGene") def startGeneGene(): session['forTopGene']=request.args.get('forTopGene') - return render_template('progress.html', url_in="searchGeneGene", url_out="showGeneTopGene") + return render_template('progress.html', url_in="searchGeneGene", url_out="showGeneTopGene",version=version()) @app.route("/searchGeneGene") @@ -2041,13 +2048,13 @@ def showGeneTopGene (): results_content=result_f.read() else: print(f"Warning: Result file {result_file_path} not found for showGeneTopGene.") - return render_template('sentences.html', sentences=results_content+"<p><br>") + return render_template('sentences.html', sentences=results_content+"<p><br>",no_footer=True,version=version()) # Generate a page that lists all the top 150 addiction genes with links to cytoscape graph. @app.route("/allTopGenes") def top150genes(): - return render_template("topAddictionGene.html") + return render_template("topAddictionGene.html",no_footer=True,version=version()) if __name__ == '__main__': diff --git a/static/style.css b/static/style.css index d4cc2ee..3030424 100644 --- a/static/style.css +++ b/static/style.css @@ -72,3 +72,40 @@ a:active { ul { margin-left:40px; } + +.main { + min-height: 70vh; + padding-bottom: 100px; +} + +.navbar { + background-color: #336699; + border-color: #080808; +} + +body +{ +} + +.footer { + color: black; + background-color: #f5f5f5; + border-color: #080808; + font-size: small; + padding: 3px 0; + position: absolute; + left: 0; + bottom: 0; + width: 100%; +} + +.footer a { + text-decoration: none; + transition: color 0.3s ease; + color: #1e87f0; + cursor: pointer; +} + +.footer a:hover { + color: #adb5bd; +} diff --git a/templates/about.html b/templates/about.html index bdf52f7..3cdefc5 100644 --- a/templates/about.html +++ b/templates/about.html @@ -7,25 +7,25 @@ <h3> About GeneCup </h3> <hr> - <p>GeneCup searches PubMed to find abstracts containing genes of interest and keywords in the custom ontologies. - The title and abstracts corresponding to the PMIDs are then retrieved from a - <a href="https://dataguide.nlm.nih.gov/edirect/archive.html">local archive of the PubMed</a>. - No limit on the date of publication is set. Each abstract is then broken down into sentences, - which are then filtered by gene names and keywords. We also parse the GWAS catalog to obtain + <p>GeneCup searches PubMed to find abstracts containing genes of interest and keywords in the custom ontologies. + The title and abstracts corresponding to the PMIDs are then retrieved from a + <a href="https://dataguide.nlm.nih.gov/edirect/archive.html">local archive of the PubMed</a>. + No limit on the date of publication is set. Each abstract is then broken down into sentences, + which are then filtered by gene names and keywords. We also parse the GWAS catalog to obtain genetics associations with the keywords of the custom ontology. - <p>A list of curated <a href="/ontology">addiction-related keywords</a> can be used to search - addiction-related genes. We compiled the most studied 100 addiction related genes - by searching 29,761 human genes against addiction related keywords. - To ensure comprehensive coverage, gene alias obtained from NCBI gene database were included in the search. - The results were extensively curated to remove over 900 alias that matched words - that were not gene name or wrong genes. Some incorrect results remained because the same name - also produced correct results. The resulting 61,000 sentences are archived localy and can be accessed - via the <a href="/allTopGenes">Addiction Genes</a> link. We also archived 5.3 million PMIDs - associated with these gene for efficient search of query gene to addiction gene relations. - We obtain 23,000 genetics associations with the addiction and psychiatric phenotypes from GWAS catalog. + <p>A list of curated <a href="/ontology">addiction-related keywords</a> can be used to search + addiction-related genes. We compiled the most studied 100 addiction related genes + by searching 29,761 human genes against addiction related keywords. + To ensure comprehensive coverage, gene alias obtained from NCBI gene database were included in the search. + The results were extensively curated to remove over 900 alias that matched words + that were not gene name or wrong genes. Some incorrect results remained because the same name + also produced correct results. The resulting 61,000 sentences are archived localy and can be accessed + via the <a href="/allTopGenes">Addiction Genes</a> link. We also archived 5.3 million PMIDs + associated with these gene for efficient search of query gene to addiction gene relations. + We obtain 23,000 genetics associations with the addiction and psychiatric phenotypes from GWAS catalog. These results are included in the search by default. - + <p> We plan to update the local PubMed archive daily and the EBI GWAS catalog quarterly. </div> @@ -37,9 +37,7 @@ Cite: Gunturkun MH, Flashner E, Wang T, Mulligan MK, Williams RW, Prins P, Chen H. <a href="https://academic.oup.com/g3journal/article/12/5/jkac059/6548160" target=_new>GeneCup: mining PubMed and GWAS catalog for gene-keyword relationships.</a> G3 (Bethesda). 2022 May 6;12(5):jkac059. doi: 10.1093/g3journal/jkac059. PMID: 35285473; PMCID: PMC9073678. -<p> <a href="https://github.com/chen42/ratspub"> Source code </a> +<p> <a href="https://git.genenetwork.org/genecup/">Source code </a> {% endblock %} - - diff --git a/templates/index.html b/templates/index.html index 526820b..d098324 100644 --- a/templates/index.html +++ b/templates/index.html @@ -6,24 +6,23 @@ <div class="row"> <div class="col-md-9"> - <p>GeneCup is a tool to efficiently and comprehensively answer the question <b>"What do we know about these genes and the topic I study?". + <p>GeneCup is a tool to answer the question <b>"What do we know about these genes and the topic I study?". </b> GeneCup answers this question by searching PubMed to find <i>sentences</i> containing gene symbols and a custom list of keywords. - These keywords are organized into a user defined ontology, which groups related keywords together for better organization of the results. + <!-- These keywords are organized into a user defined ontology, which groups related keywords together for better organization of the results. --> Human GWAS findings from <a href="https://www.ebi.ac.uk/gwas/">NHGRI-EBI GWAS catalog </a>are also included in the search. - These gene-keyword relationships are presented as an interactive graph and a table. + These gene-keyword relationships are presented as a graph or a table. <!--Gene alias often include non-specific words and are thus not included in the initial search. However, a list of alias can be found by clicking on the gene symbol in the results section. These alias then can be searched with a single click. --> - As an example, we present a curated ontology for drug addiction with over <a href="/ontology"> 300 keywords organized into seven categories.</a> - Furthermore, <b>deep learning </b> is used to distinguish automatically the sentences describing systemic stress from those describing cellular stress. - A user account (free) is needed to create custom ontologies and save the search results. + Here we present a curated ontology for drug addiction with over <a href="/ontology"> 300 keywords organized into seven categories.</a> + <b>Deep learning </b> is used to distinguish, for example, the sentences describing systemic stress from those describing cellular stress. + A user account (free) is needed to create custom ontologies and save the search results. We do not track you. </p> <p>Example: </b><a href=/progress?type=GWAS&type=addiction&type=drug&type=brain&type=stress&type=psychiatric&type=cell&type=function&query=Rgma+Penk>Rgma Penk</a>.</p> {%if ("name" not in session) %} By default, the <a href="/ontology">addiction</a> ontology is used. Custom ontologies can be created in user accounts. - Please choose keyword categories to be included in the search. <form action="/progress"> <div id="check_selection"></div> diff --git a/templates/layout.html b/templates/layout.html index 20dbcbe..1c73fd9 100644 --- a/templates/layout.html +++ b/templates/layout.html @@ -4,7 +4,7 @@ <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> - + <!-- Bootstrap CSS --> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous"> <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script> @@ -16,11 +16,11 @@ <body> <!-- Navigation bar --> -<nav class="navbar navbar-expand-lg navbar-dark bg-dark"> +<nav class="navbar navbar-expand-lg navbar-dark"> <div class="col-1"> <a href="/"><div class='img'><img src="/static/white_logo.png" class="img-fluid", style="width:60%"></div></a> </div> - + <a class="navbar-brand" href="/">GeneCup</a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation"> <span class="navbar-toggler-icon"></span> @@ -90,15 +90,29 @@ {% endif %} {% endwith %} </div> -<br> -<div class="container"> +<div class="container main"> {% block content %}{% endblock %} </div> +{% if not no_footer %} + <footer class="footer footer-dark"> + <div class="container"> + <div class="row"> + <div class="col-md-6"> + GeneCup {{ version }}: PubMed + GWAS catalog deep learning. + Cite <a href="https://academic.oup.com/g3journal/article/12/5/jkac059/6548160">GeneCup.</a> + </div> + <div class="col-md-6" align="right"> + <a href="https://git.genenetwork.org/genecup/">Code</a> + powered by <a href="https://www.tensorflow.org/">Tensorflow</a> on <a href="https://genenetwork.org/">GeneNetwork.org</a> + </div> + </div> + </div> + </footer> +{% endif %} + <script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script> <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script> </body> - - |
