version initiale

This commit is contained in:
Francois Pelletier 2023-05-07 13:27:18 -04:00
commit 7b2e43c5f7
13 changed files with 278 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/.idea/

18
Dockerfile Normal file
View file

@ -0,0 +1,18 @@
# Use an official Python runtime as a parent image
FROM python:3.9-slim-buster
# Set the working directory to /app
WORKDIR /app
# Copy the requirements file into the container and install dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Copy the rest of the application code into the container
COPY main.py .
# Expose the port that the application will listen on
EXPOSE 8088
# Start the FastAPI application
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8088"]

BIN
backgrounds/fonds_slide.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

11
build-local.sh Normal file
View file

@ -0,0 +1,11 @@
#!/bin/bash
# Build the Docker image with the "local/centrer-image-backend" tag
docker build -t local/centrer-image-backend .
# Check if the Docker image was built successfully
if [ $? -eq 0 ]; then
echo "Docker image built successfully."
else
echo "Error: Docker image build failed."
fi

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

28
docker-compose.yml Normal file
View file

@ -0,0 +1,28 @@
version: '3'
services:
centrer-image-backend:
image: local/centrer-image-backend
ports:
- "8088:8088"
networks:
- centrer-image
volumes:
- ./backgrounds:/app/backgrounds
- ./images:/app/images
- ./mosaics:/app/mosaics
centrer-image-frontend:
image: local/centrer-image-frontend
ports:
- "8501:8501"
networks:
- centrer-image
depends_on:
- centrer-image-backend
environment:
BACKEND: 'http://centrer-image-backend:8088'
networks:
centrer-image:
driver: bridge

3
images/images.md Normal file
View file

@ -0,0 +1,3 @@
# Images
Ce répertoire contient des images

1
install.sh Normal file
View file

@ -0,0 +1 @@
pip install -r requirements.txt

144
main.py Normal file
View file

