Spaces:
Paused
Paused
Ankit Thakur
commited on
Commit
·
12f2295
1
Parent(s):
89d8caa
Resolve merge conflicts
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- .gitattributes +2 -0
- Dockerfile +18 -0
- Google_Search_tool.py +41 -0
- README.md +12 -57
- __pycache__/config.cpython-310.pyc +0 -0
- __pycache__/config.cpython-39.pyc +0 -0
- __pycache__/google_search_tool.cpython-310.pyc +0 -0
- __pycache__/validate_prescription.cpython-310.pyc +0 -0
- app.py +137 -0
- apt-packages.txt +13 -0
- config.py +37 -0
- data/interactions.csv +3 -0
- data/rxnorm.db +0 -0
- data/rxnorm_names.csv +13 -0
- drug_interaction_detection/__pycache__/interaction_checker.cpython-310.pyc +0 -0
- drug_interaction_detection/interaction_checker.py +34 -0
- earnest-trilogy-465710-e7-6cc7bbbddb97.json +13 -0
- index.html +0 -13
- models/signature_sia.tflite +3 -0
- package.json +0 -20
- prescription_validation/__pycache__/fuzzy_match.cpython-310.pyc +0 -0
- prescription_validation/fuzzy_match.py +44 -0
- prescription_validation/zone_detector.py +27 -0
- public/vite.svg +0 -1
- requirements.txt +23 -0
- scripts/build_interactions_db.py +27 -0
- scripts/build_rxnorm_db.py +28 -0
- scripts/create_dummy_signature_model.py +32 -0
- signature_verification/__pycache__/signature_detector.cpython-310.pyc +0 -0
- signature_verification/__pycache__/signature_generator.cpython-310.pyc +0 -0
- signature_verification/__pycache__/signature_siamese.cpython-310.pyc +0 -0
- signature_verification/signature_detector.py +40 -0
- signature_verification/signature_generator.py +50 -0
- signature_verification/signature_siamese.py +34 -0
- src/App.svelte +0 -47
- src/app.css +0 -79
- src/assets/svelte.svg +0 -1
- src/lib/Counter.svelte +0 -10
- src/main.ts +0 -9
- src/vite-env.d.ts +0 -2
- static/signatures/fake-autograph-samples-hand-drawn-signatures-examples-of-documents-certificates-and-contracts-with-inked-and-handwritten-lettering-vector.jpg +3 -0
- static/signatures/fake-signature-hand-drawn-sample-own-autograph-fictitious-handwritten-signature-black-ink-scribble-for-sample-contracts-documents-certificates-or-letters-vector-illustration-2WKNPM2.jpg +3 -0
- static/signatures/images.png +0 -0
- static/signatures/signature-certificate-document-vector-transparent-260nw-2346804443.jpg +3 -0
- static/signatures/signature-vector-hand-drawn-autograph-600nw-2387543207.jpg +3 -0
- static/uploads/02af69ed359f4d27b46ca3cfc814288b_IMG_0082.jpg +3 -0
- static/uploads/0bc297f68f84453f8e5630a45d3baf83_vio-4.jpg +3 -0
- static/uploads/1*3xUyINxRtDf2qowd-kkGQA.jpg +3 -0
- static/uploads/255d2936a26d49429c21ce6b06b323b7_vio-4.jpg +3 -0
- static/uploads/2b090d65e18f4e1ba1a048c6a2722d03_vio-4.jpg +3 -0
.gitattributes
ADDED
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
1 |
+
*.tflite filter=lfs diff=lfs merge=lfs -text
|
2 |
+
*.jpg filter=lfs diff=lfs merge=lfs -text
|
Dockerfile
ADDED
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
FROM python:3.10-slim
|
2 |
+
|
3 |
+
WORKDIR /app
|
4 |
+
|
5 |
+
# Install minimal system dependencies
|
6 |
+
RUN apt-get update && apt-get install -y \
|
7 |
+
libgl1-mesa-glx \
|
8 |
+
libglib2.0-0 \
|
9 |
+
&& rm -rf /var/lib/apt/lists/*
|
10 |
+
|
11 |
+
# Copy requirements first for better caching
|
12 |
+
COPY requirements.txt .
|
13 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
14 |
+
|
15 |
+
# Copy application files
|
16 |
+
COPY . .
|
17 |
+
|
18 |
+
CMD ["streamlit", "run", "app.py", "--server.port=8501", "--server.address=0.0.0.0"]
|
Google_Search_tool.py
ADDED
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import logging
|
3 |
+
from googleapiclient.discovery import build
|
4 |
+
from dotenv import load_dotenv
|
5 |
+
|
6 |
+
load_dotenv()
|
7 |
+
|
8 |
+
class GoogleSearch:
|
9 |
+
"""Performs Google Custom Search API queries."""
|
10 |
+
def __init__(self):
|
11 |
+
self.api_key = os.getenv("GOOGLE_API_KEY")
|
12 |
+
self.cse_id = os.getenv("GOOGLE_CSE_ID")
|
13 |
+
self.service = None
|
14 |
+
if self.api_key and self.cse_id:
|
15 |
+
try:
|
16 |
+
self.service = build("customsearch", "v1", developerKey=self.api_key)
|
17 |
+
logging.info("✅ Google Custom Search initialized.")
|
18 |
+
except Exception as e:
|
19 |
+
logging.error(f"❌ CSE init failed: {e}")
|
20 |
+
else:
|
21 |
+
logging.warning("⚠️ GOOGLE_API_KEY or GOOGLE_CSE_ID not set; search disabled.")
|
22 |
+
|
23 |
+
def search(self, queries: list, num_results: int = 1) -> list:
|
24 |
+
if not self.service:
|
25 |
+
return []
|
26 |
+
out = []
|
27 |
+
for q in queries:
|
28 |
+
try:
|
29 |
+
resp = self.service.cse().list(q=q, cx=self.cse_id, num=num_results).execute()
|
30 |
+
items = resp.get("items", [])
|
31 |
+
formatted = [
|
32 |
+
{"title": it.get("title"), "link": it.get("link"), "snippet": it.get("snippet")}
|
33 |
+
for it in items
|
34 |
+
]
|
35 |
+
out.append({"query": q, "results": formatted})
|
36 |
+
except Exception as e:
|
37 |
+
logging.error(f"❌ Search error for '{q}': {e}")
|
38 |
+
out.append({"query": q, "results": []})
|
39 |
+
return out
|
40 |
+
|
41 |
+
google_search = GoogleSearch()
|
README.md
CHANGED
@@ -1,59 +1,14 @@
|
|
1 |
---
|
2 |
-
title:
|
3 |
-
emoji:
|
4 |
-
colorFrom:
|
5 |
-
colorTo:
|
6 |
-
sdk:
|
7 |
-
|
8 |
-
|
9 |
-
|
10 |
-
|
|
|
|
|
|
|
11 |
---
|
12 |
-
|
13 |
-
# Svelte + TS + Vite
|
14 |
-
|
15 |
-
This template should help get you started developing with Svelte and TypeScript in Vite.
|
16 |
-
|
17 |
-
## Recommended IDE Setup
|
18 |
-
|
19 |
-
[VS Code](https://code.visualstudio.com/) + [Svelte](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode).
|
20 |
-
|
21 |
-
## Need an official Svelte framework?
|
22 |
-
|
23 |
-
Check out [SvelteKit](https://github.com/sveltejs/kit#readme), which is also powered by Vite. Deploy anywhere with its serverless-first approach and adapt to various platforms, with out of the box support for TypeScript, SCSS, and Less, and easily-added support for mdsvex, GraphQL, PostCSS, Tailwind CSS, and more.
|
24 |
-
|
25 |
-
## Technical considerations
|
26 |
-
|
27 |
-
**Why use this over SvelteKit?**
|
28 |
-
|
29 |
-
- It brings its own routing solution which might not be preferable for some users.
|
30 |
-
- It is first and foremost a framework that just happens to use Vite under the hood, not a Vite app.
|
31 |
-
|
32 |
-
This template contains as little as possible to get started with Vite + TypeScript + Svelte, while taking into account the developer experience with regards to HMR and intellisense. It demonstrates capabilities on par with the other `create-vite` templates and is a good starting point for beginners dipping their toes into a Vite + Svelte project.
|
33 |
-
|
34 |
-
Should you later need the extended capabilities and extensibility provided by SvelteKit, the template has been structured similarly to SvelteKit so that it is easy to migrate.
|
35 |
-
|
36 |
-
**Why `global.d.ts` instead of `compilerOptions.types` inside `jsconfig.json` or `tsconfig.json`?**
|
37 |
-
|
38 |
-
Setting `compilerOptions.types` shuts out all other types not explicitly listed in the configuration. Using triple-slash references keeps the default TypeScript setting of accepting type information from the entire workspace, while also adding `svelte` and `vite/client` type information.
|
39 |
-
|
40 |
-
**Why include `.vscode/extensions.json`?**
|
41 |
-
|
42 |
-
Other templates indirectly recommend extensions via the README, but this file allows VS Code to prompt the user to install the recommended extension upon opening the project.
|
43 |
-
|
44 |
-
**Why enable `allowJs` in the TS template?**
|
45 |
-
|
46 |
-
While `allowJs: false` would indeed prevent the use of `.js` files in the project, it does not prevent the use of JavaScript syntax in `.svelte` files. In addition, it would force `checkJs: false`, bringing the worst of both worlds: not being able to guarantee the entire codebase is TypeScript, and also having worse typechecking for the existing JavaScript. In addition, there are valid use cases in which a mixed codebase may be relevant.
|
47 |
-
|
48 |
-
**Why is HMR not preserving my local component state?**
|
49 |
-
|
50 |
-
HMR state preservation comes with a number of gotchas! It has been disabled by default in both `svelte-hmr` and `@sveltejs/vite-plugin-svelte` due to its often surprising behavior. You can read the details [here](https://github.com/rixo/svelte-hmr#svelte-hmr).
|
51 |
-
|
52 |
-
If you have state that's important to retain within a component, consider creating an external store which would not be replaced by HMR.
|
53 |
-
|
54 |
-
```ts
|
55 |
-
// store.ts
|
56 |
-
// An extremely simple external store
|
57 |
-
import { writable } from "svelte/store";
|
58 |
-
export default writable(0);
|
59 |
-
```
|
|
|
1 |
---
|
2 |
+
title: RxGuard Prescription Validator
|
3 |
+
emoji: ⚕️
|
4 |
+
colorFrom: blue
|
5 |
+
colorTo: indigo
|
6 |
+
sdk: streamlit
|
7 |
+
sdk_version: 1.36.0
|
8 |
+
app_file: app.py
|
9 |
+
hf_oauth: true
|
10 |
+
hardware:
|
11 |
+
accelerator: T4
|
12 |
+
cpu: 2
|
13 |
+
memory: 16
|
14 |
---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
__pycache__/config.cpython-310.pyc
ADDED
Binary file (522 Bytes). View file
|
|
__pycache__/config.cpython-39.pyc
ADDED
Binary file (598 Bytes). View file
|
|
__pycache__/google_search_tool.cpython-310.pyc
ADDED
Binary file (1.87 kB). View file
|
|
__pycache__/validate_prescription.cpython-310.pyc
ADDED
Binary file (9.28 kB). View file
|
|
app.py
ADDED
@@ -0,0 +1,137 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import streamlit as st
|
3 |
+
from PIL import Image
|
4 |
+
from config import (
|
5 |
+
STATIC_DIR,
|
6 |
+
UPLOADS_DIR,
|
7 |
+
HF_TOKEN,
|
8 |
+
GOOGLE_API_KEY,
|
9 |
+
GOOGLE_CSE_ID,
|
10 |
+
GEMINI_API_KEY,
|
11 |
+
DEVICE
|
12 |
+
)
|
13 |
+
|
14 |
+
# ─── App Configuration ────────────────────────────────────────────────────
|
15 |
+
st.set_page_config(
|
16 |
+
page_title="RxGuard Prescription Validator",
|
17 |
+
page_icon="⚕️",
|
18 |
+
layout="wide",
|
19 |
+
menu_items={
|
20 |
+
'Get Help': 'https://github.com/your-repo',
|
21 |
+
'About': "RxGuard v1.0 - Advanced Prescription Validation"
|
22 |
+
}
|
23 |
+
)
|
24 |
+
|
25 |
+
# ─── Session State ────────────────────────────────────────────────────────
|
26 |
+
if "analysis_result" not in st.session_state:
|
27 |
+
st.session_state.analysis_result = None
|
28 |
+
if "uploaded_filename" not in st.session_state:
|
29 |
+
st.session_state.uploaded_filename = None
|
30 |
+
|
31 |
+
# ─── UI Components ────────────────────────────────────────────────────────
|
32 |
+
def show_service_status():
|
33 |
+
"""Displays service connectivity status."""
|
34 |
+
cols = st.columns(4)
|
35 |
+
with cols[0]:
|
36 |
+
st.metric("HuggingFace", "✅" if HF_TOKEN else "❌")
|
37 |
+
with cols[1]:
|
38 |
+
st.metric("Google API", "✅" if GOOGLE_API_KEY else "❌")
|
39 |
+
with cols[2]:
|
40 |
+
st.metric("Gemini", "✅" if GEMINI_API_KEY else "❌")
|
41 |
+
with cols[3]:
|
42 |
+
st.metric("Device", DEVICE.upper())
|
43 |
+
|
44 |
+
def display_patient_info(info: dict):
|
45 |
+
"""Displays patient information in a formatted card."""
|
46 |
+
with st.container(border=True):
|
47 |
+
st.subheader("👤 Patient Details")
|
48 |
+
cols = st.columns(2)
|
49 |
+
with cols[0]:
|
50 |
+
st.markdown(f"**Name:** {info.get('Name', 'Not detected')}")
|
51 |
+
st.markdown(f"**Age:** {info.get('Age', 'N/A')}")
|
52 |
+
with cols[1]:
|
53 |
+
st.markdown(f"**Date:** {info.get('Date', 'N/A')}")
|
54 |
+
st.markdown(f"**Physician:** {info.get('PhysicianName', 'N/A')}")
|
55 |
+
|
56 |
+
def display_medications(medications: list):
|
57 |
+
"""Displays medication information with verification."""
|
58 |
+
st.subheader("💊 Medications")
|
59 |
+
if not medications:
|
60 |
+
st.warning("No medications detected in prescription")
|
61 |
+
return
|
62 |
+
|
63 |
+
for med in medications:
|
64 |
+
with st.expander(f"{med.get('drug_raw', 'Unknown Medication')}"):
|
65 |
+
cols = st.columns([1, 2])
|
66 |
+
with cols[0]:
|
67 |
+
st.markdown(f"""
|
68 |
+
**Dosage:** `{med.get('dosage', 'N/A')}`
|
69 |
+
**Frequency:** `{med.get('frequency', 'N/A')}`
|
70 |
+
""")
|
71 |
+
|
72 |
+
with cols[1]:
|
73 |
+
if verification := med.get("verification"):
|
74 |
+
if dosage := verification.get("standard_dosage"):
|
75 |
+
st.success(f"**Standard Dosage:** {dosage}")
|
76 |
+
if side_effects := verification.get("side_effects"):
|
77 |
+
st.warning(f"**Side Effects:** {side_effects}")
|
78 |
+
if interactions := verification.get("interactions"):
|
79 |
+
st.error(f"**Interactions:** {interactions}")
|
80 |
+
|
81 |
+
# ─── Main Application ─────────────────────────────────────────────────────
|
82 |
+
def main():
|
83 |
+
st.title("⚕️ RxGuard Prescription Validator")
|
84 |
+
st.caption("AI-powered prescription verification system")
|
85 |
+
|
86 |
+
show_service_status()
|
87 |
+
|
88 |
+
# Only enable upload if required services are available
|
89 |
+
if all([HF_TOKEN, GOOGLE_API_KEY, GEMINI_API_KEY]):
|
90 |
+
uploaded_file = st.file_uploader(
|
91 |
+
"Upload prescription image (PNG/JPG/JPEG):",
|
92 |
+
type=["png", "jpg", "jpeg"],
|
93 |
+
help="Clear image of the prescription"
|
94 |
+
)
|
95 |
+
|
96 |
+
if uploaded_file and uploaded_file.name != st.session_state.uploaded_filename:
|
97 |
+
with st.status("Analyzing prescription...", expanded=True) as status:
|
98 |
+
try:
|
99 |
+
# Store the uploaded file
|
100 |
+
st.session_state.uploaded_filename = uploaded_file.name
|
101 |
+
file_path = os.path.join(UPLOADS_DIR, uploaded_file.name)
|
102 |
+
|
103 |
+
with open(file_path, "wb") as f:
|
104 |
+
f.write(uploaded_file.getvalue())
|
105 |
+
|
106 |
+
# Import processing function only when needed
|
107 |
+
from validate_prescription import extract_prescription_info
|
108 |
+
st.session_state.analysis_result = extract_prescription_info(file_path)
|
109 |
+
|
110 |
+
status.update(label="Analysis complete!", state="complete", expanded=False)
|
111 |
+
except Exception as e:
|
112 |
+
st.error(f"Processing failed: {str(e)}")
|
113 |
+
st.session_state.analysis_result = {"error": str(e)}
|
114 |
+
status.update(label="Analysis failed", state="error")
|
115 |
+
|
116 |
+
# Display results if available
|
117 |
+
if st.session_state.analysis_result:
|
118 |
+
result = st.session_state.analysis_result
|
119 |
+
|
120 |
+
if result.get("error"):
|
121 |
+
st.error(f"❌ Error: {result['error']}")
|
122 |
+
else:
|
123 |
+
tab1, tab2 = st.tabs(["Patient Information", "Medication Details"])
|
124 |
+
|
125 |
+
with tab1:
|
126 |
+
if uploaded_file:
|
127 |
+
st.image(uploaded_file, use_column_width=True)
|
128 |
+
display_patient_info(result["info"])
|
129 |
+
|
130 |
+
with tab2:
|
131 |
+
display_medications(result["info"].get("Medications", []))
|
132 |
+
|
133 |
+
if st.toggle("Show technical details"):
|
134 |
+
st.json(result.get("debug_info", {}))
|
135 |
+
|
136 |
+
if __name__ == "__main__":
|
137 |
+
main()
|
apt-packages.txt
ADDED
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
libgl1
|
2 |
+
libgl1-mesa-glx
|
3 |
+
libglib2.0-0
|
4 |
+
tesseract-ocr
|
5 |
+
tesseract-ocr-hin
|
6 |
+
git
|
7 |
+
git-lfs
|
8 |
+
curl
|
9 |
+
libssl-dev
|
10 |
+
libffi-dev
|
11 |
+
python3-dev
|
12 |
+
build-essential
|
13 |
+
libsqlite3-dev
|
config.py
ADDED
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import torch
|
3 |
+
from dotenv import load_dotenv
|
4 |
+
|
5 |
+
load_dotenv()
|
6 |
+
|
7 |
+
# ─── Directory Configuration ────────────────────────────────────────────────
|
8 |
+
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
9 |
+
STATIC_DIR = os.path.join(BASE_DIR, 'static')
|
10 |
+
os.makedirs(STATIC_DIR, exist_ok=True)
|
11 |
+
|
12 |
+
# ─── API Secrets ────────────────────────────────────────────────────────────
|
13 |
+
HF_TOKEN = os.getenv("HUGGINGFACE_HUB_TOKEN") # For Hugging Face models
|
14 |
+
GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY") # For Gemini and Custom Search
|
15 |
+
GOOGLE_CSE_ID = os.getenv("GOOGLE_CSE_ID") # For medication verification
|
16 |
+
GEMINI_API_KEY = os.getenv("GEMINI_API_KEY") # Alternative Gemini auth
|
17 |
+
HALODOC_API_KEY = os.getenv("HALODOC_API_KEY") # Future integration
|
18 |
+
|
19 |
+
# ─── Model Configuration ────────────────────────────────────────────────────
|
20 |
+
HF_MODELS = {
|
21 |
+
"donut": "naver-clova-ix/donut-base-finetuned-cord-v2",
|
22 |
+
"phi3": "microsoft/phi-3-mini-4k-instruct",
|
23 |
+
}
|
24 |
+
GEMINI_MODEL_NAME = "gemini-1.5-flash" # Balanced for speed and accuracy
|
25 |
+
|
26 |
+
# ─── Processing Parameters ─────────────────────────────────────────────────
|
27 |
+
LEV_THRESH = 0.75 # Levenshtein similarity threshold
|
28 |
+
SIG_THRESH = 0.65 # Signature verification threshold
|
29 |
+
|
30 |
+
# ─── File Paths ───────────────────────────────────────────────────────────
|
31 |
+
DB_PATH = os.path.join(STATIC_DIR, "rxguard.db")
|
32 |
+
UPLOADS_DIR = os.path.join(STATIC_DIR, "uploads")
|
33 |
+
os.makedirs(UPLOADS_DIR, exist_ok=True)
|
34 |
+
|
35 |
+
# ─── Hardware Configuration ────────────────────────────────────────────────
|
36 |
+
DEVICE = "cpu" # Force CPU for Hugging Face Spaces compatibility
|
37 |
+
USE_GPU = False
|
data/interactions.csv
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
cui1,cui2,severity,advice
|
2 |
+
1191,6801,moderate,"Monitor blood glucose closely; aspirin may enhance the hypoglycemic effect of metformin."
|
3 |
+
4241,1113,moderate,"Ascorbic acid can increase the absorption of iron. While often beneficial, monitor for iron overload in susceptible patients."
|
data/rxnorm.db
ADDED
Binary file (24.6 kB). View file
|
|
data/rxnorm_names.csv
ADDED
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
name,cui
|
2 |
+
Aspirin,1191
|
3 |
+
Acetaminophen,161
|
4 |
+
Amoxicillin,723
|
5 |
+
Metformin,6801
|
6 |
+
Lisinopril,29046
|
7 |
+
Ferrous Sulfate,4241
|
8 |
+
Ascorbic Acid,1113
|
9 |
+
Calcium Carbonate / Vitamin D3,12345
|
10 |
+
Clonazepam,2623
|
11 |
+
Meganeuron,54321
|
12 |
+
Creatine,9876
|
13 |
+
Salbutamol,9648
|
drug_interaction_detection/__pycache__/interaction_checker.cpython-310.pyc
ADDED
Binary file (1.6 kB). View file
|
|
drug_interaction_detection/interaction_checker.py
ADDED
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# drug_interaction_detection/interaction_checker.py
|
2 |
+
import sqlite3
|
3 |
+
from config import DB_PATH
|
4 |
+
|
5 |
+
class InteractionChecker:
|
6 |
+
def __init__(self, db_path: str = DB_PATH):
|
7 |
+
self.conn = sqlite3.connect(db_path)
|
8 |
+
self.conn.row_factory = sqlite3.Row
|
9 |
+
|
10 |
+
def find(self, cuis: list[str]) -> list[dict]:
|
11 |
+
if len(cuis) < 2: return []
|
12 |
+
|
13 |
+
# Create a list of all unique pairs
|
14 |
+
pairs = []
|
15 |
+
for i in range(len(cuis)):
|
16 |
+
for j in range(i + 1, len(cuis)):
|
17 |
+
# Ensure consistent order for querying (e.g., smaller CUI first)
|
18 |
+
pair = tuple(sorted((cuis[i], cuis[j])))
|
19 |
+
pairs.append(pair)
|
20 |
+
|
21 |
+
if not pairs: return []
|
22 |
+
|
23 |
+
# Query for all pairs at once
|
24 |
+
placeholders = ", ".join(["(?, ?)"] * len(pairs))
|
25 |
+
flat_params = [item for pair in pairs for item in pair]
|
26 |
+
|
27 |
+
query = f"""
|
28 |
+
SELECT cui1, cui2, severity, advice
|
29 |
+
FROM interactions
|
30 |
+
WHERE (cui1, cui2) IN ({placeholders})
|
31 |
+
"""
|
32 |
+
|
33 |
+
rows = self.conn.execute(query, flat_params).fetchall()
|
34 |
+
return [{"pair": (r["cui1"], r["cui2"]), "severity": r["severity"], "advice": r["advice"]} for r in rows]
|
earnest-trilogy-465710-e7-6cc7bbbddb97.json
ADDED
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"type": "service_account",
|
3 |
+
"project_id": "earnest-trilogy-465710-e7",
|
4 |
+
"private_key_id": "6cc7bbbddb970f16876e3f4120ff18be704f5ee5",
|
5 |
+
"private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCL9vzHnI9qvAMm\n4VvadaDB//B8ibFEi3BNojVaWg5iM12nhIRhhDTCHEryjrQUmxslGiJ/xslQzO6c\nueAXVGiaT+dyZJ6q004aRpjfjkV+T8bmIX8B2hx9bJZ0mTn4rN7Yeg2mOR7F+Psf\njjSm91sX3FTtDO6lRd6olGiRCXn65GPkbXPUZW52xuEVwRuFhsNcJnKgRD3f7rko\nbnBZ0/un7HRg5eNZspHW4eOynOSZcoykCfnQbB4noRsLyGi3VknZDCXpRc9dhlLf\nDZFhjnqzwsoy24Z6uoPSctAX1itL54zQ0x25xUS4pvPO3jy7W9ONctRjzp2oQyIC\n199jv0thAgMBAAECggEAARzStqpT+bDv1E+ACwu6k4mLluKC0YT2Eh3pxJOORUxR\nJc+Kka+1pNHerCXY1ghDcNnvyoTRDUarF71CrrjLFz1ESBnh6NZ4FVgjPLVdMCNQ\nSnxwwvoMgNg77BvFiih4odn63AvcnA2uFFqtaI2IkIyIiHUHpsdth85HNFjx5T3y\nxQwx0P+7QxFXpaQkDXUFqcTysJNzZbhKHVJAtmjVo32H103PKJtYbmVdNci0fxE4\n5SugGB/AvFrmZ2rPzVraIarFueE2m6ENrpU+ESwBFnASwJ0uAqir0ZEz+7VXPGYL\nGwk6rkhhK+GD+z+CRkA6RDuObkUG1ijqT9kf+DCiAQKBgQDDRrDSf6WXhiuh6Cl8\n5JGjD1xfw0T6U63bJdRXN1iPE5+WUwq7Jdtb1tjB9ocNg/6ISIfHBrribLapwrJl\npFQl4UOB731K0NLWbIhiJeVdX/By5boYQK7FB0J4+dQZfIgzl0gsJuT+1wsxvTMJ\nXImmaG+bFLEtEuWOEDrM4OflgQKBgQC3fSM3PIY3o9XC7EIU97G5eWQRAjvSYU6l\nfETIR93ycxahPhU+JvB6ncteJTSEdLLAKq+VnxOvXH/3tlufMmHeQuSEx2IGip2B\nvqnNV8IhY7aQtA0enfT88PmlpEnM+mBPDPKAoJXOoRTanKiMXuwhVFsqfRo8A597\nCxzDPZsV4QKBgC4bWNC61uxp/49g87rLdR+dFuB9iKHadChUTEizhrNxnLKvtM7v\nZ1XN6qwRe13TlpuzFGwHyMSBireWguzA2iV/hKL/WwP5Pm7mfWU/MWLUrj9SwpfL\nXfijeCx8QHosDzSvOZlDLbqGJ9x8obpKIS4rZn6lahgMaCsc5eVODTMBAoGAaQRJ\nFIMielPds1tPEvsVEAeHGykBHg6tWY9/OnXPdMUj7ZM/yzu0JSmMzMxUe37jE5Ma\nvXK3bIVvhFItrDbExtXYPppy4zWQokKCotEYfc25Hqa+X4ieP+qXp5MY3iVq27OY\nU8AVHZcZ/WjuGrD1SroiF3ZUfobAT0bz5larHWECgYAki/VOTjmOfvtR+6aLZW0T\nuPYomIZGCxCY+b7zoxk3YPEd15KG25SI0JsdK2QDwbGyQan4X/eZQlwFacGH+2is\nsOpYrsMuOktTUtjTmOCPq3+6a22k6yxXvxGIjn0XcP1Pgh/aAi2yi2Ejlxgr4JpU\nJUzmpT+C4PQCHMepVFoaLw==\n-----END PRIVATE KEY-----\n",
|
6 |
+
"client_email": "aihackathon@earnest-trilogy-465710-e7.iam.gserviceaccount.com",
|
7 |
+
"client_id": "102524806684057807383",
|
8 |
+
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
|
9 |
+
"token_uri": "https://oauth2.googleapis.com/token",
|
10 |
+
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
|
11 |
+
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/aihackathon%40earnest-trilogy-465710-e7.iam.gserviceaccount.com",
|
12 |
+
"universe_domain": "googleapis.com"
|
13 |
+
}
|
index.html
DELETED
@@ -1,13 +0,0 @@
|
|
1 |
-
<!doctype html>
|
2 |
-
<html lang="en">
|
3 |
-
<head>
|
4 |
-
<meta charset="UTF-8" />
|
5 |
-
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
6 |
-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
7 |
-
<title>Vite + Svelte + TS</title>
|
8 |
-
</head>
|
9 |
-
<body>
|
10 |
-
<div id="app"></div>
|
11 |
-
<script type="module" src="/src/main.ts"></script>
|
12 |
-
</body>
|
13 |
-
</html>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
models/signature_sia.tflite
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:9ba80e2f5c688e9fd9fc766a85f266d0a47201def65265ef294168546a2814de
|
3 |
+
size 804888
|
package.json
DELETED
@@ -1,20 +0,0 @@
|
|
1 |
-
{
|
2 |
-
"name": "svelte",
|
3 |
-
"private": true,
|
4 |
-
"version": "0.0.0",
|
5 |
-
"type": "module",
|
6 |
-
"scripts": {
|
7 |
-
"dev": "vite",
|
8 |
-
"build": "vite build",
|
9 |
-
"preview": "vite preview",
|
10 |
-
"check": "svelte-check --tsconfig ./tsconfig.app.json && tsc -p tsconfig.node.json"
|
11 |
-
},
|
12 |
-
"devDependencies": {
|
13 |
-
"@sveltejs/vite-plugin-svelte": "^5.0.3",
|
14 |
-
"@tsconfig/svelte": "^5.0.4",
|
15 |
-
"svelte": "^5.28.1",
|
16 |
-
"svelte-check": "^4.1.6",
|
17 |
-
"typescript": "~5.8.3",
|
18 |
-
"vite": "^6.3.5"
|
19 |
-
}
|
20 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
prescription_validation/__pycache__/fuzzy_match.cpython-310.pyc
ADDED
Binary file (1.52 kB). View file
|
|
prescription_validation/fuzzy_match.py
ADDED
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# prescription_validation/fuzzy_match.py
|
2 |
+
import sqlite3
|
3 |
+
import re
|
4 |
+
from rapidfuzz.distance import Levenshtein
|
5 |
+
from config import DB_PATH, LEV_THRESH
|
6 |
+
|
7 |
+
class RxLookup:
|
8 |
+
def __init__(self, db_path: str = DB_PATH):
|
9 |
+
self.conn = sqlite3.connect(db_path)
|
10 |
+
self.conn.row_factory = sqlite3.Row
|
11 |
+
self.drugs = self.conn.execute("SELECT name, cui FROM drugs").fetchall()
|
12 |
+
|
13 |
+
def _clean_token(self, token: str) -> str:
|
14 |
+
"""Removes dosage, form factor, and non-alpha characters."""
|
15 |
+
cleaned = token.lower()
|
16 |
+
cleaned = re.sub(r'(\d+)\s*(mg|ml|mcg|tab|cap|#)', '', cleaned)
|
17 |
+
cleaned = re.sub(r'[^a-z]', '', cleaned)
|
18 |
+
return cleaned
|
19 |
+
|
20 |
+
def match(self, token: str) -> tuple[str | None, str | None]:
|
21 |
+
if not token:
|
22 |
+
return (None, None)
|
23 |
+
|
24 |
+
cleaned_token = self._clean_token(token)
|
25 |
+
if not cleaned_token:
|
26 |
+
return (None, None)
|
27 |
+
|
28 |
+
best_match = None
|
29 |
+
min_distance = float('inf')
|
30 |
+
|
31 |
+
for row in self.drugs:
|
32 |
+
name, cui = row["name"], row["cui"]
|
33 |
+
cleaned_db_name = self._clean_token(name)
|
34 |
+
|
35 |
+
distance = Levenshtein.distance(cleaned_token, cleaned_db_name)
|
36 |
+
|
37 |
+
if distance < min_distance:
|
38 |
+
min_distance = distance
|
39 |
+
best_match = (name, cui)
|
40 |
+
|
41 |
+
if best_match and min_distance / len(cleaned_token) < LEV_THRESH:
|
42 |
+
return best_match
|
43 |
+
|
44 |
+
return (None, None)
|
prescription_validation/zone_detector.py
ADDED
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# prescription_validation/zone_detector.py
|
2 |
+
import cv2
|
3 |
+
import numpy as np
|
4 |
+
from ultralytics import YOLO
|
5 |
+
|
6 |
+
class ZoneDetector:
|
7 |
+
def __init__(self, model_path: str = "models/signature_model.pt"):
|
8 |
+
try:
|
9 |
+
self.model = YOLO(model_path)
|
10 |
+
print(f"✅ Loaded local YOLO model from '{model_path}'")
|
11 |
+
except Exception as e:
|
12 |
+
self.model = None
|
13 |
+
print(f"❌ Failed to load local YOLO model from '{model_path}'. Please ensure the model file exists.")
|
14 |
+
|
15 |
+
def detect(self, img: np.ndarray) -> list[dict]:
|
16 |
+
if not self.model: return []
|
17 |
+
rgb_img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
|
18 |
+
results = self.model(rgb_img, verbose=False)
|
19 |
+
detections = []
|
20 |
+
for box in results[0].boxes:
|
21 |
+
x1, y1, x2, y2 = box.xyxy[0].tolist()
|
22 |
+
class_id = int(box.cls[0])
|
23 |
+
detections.append({
|
24 |
+
"label": self.model.names[class_id],
|
25 |
+
"bbox": tuple(map(int, [x1, y1, x2, y2])),
|
26 |
+
})
|
27 |
+
return detections
|
public/vite.svg
DELETED
requirements.txt
ADDED
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Core
|
2 |
+
streamlit==1.36.0
|
3 |
+
python-dotenv==1.0.1
|
4 |
+
|
5 |
+
# AI & Vision
|
6 |
+
# Using Google's recommended versions for Gemini and Vision
|
7 |
+
google-generativeai==0.7.1
|
8 |
+
google-cloud-vision==3.7.3
|
9 |
+
torch==2.3.1
|
10 |
+
pillow==10.3.0
|
11 |
+
transformers==4.41.0
|
12 |
+
|
13 |
+
# OCR
|
14 |
+
paddleocr==2.7.3
|
15 |
+
# Using the CPU version of paddlepaddle for broader compatibility on HF Spaces
|
16 |
+
paddlepaddle==2.6.1
|
17 |
+
|
18 |
+
# Utils
|
19 |
+
numpy==1.26.4
|
20 |
+
requests==2.32.3
|
21 |
+
opencv-python-headless==4.10.0.84
|
22 |
+
scikit-image==0.22.0
|
23 |
+
pytz==2024.1
|
scripts/build_interactions_db.py
ADDED
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
#!/usr/bin/env python3
|
2 |
+
import sqlite3, csv, argparse
|
3 |
+
import sys, os
|
4 |
+
sys.path.append(os.getcwd())
|
5 |
+
from config import DB_PATH, INTERACTIONS_CSV
|
6 |
+
|
7 |
+
def build_db(db_path: str, csv_path: str):
|
8 |
+
conn = sqlite3.connect(db_path)
|
9 |
+
c = conn.cursor()
|
10 |
+
c.execute("DROP TABLE IF EXISTS interactions")
|
11 |
+
c.execute("CREATE TABLE interactions(cui1 TEXT, cui2 TEXT, severity TEXT, advice TEXT, UNIQUE(cui1, cui2))")
|
12 |
+
|
13 |
+
with open(csv_path, newline='', encoding='utf-8') as f:
|
14 |
+
reader = csv.DictReader(f)
|
15 |
+
for row in reader:
|
16 |
+
# Ensure consistent order to avoid duplicate entries like (A,B) and (B,A)
|
17 |
+
cui1, cui2 = sorted((row['cui1'], row['cui2']))
|
18 |
+
c.execute(
|
19 |
+
"INSERT OR IGNORE INTO interactions(cui1, cui2, severity, advice) VALUES(?,?,?,?)",
|
20 |
+
(cui1, cui2, row['severity'], row['advice'])
|
21 |
+
)
|
22 |
+
conn.commit()
|
23 |
+
conn.close()
|
24 |
+
|
25 |
+
if __name__ == "__main__":
|
26 |
+
build_db(DB_PATH, INTERACTIONS_CSV)
|
27 |
+
print(f"✅ Interactions DB built at {DB_PATH}")
|
scripts/build_rxnorm_db.py
ADDED
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
#!/usr/bin/env python3
|
2 |
+
import sqlite3, csv, argparse
|
3 |
+
from metaphone import doublemetaphone
|
4 |
+
import sys, os
|
5 |
+
sys.path.append(os.getcwd())
|
6 |
+
from config import DB_PATH, RXNORM_CSV
|
7 |
+
|
8 |
+
def build_db(db_path: str, csv_path: str):
|
9 |
+
conn = sqlite3.connect(db_path)
|
10 |
+
c = conn.cursor()
|
11 |
+
c.execute("DROP TABLE IF EXISTS drugs")
|
12 |
+
c.execute("DROP TABLE IF EXISTS metaphone")
|
13 |
+
c.execute("CREATE TABLE drugs(name TEXT PRIMARY KEY, cui TEXT)")
|
14 |
+
c.execute("CREATE TABLE metaphone(meta TEXT, name TEXT, cui TEXT)")
|
15 |
+
|
16 |
+
with open(csv_path, newline='', encoding='utf-8') as f:
|
17 |
+
reader = csv.DictReader(f)
|
18 |
+
for row in reader:
|
19 |
+
nm, cui = row['name'], row['cui']
|
20 |
+
c.execute("INSERT OR IGNORE INTO drugs VALUES(?,?)", (nm, cui))
|
21 |
+
meta = doublemetaphone(nm)[0]
|
22 |
+
c.execute("INSERT INTO metaphone VALUES(?,?,?)", (meta, nm, cui))
|
23 |
+
conn.commit()
|
24 |
+
conn.close()
|
25 |
+
|
26 |
+
if __name__ == "__main__":
|
27 |
+
build_db(DB_PATH, RXNORM_CSV)
|
28 |
+
print(f"✅ RxNorm DB built at {DB_PATH}")
|
scripts/create_dummy_signature_model.py
ADDED
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
#!/usr/bin/env python3
|
2 |
+
import os
|
3 |
+
import tensorflow as tf
|
4 |
+
from tensorflow.keras.layers import Input, Flatten, Dense, Subtract
|
5 |
+
from tensorflow.keras.models import Model
|
6 |
+
import sys
|
7 |
+
sys.path.append(os.getcwd())
|
8 |
+
from config import SIA_MODEL_PATH
|
9 |
+
|
10 |
+
def build_dummy_siamese():
|
11 |
+
inp_a = Input(shape=(224, 224, 1), name="img_a")
|
12 |
+
inp_b = Input(shape=(224, 224, 1), name="img_b")
|
13 |
+
encoder_input = Input(shape=(224, 224, 1))
|
14 |
+
x = Flatten()(encoder_input)
|
15 |
+
x = Dense(16, activation="relu")(x)
|
16 |
+
encoder = Model(encoder_input, x)
|
17 |
+
encoded_a = encoder(inp_a)
|
18 |
+
encoded_b = encoder(inp_b)
|
19 |
+
distance = Subtract()([encoded_a, encoded_b])
|
20 |
+
model = Model(inputs=[inp_a, inp_b], outputs=distance)
|
21 |
+
return model
|
22 |
+
|
23 |
+
if __name__ == "__main__":
|
24 |
+
print("Building and converting dummy Siamese model...")
|
25 |
+
model = build_dummy_siamese()
|
26 |
+
converter = tf.lite.TFLiteConverter.from_keras_model(model)
|
27 |
+
converter.optimizations = [tf.lite.Optimize.DEFAULT]
|
28 |
+
tflite_model = converter.convert()
|
29 |
+
os.makedirs(os.path.dirname(SIA_MODEL_PATH), exist_ok=True)
|
30 |
+
with open(SIA_MODEL_PATH, "wb") as f:
|
31 |
+
f.write(tflite_model)
|
32 |
+
print(f"✅ Dummy TFLite signature model saved to '{SIA_MODEL_PATH}'")
|
signature_verification/__pycache__/signature_detector.cpython-310.pyc
ADDED
Binary file (1.5 kB). View file
|
|
signature_verification/__pycache__/signature_generator.cpython-310.pyc
ADDED
Binary file (1.91 kB). View file
|
|
signature_verification/__pycache__/signature_siamese.cpython-310.pyc
ADDED
Binary file (1.84 kB). View file
|
|
signature_verification/signature_detector.py
ADDED
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# signature_verification/signature_detector.py
|
2 |
+
import cv2
|
3 |
+
import numpy as np
|
4 |
+
|
5 |
+
class SignatureDetector:
|
6 |
+
"""
|
7 |
+
Detects and crops a signature from a prescription image using refined OpenCV contour analysis.
|
8 |
+
This method is fast, offline, and does not require a pre-trained model.
|
9 |
+
"""
|
10 |
+
def crop(self, img: np.ndarray) -> np.ndarray | None:
|
11 |
+
if img is None: return None
|
12 |
+
|
13 |
+
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
|
14 |
+
blurred = cv2.GaussianBlur(gray, (5, 5), 0)
|
15 |
+
_, thresh = cv2.threshold(blurred, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
|
16 |
+
|
17 |
+
contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
|
18 |
+
if not contours: return None
|
19 |
+
|
20 |
+
height, width = img.shape[:2]
|
21 |
+
significant_contours = []
|
22 |
+
for cnt in contours:
|
23 |
+
x, y, w, h = cv2.boundingRect(cnt)
|
24 |
+
# Filter for contours that are reasonably large and in the bottom 70% of the page
|
25 |
+
if w > 50 and h > 15 and y > height * 0.3:
|
26 |
+
if w < width * 0.8 and h < height * 0.4:
|
27 |
+
significant_contours.append(cnt)
|
28 |
+
|
29 |
+
if not significant_contours: return None
|
30 |
+
|
31 |
+
largest_contour = max(significant_contours, key=cv2.contourArea)
|
32 |
+
x, y, w, h = cv2.boundingRect(largest_contour)
|
33 |
+
|
34 |
+
padding = 15
|
35 |
+
x1 = max(0, x - padding)
|
36 |
+
y1 = max(0, y - padding)
|
37 |
+
x2 = min(width, x + w + padding)
|
38 |
+
y2 = min(height, y + h + padding)
|
39 |
+
|
40 |
+
return img[y1:y2, x1:x2]
|
signature_verification/signature_generator.py
ADDED
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import logging
|
3 |
+
import numpy as np
|
4 |
+
import cv2
|
5 |
+
from dotenv import load_dotenv
|
6 |
+
from huggingface_hub import InferenceClient, HfFolder
|
7 |
+
from PIL import Image
|
8 |
+
|
9 |
+
load_dotenv()
|
10 |
+
|
11 |
+
# Acquire token
|
12 |
+
hf_token = os.getenv("HUGGING_FACE_HUB_TOKEN") or HfFolder.get_token()
|
13 |
+
if not hf_token:
|
14 |
+
print("❌ No Hugging Face token found. Signature generation will fail.")
|
15 |
+
CLIENT = None
|
16 |
+
else:
|
17 |
+
print("✅ Using Hugging Face token for signature generation.")
|
18 |
+
CLIENT = InferenceClient(token=hf_token)
|
19 |
+
|
20 |
+
MODEL_ID = "stabilityai/stable-diffusion-xl-base-1.0"
|
21 |
+
|
22 |
+
def generate_signatures(name: str, num_variations: int = 3) -> list:
|
23 |
+
"""Generates multiple signature variations for a given name."""
|
24 |
+
if CLIENT is None:
|
25 |
+
return []
|
26 |
+
|
27 |
+
prompts = [
|
28 |
+
f"A clean, elegant, handwritten signature of the name '{name}' on a plain white background. Cursive, professional.",
|
29 |
+
f"Calligraphy signature of '{name}'. Black ink on white paper. Minimalist, artistic.",
|
30 |
+
f"A doctor's signature for '{name}'. Quick, scribbled, but legible. Official-looking script."
|
31 |
+
]
|
32 |
+
images = []
|
33 |
+
for i in range(num_variations):
|
34 |
+
prompt = prompts[i % len(prompts)]
|
35 |
+
logging.info(f"Generating signature variation {i+1} for '{name}'…")
|
36 |
+
try:
|
37 |
+
pil_img = CLIENT.text_to_image(
|
38 |
+
prompt,
|
39 |
+
model=MODEL_ID,
|
40 |
+
negative_prompt=(
|
41 |
+
"photograph, text, multiple signatures, watermark, blurry, colorful, background"
|
42 |
+
),
|
43 |
+
guidance_scale=8.0
|
44 |
+
)
|
45 |
+
np_img = np.array(pil_img)
|
46 |
+
cv_img = cv2.cvtColor(np_img, cv2.COLOR_RGB2BGR)
|
47 |
+
images.append(cv_img)
|
48 |
+
except Exception as e:
|
49 |
+
logging.error(f"❌ Signature generation failed: {e}")
|
50 |
+
return images
|
signature_verification/signature_siamese.py
ADDED
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# signature_verification/signature_siamese.py
|
2 |
+
import cv2
|
3 |
+
import numpy as np
|
4 |
+
import tensorflow as tf
|
5 |
+
from config import SIA_MODEL_PATH, SIG_THRESH
|
6 |
+
|
7 |
+
class SignatureSiamese:
|
8 |
+
def __init__(self, model_path: str = SIA_MODEL_PATH):
|
9 |
+
try:
|
10 |
+
self.interp = tf.lite.Interpreter(model_path=model_path)
|
11 |
+
self.interp.allocate_tensors()
|
12 |
+
ids = self.interp.get_input_details()
|
13 |
+
self.in1, self.in2 = ids[0]['index'], ids[1]['index']
|
14 |
+
self.out_idx = self.interp.get_output_details()[0]['index']
|
15 |
+
print(f"✅ Loaded TFLite Siamese model from {model_path}")
|
16 |
+
except Exception as e:
|
17 |
+
self.interp = None
|
18 |
+
print(f"❌ Failed to load Siamese model from {model_path}: {e}")
|
19 |
+
|
20 |
+
def _prep(self, img: np.ndarray) -> np.ndarray:
|
21 |
+
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) if img.ndim == 3 else img
|
22 |
+
r = cv2.resize(gray, (224, 224))
|
23 |
+
return r.reshape(1, 224, 224, 1).astype(np.float32) / 255.0
|
24 |
+
|
25 |
+
def verify(self, img1: np.ndarray, img2: np.ndarray) -> tuple[bool, float]:
|
26 |
+
if not self.interp:
|
27 |
+
return False, float('inf')
|
28 |
+
inp1, inp2 = self._prep(img1), self._prep(img2)
|
29 |
+
self.interp.set_tensor(self.in1, inp1)
|
30 |
+
self.interp.set_tensor(self.in2, inp2)
|
31 |
+
self.interp.invoke()
|
32 |
+
diff = self.interp.get_tensor(self.out_idx)
|
33 |
+
dist = np.linalg.norm(diff)
|
34 |
+
return dist < SIG_THRESH, dist
|
src/App.svelte
DELETED
@@ -1,47 +0,0 @@
|
|
1 |
-
<script lang="ts">
|
2 |
-
import svelteLogo from './assets/svelte.svg'
|
3 |
-
import viteLogo from '/vite.svg'
|
4 |
-
import Counter from './lib/Counter.svelte'
|
5 |
-
</script>
|
6 |
-
|
7 |
-
<main>
|
8 |
-
<div>
|
9 |
-
<a href="https://vite.dev" target="_blank" rel="noreferrer">
|
10 |
-
<img src={viteLogo} class="logo" alt="Vite Logo" />
|
11 |
-
</a>
|
12 |
-
<a href="https://svelte.dev" target="_blank" rel="noreferrer">
|
13 |
-
<img src={svelteLogo} class="logo svelte" alt="Svelte Logo" />
|
14 |
-
</a>
|
15 |
-
</div>
|
16 |
-
<h1>Vite + Svelte</h1>
|
17 |
-
|
18 |
-
<div class="card">
|
19 |
-
<Counter />
|
20 |
-
</div>
|
21 |
-
|
22 |
-
<p>
|
23 |
-
Check out <a href="https://github.com/sveltejs/kit#readme" target="_blank" rel="noreferrer">SvelteKit</a>, the official Svelte app framework powered by Vite!
|
24 |
-
</p>
|
25 |
-
|
26 |
-
<p class="read-the-docs">
|
27 |
-
Click on the Vite and Svelte logos to learn more
|
28 |
-
</p>
|
29 |
-
</main>
|
30 |
-
|
31 |
-
<style>
|
32 |
-
.logo {
|
33 |
-
height: 6em;
|
34 |
-
padding: 1.5em;
|
35 |
-
will-change: filter;
|
36 |
-
transition: filter 300ms;
|
37 |
-
}
|
38 |
-
.logo:hover {
|
39 |
-
filter: drop-shadow(0 0 2em #646cffaa);
|
40 |
-
}
|
41 |
-
.logo.svelte:hover {
|
42 |
-
filter: drop-shadow(0 0 2em #ff3e00aa);
|
43 |
-
}
|
44 |
-
.read-the-docs {
|
45 |
-
color: #888;
|
46 |
-
}
|
47 |
-
</style>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/app.css
DELETED
@@ -1,79 +0,0 @@
|
|
1 |
-
:root {
|
2 |
-
font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
|
3 |
-
line-height: 1.5;
|
4 |
-
font-weight: 400;
|
5 |
-
|
6 |
-
color-scheme: light dark;
|
7 |
-
color: rgba(255, 255, 255, 0.87);
|
8 |
-
background-color: #242424;
|
9 |
-
|
10 |
-
font-synthesis: none;
|
11 |
-
text-rendering: optimizeLegibility;
|
12 |
-
-webkit-font-smoothing: antialiased;
|
13 |
-
-moz-osx-font-smoothing: grayscale;
|
14 |
-
}
|
15 |
-
|
16 |
-
a {
|
17 |
-
font-weight: 500;
|
18 |
-
color: #646cff;
|
19 |
-
text-decoration: inherit;
|
20 |
-
}
|
21 |
-
a:hover {
|
22 |
-
color: #535bf2;
|
23 |
-
}
|
24 |
-
|
25 |
-
body {
|
26 |
-
margin: 0;
|
27 |
-
display: flex;
|
28 |
-
place-items: center;
|
29 |
-
min-width: 320px;
|
30 |
-
min-height: 100vh;
|
31 |
-
}
|
32 |
-
|
33 |
-
h1 {
|
34 |
-
font-size: 3.2em;
|
35 |
-
line-height: 1.1;
|
36 |
-
}
|
37 |
-
|
38 |
-
.card {
|
39 |
-
padding: 2em;
|
40 |
-
}
|
41 |
-
|
42 |
-
#app {
|
43 |
-
max-width: 1280px;
|
44 |
-
margin: 0 auto;
|
45 |
-
padding: 2rem;
|
46 |
-
text-align: center;
|
47 |
-
}
|
48 |
-
|
49 |
-
button {
|
50 |
-
border-radius: 8px;
|
51 |
-
border: 1px solid transparent;
|
52 |
-
padding: 0.6em 1.2em;
|
53 |
-
font-size: 1em;
|
54 |
-
font-weight: 500;
|
55 |
-
font-family: inherit;
|
56 |
-
background-color: #1a1a1a;
|
57 |
-
cursor: pointer;
|
58 |
-
transition: border-color 0.25s;
|
59 |
-
}
|
60 |
-
button:hover {
|
61 |
-
border-color: #646cff;
|
62 |
-
}
|
63 |
-
button:focus,
|
64 |
-
button:focus-visible {
|
65 |
-
outline: 4px auto -webkit-focus-ring-color;
|
66 |
-
}
|
67 |
-
|
68 |
-
@media (prefers-color-scheme: light) {
|
69 |
-
:root {
|
70 |
-
color: #213547;
|
71 |
-
background-color: #ffffff;
|
72 |
-
}
|
73 |
-
a:hover {
|
74 |
-
color: #747bff;
|
75 |
-
}
|
76 |
-
button {
|
77 |
-
background-color: #f9f9f9;
|
78 |
-
}
|
79 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/assets/svelte.svg
DELETED
src/lib/Counter.svelte
DELETED
@@ -1,10 +0,0 @@
|
|
1 |
-
<script lang="ts">
|
2 |
-
let count: number = $state(0)
|
3 |
-
const increment = () => {
|
4 |
-
count += 1
|
5 |
-
}
|
6 |
-
</script>
|
7 |
-
|
8 |
-
<button onclick={increment}>
|
9 |
-
count is {count}
|
10 |
-
</button>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/main.ts
DELETED
@@ -1,9 +0,0 @@
|
|
1 |
-
import { mount } from 'svelte'
|
2 |
-
import './app.css'
|
3 |
-
import App from './App.svelte'
|
4 |
-
|
5 |
-
const app = mount(App, {
|
6 |
-
target: document.getElementById('app')!,
|
7 |
-
})
|
8 |
-
|
9 |
-
export default app
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/vite-env.d.ts
DELETED
@@ -1,2 +0,0 @@
|
|
1 |
-
/// <reference types="svelte" />
|
2 |
-
/// <reference types="vite/client" />
|
|
|
|
|
|
static/signatures/fake-autograph-samples-hand-drawn-signatures-examples-of-documents-certificates-and-contracts-with-inked-and-handwritten-lettering-vector.jpg
ADDED
![]() |
Git LFS Details
|
static/signatures/fake-signature-hand-drawn-sample-own-autograph-fictitious-handwritten-signature-black-ink-scribble-for-sample-contracts-documents-certificates-or-letters-vector-illustration-2WKNPM2.jpg
ADDED
![]() |
Git LFS Details
|
static/signatures/images.png
ADDED
![]() |
static/signatures/signature-certificate-document-vector-transparent-260nw-2346804443.jpg
ADDED
![]() |
Git LFS Details
|
static/signatures/signature-vector-hand-drawn-autograph-600nw-2387543207.jpg
ADDED
![]() |
Git LFS Details
|
static/uploads/02af69ed359f4d27b46ca3cfc814288b_IMG_0082.jpg
ADDED
![]() |
Git LFS Details
|
static/uploads/0bc297f68f84453f8e5630a45d3baf83_vio-4.jpg
ADDED
![]() |
Git LFS Details
|
static/uploads/1*3xUyINxRtDf2qowd-kkGQA.jpg
ADDED
![]() |
Git LFS Details
|
static/uploads/255d2936a26d49429c21ce6b06b323b7_vio-4.jpg
ADDED
![]() |
Git LFS Details
|
static/uploads/2b090d65e18f4e1ba1a048c6a2722d03_vio-4.jpg
ADDED
![]() |
Git LFS Details
|