Skip to content

Commit b0510ae

Browse files
committed
first commit
0 parents  commit b0510ae

File tree

13 files changed

+660
-0
lines changed

13 files changed

+660
-0
lines changed

.gitignore

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
app/static/ref/hg19*
2+
app/static/dynamic_bam/*.bam
3+
app/static/dynamic_bam/*.bam.bai
4+
*.py[cod]
5+
6+
# 忽略所有 __pycache__ 目录及其内容
7+
__pycache__/
8+
9+
# build dir
10+
dist/

README.md

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# IGV Viewer Web Application
2+
This project provides a web-based interface for visualizing BAM files using IGV.js. Users can view genomic data in an interactive IGV browser, specifying loci of interest for visualization.
3+
Input bam files should be accessible and valid in the app server.
4+
5+
## Features
6+
+ **Interactive Genomic Visualization**: View genomic alignments and variations from BAM files directly in the browser using the IGV.js library.
7+
+ **User Customization**: Users can input the locus (chromosome region) and BAM file path, and the visualization will update dynamically.
8+
+ **Genome Reference**: The app uses the `hg19` reference genome, but it can be customized to support other genomes if necessary.
9+
10+
## Requirements
11+
+ Python 3.x
12+
+ Flask (for backend)
13+
+ IGV.js (JavaScript library for genomic visualization)
14+
15+
## Installation
16+
### Step 1: Clone the Repository
17+
Clone the repository to your local machine:
18+
19+
```bash
20+
git clone https://github.com/liuyanbioinfo/igv_web_app.git
21+
cd igv_web_app
22+
```
23+
24+
### Step 2: Download the reference files
25+
Download hg19 reference genome and index files.
26+
27+
(Optional) You can change to other refernce versions by editing `fastaURL` and `indexURL` in `/static/index.html`:
28+
29+
```bash
30+
cp /path/to/hg19.fa /static/ref/hg19.fa
31+
cp /path/to/hg19.fa.fai /static/ref/hg19.fa.fai
32+
```
33+
34+
### Step 3: Install Dependencies
35+
Install the required Python packages using `pip`:
36+
37+
```bash
38+
pip install -r requirements.txt
39+
```
40+
41+
### Step 4: Run the Application
42+
Start the Flask application:
43+
44+
```bash
45+
python run.py
46+
```
47+
48+
The application will be available at `http://127.0.0.1:5000/` by default.
49+
50+
## Usage
51+
1. **Open the Web Application**: Go to `http://127.0.0.1:5000/` in your web browser.
52+
2. **Input Locus**: In the "Locus" input field, provide a chromosome region in the format `chr7:55241607-55241807`. This will specify the region of the genome to display in the IGV viewer.
53+
3. **Input BAM File Path**: Provide the absolute file path to the BAM file you wish to visualize. Ensure the path is valid and the file is accessible.
54+
4. **Click Update IGV**: Once the locus and BAM path are entered, click the "Update IGV" button. The IGV viewer will refresh and display the BAM file content for the specified locus.
55+
5. **Visualize Data**: The genomic alignments will be displayed in the IGV viewer. You can zoom in/out, scroll, and interact with the visualization.
56+
57+
58+

app/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from flask import Flask
2+
3+
app = Flask(__name__)
4+
5+
from app import routes

app/routes.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
from app import app
2+
from flask import Flask, request, jsonify, render_template
3+
import os
4+
5+
6+
def is_allowed_path(file_path):
7+
"""
8+
检查文件路径是否在允许的目录中。
9+
"""
10+
# 合法路径和映射目录
11+
if os.path.basename(file_path) == "test.cons.bam":
12+
return True
13+
ALLOWED_DIRECTORIES = ["/"] # 如果需要限制目录,更改为本地项目路径,例如 /path/to/projects
14+
for allowed_dir in ALLOWED_DIRECTORIES:
15+
if file_path.startswith(allowed_dir) and os.path.exists(file_path):
16+
return True
17+
return False
18+
19+
def create_symlink(file_path):
20+
"""
21+
为合法路径创建软链接,并返回映射路径。
22+
"""
23+
if not is_allowed_path(file_path):
24+
return None
25+
26+
print("app.root_path", app.root_path)
27+
SYMLINK_DIR = app.root_path + "/static/dynamic_bam/"
28+
print("app.root_path", SYMLINK_DIR)
29+
# 初始化动态 BAM 文件目录
30+
os.makedirs(SYMLINK_DIR, exist_ok=True)
31+
32+
file_name = os.path.basename(file_path)
33+
symlink_path = os.path.join(SYMLINK_DIR, file_name)
34+
35+
# 如果软链接不存在,创建它
36+
if not os.path.exists(symlink_path):
37+
os.symlink(file_path, symlink_path)
38+
# flask 应用只能访问app.root_path下路径, 相当于app.root_path为'/'
39+
return f"/static/dynamic_bam/{file_name}"
40+
41+
@app.route("/")
42+
def index():
43+
"""
44+
渲染前端页面
45+
"""
46+
return render_template("index.html")
47+
48+
@app.route("/api/check-bam", methods=["POST"])
49+
def check_bam():
50+
"""
51+
检查 BAM 文件路径,并返回映射的静态路径。
52+
"""
53+
data = request.json
54+
bam_path = data.get("bam_path")
55+
print(f"bam_path:{bam_path}")
56+
57+
if not bam_path:
58+
return jsonify({"error": "BAM path is required."}), 400
59+
60+
symlink = create_symlink(bam_path)
61+
if not symlink:
62+
return jsonify({"error": "File not found or not allowed."}), 404
63+
64+
bam_index_symlink = create_symlink(bam_path + ".bai")
65+
if not bam_index_symlink:
66+
return jsonify({"error": "Index file not found."}), 404
67+
68+
return jsonify({"bam_url": symlink, "bam_index_url": bam_index_symlink})
69+
70+
71+

app/static/testdata/test.cons.bam

1.3 MB
Binary file not shown.
27.1 KB
Binary file not shown.

app/templates/index.html

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<title>BAM IGV Viewer</title>
7+
<script src="https://igv.org/web/release/2.6.6/dist/igv.min.js"></script>
8+
<style>
9+
body {
10+
font-family: Arial, sans-serif;
11+
margin: 20px;
12+
line-height: 1.5;
13+
}
14+
15+
.form-container {
16+
margin-bottom: 20px;
17+
}
18+
19+
label {
20+
font-weight: bold;
21+
}
22+
23+
.form-row {
24+
display: flex;
25+
align-items: center;
26+
margin-bottom: 10px;
27+
}
28+
29+
.form-row input {
30+
flex: 1;
31+
padding: 8px;
32+
margin-left: 10px;
33+
font-size: 16px;
34+
}
35+
36+
.form-row button {
37+
padding: 10px 20px;
38+
font-size: 18px;
39+
background-color: #007BFF;
40+
color: white;
41+
border: none;
42+
border-radius: 4px;
43+
cursor: pointer;
44+
}
45+
46+
.form-row button:hover {
47+
background-color: #0056b3;
48+
}
49+
50+
#igv-div {
51+
width: 100%;
52+
height: 1200px;
53+
border: 1px solid #ccc;
54+
}
55+
</style>
56+
</head>
57+
<body>
58+
<h1>BAM IGV Viewer</h1>
59+
60+
<div class="form-container">
61+
<div class="form-row">
62+
<label for="locus">Locus:</label>
63+
<input type="text" id="locus" placeholder="e.g., chr7:55241607-55241807">
64+
</div>
65+
<div class="form-row">
66+
<label for="bam_path">BAM Path:</label>
67+
<input type="text" id="bam_path" placeholder="Enter the absolute path of your BAM file">
68+
</div>
69+
<div class="form-row">
70+
<button id="update-button">Update IGV</button>
71+
</div>
72+
</div>
73+
74+
<div id="igv-div"></div>
75+
76+
<script>
77+
let browser;
78+
79+
function createBrowser(locus, bamUrl, bamIndexUrl) {
80+
// Dispose of the existing browser to avoid rendering conflicts
81+
if (browser) {
82+
browser.dispose();
83+
}
84+
85+
const igvDiv = document.getElementById("igv-div");
86+
87+
// Ensure the igvDiv is cleared before creating a new browser instance
88+
igvDiv.innerHTML = "";
89+
90+
// Extract the basename of the BAM file from its URL
91+
const bamName = bamUrl.split('/').pop();
92+
93+
const options = {
94+
genome: {
95+
id: "hg19",
96+
fastaURL: "/static/ref/hg19.fa",
97+
indexURL: "/static/ref/hg19.fa.fai",
98+
},
99+
locus: locus,
100+
tracks: [
101+
{
102+
name: bamName, // Use the basename of the BAM file
103+
type: "alignment",
104+
format: "bam",
105+
url: bamUrl,
106+
indexURL: bamIndexUrl,
107+
visibilityWindow: -1,
108+
height: 1000
109+
}
110+
]
111+
};
112+
113+
igv.createBrowser(igvDiv, options).then(newBrowser => {
114+
browser = newBrowser;
115+
console.log("IGV Browser created");
116+
});
117+
}
118+
119+
document.getElementById("update-button").addEventListener("click", function () {
120+
const locus = document.getElementById("locus").value.trim();
121+
const bamPath = document.getElementById("bam_path").value.trim();
122+
123+
if (!locus || !bamPath) {
124+
alert("Please enter both Locus and BAM Path!");
125+
return;
126+
}
127+
128+
fetch("/api/check-bam", {
129+
method: "POST",
130+
headers: {
131+
"Content-Type": "application/json"
132+
},
133+
body: JSON.stringify({ bam_path: bamPath })
134+
})
135+
.then(response => {
136+
if (!response.ok) {
137+
return response.json().then(data => {
138+
throw new Error(data.error || "Unknown error.");
139+
});
140+
}
141+
return response.json();
142+
})
143+
.then(data => {
144+
createBrowser(locus, data.bam_url, data.bam_index_url);
145+
})
146+
.catch(error => {
147+
alert(error.message);
148+
});
149+
});
150+
</script>
151+
</body>
152+
</html>

0 commit comments

Comments
 (0)