Spaces:
Sleeping
Sleeping
Venkat V
commited on
Commit
Β·
ff2cc46
1
Parent(s):
6baf36d
plumbing code for streamlit, fast api
Browse files- .DS_Store +0 -0
- app.py +72 -1
- graph_module/.DS_Store +0 -0
- graph_module/__init__.py +79 -1
- ocr_module/__init__.py +18 -1
- requirements.txt +2 -0
- streamlit_app.py +65 -1
- summarizer_module/__init__.py +34 -1
- yolo_module/__init__.py +16 -1
.DS_Store
CHANGED
Binary files a/.DS_Store and b/.DS_Store differ
|
|
app.py
CHANGED
@@ -1 +1,72 @@
|
|
1 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# app.py
|
2 |
+
from fastapi import FastAPI, UploadFile, File,Form
|
3 |
+
from fastapi.middleware.cors import CORSMiddleware
|
4 |
+
from fastapi.responses import JSONResponse
|
5 |
+
import uvicorn
|
6 |
+
from PIL import Image
|
7 |
+
import io
|
8 |
+
import json
|
9 |
+
|
10 |
+
# Import your pipeline modules
|
11 |
+
from yolo_module import run_yolo
|
12 |
+
from ocr_module import extract_text
|
13 |
+
from graph_module import map_arrows, build_flowchart_json
|
14 |
+
from summarizer_module import summarize_flowchart
|
15 |
+
|
16 |
+
app = FastAPI()
|
17 |
+
|
18 |
+
# CORS for Streamlit access
|
19 |
+
app.add_middleware(
|
20 |
+
CORSMiddleware,
|
21 |
+
allow_origins=["*"],
|
22 |
+
allow_credentials=True,
|
23 |
+
allow_methods=["*"],
|
24 |
+
allow_headers=["*"],
|
25 |
+
)
|
26 |
+
|
27 |
+
@app.post("/process-image")
|
28 |
+
async def process_image(file: UploadFile = File(...), debug: str = Form("false")):
|
29 |
+
debug_mode = debug.lower() == "true"
|
30 |
+
|
31 |
+
debug_log = []
|
32 |
+
if debug_mode: debug_log.append("π₯ Received file: file")
|
33 |
+
print("π₯ Received file:", file.filename)
|
34 |
+
|
35 |
+
|
36 |
+
contents = await file.read()
|
37 |
+
image = Image.open(io.BytesIO(contents)).convert("RGB")
|
38 |
+
if debug_mode: debug_log.append("β
Image loaded and converted to RGB")
|
39 |
+
print("β
Image loaded and converted to RGB")
|
40 |
+
|
41 |
+
# Step 1: Run YOLO detection
|
42 |
+
boxes, arrows = run_yolo(image)
|
43 |
+
if debug_mode: debug_log.append(f"π¦ YOLO detected {len(boxes)} boxes and {len(arrows)} arrows")
|
44 |
+
|
45 |
+
# Step 2: OCR on boxes
|
46 |
+
for box in boxes:
|
47 |
+
box["text"] = extract_text(image, box["bbox"])
|
48 |
+
if debug_mode: debug_log.append(f"π OCR text for box {box['id']}: {box['text']}")
|
49 |
+
print(f"π OCR text for box {box['id']}: {box['text']}")
|
50 |
+
|
51 |
+
# Step 3: Map arrows to boxes and build graph
|
52 |
+
edges = map_arrows(boxes, arrows)
|
53 |
+
if debug_mode: debug_log.append(f"π§ Mapped {len(edges)} edges from arrows to boxes")
|
54 |
+
flowchart_json = build_flowchart_json(boxes, edges)
|
55 |
+
print("π§ Flowchart JSON structure:")
|
56 |
+
print(json.dumps(flowchart_json, indent=2))
|
57 |
+
|
58 |
+
|
59 |
+
|
60 |
+
# Step 4: Summarize flowchart in English
|
61 |
+
summary = summarize_flowchart(flowchart_json)
|
62 |
+
print("π Generated English summary:")
|
63 |
+
print(summary)
|
64 |
+
|
65 |
+
return JSONResponse({
|
66 |
+
"flowchart": flowchart_json,
|
67 |
+
"summary": summary,
|
68 |
+
"debug": "\n".join(debug_log) if debug_mode else ""
|
69 |
+
})
|
70 |
+
|
71 |
+
if __name__ == "__main__":
|
72 |
+
uvicorn.run(app, host="0.0.0.0", port=7860)
|
graph_module/.DS_Store
ADDED
Binary file (6.15 kB). View file
|
|
graph_module/__init__.py
CHANGED
@@ -1 +1,79 @@
|
|
1 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# flowchart_builder.py
|
2 |
+
|
3 |
+
from shapely.geometry import box, Point
|
4 |
+
|
5 |
+
|
6 |
+
def map_arrows(nodes, arrows):
|
7 |
+
"""
|
8 |
+
Matches arrows to source and target nodes based on geometric positions.
|
9 |
+
Returns a list of edges as (source_id, target_id).
|
10 |
+
"""
|
11 |
+
for node in nodes:
|
12 |
+
node["shape"] = box(*node["bbox"])
|
13 |
+
|
14 |
+
edges = []
|
15 |
+
for arrow in arrows:
|
16 |
+
tail_point = Point(arrow["tail"])
|
17 |
+
head_point = Point(arrow["head"])
|
18 |
+
|
19 |
+
source = next((n["id"] for n in nodes if n["shape"].contains(tail_point)), None)
|
20 |
+
target = next((n["id"] for n in nodes if n["shape"].contains(head_point)), None)
|
21 |
+
|
22 |
+
if source and target:
|
23 |
+
edges.append((source, target))
|
24 |
+
return edges
|
25 |
+
|
26 |
+
|
27 |
+
def build_flowchart_json(nodes, edges):
|
28 |
+
"""
|
29 |
+
Builds a structured JSON from node and edge data.
|
30 |
+
"""
|
31 |
+
# Step 1: Build graph
|
32 |
+
graph = {n["id"]: {"text": n.get("text", ""), "type": n["type"], "next": []} for n in nodes}
|
33 |
+
for source, target in edges:
|
34 |
+
graph[source]["next"].append(target)
|
35 |
+
|
36 |
+
# Step 2: Build flowchart JSON format
|
37 |
+
flowchart_json = {
|
38 |
+
"start": next((n["id"] for n in nodes if n["type"] == "start"), None),
|
39 |
+
"steps": []
|
40 |
+
}
|
41 |
+
|
42 |
+
for node_id, info in graph.items():
|
43 |
+
step = {
|
44 |
+
"id": node_id,
|
45 |
+
"text": info["text"],
|
46 |
+
"type": info["type"]
|
47 |
+
}
|
48 |
+
if info["type"] == "decision" and len(info["next"]) >= 2:
|
49 |
+
step["branches"] = {
|
50 |
+
"yes": info["next"][0],
|
51 |
+
"no": info["next"][1]
|
52 |
+
}
|
53 |
+
elif len(info["next"]) == 1:
|
54 |
+
step["next"] = info["next"][0]
|
55 |
+
flowchart_json["steps"].append(step)
|
56 |
+
|
57 |
+
return flowchart_json
|
58 |
+
|
59 |
+
|
60 |
+
# Example usage
|
61 |
+
if __name__ == "__main__":
|
62 |
+
nodes = [
|
63 |
+
{"id": "node1", "bbox": [100, 100, 200, 150], "text": "Start", "type": "start"},
|
64 |
+
{"id": "node2", "bbox": [300, 100, 400, 150], "text": "Is valid?", "type": "decision"},
|
65 |
+
{"id": "node3", "bbox": [500, 50, 600, 100], "text": "Approve", "type": "process"},
|
66 |
+
{"id": "node4", "bbox": [500, 150, 600, 200], "text": "Reject", "type": "process"}
|
67 |
+
]
|
68 |
+
|
69 |
+
arrows = [
|
70 |
+
{"id": "arrow1", "tail": (200, 125), "head": (300, 125)},
|
71 |
+
{"id": "arrow2", "tail": (400, 125), "head": (500, 75)},
|
72 |
+
{"id": "arrow3", "tail": (400, 125), "head": (500, 175)}
|
73 |
+
]
|
74 |
+
|
75 |
+
edges = map_arrows(nodes, arrows)
|
76 |
+
flowchart_json = build_flowchart_json(nodes, edges)
|
77 |
+
|
78 |
+
import json
|
79 |
+
print(json.dumps(flowchart_json, indent=2))
|
ocr_module/__init__.py
CHANGED
@@ -1 +1,18 @@
|
|
1 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import pytesseract
|
2 |
+
from PIL import Image
|
3 |
+
|
4 |
+
def extract_text(image, bbox):
|
5 |
+
"""
|
6 |
+
Run OCR on a cropped region of the image.
|
7 |
+
|
8 |
+
Parameters:
|
9 |
+
image (PIL.Image): The full image.
|
10 |
+
bbox (list): [x1, y1, x2, y2] coordinates of the region to crop.
|
11 |
+
|
12 |
+
Returns:
|
13 |
+
str: Extracted text.
|
14 |
+
"""
|
15 |
+
x1, y1, x2, y2 = bbox
|
16 |
+
cropped = image.crop((x1, y1, x2, y2))
|
17 |
+
text = pytesseract.image_to_string(cropped).strip()
|
18 |
+
return text
|
requirements.txt
CHANGED
@@ -4,3 +4,5 @@ pillow
|
|
4 |
shapely
|
5 |
pytesseract
|
6 |
transformers
|
|
|
|
|
|
4 |
shapely
|
5 |
pytesseract
|
6 |
transformers
|
7 |
+
torch
|
8 |
+
python-multipart
|
streamlit_app.py
CHANGED
@@ -1 +1,65 @@
|
|
1 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# streamlit_app.py
|
2 |
+
import streamlit as st
|
3 |
+
import requests
|
4 |
+
import json
|
5 |
+
from PIL import Image
|
6 |
+
import io
|
7 |
+
|
8 |
+
API_URL = "http://localhost:7860/process-image" # Change if hosted elsewhere
|
9 |
+
|
10 |
+
st.set_page_config(page_title="Flowchart to English", layout="wide")
|
11 |
+
st.title("π Flowchart to Plain English")
|
12 |
+
|
13 |
+
# Debug mode switch
|
14 |
+
debug_mode = st.toggle("π§ Show Debug Info", value=False)
|
15 |
+
|
16 |
+
uploaded_file = st.file_uploader("Upload a flowchart image", type=["png", "jpg", "jpeg"])
|
17 |
+
|
18 |
+
if uploaded_file:
|
19 |
+
# Resize image for smaller canvas
|
20 |
+
image = Image.open(uploaded_file)
|
21 |
+
max_width = 600
|
22 |
+
ratio = max_width / float(image.size[0])
|
23 |
+
new_height = int((float(image.size[1]) * float(ratio)))
|
24 |
+
resized_image = image.resize((max_width, new_height))
|
25 |
+
st.image(resized_image, caption="Uploaded Image", use_container_width=False)
|
26 |
+
|
27 |
+
if st.button("π Analyze Flowchart"):
|
28 |
+
progress = st.progress(0, text="Sending image to backend...")
|
29 |
+
|
30 |
+
try:
|
31 |
+
response = requests.post(
|
32 |
+
API_URL,
|
33 |
+
files={"file": uploaded_file.getvalue()},
|
34 |
+
data={"debug": str(debug_mode).lower()}
|
35 |
+
)
|
36 |
+
progress.progress(50, text="Processing detection, OCR, and reasoning...")
|
37 |
+
|
38 |
+
if response.status_code == 200:
|
39 |
+
data = response.json()
|
40 |
+
progress.progress(80, text="Generating explanation using LLM...")
|
41 |
+
|
42 |
+
# Display debug summary from backend (conditionally)
|
43 |
+
if debug_mode:
|
44 |
+
debug_info = data.get("debug", "No debug info available.")
|
45 |
+
st.markdown("### π§ͺ Debug Pipeline Info")
|
46 |
+
st.code(debug_info, language="markdown")
|
47 |
+
|
48 |
+
# Display side-by-side columns
|
49 |
+
col1, col2 = st.columns(2)
|
50 |
+
|
51 |
+
with col1:
|
52 |
+
st.subheader("π§ Flowchart JSON")
|
53 |
+
st.json(data["flowchart"])
|
54 |
+
|
55 |
+
with col2:
|
56 |
+
st.subheader("π English Summary")
|
57 |
+
st.markdown(data["summary"])
|
58 |
+
|
59 |
+
progress.progress(100, text="Done!")
|
60 |
+
else:
|
61 |
+
st.error(f"Something went wrong: {response.status_code}")
|
62 |
+
except Exception as e:
|
63 |
+
st.error(f"An error occurred: {e}")
|
64 |
+
else:
|
65 |
+
st.info("Upload a flowchart image to begin.")
|
summarizer_module/__init__.py
CHANGED
@@ -1 +1,34 @@
|
|
1 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# summarizer_module/__init__.py
|
2 |
+
|
3 |
+
from transformers import AutoTokenizer, AutoModelForCausalLM, pipeline
|
4 |
+
|
5 |
+
# Use a small local model (e.g., Phi-2)
|
6 |
+
MODEL_ID = "microsoft/phi-2" # Ensure it's downloaded and cached locally
|
7 |
+
|
8 |
+
# Load model and tokenizer
|
9 |
+
model = AutoModelForCausalLM.from_pretrained(MODEL_ID)
|
10 |
+
tokenizer = AutoTokenizer.from_pretrained(MODEL_ID)
|
11 |
+
summarizer = pipeline("text-generation", model=model, tokenizer=tokenizer)
|
12 |
+
|
13 |
+
def summarize_flowchart(flowchart_json):
|
14 |
+
"""
|
15 |
+
Given a flowchart JSON with 'start' and 'steps', returns a plain English explanation
|
16 |
+
formatted as bullets and sub-bullets.
|
17 |
+
|
18 |
+
Args:
|
19 |
+
flowchart_json (dict): Structured representation of flowchart
|
20 |
+
|
21 |
+
Returns:
|
22 |
+
str: Bullet-style natural language summary of the logic
|
23 |
+
"""
|
24 |
+
prompt = (
|
25 |
+
"Turn the following flowchart into a bullet-point explanation in plain English.\n"
|
26 |
+
"Use bullets for steps and sub-bullets for branches.\n"
|
27 |
+
"\n"
|
28 |
+
f"Flowchart JSON:\n{flowchart_json}\n"
|
29 |
+
"\nExplanation:"
|
30 |
+
)
|
31 |
+
|
32 |
+
result = summarizer(prompt, max_new_tokens=300, do_sample=False)[0]["generated_text"]
|
33 |
+
explanation = result.split("Explanation:")[-1].strip()
|
34 |
+
return explanation
|
yolo_module/__init__.py
CHANGED
@@ -1 +1,16 @@
|
|
1 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# yolo_module/__init__.py
|
2 |
+
|
3 |
+
def run_yolo(image):
|
4 |
+
"""
|
5 |
+
Placeholder YOLO inference function.
|
6 |
+
In actual implementation, load your model and return detected boxes/arrows.
|
7 |
+
"""
|
8 |
+
# For now, return dummy values
|
9 |
+
boxes = [
|
10 |
+
{"id": "node1", "bbox": [100, 100, 200, 150], "text": "", "type": "start"},
|
11 |
+
{"id": "node2", "bbox": [300, 100, 400, 150], "text": "", "type": "decision"},
|
12 |
+
]
|
13 |
+
arrows = [
|
14 |
+
{"id": "arrow1", "tail": (200, 125), "head": (300, 125)}
|
15 |
+
]
|
16 |
+
return boxes, arrows
|