-1

I am trying to build a web app that will allow users to upload two files (a tiff image and an Excel file called the mapping file). So far, I have the following app.py and dynamic.html files:

from fastapi import FastAPI, UploadFile, File, Request
from fastapi.templating import Jinja2Templates
import base64
import io
import pandas as pd
from PIL import Image, ImageOps

app = FastAPI()

templates = Jinja2Templates(directory="templates")

@app.get("/")
def dynamic_file(request: Request):
    return templates.TemplateResponse("dynamic.html", {"request": request})

@app.post("/dynamic")
def dynamic(request: Request, image_file: UploadFile = File(...), mapping_file: UploadFile = File(...)):
    # Read the image file contents
    image_contents = image_file.file.read()
    image_file.file.close()

    # if image type is tiff
    if image_file.content_type == "image/tiff":
        # Convert TIFF image to PNG
        image = Image.open(io.BytesIO(image_contents))
        image_bytes = io.BytesIO()
        image.save(image_bytes, format="PNG")
        encoded_image = base64.b64encode(image_bytes.getvalue()).decode("utf-8")
    else:
        # Encode other image formats directly
        image = Image.open(io.BytesIO(image_contents))
        # Resize the image to half the size of the screen
        width, height = image.size
        new_size = (width // 2, height // 2)
        resized_image = ImageOps.fit(image, new_size)
        # Encode the resized image as a base64 string
        image_bytes = io.BytesIO()
        resized_image.save(image_bytes, format="PNG")
        encoded_image = base64.b64encode(image_bytes.getvalue()).decode("utf-8")
    
    
    # Read the mapping file contents
    mapping_contents = mapping_file.file.read()
    mapping_file.file.close()
    # Read the mapping file as a Pandas DataFrame
    mapping_df = pd.read_excel(io.BytesIO(mapping_contents))

    return templates.TemplateResponse(
        "dynamic.html", {"request": request, "img": encoded_image, "mapping": mapping_df})

<html>
  <head>
    <title>Rendering Dynamic Images Using FastAPI</title>
  </head>
  <body>

    <form action="/dynamic" enctype="multipart/form-data" method="POST">
      <input name="file" type="file" />
      <input type="submit" />
    </form>

    {% if img %}
    <h1>Rendered Image</h1>
    <img src="data:image/jpeg;base64,{{ img }}" />
    {% else %}
    <h1>Image will be render here...</h1>
    {% endif %}

  </body>
</html>

My webapp used to work fine by displaying a button to upload the image file until I added the following second attempt of importing a file.

# Read the mapping file contents
mapping_contents = mapping_file.file.read()
mapping_file.file.close()
# Read the mapping file as a Pandas DataFrame
mapping_df = pd.read_excel(io.BytesIO(mapping_contents))

When I do that I get the following error message on the localhost control panel:

POST http://127.0.0.1:8000/dynamic 422 (Unprocessable Entity)
{"detail":[{"loc":["body","image_file"],"msg":"field required","type":"value_error.missing"},{"loc":["body","mapping_file"],"msg":"field required","type":"value_error.missing"}]}

How can I change my code to have this second import button functionality?

Chris
  • 18,724
  • 6
  • 46
  • 80
Jeanne Chaverot
  • 147
  • 3
  • 11
  • Does this answer your question? [How to add both file and JSON body in a FastAPI POST request?](https://stackoverflow.com/questions/65504438/how-to-add-both-file-and-json-body-in-a-fastapi-post-request) – Chris Apr 28 '23 at 10:14
  • Every option included in [this answer](https://stackoverflow.com/a/70640522/17865804) demonstrates how to upload multiple files from HTML/JavaScript frontend to FastAPI backend. You might also find [this](https://stackoverflow.com/a/70657621/17865804), [this](https://stackoverflow.com/a/74810115/17865804) and [this](https://stackoverflow.com/a/71170051/17865804) helpful as well. – Chris Apr 28 '23 at 10:17
  • Not a fix but note that the [](https://html.spec.whatwg.org/dev/embedded-content.html#the-input-element) tag does not use and does not need a closing slash and never has in any HTML specification. The same is true for `` and every other HTML tag. – Rob Apr 28 '23 at 14:40

1 Answers1

0

The error message you are seeing suggests that the server is expecting both image_file and mapping_file fields to be present in the POST request body, but one or both of them are missing.

To fix this error, you need to ensure that both files are being uploaded in the POST request body. In your HTML form, you can add an additional input element with type="file" to allow users to upload the mapping file:

<form action="/dynamic" enctype="multipart/form-data" method="POST">
  <label for="image-file">Select image file:</label>
  <input name="image_file" id="image-file" type="file" />
  <br>
  <label for="mapping-file">Select mapping file:</label>
  <input name="mapping_file" id="mapping-file" type="file" />
  <br>
  <input type="submit" value="Upload" />
</form>

Then, in your FastAPI dynamic function, you can access the mapping_file parameter in the same way you are accessing the image_file parameter:

@app.post("/dynamic")
def dynamic(request: Request, image_file: UploadFile = File(...), mapping_file: UploadFile = File(...)):
    # Read the image file contents
    image_contents = image_file.file.read()
    image_file.file.close()

    # if image type is tiff
    if image_file.content_type == "image/tiff":
        # Convert TIFF image to PNG
        image = Image.open(io.BytesIO(image_contents))
        image_bytes = io.BytesIO()
        image.save(image_bytes, format="PNG")
        encoded_image = base64.b64encode(image_bytes.getvalue()).decode("utf-8")
    else:
        # Encode other image formats directly
        image = Image.open(io.BytesIO(image_contents))
        # Resize the image to half the size of the screen
        width, height = image.size
        new_size = (width // 2, height // 2)
        resized_image = ImageOps.fit(image, new_size)
        # Encode the resized image as a base64 string
        image_bytes = io.BytesIO()
        resized_image.save(image_bytes, format="PNG")
        encoded_image = base64.b64encode(image_bytes.getvalue()).decode("utf-8")
    
    
    # Read the mapping file contents
    mapping_contents = mapping_file.file.read()
    mapping_file.file.close()
    # Read the mapping file as a Pandas DataFrame
    mapping_df = pd.read_excel(io.BytesIO(mapping_contents))

    return templates.TemplateResponse(
        "dynamic.html", {"request": request, "img": encoded_image, "mapping": mapping_df})

This should allow users to upload both an image file and a mapping file, and process them in the dynamic function.

Jannickla
  • 27
  • 5