diff --git a/.gitignore b/.gitignore index c5cf332..b7fdb39 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,18 @@ dist/ stlearn/static/* uploads/spatial uploads/filtered_feature_bc_matrix.h5 +uploads/*.h5ad +temp_plots/ +temp_tiling/ temp_tiling/ temp_plots/* +temp_plots/ +temp_tiling/ +tiling/ +.DS_Store +uploads/* +temp_plots/ +temp_tiling/ tiling/ +.DS_Store + diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..aec1ffe --- /dev/null +++ b/Dockerfile @@ -0,0 +1,6 @@ +FROM python:3.8 + +WORKDIR usr/src/flask_app +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt +COPY . . diff --git a/README.md b/README.md index c413a6d..daf44a3 100644 --- a/README.md +++ b/README.md @@ -1,30 +1,18 @@ -# stlearn-bokeh -stlearn with interactive web app +# Tutorial how to run i-stLearn +We build a webapp version that wrap stLearn functions. ## Step 1: -Install stlearn dev version by: - -``` git clone https://github.com/BiomedicalMachineLearning/stLearn ``` - -``` cd stLearn ``` - -``` python setup.py install ``` +Install stlearn by this instruction: https://stlearn.readthedocs.io/en/latest/installation.html ## Step 2: -Clone stlearn interactive app: - -``` git clone https://github.com/BiomedicalMachineLearning/stlearn_interactive.git ``` - -``` cd stlearn_interactive ``` - -``` pip install -r requirements.txt ``` +Run the stlearn interactive app: -## Step 3: +``` stlearn launch ``` -Run the stlearn interactive app: +## Step 3: Tutorial -``` FLASK_APP=app.py flask run ``` +- [Tutorial - stLearn Interactive](https://github.com/BiomedicalMachineLearning/stlearn_interactive/blob/master/docs/stlearn_interactive_tutorial.pdf) diff --git a/app.py b/app.py index 12d7728..be4d8f2 100644 --- a/app.py +++ b/app.py @@ -14,11 +14,13 @@ from werkzeug.utils import secure_filename import tempfile import traceback +from datetime import timedelta import os, sys import stlearn import scanpy import numpy +import numpy as np import asyncio from bokeh.server.server import BaseServer @@ -45,61 +47,71 @@ "preprocessed": [False, "Preprocessing"], "clustering": [False, "Clustering"], "psts": [False, "Spatial trajectory"], - "cci": [False, "Cell-cell interaction"], "dea": [False, "DEA"], + "lr": [False, "Ligand-receptor analysis"], + "cci": [False, "CCI"], # _params suffix important for templates/progress.html "preprocessed_params": {}, "cci_params": {}, "cluster_params": {}, "psts_params": {}, "dea_params": {}, + "lr_params": {}, } # print(stlearn, file=sys.stdout) -app = Flask(__name__) -app.secret_key = b'_5#y2L"F4Q8z\n\xec]/' +server = Flask(__name__) +server.secret_key = b'_5#y2L"F4Q8z\n\xec]/' +server.config["PERMANENT_SESSION_LIFETIME"] = timedelta(seconds=20) UPLOAD_FOLDER = "uploads/" TEMPLATES_AUTO_RELOAD = True -app.config["UPLOAD_FOLDER"] = UPLOAD_FOLDER -app.config["SEND_FILE_MAX_AGE_DEFAULT"] = 0 -app.config["TEMPLATES_AUTO_RELOAD"] = TEMPLATES_AUTO_RELOAD -app.config["SESSION_PERMANENT"] = False +server.config["UPLOAD_FOLDER"] = UPLOAD_FOLDER +server.config["SEND_FILE_MAX_AGE_DEFAULT"] = 0 +server.config["TEMPLATES_AUTO_RELOAD"] = TEMPLATES_AUTO_RELOAD +server.config["SESSION_PERMANENT"] = False -@app.route("/", methods=["GET"]) +@server.route("/", methods=["GET"]) def index(): return render_template("index.html", step_log=step_log) -@app.route("/upload") +@server.route("/upload") def upload(): return render_template("upload.html", step_log=step_log, flash_bool=True) -@app.route("/preprocessing", methods=["GET", "POST"]) +@server.route("/preprocessing", methods=["GET", "POST"]) def preprocessing(): global adata, step_log updated_page = views.run_preprocessing(request, adata, step_log) return updated_page -@app.route("/clustering", methods=["GET", "POST"]) +@server.route("/clustering", methods=["GET", "POST"]) def clustering(): global adata, step_log updated_page = views.run_clustering(request, adata, step_log) return updated_page -@app.route("/cci", methods=["GET", "POST"]) +@server.route("/lr", methods=["GET", "POST"]) +def lr(): + global adata, step_log + updated_page = views.run_lr(request, adata, step_log) + return updated_page + + +@server.route("/cci", methods=["GET", "POST"]) def cci(): global adata, step_log updated_page = views.run_cci(request, adata, step_log) return updated_page -@app.route("/psts", methods=["GET", "POST"]) +@server.route("/psts", methods=["GET", "POST"]) def psts(): global adata, step_log @@ -110,7 +122,7 @@ def psts(): return updated_page -@app.route("/dea", methods=["GET", "POST"]) +@server.route("/dea", methods=["GET", "POST"]) def dea(): global adata, step_log updated_page = views.run_dea(request, adata, step_log) @@ -126,21 +138,21 @@ def dea(): ] -@app.route("/folder_uploader", methods=["GET", "POST"]) +@server.route("/folder_uploader", methods=["GET", "POST"]) def folder_uploader(): if request.method == "POST": # Clean uploads folder before upload a new data import shutil - shutil.rmtree(app.config["UPLOAD_FOLDER"]) - os.makedirs(app.config["UPLOAD_FOLDER"]) - open(app.config["UPLOAD_FOLDER"] + "/.gitkeep", "a").close() + shutil.rmtree(server.config["UPLOAD_FOLDER"]) + os.makedirs(server.config["UPLOAD_FOLDER"]) + open(server.config["UPLOAD_FOLDER"] + "/.gitkeep", "a").close() # os.mknod() # Get list of files from selected folder files = request.files.getlist("file") - os.mkdir(os.path.join(app.config["UPLOAD_FOLDER"], "spatial")) + os.mkdir(os.path.join(server.config["UPLOAD_FOLDER"], "spatial")) # allow_upload_files = list(map(lambda x: x ),allow_files) @@ -150,50 +162,53 @@ def folder_uploader(): filename = secure_filename(file.filename) + print(filename) + if allow_files[0] in filename: - file.save(os.path.join(app.config["UPLOAD_FOLDER"], filename)) + file.save(os.path.join(server.config["UPLOAD_FOLDER"], filename)) os.rename( - os.path.join(app.config["UPLOAD_FOLDER"], filename), - os.path.join(app.config["UPLOAD_FOLDER"], allow_files[0]), + os.path.join(server.config["UPLOAD_FOLDER"], filename), + os.path.join(server.config["UPLOAD_FOLDER"], allow_files[0]), ) uploaded.append(allow_files[0]) for allow_file in allow_files[1:]: if allow_file in filename: + file.save( - os.path.join(app.config["UPLOAD_FOLDER"] + "/spatial", filename) + os.path.join( + server.config["UPLOAD_FOLDER"] + "/spatial", filename + ) ) os.rename( os.path.join( - app.config["UPLOAD_FOLDER"] + "/spatial", filename + server.config["UPLOAD_FOLDER"] + "/spatial", filename ), os.path.join( - app.config["UPLOAD_FOLDER"] + "/spatial", allow_file + server.config["UPLOAD_FOLDER"] + "/spatial", allow_file ), ) uploaded.append(allow_file) - - print(i) i += 1 if len(uploaded) == 5: flash("File uploaded successfully") global adata, step_log - step_log = { - "uploaded": [False, "Upload file"], - "preprocessed": [False, "Preprocessing"], - "clustering": [False, "Clustering"], - "psts": [False, "Spatial trajectory"], - "cci": [False, "Cell-cell interaction"], - "dea": [False, "Differential expression analysis"], - # _params suffix important for templates/progress.html - "preprocessed_params": {}, - "cci_params": {}, - "cluster_params": {}, - "psts_params": {}, - "dea_params": {}, - } - adata = stlearn.Read10X(app.config["UPLOAD_FOLDER"]) + # step_log = { + # "uploaded": [False, "Upload file"], + # "preprocessed": [False, "Preprocessing"], + # "clustering": [False, "Clustering"], + # "psts": [False, "Spatial trajectory"], + # "cci_rank": [False, "Cell-cell interaction"], + # "dea": [False, "Differential expression analysis"], + # # _params suffix important for templates/progress.html + # "preprocessed_params": {}, + # "cci_params": {}, + # "cluster_params": {}, + # "psts_params": {}, + # "dea_params": {}, + # } + adata = stlearn.Read10X(server.config["UPLOAD_FOLDER"]) adata.var_names_make_unique() # removing duplicates # ensuring compatible format for CCI, since need _ to pair LRs # adata.var_names = numpy.array( @@ -213,7 +228,7 @@ def folder_uploader(): return redirect(url_for("upload")) -@app.route("/file_uploader", methods=["GET", "POST"]) +@server.route("/file_uploader", methods=["GET", "POST"]) def file_uploader(): if request.method == "POST": @@ -222,18 +237,19 @@ def file_uploader(): # Clean uploads folder before upload a new data import shutil - shutil.rmtree(app.config["UPLOAD_FOLDER"]) - os.makedirs(app.config["UPLOAD_FOLDER"]) - open(app.config["UPLOAD_FOLDER"] + "/.gitkeep", "a").close() + shutil.rmtree(server.config["UPLOAD_FOLDER"]) + os.makedirs(server.config["UPLOAD_FOLDER"]) + open(server.config["UPLOAD_FOLDER"] + "/.gitkeep", "a").close() # os.mknod() f = request.files["file"] filename = secure_filename(f.filename) - f.save(os.path.join(app.config["UPLOAD_FOLDER"], filename)) + f.save(os.path.join(server.config["UPLOAD_FOLDER"], filename)) try: - adata = scanpy.read_h5ad(app.config["UPLOAD_FOLDER"] + "/" + f.filename) + adata = scanpy.read_h5ad(server.config["UPLOAD_FOLDER"] + "/" + f.filename) except: flash("Upload ERROR: Please choose the right AnnData file ") + ### Updating log file with current anndata state ### step_log["uploaded"][0] = True if "n_cells" in adata.var.columns: @@ -247,10 +263,13 @@ def file_uploader(): if "global_graph" in adata.uns: step_log["psts"][0] = True + step_log["lr"][0] = "lr_summary" in adata.uns + step_log["cci"][0] = np.any(["lr_cci_" in key for key in adata.uns]) + return redirect(url_for("upload")) -@app.route("/choose_cluster", methods=["GET", "POST"]) +@server.route("/choose_cluster", methods=["GET", "POST"]) def choose_cluster(): menu = [] @@ -268,7 +287,7 @@ def choose_cluster(): ) -@app.route("/convert_clusters", methods=["GET", "POST"]) +@server.route("/convert_clusters", methods=["GET", "POST"]) def convert_clusters(): if request.method == "POST": adata.obs["clusters"] = adata.obs[request.form["convert_clusters"]] @@ -278,9 +297,11 @@ def convert_clusters(): return redirect(url_for("psts")) -@app.route("/gene_plot") +@server.route("/gene_plot") def gene_plot(): - script = server_document("http://127.0.0.1:5006/bokeh_gene_plot") + script = server_document( + "http://%s:5006/bokeh_gene_plot" % request.host.split(":")[0] + ) return render_template( "gene_plot.html", script=script, @@ -290,9 +311,11 @@ def gene_plot(): ) -@app.route("/cluster_plot") +@server.route("/cluster_plot") def cluster_plot(): - script = server_document("http://127.0.0.1:5006/bokeh_cluster_plot") + script = server_document( + "http://%s:5006/bokeh_cluster_plot" % request.host.split(":")[0] + ) return render_template( "cluster_plot.html", script=script, @@ -302,11 +325,13 @@ def cluster_plot(): ) -@app.route("/cci_plot") -def cci_plot(): - script = server_document("http://127.0.0.1:5006/bokeh_cci_plot") +@server.route("/lr_plot") +def lr_plot(): + script = server_document( + "http://%s:5006/bokeh_lr_plot" % request.host.split(":")[0] + ) return render_template( - "cci_plot.html", + "lr_plot.html", script=script, template="Flask", relative_urls=False, @@ -314,9 +339,25 @@ def cci_plot(): ) -@app.route("/annotate_plot") +@server.route("/spatial_cci_plot") +def spatial_cci_plot(): + script = server_document( + "http://%s:5006/bokeh_spatial_cci_plot" % request.host.split(":")[0] + ) + return render_template( + "spatial_cci_plot.html", + script=script, + template="Flask", + relative_urls=False, + step_log=step_log, + ) + + +@server.route("/annotate_plot") def annotate_plot(): - script = server_document("http://127.0.0.1:5006/bokeh_annotate_plot") + script = server_document( + "http://%s:5006/bokeh_annotate_plot" % request.host.split(":")[0] + ) return render_template( "annotate_plot.html", script=script, @@ -326,7 +367,7 @@ def annotate_plot(): ) -@app.route("/save_adata", methods=["POST"]) +@server.route("/save_adata", methods=["POST"]) def save_adata(): if request.method == "POST": fd, path = tempfile.mkstemp() @@ -355,8 +396,8 @@ def save_adata(): # sc.pp.scale(adata)g # adata.uns["lr"] = ['Gfap_Ctss'] -# st.tl.cci.lr(adata=adata) -# st.tl.cci.permutation(adata,n_pairs=1) +# st.tl.cci_rank.lr(adata=adata) +# st.tl.cci_rank.permutation(adata,n_pairs=1) # sc.pp.pca(adata) # sc.pp.neighbors(adata) @@ -405,16 +446,33 @@ def modify_doc_cluster_plot(doc): gp_object.min_logfoldchange.on_change("value", gp_object.update_data) -def modify_doc_cci_plot(doc): - from stlearn.plotting.classes_bokeh import BokehCciPlot +def modify_doc_spatial_cci_plot(doc): + from stlearn.plotting.classes_bokeh import BokehSpatialCciPlot + + gp_object = BokehSpatialCciPlot(adata) + doc.add_root(row(gp_object.layout, width=800)) + + gp_object.annot_select.on_change("value", gp_object.update_list) + gp_object.annot_select.on_change("value", gp_object.update_data) + gp_object.lr_select.on_change("value", gp_object.update_data) + gp_object.data_alpha.on_change("value", gp_object.update_data) + gp_object.tissue_alpha.on_change("value", gp_object.update_data) + gp_object.spot_size.on_change("value", gp_object.update_data) + gp_object.list_cluster.on_change("active", gp_object.update_data) + gp_object.output_backend.on_change("value", gp_object.update_data) + + +def modify_doc_lr_plot(doc): + from stlearn.plotting.classes_bokeh import BokehLRPlot - gp_object = BokehCciPlot(adata) + gp_object = BokehLRPlot(adata) doc.add_root(row(gp_object.layout, width=800)) gp_object.data_alpha.on_change("value", gp_object.update_data) gp_object.tissue_alpha.on_change("value", gp_object.update_data) gp_object.spot_size.on_change("value", gp_object.update_data) - gp_object.het_select.on_change("value", gp_object.update_data) + # gp_object.het_select.on_change("value", gp_object.update_data) + gp_object.lr_select.on_change("value", gp_object.update_data) gp_object.output_backend.on_change("value", gp_object.update_data) @@ -434,8 +492,11 @@ def modify_doc_annotate_plot(doc): # App for cluster_plot bkapp2 = Application(FunctionHandler(modify_doc_cluster_plot)) -# App for cci_plot -bkapp3 = Application(FunctionHandler(modify_doc_cci_plot)) +# App for lr_plot +bkapp3 = Application(FunctionHandler(modify_doc_lr_plot)) + +# App for cci_spatial_plot +bkapp3_1 = Application(FunctionHandler(modify_doc_spatial_cci_plot)) # App for annotate_plot bkapp4 = Application(FunctionHandler(modify_doc_annotate_plot)) @@ -448,11 +509,18 @@ def bk_worker(): { "/bokeh_gene_plot": bkapp, "/bokeh_cluster_plot": bkapp2, - "/bokeh_cci_plot": bkapp3, + # "/bokeh_cci_plot": bkapp3, + "/bokeh_lr_plot": bkapp3, + "/bokeh_spatial_cci_plot": bkapp3_1, "/bokeh_annotate_plot": bkapp4, }, io_loop=IOLoop(), - allow_websocket_origin=["127.0.0.1:5000", "localhost:5000"], + allow_websocket_origin=[ + "127.0.0.1:8000", + "localhost:8000", + "0.0.0.0:8000", + "*", + ], ) server.start() server.io_loop.start() @@ -462,5 +530,5 @@ def bk_worker(): Thread(target=bk_worker).start() -if __name__ == "__main__": - app.run(host="0.0.0.0", port=5005, debug=True, use_reloader=True) +# if __name__ == "__main__": +# app.run(host="0.0.0.0", port=5005, debug=True, use_reloader=True) diff --git a/docs/stlearn_interactive_tutorial.pdf b/docs/stlearn_interactive_tutorial.pdf new file mode 100644 index 0000000..77dfeaa Binary files /dev/null and b/docs/stlearn_interactive_tutorial.pdf differ diff --git a/requirements.txt b/requirements.txt index 7e58a80..453db43 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,6 @@ -Flask==1.1.2 -WTForms==2.3.1 +Flask==2.0.3 +flask_wtf==1.0.0 +gunicorn +markupsafe==2.1.0 +stlearn +WTForms==3.0.1 diff --git a/source/forms/forms.py b/source/forms/forms.py index 21bc61b..0eef6b1 100644 --- a/source/forms/forms.py +++ b/source/forms/forms.py @@ -159,7 +159,71 @@ def getPreprocessForm(): return createSuperForm(elements, element_fields, element_values) -def getCCIForm(): +def getLRForm(): + """Gets the LR form generated from the superform above. + + Returns: + FlaskForm: With attributes that allow for inputs that are \ + related to LR analysis. + """ + elements = [ + "Species", + "Spot neighbourhood (-1: smallest neighbourhood, 0: within-spot mode)", + "Minimum spots with LR scores", + "N random gene pairs (permutations)", + "CPUs", + ] + element_fields = [ + "SelectField", + "IntegerField", + "IntegerField", + "IntegerField", + "IntegerField", + ] + element_values = [ + [("Human", "Human"), ("Mouse", "Mouse")], + -1, + 20, + 100, + 2, + ] + return createSuperForm(elements, element_fields, element_values) + + +def getCCIForm(adata): + """Gets the CCI form generated from the superform above. + + Returns: + FlaskForm: With attributes that allow for inputs that are + related to CCI analysis. + """ + elements = [ + "Cell information (only discrete labels available, unless mixture already in anndata.uns)", + "Minimum spots for LR to be considered", + "Spot mixture (only if the 'Cell Information' label selected available in anndata.uns)", + "Cell proportion cutoff (value above which cell is considered in spot if 'Spot mixture' selected)", + "Permutations (recommend atleast 1000)", + ] + element_fields = [ + "SelectField", + "IntegerField", + "BooleanField", + "FloatField", + "IntegerField", + ] + if type(adata) == type(None): + fields = [] + mix = False + else: + fields = [ + key for key in adata.obs.keys() if type(adata.obs[key].values[0]) == str + ] + mix = fields[0] in adata.uns.keys() + element_values = [fields, 20, mix, 0.2, 100] + return createSuperForm(elements, element_fields, element_values) + + +def getCCIForm_old(): """Gets the CCI form generated from the superform above. Returns: diff --git a/source/forms/views.py b/source/forms/views.py index e0e90c7..08c89c2 100644 --- a/source/forms/views.py +++ b/source/forms/views.py @@ -5,6 +5,7 @@ import sys import numpy +import numpy as np from flask import flash from source.forms import forms @@ -21,8 +22,9 @@ # Creating the forms using a class generator # PreprocessForm = forms.getPreprocessForm() -CCIForm = forms.getCCIForm() +# CCIForm = forms.getCCIForm() #OLD ClusterForm = forms.getClusterForm() +LRForm = forms.getLRForm() def run_preprocessing(request, adata, step_log): @@ -77,90 +79,96 @@ def run_preprocessing(request, adata, step_log): return updated_page +def run_lr(request, adata, step_log): + """Runs LR analysis.""" + + form = LRForm(request.form) + + if not form.validate_on_submit(): + flash_errors(form) + + elif type(adata) == type(None): + flash("Need to load data first!") + + else: + step_log["lr_params"] = vhs.getData(form) + print(step_log["lr_params"], file=sys.stdout) + elements = numpy.array(list(step_log["lr_params"].keys())) + # order: Species, Spot neighbourhood, min_spots, n_pairs, CPUs + element_values = list(step_log["lr_params"].values()) + dist = element_values[1] + dist = dist if dist != -1 else None + + # Loading the LR databases available within stlearn (from NATMI) + lrs = st.tl.cci.load_lrs(["connectomeDB2020_lit"], species=element_values[0]) + + # Running the analysis # + st.tl.cci.run( + adata, + lrs, + min_spots=element_values[2], + distance=dist, + n_pairs=element_values[3], + n_cpus=element_values[-1], + ) + flash("LR analysis is completed!") + + step_log["lr"][0] = "lr_summary" in adata.uns + + updated_page = render_template( + "lr.html", + title=step_log["lr"][1], + lr_form=form, + flash_bool=True, + step_log=step_log, + ) + return updated_page + + def run_cci(request, adata, step_log): """Performs CCI analysis.""" + CCIForm = forms.getCCIForm(adata) form = CCIForm(request.form) - step_log["cci_params"] = vhs.getData(form) - print(step_log["cci_params"], file=sys.stdout) - elements = numpy.array(list(step_log["cci_params"].keys())) - # order: cell_het file, neighbour_dist, L-R pairs, permutations - element_values = list(step_log["cci_params"].values()) - - cell_het = type(element_values[0]) != type(None) - lrs, msg = vhs.getLR(element_values[2], adata.var_names) - print(lrs, file=sys.stdout) - if not form.validate_on_submit(): flash_errors(form) elif type(adata) == type(None): flash("Need to load data first!") - elif msg != "": - flash(msg) - else: - """If we have been through the steps of: - 1. Choosing with/without cell heterogeneity information. - 2. Choosing with/without permutation testing. - 3. Filling out information. - We are now ready to take the form input & perform CCI analysis. - """ - try: - adata.uns["lr"] = lrs - st.tl.cci.lr(adata=adata, distance=element_values[1]) - st.pl.het_plot(adata, use_het="cci_lr", image_alpha=0.7) - savePlot("cell_lr.png") - - dist, n_pairs = element_values[1], element_values[-1] - if cell_het: # Conduct with cell heterogeneity information # - print("We are using the cell heterogeneity!", file=sys.stdout) - # Adding the label transfer information # - st.add.labels(adata, element_values[0], sep="\t") - st.pl.cluster_plot(adata, use_label="predictions") - savePlot("label_transfer.png") # saves to temp_pots - - # Calculating cell heterogeneity # - st.tl.cci.het.count(adata, distance=dist, use_label="label_transfer") - st.pl.het_plot(adata, use_het="cci_het") - savePlot("cell_het.png") - - # Merging with the lR values # - st.tl.cci.merge(adata, use_lr="cci_lr", use_het="cci_het") - st.pl.het_plot(adata, use_het="merged", cell_alpha=0.7) - savePlot("merged.png") - - if n_pairs != 0: # Permutation testing # - st.tl.cci.permutation( - adata, - use_het="cci_het" if cell_het else None, - n_pairs=n_pairs, - distance=dist, - ) - st.pl.het_plot( - adata, - cell_alpha=0.7, - use_het="merged_pvalues" if cell_het else "lr_pvalues", - ) - savePlot("cci-log10pvalues.png") - - st.pl.het_plot( + step_log["cci_params"] = vhs.getData(form) + print(step_log["cci_params"], file=sys.stdout) + elements = numpy.array(list(step_log["cci_params"].keys())) + # order: cell_type, min_spots, spot_mixtures, cell_prop_cutoff, sig_spots + # n_perms + element_values = list(step_log["cci_params"].values()) + + if not form.validate_on_submit(): + flash_errors(form) + + else: + try: + # Running the counting of co-occurence of cell types and LR expression # + st.tl.cci.run_cci( adata, - cell_alpha=0.7, - use_het="merged_sign" if cell_het else "lr_sign", + element_values[0], + min_spots=element_values[1], + spot_mixtures=element_values[2], + cell_prop_cutoff=element_values[3], + sig_spots=True, # Should make this not optional.. + n_perms=element_values[4], ) - savePlot("cci-sig-log10pvalues.png") - step_log["cci"][0] = True + flash("CCI analysis is completed!") - flash("CCI analysis is completed!") + except Exception as msg: + traceback.print_exc(file=sys.stdout) + flash("Analysis ERROR: " + str(msg)) + print(msg) - except Exception as msg: - traceback.print_exc(file=sys.stdout) - flash("Analysis ERROR: " + str(msg)) - print(msg) + step_log["cci"][0] = np.any(["lr_cci_" in key for key in adata.uns]) updated_page = render_template( "cci.html", diff --git a/source/readme.md b/source/readme.md index ccfc47f..1ff1c27 100644 --- a/source/readme.md +++ b/source/readme.md @@ -1,31 +1,28 @@ # Pre-processing Form Design Notes Flow of data: - app.py/preprocessing() + app.py/preprocessing() -> source/forms/views.py/run_preprocessing(request, adata, step_log) -> source/forms/forms.py/getPreprocessForm() -> templates/preprocessing.html -> templates/superform.html -Notes: - +Notes: + * step_log defined in app.py, keeps track whether pre-processing was run. -> If not run, then run_preprocessing shows just the form. -> If has run, shows banner that preprocessing complete. - + * If attempt to run_preprocessing() when adata not yet loaded, shows banner - indicating need to upload the data first. - - * source/forms/forms.py/getPreprocessForm() generates a WTForm class using a + indicating need to upload the data first. + + * source/forms/forms.py/getPreprocessForm() generates a WTForm class using a general WTForm generator, as defined in: source/forms/forms.py/createSuperForm() - + * templates/preprocessing.html is the preprocessing page, which also injects - in a form to display using templates/superform.html. - - * templates/superform.html renders a general WTForm that was created using + in a form to display using templates/superform.html. + + * templates/superform.html renders a general WTForm that was created using the source/forms/forms.py/createSuperForm() function, thereby allowing - easy generation of new forms if need to add extra information. - - - + easy generation of new forms if need to add extra information. diff --git a/static/img/Settings.gif b/static/img/Settings.gif index 5487e4b..6c170a8 100644 Binary files a/static/img/Settings.gif and b/static/img/Settings.gif differ diff --git a/templates/__init__.py b/templates/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/templates/base.html b/templates/base.html index 2a14f39..e866686 100644 --- a/templates/base.html +++ b/templates/base.html @@ -66,6 +66,11 @@ settings_suggest

Preprocessing

+ + @@ -164,6 +175,11 @@ {% if step_log.cci[0] %} $( "#cci_plot" ).removeClass( "disabled" ) + $( "#spatial_cci_plot" ).removeClass( "disabled" ) + {% endif %} + + {% if step_log.lr[0] %} + $( "#lr_plot" ).removeClass( "disabled" ) {% endif %} {% if step_log.clustering[0] %} @@ -173,6 +189,7 @@ {% endif %} {% if step_log.preprocessed[0] %} + $( "#lr" ).removeClass( "disabled" ) $( "#cci" ).removeClass( "disabled" ) $( "#clustering" ).removeClass( "disabled" ) {% endif %} diff --git a/templates/cci.html b/templates/cci.html index db7c25b..958a592 100644 --- a/templates/cci.html +++ b/templates/cci.html @@ -15,11 +15,6 @@

Cell-cell interaction analysis

{% include "superform.html" %} {% endwith %} - {% endblock %} diff --git a/templates/cci_old.html b/templates/cci_old.html new file mode 100644 index 0000000..db7c25b --- /dev/null +++ b/templates/cci_old.html @@ -0,0 +1,29 @@ +{% extends 'base.html' %} + +{% block content %} +
+
+

Cell-cell interaction analysis

+
+
+ + {# Gives the user errors if didn't fill out form correctly #} + {% include "flash_header.html" %} + + {# Below is the form. #} + {% with superForm=cci_form %} + {% include "superform.html" %} + {% endwith %} + + +
+
+{% endblock %} + +{% block status %} +{% include "progress.html" %} +{% endblock %} diff --git a/templates/lr.html b/templates/lr.html new file mode 100644 index 0000000..501d12c --- /dev/null +++ b/templates/lr.html @@ -0,0 +1,24 @@ +{% extends 'base.html' %} + +{% block content %} +
+
+

Ligand-receptor interaction analysis

+
+
+ + {# Gives the user errors if didn't fill out form correctly #} + {% include "flash_header.html" %} + + {# Below is the form. #} + {% with superForm=lr_form %} + {% include "superform.html" %} + {% endwith %} + +
+
+{% endblock %} + +{% block status %} +{% include "progress.html" %} +{% endblock %} diff --git a/templates/lr_plot.html b/templates/lr_plot.html new file mode 100644 index 0000000..724ec06 --- /dev/null +++ b/templates/lr_plot.html @@ -0,0 +1,20 @@ +{% extends 'base.html' %} +{% block content %} +
+
+

Ligand-receptor significant interactions plot

+

Interactive version

+
+
+ {{ script|safe }} +
+ Loading + Loading plot... +
+
+
+{% endblock %} + +{% block status %} +{% include "progress.html" %} +{% endblock %} diff --git a/templates/spatial_cci_plot.html b/templates/spatial_cci_plot.html new file mode 100644 index 0000000..cb7ef9c --- /dev/null +++ b/templates/spatial_cci_plot.html @@ -0,0 +1,20 @@ +{% extends 'base.html' %} +{% block content %} +
+
+

Spatial cell-cell interactions plot

+

Interactive version

+
+
+ {{ script|safe }} +
+ Loading + Loading plot... +
+
+
+{% endblock %} + +{% block status %} +{% include "progress.html" %} +{% endblock %} diff --git a/templates/upload.html b/templates/upload.html index c69316c..0202328 100644 --- a/templates/upload.html +++ b/templates/upload.html @@ -9,7 +9,7 @@

Uploading files

-
+

Option 1: Open Visium data

@@ -32,7 +32,7 @@
Please select the path to your Visium folder. It should include:
filt
- +

Option 2: Open AnnData object

diff --git a/wsgi.py b/wsgi.py new file mode 100644 index 0000000..a209a5d --- /dev/null +++ b/wsgi.py @@ -0,0 +1,4 @@ +from app import server + +if __name__ == "__main__": + server.run(host="0.0.0.0", port=8000, debug=True)