From 65c6944f52054f7093118a3044c33a10a5ce0b07 Mon Sep 17 00:00:00 2001 From: Andy Wang Date: Tue, 19 May 2026 13:43:29 -0700 Subject: [PATCH] implemented autofocusing UI --- src/App.js | 95 ++++++------- src/components/Focus.jsx | 280 ++++++++++++++++++++++++++------------- 2 files changed, 228 insertions(+), 147 deletions(-) diff --git a/src/App.js b/src/App.js index b14666d..c45fdeb 100644 --- a/src/App.js +++ b/src/App.js @@ -210,64 +210,29 @@ function App() { ]; const cameraComponent = ( -
-
- {/* */} - - - - - - - - -
-
-
-
-
-
-
- ); +
+ + + + + + + + +
+ ); const framingAndFocusComponent = ( -
-
+
- + {/* Pass setDisplayedImage to Focus */} +
-
); const settingsComponent = ( @@ -459,11 +424,27 @@ function App() { className="mainPanel" style={{ minHeight: infoBarHidden ? '97vh' : '75vh' }} > - {/* Camera tab needs to persist so that JS9 has always a valid canvas to load in */} -
- {cameraComponent} + {/* Shared Interface for Camera and Focus Tabs */} +
+ + {/* Render Camera Controls on the left */} +
+ {cameraComponent} +
+ + {/* Render Focus Controls on the left */} +
+ {framingAndFocusComponent} +
+ + {/* Shared JS9 Display on the right */} +
+
+
+
+
- {activeTab === 'framing_and_focus' && framingAndFocusComponent} + {activeTab === 'telescope' && } {activeTab === 'settings' && settingsComponent}
diff --git a/src/components/Focus.jsx b/src/components/Focus.jsx index 6991b38..1e460e7 100644 --- a/src/components/Focus.jsx +++ b/src/components/Focus.jsx @@ -1,114 +1,214 @@ -import { useState, useEffect } from 'react'; +import { useState } from 'react'; import React from "react"; +import BeatLoader from 'react-spinners/BeatLoader'; +import { Line } from 'react-chartjs-2'; +import "chart.js/auto"; -// const backendUrl = 'http://localhost:3005'; -const backendUrl = '/api' +const backendUrl = '/api'; +function Focus({ isDisabled, setDisplayedImage }) { + const [steps, setSteps] = useState(20); + const [stepSize, setStepSize] = useState(10); + const [exposure, setExposure] = useState(1.0); + + const [isRunning, setIsRunning] = useState(false); + const [progressMsg, setProgressMsg] = useState(''); + const [resultMsg, setResultMsg] = useState(''); -function Focus() { - const [focuserPosition, setFocuserPosition] = useState(''); - const [filename, setFilename] = useState(''); - const [sid, setSid] = useState(''); - const [img, setImg] = useState(); + const [chartData, setChartData] = useState({ labels: [], data: [] }); - const refreshSid = () => { - // Generate a timestamp session id - const sessionId = Date.now().toString(); - setSid(sessionId); - }; + const handleRunAutofocus = async () => { + setIsRunning(true); + setResultMsg(''); + setChartData({ labels: [], data: [] }); // Clear the chart for a new run + + let collectedPositions = []; + let collectedHFDs = []; - useEffect(() => { - refreshSid(); - }, []); + // step loop + for (let i = 0; i < steps; i++) { + setProgressMsg(`Taking exposure ${i + 1} of ${steps}...`); + + try { + const stepRes = await fetch(`${backendUrl}/autofocus/step`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + exposure: exposure, + step_size: stepSize, + is_last_step: (i === steps - 1) + }) + }); - const handleFilenameChange = (event) => { - setFilename(event.target.value); - }; - - const handleFocuserPositionChange = (event) => { - setFocuserPosition(event.target.value); - }; + if (!stepRes.ok) throw new Error('Network response was not ok'); + const data = await stepRes.json(); - const handleSendButtonClick = () => { - // Send request to backend with focuserPosition and filename - console.log(`Sending request to backend with focuserPosition: ${focuserPosition} and filename: ${filename}`); + if (data.status !== 'success') { + throw new Error(data.message || 'Camera error'); + } - fetch(`${backendUrl}/api/add_focus_datapoint`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ sid, focuserPosition, filename }) - }) - .then(response => { - if (!response.ok) { - throw new Error('Network response was not ok'); + // Record data + const currentPos = i * stepSize; + collectedPositions.push(currentPos); + collectedHFDs.push(data.hfd); + + // Update the live chart state + setChartData({ + labels: [...collectedPositions], + data: [...collectedHFDs] + }); + + // Update the JS9 Viewer + setDisplayedImage((old) => { + if (old === data.image_url && window.JS9) { + window.JS9.RefreshImage(data.image_url); + } + return data.image_url; + }); + + } catch (error) { + setIsRunning(false); + setProgressMsg(''); + setResultMsg(`Sequence failed at step ${i + 1}: ${error.message}`); + return; } - return response.json(); - }) - .then(data => { - console.log('Response from backend:', data); - fetchPlot(); - }) - .catch(error => { - console.error('Error sending request to backend:', error); - }); - }; + } + + // fit v curve + setProgressMsg(`Analyzing V-Curve data...`); + + try { + const analyzeRes = await fetch(`${backendUrl}/autofocus/analyze`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + positions: collectedPositions, + hfds: collectedHFDs, + step_size: stepSize, + total_steps: steps + }) + }); - const fetchPlot = async () => { - const res = await fetch(`${backendUrl}/plot/${sid}`, { - }); - const imageBlob = await res.blob(); - const imageObjectURL = URL.createObjectURL(imageBlob); - setImg(imageObjectURL); + const finalData = await analyzeRes.json(); + + setIsRunning(false); + setProgressMsg(''); + + if (finalData.status === 'success') { + setResultMsg(`Success! Best focus found at position: ${finalData.best_position}`); + } else { + setResultMsg(`Analysis Failed: ${finalData.message}`); + } + } catch (error) { + setIsRunning(false); + setProgressMsg(''); + setResultMsg(`Analysis failed: ${error.message}`); + } }; - const handleResetButtonClick = () => { - // Send request to backend to analyze data - console.log('Sending request to backend to analyze data'); + // chart config + const plotData = { + labels: chartData.labels, + datasets: [ + { + label: "Measured HFD", + data: chartData.data, + borderColor: "#d17d00", + backgroundColor: "#00c3d1", + tension: 0.3, + pointRadius: 4, + }, + ], + }; - fetch(`${backendUrl}/api/reset`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' + const plotOptions = { + responsive: true, + maintainAspectRatio: false, + plugins: { + legend: { display: false } + }, + scales: { + x: { + title: { display: true, text: 'Relative Focuser Step (Moving In)', color: '#bdbdbc' }, + ticks: { color: '#bdbdbc' }, + grid: { color: '#444' } }, - body: JSON.stringify({ - sid - }) - }) - .then(response => { - if (!response.ok) { - throw new Error('Network response was not ok'); - } - return response.json(); - }) - .then(data => { - console.log('Response from backend:', data); - setImg(null); - refreshSid(); - }) - .catch(error => { - console.error('Error sending request to backend:', error); - refreshSid(); - }); + y: { + title: { display: true, text: 'Half-Flux Diameter (Pixels)', color: '#bdbdbc' }, + ticks: { color: '#bdbdbc' }, + grid: { color: '#444' } + } + } }; return ( -
- Focus -
- - - - - +
+ Automated V-Curve Autofocus + +
+ + + + + + + {isRunning ? ( +
+ + {progressMsg} +
+ ) : ( + + )}
- - {img && ( - An example image + + {resultMsg && ( +
+ {resultMsg} +
+ )} + + {/* live chart */} + {chartData.labels.length > 0 && ( +
+

V-Curve Data

+ + {/* The new wrapper div that forces the chart to behave */} +
+ +
+ +
)}
); } -export default Focus; +export default Focus; \ No newline at end of file