Skip to content

Commit

Permalink
Transcription endpoint to Upload Photo implemeneted
Browse files Browse the repository at this point in the history
  • Loading branch information
naheyansheikh committed Dec 6, 2024
1 parent 531e6da commit 2de023b
Show file tree
Hide file tree
Showing 10 changed files with 259 additions and 21 deletions.
3 changes: 2 additions & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,5 +44,6 @@
"jest": "^29.7.0",
"react-test-renderer": "^18.3.1",
"vite": "^5.4.1"
}
},
"proxy": "http://localhost:5000"
}
4 changes: 4 additions & 0 deletions frontend/src/main.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import LogHistory from "./pages/log_history/LogHistory.jsx";
import UploadPhoto from "./pages/upload_photo/UploadPhoto.jsx";
import ManualEntry from "./pages/manual_entry/ManualEntry.jsx";
import LogCode from "./pages/log_code/LogCode.jsx";
import LoadTranscription from "./pages/load_transcription/LoadTranscription.jsx";
import LoadingScreen from "./pages/loading_screen/LoadingScreen.jsx";
import "./index.css";

createRoot(document.getElementById("root")).render(
Expand All @@ -26,6 +28,8 @@ createRoot(document.getElementById("root")).render(
<Route path="/upload-photo" element={<UploadPhoto />} />
<Route path="/manualEntry" element={<ManualEntry />} />
<Route path="/logCode" element={<LogCode />} />
<Route path="/load-transcription" element={<LoadTranscription />} />
<Route path="/loading-screen" element={<LoadingScreen />} />
</Route>
<Route path="*" element={<NotFound />} />
</Routes>
Expand Down
28 changes: 28 additions & 0 deletions frontend/src/pages/load_transcription/LoadTranscription.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
.transcription-container {
padding: 20px;
}

.transcription-header {
display: flex;
align-items: center;
gap: 16px;
margin-bottom: 20px;
}

.back-button {
background: none;
border: none;
cursor: pointer;
}

.back-icon {
width: 24px;
height: 24px;
}

.transcription-content {
background: white;
padding: 20px;
border-radius: 8px;
white-space: pre-wrap;
}
30 changes: 30 additions & 0 deletions frontend/src/pages/load_transcription/LoadTranscription.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { useLocation, useNavigate } from "react-router-dom";
import { NavContentWrapper } from "../../components/NavContentWrapper/NavContentWrapper";
import { ChevronLeftIcon } from "@heroicons/react/24/solid";
import "./LoadTranscription.css";

export default function LoadTranscription() {
const location = useLocation();
const navigate = useNavigate();
const transcription = location.state?.transcription;

return (
<NavContentWrapper>
<div className="transcription-container">
<div className="transcription-header">
<button
className="back-button"
onClick={() => navigate("/upload-photo")}
>
<ChevronLeftIcon className="back-icon" />
</button>
<h2>Transcription Result</h2>
</div>

<div className="transcription-content">
{transcription || "No transcription data available"}
</div>
</div>
</NavContentWrapper>
);
}
46 changes: 46 additions & 0 deletions frontend/src/pages/loading_screen/LoadingScreen.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
.loading-screen {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
text-align: center;
}

.loading-screen h2, h3 {
margin: 5px 0;
color: #333;
}

.progress-bar-container {
width: 60%;
height: 20px;
border-radius: 30px;
border: 1px solid #244B94;
background-color: #FFFF;
margin: 40px 0;
position: relative;
}

.progress-bar {
height: 100%;
background: #244B94;
border-radius: 20px;
transition: width 0.3s ease;
}

.progress-text {
position: absolute;
right: -40px;
top: -2px;
color: #000000;
}

.cancel-icon {
margin-right: 8px;
font-size: 18px;
}

.cancel-button:hover {
background: #f5f5f5;
}
85 changes: 85 additions & 0 deletions frontend/src/pages/loading_screen/LoadingScreen.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { useEffect, useState } from "react";
import { useLocation, useNavigate } from "react-router-dom";
import { CLButtonSecondary } from "../../components/Buttons/CLButtons";
import "./LoadingScreen.css";

export default function LoadingScreen() {
const location = useLocation();
const navigate = useNavigate();
const [progress, setProgress] = useState(0);
const imageFile = location.state?.imageFile;

useEffect(() => {
if (!imageFile) {
navigate("/upload-photo");
return;
}

const handleTranscription = async () => {
try {
const formData = new FormData();
formData.append("image", imageFile);

// Progress simulation
let currentProgress = 0;
const progressInterval = setInterval(() => {
const increment = Math.random() * 3 + 1; // Random increment between 1-4
currentProgress = Math.min(currentProgress + increment, 85);
setProgress(Math.round(currentProgress));
}, 300);

const response = await fetch("/api/transcribe", {
method: "POST",
body: formData,
});

clearInterval(progressInterval);

const data = await response.json();

if (!response.ok) {
throw new Error(
data.error || `HTTP error! status: ${response.status}`
);
}

// Complete the progress bar
setProgress(100);

await new Promise((resolve) => setTimeout(resolve, 500));

// Navigate to results
navigate("/load-transcription", {
state: { transcription: data.transcription },
replace: true,
});
} catch (error) {
console.error("Error during transcription:", error);
alert("Failed to transcribe image: " + error.message);
navigate(-1);
}
};

handleTranscription();
}, [imageFile, navigate]);

return (
<div className="loading-screen">
<h2>Transcribing photos</h2>
<h3>to standardized template...</h3>

<div className="progress-bar-container">
<div className="progress-bar" style={{ width: `${progress}%` }} />
<span className="progress-text">{progress}%</span>
</div>

<CLButtonSecondary
onClick={() => navigate("/upload-photo")}
width={"250px"}
>
<span className="cancel-icon">×</span>
Cancel Transcription
</CLButtonSecondary>
</div>
);
}
3 changes: 2 additions & 1 deletion frontend/src/pages/log_code/LogCode.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import Logo from "../../assets/images/logo.png";
import "./LogCode.css";

const LogCode = () => {
const [setLogbookCode] = useState("");
const [logbookCode, setLogbookCode] = useState("");
const [termsAccepted, setTermsAccepted] = useState(false);
const [loading, setLoading] = useState(false);
const navigate = useNavigate();
Expand All @@ -18,6 +18,7 @@ const LogCode = () => {
return alert("Please accept the terms and conditions");
}
try {
console.log("Logbook Code:", logbookCode); // exmaple use for now
navigate("/home");
} catch {
alert("Something went wrong. Please try again.");
Expand Down
67 changes: 50 additions & 17 deletions frontend/src/pages/upload_photo/UploadPhoto.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,44 +9,77 @@ import {
ChevronRightIcon,
ChevronDoubleLeftIcon,
} from "@heroicons/react/24/solid";
import LoadingScreen from "../loading_screen/LoadingScreen";

export default function UploadPhoto() {
const navigate = useNavigate();
const [files, setFiles] = useState([]);
const [isLoading] = useState(false);
const [progress] = useState(0);

const handleTranscribe = () => {
navigate("/load-transcription");
if (files.length === 0) {
alert("Please upload an image first");
return;
}

navigate("/loading-screen", {
state: { imageFile: files[0].file },
});
};

return (
<>
<NavContentWrapper>
<MainContent handleTranscribe={handleTranscribe} />
</NavContentWrapper>
<div className="transcribe-button-container">
<button className="transcribe-button" onClick={handleTranscribe}>
Transcribe
</button>
</div>
{isLoading ? (
<LoadingScreen progress={progress} />
) : (
<>
<NavContentWrapper>
<MainContent
files={files}
setFiles={setFiles}
handleTranscribe={handleTranscribe}
/>
</NavContentWrapper>
<div className="transcribe-button-container">
<button
className="transcribe-button"
onClick={handleTranscribe}
disabled={files.length === 0}
>
Transcribe
</button>
</div>
</>
)}
</>
);
}

function MainContent({ handleTranscribe }) {
function MainContent({ files, setFiles, handleTranscribe }) {
const navigate = useNavigate();
const [files, setFiles] = useState([]);
const [showPreview, setShowPreview] = useState(false);

/** Allowed file types */
const ALLOWED_FILE_TYPES = ["image/png", "image/jpeg"];

/** Handle files from input or drop */
const handleFiles = (newFiles) => {
const filesWithPreview = newFiles.map((file) => ({
...file,
timestamp: Date.now(),
preview: URL.createObjectURL(file),
}));
setFiles((prev) => [...filesWithPreview, ...prev]); // Add new files to beginning
console.log("Received files:", newFiles); // Debug log

const filesArray = Array.from(newFiles);
console.log("Files array:", filesArray); // Debug log

const filesWithPreview = filesArray.map((file) => {
console.log("Processing file:", file); // Debug log
return {
file: file, // Store the actual File object
timestamp: Date.now(),
preview: URL.createObjectURL(file),
};
});

setFiles(filesWithPreview);
};

/** Toggle preview visibility */
Expand Down
8 changes: 8 additions & 0 deletions frontend/vite.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,12 @@ import react from '@vitejs/plugin-react'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
server: {
proxy: {
'/api': {
target: 'http://localhost:5000',
changeOrigin: true,
}
}
}
})
6 changes: 4 additions & 2 deletions transcription/app.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
from flask import Flask, request, jsonify
from flask_cors import CORS
from PIL import Image
from transformers import AutoProcessor, AutoModelForCausalLM
import torch

app = Flask(__name__)
CORS(app)

# load model and processor once during init
device = "cuda:0" if torch.cuda.is_available() else "cpu"
Expand All @@ -12,7 +14,7 @@
model = AutoModelForCausalLM.from_pretrained("microsoft/Florence-2-large", torch_dtype=torch_dtype, trust_remote_code=True).to(device)
processor = AutoProcessor.from_pretrained("microsoft/Florence-2-large", trust_remote_code=True)

@app.route("/transcribe", methods=["POST"])
@app.route("/api/transcribe", methods=["POST"])
def transcribe():
if "image" not in request.files:
return jsonify({"error": "No image file provided"}), 400
Expand All @@ -37,4 +39,4 @@ def transcribe():
return jsonify({"error": str(e)}), 500

if __name__ == "__main__":
app.run(debug=True)
app.run(host='0.0.0.0',port=5000)

0 comments on commit 2de023b

Please sign in to comment.