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 = (
-
-
+
-
+ {/* 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
-
-
File name:
-
-
Focuser Position:
-
-
Send
+
+ Automated V-Curve Autofocus
+
+
- Reset
- {img && (
-
+
+ {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