0

Frontend = React, backend = FastApi. How can I simply send an image from the frontend, and have the backend return the result back to the app?

This is my front end look like

// pages/about.js

import React, {useState, useRef} from "react";
import "../App.css";


const StitchImage = () => {
    const [image1, setImage1] = useState(null);
    const [image2, setImage2] = useState(null);
    const inputRef1 = useRef();
    const inputRef2 = useRef();

    const [stitchedImage,setStitchedImage] = useState(null);
    const handleImage1Change = (e) => {
        const file = e.target.files[0];
        if (file) {
            const reader = new FileReader();
            reader.onloadend = () => {
            console.log(file)
            setImage1(reader.result);
            };
            reader.readAsDataURL(file); 
        }
    };
  
    const handleImage2Change = (e) => {
        const file = e.target.files[0];
        if (file) {
            const reader = new FileReader();
            reader.onloadend = () => {
            setImage2(reader.result);
            };
            reader.readAsDataURL(file);
        }
    };

    const removeImage1 = () => {
        setImage1(null);
        if (inputRef1.current) {
            inputRef1.current.value = ''; // Resetting the input field value
        }
    };

    const removeImage2 = () => {
        setImage2(null);
        if (inputRef2.current) {
            inputRef2.current.value = ''; // Resetting the input field value
        }
    };

    

    const stitchingImages = async () => {
        
        if (!image1 || !image2) {
            // Handle case where both images are not selected
            window.alert('Please select both images before stitching.');
            return;
        }
        const formData = new FormData();

        formData.append('image1', image1);
        formData.append('image2', image2)
        
        try {
            const response = await fetch('/process-images', {
                method: 'POST',
                body: formData
            });
            if(!response.ok) {
                throw new Error('Image stitching failed'); 
            }

            const data = await response.json();

            setStitchedImage(data.stitchedImage);
        } catch (error) {
            let message = error.message;
            if(error.response) {
            // The request was made and the server responded with a status code
            // that falls out of the range of 2xx
                message = error.response.data.message; 
            } else if (error.request) {
                // The request was made but no response was received
                message = 'No response received';
            } else {
                // Something else happened in making the request  
                message = error.message;  
            }
            // Handle error
            window.alert(message);
        }
    };

    

    return (
        <div className="image-uploader">
                <div className="image-container">
                    <h2>Image 1</h2>
                    <input type="file" accept="image/jpg, image/jpeg, image/png" onChange={handleImage1Change} ref={inputRef1}/>
                    {image1 && (
                        <div>
                            <img src={image1} alt="" className="preview-img" />
                            <br/>
                            <button onClick={removeImage1}>Remove Image 1</button>
                        </div>
                    )}
                </div>
                <div className="image-container">
                    <h2>Image 2</h2>
                    <input type="file" accept="image/jpg, image/jpeg, image/png" onChange={handleImage2Change} ref={inputRef2}/>
                    {image2 && (
                        <div>
                            <img src={image2} alt="" className="preview-img" />
                            <br/>
                            <button onClick={removeImage2}>Remove Image 2</button>
                        </div>
                    )}
                </div>
            
            <button onClick={stitchingImages} className="stitch-button">Stitch Images</button>
            {stitchedImage && (
            <img src={stitchedImage} alt="Stitched" />
            )}
        </div>
        
    );
  };

export default StitchImage;

And back end:

import shutil
from fastapi import FastAPI, File, HTTPException, UploadFile, Form
import os
import subprocess
from pydantic import ValidationError


app=FastAPI()

def validate_file_type(filename: str):
    allowed_extensions = ('.jpg', '.jpeg', '.png')
    ext = os.path.splitext(filename)[1]
    if ext.lower() not in allowed_extensions:
        raise HTTPException(status_code=422, detail="Only JPEG and PNG files allowed")