@ -0,0 +1,144 @@
import datetime
from fastapi import FastAPI, File, UploadFile
from PIL import Image
import os
import io
from typing import List
from pydantic import BaseModel
import random
import glob
import hashlib
app = FastAPI()
BACKGROUND_DIR = 'backgrounds'
IMAGE_DIR = 'images'
IMAGE_EXT = '.png'
class MosaicRequest(BaseModel):
rows: int
columns: int
buffer_size: int
keywords: List[str]
background_file: str
class BackgroundRequest(BaseModel):
backgrounds: List[str]
def get_images():
image_files = [f for f in os.listdir(IMAGE_DIR) if f.endswith(IMAGE_EXT)]
image_files.sort()
return [Image.open(os.path.join(IMAGE_DIR, f)) for f in image_files]
def create_mosaic(images, rows, cols, buffer_size):
mosaic_width = cols * images[0].size[0] + (cols - 1) * buffer_size
mosaic_height = rows * images[0].size[1] + (rows - 1) * buffer_size
mosaic = Image.new('RGBA', (mosaic_width, mosaic_height), color=(0, 0, 0, 0))
for i, img in enumerate(images):
row = i // cols
col = i % cols
offset_x = (images[0].size[0] - img.size[0]) // 2
offset_y = (images[0].size[1] - img.size[1]) // 2
pos_x = col * (img.size[0] + buffer_size)
pos_y = row * (img.size[1] + buffer_size)
pos = (pos_x + offset_x, pos_y + offset_y)
mosaic.paste(img, pos)
return mosaic
def place_image_on_background(foreground: Image.Image, background: Image.Image) -> Image.Image:
# Compute the position to paste the foreground image
x = (background.width - foreground.width) // 2
y = (background.height - foreground.height) // 2
# Create a new image with the background and paste the foreground onto it
result = Image.new("RGBA", (background.width, background.height), (255, 255, 255, 0))
result.paste(background, (0, 0))
result.alpha_composite(foreground, (x, y))
return result
@app.get("/backgrounds/")
async def list_backgrounds():
background_files = [f for f in os.listdir(BACKGROUND_DIR) if f.endswith(IMAGE_EXT)]
background_files.sort()
background_dict = {"backgrounds": background_files}
return BackgroundRequest(**background_dict)
@app.post("/upload_file/")
async def upload_file(file: UploadFile = File(...), directory=IMAGE_DIR):
# check if the file is an image file
if not file.content_type.startswith('image'):
raise ValueError('File is not an image')
# read the file data and create a Pillow Image object
image = Image.open(file.file)
# save the PNG image to the "images/" directory
filename = os.path.join(directory, os.path.splitext(file.filename)[0] + '.png')
image.save(filename)
return {"filename": filename}
@app.post("/upload_background")
async def upload_background(file: UploadFile = File(...)):
filename_d = await upload_file(file, directory=BACKGROUND_DIR)
return filename_d
@app.post("/generate_mosaic")
async def generate_mosaic(request: MosaicRequest):
images = []
for keyword in request.keywords:
file_list = glob.glob(f'images/{keyword}*.png')
if not file_list:
return {"message": f"No images found for keyword: {keyword}"}
selected_file = random.choice(file_list)
with open(selected_file, "rb") as f:
images.append(Image.open(io.BytesIO(f.read())))
# select background image
background = Image.open(f"backgrounds/{request.background_file}")
# resize images to have the same height
height = min(background.height - (request.rows + 1) * request.buffer_size,
min(img.size[1] for img in images)) / request.rows
images = [img.resize((round(img.size[0]*height/img.size[1]),
round(height))) for img in images]
# create grid
grid = create_mosaic(images, request.rows, request.columns, request.buffer_size)
# select background image
background = Image.open(f"backgrounds/{request.background_file}")
# place grid on background
final_image = place_image_on_background(grid, background)
# convert image to bytes
img_bytes = final_image.tobytes()
# create MD5 hash object
hash_obj = hashlib.md5()
# update hash object with image bytes
hash_obj.update(img_bytes)
# get hex representation of hash
hash_hex = hash_obj.hexdigest()
# convert hex to string
hash_str = str(hash_hex)
# save final image
now = datetime.datetime.now()
filename_datetime = now.strftime("%Y-%m-%d_%H-%M-%S")
final_image.save(f"mosaics/{hash_str}-{filename_datetime}.png")
return {"message": "Mosaic image created successfully"}

3
mosaics/mosaic.md Normal file
View file

@ -0,0 +1,3 @@
# Images
Ce répertoire contient des mosaiques générées

6
requirements.txt Normal file
View file

@ -0,0 +1,6 @@
fastapi==0.95.1
uvicorn==0.22.0
Pillow==9.5.0
python-multipart==0.0.6
pydantic~=1.10.7
requests~=2.30.0

4
run.sh Normal file
View file

@ -0,0 +1,4 @@
#!/bin/bash
docker-compose down
docker-compose up

50
test.http Normal file
View file

@ -0,0 +1,50 @@
POST http://localhost:8088/generate_mosaic HTTP/1.1
Content-Type: application/json
{
"rows": 1,
"columns": 1,
"buffer_size": 150,
"background_file": "fonds_slide.png",
"keywords": ["unicorn"]
}
################################
POST http://localhost:8088/generate_mosaic HTTP/1.1
Content-Type: application/json
{
"rows": 1,
"columns": 2,
"buffer_size": 150,
"background_file": "fonds_slide.png",
"keywords": ["unicorn","thief"]
}
################################
POST http://localhost:8088/generate_mosaic HTTP/1.1
Content-Type: application/json
{
"rows": 2,
"columns": 2,
"buffer_size": 150,
"background_file": "fonds_slide.png",
"keywords": ["thief","unicorn","unicorn","thief"]
}
################################
POST http://localhost:8088/generate_mosaic HTTP/1.1
Content-Type: application/json
{
"rows": 10,
"columns": 10,
"buffer_size": 100,
"keywords": ["cat", "dog"],
"background_file": "fonds_slide.png"
}