diff --git a/frontend/package.json b/frontend/package.json index 70c2ef0b..a13d368c 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -20,6 +20,7 @@ "react-hook-form": "^7.33.1", "react-plotly.js": "^2.5.1", "react-scripts": "5.0.1", + "react-top-loading-bar": "^2.1.0", "typescript": "^4.4.2", "web-vitals": "^2.1.0", "yup": "^0.32.11" diff --git a/frontend/src/components/CalcSpectrum.tsx b/frontend/src/components/CalcSpectrum.tsx index 8b799f66..8ac7a58b 100644 --- a/frontend/src/components/CalcSpectrum.tsx +++ b/frontend/src/components/CalcSpectrum.tsx @@ -7,6 +7,8 @@ import { Controller, useForm } from "react-hook-form"; import Switch from "@mui/material/Switch"; import FormControlLabel from "@mui/material/FormControlLabel"; import CircularProgress from "@mui/material/CircularProgress"; +import Button from "@mui/material/Button"; +import LoadingBar from "react-top-loading-bar"; import { CalcSpectrumPlotData, CalcSpectrumResponseData } from "../constants"; import { FormValues } from "./types"; import { Database } from "./fields/Database"; @@ -22,7 +24,6 @@ import { WavenumberRangeSlider } from "./fields/WavenumberRangeSlider"; import { CalcSpectrumButton } from "./fields/CalSpectrumButtom"; import { CalcSpectrumPlot } from "./CalcSpectrumPlot"; import { ErrorAlert } from "./ErrorAlert"; - interface Response { data?: T; error?: string; @@ -41,6 +42,8 @@ export const CalcSpectrum: React.FC = () => { const [useGesia, setUseGesia] = useState(false); const [useSlit, setUseSlit] = useState(false); // checking that user wants to apply the slit function or not in available modes const [useSimulateSlitFunction, setUseSimulateSlitFunction] = useState(false); // checking the mode and enable or disable slit feature + + const [progress, setProgress] = useState(0); //control the progress bar const Schema = yup.object().shape({ useNonEqi: yup.boolean(), use_simulate_slit: yup.boolean(), @@ -120,12 +123,16 @@ export const CalcSpectrum: React.FC = () => { defaultValues: { species: [{ molecule: "CO", mole_fraction: 0.1 }] }, resolver: yupResolver(Schema), }); + const [downloadButton, setDownloadButton] = useState(true); const handleBadResponse = (message: string) => { setCalcSpectrumResponse(undefined); setError(message); }; - const onSubmit = async (data: FormValues): Promise => { + const onSubmit = async ( + data: FormValues, + endpoint: string + ): Promise => { if (useSlit == true) { if (data.mode === "radiance_noslit") { data.mode = "radiance"; @@ -134,41 +141,85 @@ export const CalcSpectrum: React.FC = () => { data.mode = "transmittance"; } } - + setDownloadButton(true); setLoading(true); - console.log(data); setError(undefined); - setPlotData({ max_wavenumber_range: data.max_wavenumber_range, min_wavenumber_range: data.min_wavenumber_range, mode: data.mode, species: data.species, }); + import(/* webpackIgnore: true */ "./config.js").then(async (module) => { - const rawResponse = await axios.post( - module.apiEndpoint + `calculate-spectrum`, - data - ); - if ( - rawResponse.data.data === undefined && - rawResponse.data.error === undefined - ) { - handleBadResponse("Bad response from backend!"); - } else { + if (endpoint === "calculate-spectrum") { + setProgress(30); + + const rawResponse = await axios({ + url: module.apiEndpoint + `calculate-spectrum`, + method: "POST", + data: data, + headers: { + "Content-Type": "application/json", + }, + }); + if ( + rawResponse.data.data === undefined && + rawResponse.data.error === undefined + ) { + handleBadResponse("Bad response from backend!"); + setDownloadButton(true); + } else { + const response = await rawResponse.data; + if (response.error) { + handleBadResponse(response.error); + setDownloadButton(true); + } else { + setCalcSpectrumResponse(response); + setDownloadButton(false); + } + } + + setProgress(100); + setLoading(false); + } + + if (endpoint === "download-spectrum") { + setProgress(30); + setLoading(false); + const rawResponse = await axios({ + url: module.apiEndpoint + `download-spectrum`, + method: "POST", + responseType: "blob", + data: data, + headers: { + "Content-Type": "application/json", + }, + }); + const url = window.URL.createObjectURL(new Blob([rawResponse.data])); + const link = document.createElement("a"); + link.href = url; + link.setAttribute( + "download", + `${data.mode}_${data.min_wavenumber_range}_${data.max_wavenumber_range}.spec` + ); + document.body.appendChild(link); + link.click(); + setDownloadButton(false); const response = await rawResponse.data; if (response.error) { handleBadResponse(response.error); } else { - setCalcSpectrumResponse(response); + setDownloadButton(false); } + setDownloadButton(false); + setProgress(100); } - setLoading(false); //setLoading(false) is called after the response is received }); }; + const databaseWatch = watch("database"); const modeWatch = watch("mode"); - React.useEffect(() => { if (databaseWatch === "geisa") { setUseGesia(true); @@ -187,6 +238,21 @@ export const CalcSpectrum: React.FC = () => { } }, [databaseWatch, modeWatch]); + //downloadButton + const DownloadSpectrum: React.FC = () => ( + + ); + //equilibrium-switch const UseNonEquilibriumCalculations = () => ( { )} /> ); + //slit-switch const UseSimulateSlit = () => ( { /> ); return ( -
- {error ? : null} - - - - - - - - - - - - - - - - + <> + setProgress(0)} + /> + onSubmit(data, `calculate-spectrum`))} + > + {error ? : null} + + + + + + + + + + + + - {isNonEquilibrium ? ( - <> - - - - - - - - ) : null} + + + - - - + {isNonEquilibrium ? ( + <> + + + + + + + + ) : null} - - - + + + - - - + + + - {useSimulateSlitFunction ? ( - + - ) : null} - {useSimulateSlitFunction ? ( - useSlit ? ( + {useSimulateSlitFunction ? ( + + + + ) : null} + + {useSimulateSlitFunction ? ( + useSlit ? ( + + + + ) : null + ) : null} + {useGesia ? null : ( - + - ) : null - ) : null} - {useGesia ? null : ( + )} + - + + + + - )} - - - - - - {loading ? ( -
- -
- ) : ( - calcSpectrumResponse?.data && - plotData?.species && ( - - ) - )} + + {loading ? ( +
+ +
+ ) : ( + calcSpectrumResponse?.data && + plotData?.species && ( + + ) + )} +
-
- + + ); }; diff --git a/frontend/yarn.lock b/frontend/yarn.lock index d178445c..5c73a881 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -9055,6 +9055,11 @@ react-scripts@5.0.1: optionalDependencies: fsevents "^2.3.2" +react-top-loading-bar@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/react-top-loading-bar/-/react-top-loading-bar-2.1.0.tgz#61198174e734606056f64262da312c02414b6f36" + integrity sha512-07IPCC4fThfkH5PHm7d49s9UAq2rpy4RAyD+5gtwtBbBrwxkvcff7ZlbCgzrBXq8AvcGafDkpjcGdntJ4F0O9A== + react-transition-group@^4.4.2: version "4.4.2" resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.2.tgz#8b59a56f09ced7b55cbd53c36768b922890d5470" diff --git a/server/main.py b/server/main.py index e438aa2c..f809145b 100644 --- a/server/main.py +++ b/server/main.py @@ -1,13 +1,18 @@ +import os +import radis +import numpy as np from typing import List, Optional -from fastapi import FastAPI +from fastapi import BackgroundTasks, FastAPI from pydantic import BaseModel -import radis from fastapi.middleware.cors import CORSMiddleware from pydantic.typing import Literal +from fastapi.responses import FileResponse + + # for high resolution radis.config["GRIDPOINTS_PER_LINEWIDTH_WARN_THRESHOLD"] = 7 -app = FastAPI() +app = FastAPI() app.add_middleware( CORSMiddleware, allow_origins=["*"], @@ -15,8 +20,7 @@ allow_methods=["*"], allow_headers=["*"], ) - - +# structure of the request class Species(BaseModel): molecule: str mole_fraction: float @@ -32,36 +36,76 @@ class Payload(BaseModel): trot: Optional[float] = None path_length: float simulate_slit: Optional[int] = None - mode: Literal["absorbance", "transmittance_noslit", "radiance_noslit", "transmittance", "radiance"] + use_simulate_slit: bool + mode: Literal[ + "absorbance", + "transmittance_noslit", + "radiance_noslit", + "transmittance", + "radiance", + ] database: Literal["hitran", "geisa"] - use_simulate_slit: bool = False +# calculating the spectrum return back the spectrum +def calculate_spectrum(payload): + print(">> Payload : ") + print(payload) + spectrum = radis.calc_spectrum( + payload.min_wavenumber_range, + payload.max_wavenumber_range, + molecule=[species.molecule for species in payload.species], + mole_fraction={ + species.molecule: species.mole_fraction for species in payload.species + }, + # TODO: Hard-coding "1,2,3" as the isotopologue for the time-being + isotope={species.molecule: "1,2,3" for species in payload.species}, + pressure=payload.pressure, + Tgas=payload.tgas, + Tvib=payload.tvib, + Trot=payload.trot, + path_length=payload.path_length, + export_lines=False, + wstep="auto", + databank=payload.database, + use_cached=True, + ) + return spectrum + + +# create the folder in server for better organization +DOWNLOADED_SPECFILES_DIRECTORY = "DOWNLOADED_SPECFILES" + + +def create_download_directory(): + + if os.path.exists(DOWNLOADED_SPECFILES_DIRECTORY): + print(" >> Folder already exists ") + else: + print(">> creating DOWNLOADED_SPECFILES folder") + os.mkdir(DOWNLOADED_SPECFILES_DIRECTORY) + + +# delete the file after giving the file response back to the user +def delete_spec(file_path: str): + + if os.path.exists(file_path): + print(" >> Removing file......") + os.remove(file_path) + print(" >> File removed") + else: + print(" >> File is not found ") + + +# "/calculate-spectrum " --> to calculate the spectrum and return back the x and y coordinates @app.post("/calculate-spectrum") -async def calculate_spectrum(payload: Payload): +async def calc_spectrum(payload: Payload): print(payload) try: - spectrum = radis.calc_spectrum( - payload.min_wavenumber_range, - payload.max_wavenumber_range, - molecule=[species.molecule for species in payload.species], - mole_fraction={ - species.molecule: species.mole_fraction for species in payload.species - }, - # TODO: Hard-coding "1,2,3" as the isotopologue for the time-being - isotope={species.molecule: "1,2,3" for species in payload.species}, - pressure=payload.pressure, - Tgas=payload.tgas, - Tvib=payload.tvib, - Trot=payload.trot, - path_length=payload.path_length, - export_lines=False, - wstep="auto", - databank=payload.database, - use_cached=True, - ) + spectrum = calculate_spectrum(payload) if payload.use_simulate_slit is True: + print("Applying simulate slit") spectrum.apply_slit(payload.simulate_slit, "nm") except radis.misc.warning.EmptyDatabaseError: @@ -73,8 +117,10 @@ async def calculate_spectrum(payload: Payload): wunit = spectrum.get_waveunit() iunit = "default" - x, y = spectrum.get(payload.mode, wunit=wunit, Iunit=iunit) - + xNan, yNan = spectrum.get(payload.mode, wunit=wunit, Iunit=iunit) + # to remove the nan values from x and y + x = xNan[~np.isnan(xNan)] + y = yNan[~np.isnan(yNan)] # Reduce payload size threshold = 5e7 if len(spectrum) * 8 * 2 > threshold: @@ -94,3 +140,31 @@ async def calculate_spectrum(payload: Payload): "units": spectrum.units[payload.mode], }, } + + +# "/download-spectrum"--> to return back the .spec file and delete that file after giving the fileresponse back +@app.post("/download-spectrum") +async def download_spec(payload: Payload, background_tasks: BackgroundTasks): + + try: + create_download_directory() + spectrum = calculate_spectrum(payload) + file_name_spec = spectrum.get_name() + file_name = f"{file_name_spec}.spec" + file_path = f"{DOWNLOADED_SPECFILES_DIRECTORY/file_name}" + if payload.use_simulate_slit is True: + print(" >> Applying simulate slit") + spectrum.apply_slit(payload.simulate_slit, "nm") + # returning the error response + except radis.misc.warning.EmptyDatabaseError: + return {"error": "No line in the specified wavenumber range"} + except Exception as exc: + print("Error", exc) + return {"error": str(exc)} + else: + spectrum.store(file_path, compress=True, if_exists_then="replace") + # running as a background task to delete the .spec file after giving the file response back + background_tasks.add_task(delete_spec, file_path) + return FileResponse( + file_path, media_type="application/octet-stream", filename=file_name + )