@app.post("/process-images")
async def process_images(img1: UploadFile=File(...), img2:UploadFile=File()):
    try:
        validate_file_type(img1.filename)
        validate_file_type(img2.filename)

        # Process image 1
        with open(f"{img1.filename}", "wb") as buffer:
            shutil.copyfileobj(img1.file, buffer)

        # Process image 2
        with open(f"{img2.filename}", "wb") as buffer:
            shutil.copyfileobj(img2.file, buffer)
        
        
        
        # Perform the desired operation on the images here
        # Replace 'result' with the actual result of the operation

        return {"message": "Images processed successfully"}
    except ValidationError as e:
        raise HTTPException(status_code=422, detail=str(e))
    except Exception as e:
        raise HTTPException(status_code=500, detail="Internal server error")

The server still return 422 Unprocessable Entity when i sending the images

Try to change the formData but it seems doesn't work

2
  • You've named your fields image1 and image2 in the javascript, while you've named them img1 and img2 in your endpoint. Look at the body of the 422 error to get the actual error and description from FastAPI Commented Dec 7, 2023 at 12:07
  • Please follow whenever code repeats create a separate component. I have added the solution, have a look. Commented Dec 7, 2023 at 13:33

1 Answer 1

0

Issue

The image selected by the user is stored in image1 and image2 as a Data URL. It is due to the FileReader object. This data URL is then set as the value of image1 and image2 using the setImage1 and setImage2. The data URL is a string that represents the file’s data as a base64 encoded string.

You are sending data Urls from the front end and fetching file in the backend.

Frontend

formData.append('image1', image1);
formData.append('image2', image2)

Backend

@app.post("/process-images")
async def process_images(img1: UploadFile=File(...), img2:UploadFile=File())

Solution

I have used URL.createObjectURL() to store the image as a data URL for preview.

The every() method returns true if the function returns true for all elements.

Form data image 1 and image2 keys should be named img1 and img2.

StitchImage.js

import React, { useState } from 'react';
import { ImageUploader } from './ImageUploader';

export default StitchImage = () => {
  const [stitchedImage, setStitchedImage] = useState(null);
  const [files, setFiles] = useState([null, null]);

  const stitchingImages = async () => {
    if (!files.every(Boolean)) {
      window.alert('Please select both images before stitching.');
      return;
    }

    const formData = new FormData();
    formData.append('img1', files[0]);
    formData.append('img2', files[1]);

    try {
      const response = await fetch('/process-images', {
        method: 'POST',
        body: formData,
      });

      if (!response.ok) {
        throw new Error('Image stitching failed');
      }

      const data = await response.json();
      setStitchedImage(data.stitchedImage);
    } catch (error) {
      window.alert(error.message);
    }
  };

  return (
    <div className="image-uploader">
      {[0, 1].map((index) => (
        <ImageUploader
          key={index}
          index={index}
          onFileChange={(file) => {
            setFiles((prevFiles) => {
              const newFiles = [...prevFiles];
              newFiles[index] = file;
              return newFiles;
            });
          }}
        />
      ))}
      <button onClick={stitchingImages} className="stitch-button">
        Stitch Images
      </button>
      {stitchedImage && <img src={stitchedImage} alt="Stitched" />}
    </div>
  );
};

ImageUploader.js

import React, { useState, useRef } from 'react';

export const ImageUploader = ({ index, onFileChange }) => {
    const [image, setImage] = useState(null);
    const [file, setFile] = useState(null);
    const fileInput = useRef();

    const handleImageChange = (e) => {
        const file = e.target.files[0];
        if (file) {
            setImage(URL.createObjectURL(file));
            setFile(file);
            onFileChange(file);
        }
    };

    const removeImage = () => {
        setImage(null);
        setFile(null);
        onFileChange(null);
        fileInput.current.value = null;
    };

    return (
        <div className="image-container">
            <h2>{`Image ${index + 1}`}</h2>
            <input type="file" onChange={handleImageChange} ref={fileInput} />
            {image && (
                <div>
                    <img src={image} alt="" className="preview-img" />
                    <br />
                    <button onClick={removeImage}>{`Remove Image ${index + 1}`}</button>
                </div>
            )}
        </div>
    );
};

If you wanna play around with the code

Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.