Spaces:
Sleeping
Sleeping
the coin expert initial setup and build
Browse files- README.md +7 -0
- app.py +27 -4
- firebase_setup.py +22 -0
- main.py +45 -0
- requirements.txt +6 -0
- utils/__init__.py +0 -0
- utils/coin_tools.py +22 -0
- utils/image_tools.py +39 -0
README.md
CHANGED
@@ -12,3 +12,10 @@ short_description: Can identify valuable error coins and varieties.
|
|
12 |
---
|
13 |
|
14 |
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
12 |
---
|
13 |
|
14 |
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
15 |
+
|
16 |
+
|
17 |
+
# Coin Identification AI
|
18 |
+
|
19 |
+
Upload a photo of a suspected error coin and tell us what you think it is. Coin Expert will fetch information, compare images, and help determine if your coin matches a known error or variety.
|
20 |
+
|
21 |
+
Built with OpenAI Vision + Firebase.
|
app.py
CHANGED
@@ -1,7 +1,30 @@
|
|
1 |
import gradio as gr
|
|
|
2 |
|
3 |
-
def
|
4 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
5 |
|
6 |
-
demo = gr.Interface(fn=greet, inputs="text", outputs="text")
|
7 |
-
demo.launch()
|
|
|
1 |
import gradio as gr
|
2 |
+
from main import suspected_coin_name, user_image_path # or refactor main logic into functions
|
3 |
|
4 |
+
def analyze_coin(coin_name, image):
|
5 |
+
with open("uploaded_image.jpg", "wb") as f:
|
6 |
+
f.write(image.read())
|
7 |
+
|
8 |
+
# Assign file path to global variable for downstream logic
|
9 |
+
global user_image_path, suspected_coin_name
|
10 |
+
user_image_path = "uploaded_image.jpg"
|
11 |
+
suspected_coin_name = coin_name
|
12 |
+
|
13 |
+
# Import and re-run main logic
|
14 |
+
import main # assumes main logic runs on import
|
15 |
+
|
16 |
+
return f"Uploaded {coin_name} and processed. See terminal or logs for detailed output."
|
17 |
+
|
18 |
+
interface = gr.Interface(
|
19 |
+
fn=analyze_coin,
|
20 |
+
inputs=[
|
21 |
+
gr.Textbox(label="Enter the Coin Name (e.g. 1955 Lincoln Cent DDO-001)"),
|
22 |
+
gr.File(label="Upload a Coin Image")
|
23 |
+
],
|
24 |
+
outputs="text",
|
25 |
+
title="Coin Expert - AI Coin Identifier",
|
26 |
+
description="Upload a photo of a suspected error coin and enter what you think it is. We'll try to match it and tell you if it's real."
|
27 |
+
)
|
28 |
+
|
29 |
+
interface.launch()
|
30 |
|
|
|
|
firebase_setup.py
ADDED
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import firebase_admin
|
3 |
+
from firebase_admin import credentials, storage
|
4 |
+
|
5 |
+
def init_firebase():
|
6 |
+
if not firebase_admin._apps:
|
7 |
+
cred = credentials.Certificate({
|
8 |
+
"type": "service_account",
|
9 |
+
"project_id": os.getenv("FIREBASE_PROJECT_ID"),
|
10 |
+
"private_key_id": "ignored",
|
11 |
+
"private_key": os.getenv("FIREBASE_PRIVATE_KEY").replace("\\n", "\n"),
|
12 |
+
"client_email": os.getenv("FIREBASE_CLIENT_EMAIL"),
|
13 |
+
"client_id": "ignored",
|
14 |
+
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
|
15 |
+
"token_uri": "https://oauth2.googleapis.com/token",
|
16 |
+
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
|
17 |
+
"client_x509_cert_url": f"https://www.googleapis.com/robot/v1/metadata/x509/{os.getenv('FIREBASE_CLIENT_EMAIL').replace('@', '%40')}"
|
18 |
+
})
|
19 |
+
|
20 |
+
firebase_admin.initialize_app(cred, {
|
21 |
+
'storageBucket': f"{os.getenv('FIREBASE_PROJECT_ID')}.appspot.com"
|
22 |
+
})
|
main.py
ADDED
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import openai
|
3 |
+
import firebase_admin
|
4 |
+
from firebase_admin import firestore
|
5 |
+
from firebase_setup import init_firebase
|
6 |
+
from utils.coin_tools import get_coin_data, save_coin_data
|
7 |
+
from utils.image_tools import compare_images
|
8 |
+
|
9 |
+
# Init Firebase
|
10 |
+
init_firebase()
|
11 |
+
db = firestore.client()
|
12 |
+
|
13 |
+
# Init OpenAI
|
14 |
+
openai.api_key = os.getenv("OPENAI_API_KEY")
|
15 |
+
|
16 |
+
# === USER INPUT (Simulated for now) ===
|
17 |
+
user_image_path = "images/user_coin.jpg"
|
18 |
+
suspected_coin_name = "1955 Lincoln Cent DDO-001"
|
19 |
+
|
20 |
+
# === Step 1: Check if coin already exists in Firebase ===
|
21 |
+
coin_doc = db.collection("coins").document(suspected_coin_name.replace(" ", "_")).get()
|
22 |
+
|
23 |
+
if coin_doc.exists:
|
24 |
+
print(f"β
Found {suspected_coin_name} in the database. Comparing now...")
|
25 |
+
ref_images = coin_doc.to_dict().get("reference_images", [])
|
26 |
+
result = compare_images(user_image_path, ref_images)
|
27 |
+
print(f"Match Confidence: {result['confidence']*100:.1f}%")
|
28 |
+
else:
|
29 |
+
print(f"π No match found in database. Performing web search...")
|
30 |
+
|
31 |
+
# === Step 2: Use web_search_preview tool to find data ===
|
32 |
+
response = openai.responses.create(
|
33 |
+
model="gpt-4.1",
|
34 |
+
tools=[{"type": "web_search_preview"}],
|
35 |
+
input=f"Find die marker info, image links, and estimated value for {suspected_coin_name}"
|
36 |
+
)
|
37 |
+
|
38 |
+
output_text = response.output[1]['content'][0]['text']
|
39 |
+
print("π AI Search Result:\n", output_text)
|
40 |
+
|
41 |
+
# === Step 3: Save AI result to Firebase ===
|
42 |
+
coin_data = get_coin_data(suspected_coin_name, output_text, user_image_path)
|
43 |
+
save_coin_data(suspected_coin_name, coin_data)
|
44 |
+
|
45 |
+
print(f"πΎ Saved new coin info to Firebase under {suspected_coin_name}.")
|
requirements.txt
ADDED
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
openai
|
2 |
+
firebase-admin
|
3 |
+
requests
|
4 |
+
pillow
|
5 |
+
gradio
|
6 |
+
python-dotenv
|
utils/__init__.py
ADDED
File without changes
|
utils/coin_tools.py
ADDED
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
from firebase_admin import firestore, storage
|
3 |
+
|
4 |
+
def get_coin_data(coin_name, description, image_path):
|
5 |
+
return {
|
6 |
+
"name": coin_name,
|
7 |
+
"description": description,
|
8 |
+
"reference_images": [upload_image_to_storage(image_path, coin_name)],
|
9 |
+
"source": "web_search_preview",
|
10 |
+
}
|
11 |
+
|
12 |
+
def upload_image_to_storage(image_path, coin_name):
|
13 |
+
bucket = storage.bucket()
|
14 |
+
blob = bucket.blob(f"coin_references/{coin_name.replace(' ', '_')}.jpg")
|
15 |
+
blob.upload_from_filename(image_path)
|
16 |
+
blob.make_public()
|
17 |
+
return blob.public_url
|
18 |
+
|
19 |
+
def save_coin_data(coin_name, data):
|
20 |
+
db = firestore.client()
|
21 |
+
doc_ref = db.collection("coins").document(coin_name.replace(" ", "_"))
|
22 |
+
doc_ref.set(data)
|
utils/image_tools.py
ADDED
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import openai
|
2 |
+
import requests
|
3 |
+
|
4 |
+
# Use OpenAI Vision to compare a user-uploaded image to reference images
|
5 |
+
def compare_images(user_image_path, reference_image_urls):
|
6 |
+
with open(user_image_path, "rb") as user_img:
|
7 |
+
user_bytes = user_img.read()
|
8 |
+
|
9 |
+
# Download the first reference image for comparison
|
10 |
+
if not reference_image_urls:
|
11 |
+
return {"confidence": 0.0, "reason": "No reference images available."}
|
12 |
+
|
13 |
+
ref_response = requests.get(reference_image_urls[0])
|
14 |
+
if ref_response.status_code != 200:
|
15 |
+
return {"confidence": 0.0, "reason": "Failed to fetch reference image."}
|
16 |
+
|
17 |
+
ref_bytes = ref_response.content
|
18 |
+
|
19 |
+
# Use GPT-4 Vision to compare both images
|
20 |
+
response = openai.chat.completions.create(
|
21 |
+
model="gpt-4-vision-preview",
|
22 |
+
messages=[
|
23 |
+
{"role": "system", "content": "You are an expert in identifying error coins."},
|
24 |
+
{
|
25 |
+
"role": "user",
|
26 |
+
"content": [
|
27 |
+
{"type": "text", "text": "Do these images look like the same error coin?"},
|
28 |
+
{"type": "image", "image": user_bytes},
|
29 |
+
{"type": "image", "image": ref_bytes}
|
30 |
+
]
|
31 |
+
}
|
32 |
+
],
|
33 |
+
max_tokens=300
|
34 |
+
)
|
35 |
+
|
36 |
+
result_text = response.choices[0].message.content
|
37 |
+
# Very simple scoring logic based on confidence language
|
38 |
+
confidence = 0.8 if "yes" in result_text.lower() else 0.3
|
39 |
+
return {"confidence": confidence, "summary": result_text}
|