Compare commits

..

4 Commits

Author SHA1 Message Date
dbanaszak1
9e2dae9bff fix: mobile, lang; add: wv, font 2024-01-21 23:49:36 +01:00
dbanaszak1
e3211f08ed fix: detector 2024-01-21 13:29:01 +01:00
3bc61eae1c add: logo, nav 2024-01-20 18:10:17 +01:00
152453858e front first commit 2024-01-15 13:42:18 +01:00
58 changed files with 4646 additions and 433 deletions

8
.idea/.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

6
.idea/misc.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="MarkdownSettingsMigration">
<option name="stateVersion" value="1" />
</component>
</project>

View File

@ -0,0 +1,18 @@
module.exports = {
root: true,
env: { browser: true, es2020: true },
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react-hooks/recommended',
],
ignorePatterns: ['dist', '.eslintrc.cjs'],
parser: '@typescript-eslint/parser',
plugins: ['react-refresh'],
rules: {
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
},
}

24
Frontend/CatApp/.gitignore vendored Normal file
View File

@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

View File

@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>CatApp</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

4219
Frontend/CatApp/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,31 @@
{
"name": "catapp",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview"
},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@types/react": "^18.2.43",
"@types/react-dom": "^18.2.17",
"@typescript-eslint/eslint-plugin": "^6.14.0",
"@typescript-eslint/parser": "^6.14.0",
"@vitejs/plugin-react": "^4.2.1",
"autoprefixer": "^10.4.16",
"eslint": "^8.55.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.5",
"postcss": "^8.4.33",
"tailwindcss": "^3.4.1",
"typescript": "^5.2.2",
"vite": "^5.0.8"
}
}

View File

@ -0,0 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

View File

@ -0,0 +1,13 @@
import Detector from "./components/Detector";
import Nav from "./components/Nav";
export default function App() {
return (
<>
<Nav/>
<Detector/>
</>
)
}

View File

@ -0,0 +1,121 @@
import React, { ChangeEvent, useState } from 'react';
const Detector = () => {
const [file, setFile] = useState<File | undefined>();
const [preview, setPreview] = useState<string>('https://mly5gz70zztl.i.optimole.com/cb:w3s1.35324/w:auto/h:auto/q:mauto/f:best/https://www.lifewithcatman.com/wp-content/uploads/2019/03/maine-coon-cat-photography-robert-sijka-64-57ad8f2c0277c__880.jpg');
const [results, setResults] = useState<any[]>([]);
const [loading, setLoading] = useState<boolean>(false);
const handleFileChange = (e: ChangeEvent<HTMLInputElement>) => {
if (e.target.files) {
const selectedFile = e.target.files[0];
setFile(selectedFile);
// Generate preview for the selected file
const reader = new FileReader();
reader.onloadend = () => {
setPreview(reader.result as string);
};
reader.readAsDataURL(selectedFile);
}
};
const handleSubmit = async () => {
try {
if (file) {
setLoading(true);
const formData = new FormData();
formData.append('image', file);
const response = await fetch('http://127.0.0.1:5000/api/v1/detect-cat', {
method: 'POST',
body: formData,
});
if (response.ok) {
const resultFromServer = await response.json();
// Update results list with the new result
setResults((prevResults) => [...prevResults, { file, ...resultFromServer }]);
} else {
console.error('Błąd serwera:', response.statusText);
}
} else {
console.error('Brak pliku do wysłania.');
alert('Choose a file before sending')
}
} catch (error) {
console.error('Błąd:', error);
} finally {
setLoading(false);
}
};
return (
<>
<div className="flex items-center justify-center flex-wrap">
<div className="w-full">
<div className="w-5/6 sm:w-[420px] my-6 m-auto border-[1px] border-black p-4 rounded-2xl flex flex-wrap justify-center items-center">
{preview && (
<div className="flex justify-center ">
<img className='rounded-full border-orange-500 border-2' src={preview} alt="Podgląd" style={{ maxWidth: '100%', maxHeight: '200px' }} />
</div>
)}
<input className='' type="file" onChange={handleFileChange} />
<button
className='px-4 p-1 lg:mx-2 border-[1px] text-orange-500 border-orange-500 rounded-lg shadow-lg hover:scale-125 hover:text-white hover:bg-orange-500 duration-500 font-bold mt-4 sm:mt-0'
onClick={handleSubmit}
disabled={loading}
>
IS IT?
</button>
</div>
</div>
{loading && <div className='w-full text-center text-blue-200 uppercase font-semibold'> Sending file...</div>}
{results.length > 0 && (
<div>
{results.map((result, index) => (
<div className='border-[1px] border-gray-500 m-4 rounded-lg p-4' key={index}>
<h3 className='text-center font-DM text-orange-500 font-semibold'><span>{index + 1}. </span>CAT RESULT</h3>
<table>
<thead className='font-DM'>
<tr>
<th>Image:</th>
<th>Results:</th>
</tr>
</thead>
<tbody>
{result.results && Object.keys(result.results).map((filename) => (
<tr key={filename}>
<td>
<img className='rounded-xl m-2 border-orange-500 border-[1px]' src={URL.createObjectURL(result.file)} alt="Podgląd" style={{ maxWidth: '100%', maxHeight: '100px' }} />
</td>
<td>
<p>
<span>Result:</span><span className='text-orange-500 font-DM'> {result.results[filename].isCat ? 'This is a cat' : 'Not a cat'}</span>
</p>
<ul>
{result.results[filename].predictions &&
Object.keys(result.results[filename].predictions).map((key) => (
<li key={key}>
{result.results[filename].predictions[key].score.toFixed(2) * 100}<span>% - </span>
{result.results[filename].predictions[key].label}
</li>
))}
</ul>
</td>
</tr>
))}
</tbody>
</table>
</div>
))}
</div>
)}
</div>
</>
);
};
export default Detector;

View File

@ -0,0 +1,11 @@
const Nav = () => {
return (
<div className='w-full h-32 px-8 md:px-20 shadow-2xl flex items-center justify-center'>
<img className="h-32 w-32 p-4" src="https://github.com/UniSzef/test/blob/main/logo.png?raw=true"></img>
<div className="md:text-4xl text-2xl font-DM font-bold">Be sure that your <span className="text-orange-400">cat</span> is real!</div>
</div>
)
}
export default Nav

View File

@ -0,0 +1,20 @@
import React from 'react'
const Waves = () => {
return (
<>
<svg className="absolute -z-10" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1440 320">
<path fill="#edb593" fill-opacity="1" d="M0,288L48,250.7C96,213,192,139,288,117.3C384,96,480,128,576,149.3C672,171,768,181,864,165.3C960,149,1056,107,1152,112C1248,117,1344,171,1392,197.3L1440,224L1440,0L1392,0C1344,0,1248,0,1152,0C1056,0,960,0,864,0C768,0,672,0,576,0C480,0,384,0,288,0C192,0,96,0,48,0L0,0Z"></path>
</svg>
<svg className="absolute -z-10 translate-y-[500px]" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1440 320">
<path fill="#edb593" fill-opacity="1" d="M0,192L40,202.7C80,213,160,235,240,229.3C320,224,400,192,480,160C560,128,640,96,720,101.3C800,107,880,149,960,186.7C1040,224,1120,256,1200,261.3C1280,267,1360,245,1400,234.7L1440,224L1440,320L1400,320C1360,320,1280,320,1200,320C1120,320,1040,320,960,320C880,320,800,320,720,320C640,320,560,320,480,320C400,320,320,320,240,320C160,320,80,320,40,320L0,320Z"></path>
</svg>
<div className="h-[400px] w-full -z-10 absolute bg-waves translate-y-[800px]"></div>
<svg className="absolute -z-10 translate-y-[1200px]" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1440 320">
<path fill="#edb593" fill-opacity="1" d="M0,288L26.7,261.3C53.3,235,107,181,160,181.3C213.3,181,267,235,320,240C373.3,245,427,203,480,170.7C533.3,139,587,117,640,117.3C693.3,117,747,139,800,154.7C853.3,171,907,181,960,202.7C1013.3,224,1067,256,1120,256C1173.3,256,1227,224,1280,224C1333.3,224,1387,256,1413,272L1440,288L1440,0L1413.3,0C1386.7,0,1333,0,1280,0C1226.7,0,1173,0,1120,0C1066.7,0,1013,0,960,0C906.7,0,853,0,800,0C746.7,0,693,0,640,0C586.7,0,533,0,480,0C426.7,0,373,0,320,0C266.7,0,213,0,160,0C106.7,0,53,0,27,0L0,0Z"></path>
</svg>
</>
)
}
export default Waves

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

View File

@ -0,0 +1,5 @@
@import url('https://fonts.googleapis.com/css2?family=DM+Serif+Display&display=swap');
@tailwind base;
@tailwind components;
@tailwind utilities;

View File

@ -0,0 +1,10 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.tsx'
import './index.css'
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>,
)

1
Frontend/CatApp/src/vite-env.d.ts vendored Normal file
View File

@ -0,0 +1 @@
/// <reference types="vite/client" />

View File

@ -0,0 +1,18 @@
/** @type {import('tailwindcss').Config} */
export default {
content: [
"./index.html",
"./src/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {
fontFamily: {
DM: ['DM Serif Display']
},
colors: {
waves: '#edb593',
},
},
},
plugins: [],
}

View File

@ -0,0 +1,25 @@
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}

View File

@ -0,0 +1,10 @@
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true
},
"include": ["vite.config.ts"]
}

View File

@ -0,0 +1,7 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
})

View File

@ -1,11 +0,0 @@
Cat detection
Quick guide:
1. install docker
2. build image with `./scripts/build_image.bat`
3. run container with `./scripts/start.bat`
Quick tests guide:
1. run unit tests `./scripts/run_tests.bat`
Have fun!

Binary file not shown.

BIN
cat.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 KiB

View File

Before

Width:  |  Height:  |  Size: 105 KiB

After

Width:  |  Height:  |  Size: 105 KiB

View File

@ -1,58 +1,48 @@
from io import BytesIO
import numpy as np
import torch
import torch.nn.functional as F
from PIL import Image
from keras.src.applications.resnet import preprocess_input, decode_predictions
from keras.applications.resnet import ResNet50
from torchvision.models.resnet import resnet50, ResNet50_Weights
from torchvision.transforms import transforms
model = resnet50(weights=ResNet50_Weights.DEFAULT)
model.eval()
# Define the image transformations
preprocess = transforms.Compose([
transforms.Resize(256),
transforms.CenterCrop(224),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
])
"""
Recognition file.
Model is ResNet50. Pretrained model to image recognition.
If model recognize cat then returns response with first ten CAT predictions.
If first prediction is not a cat then returns False.
If prediction is not a cat (is not within list_of_labels) then skips this prediction.
Format of response:
{
'label': {label}
'score': {score}
}
"""
model = ResNet50(weights='imagenet')
# PRIVATE Preprocess image method
def _preprocess_image(image):
def is_cat(image):
try:
img = Image.open(BytesIO(image.read()))
img = img.resize((224, 224))
img_array = np.array(img)
img_array = np.expand_dims(img_array, axis=0)
img_array = preprocess_input(img_array)
return img_array
# Preprocess the image
img_t = preprocess(img)
batch_t = torch.unsqueeze(img_t, 0)
# Make the prediction
out = model(batch_t)
# Apply softmax to get probabilities
probabilities = F.softmax(out, dim=1)
# Get the maximum predicted class and its probability
max_prob, max_class = torch.max(probabilities, dim=1)
max_prob = max_prob.item()
max_class = max_class.item()
# Check if the maximum predicted class is within the range 281-285
if 281 <= max_class <= 285:
return max_class, max_prob
else:
return max_class, None
except Exception as e:
print(f"Error preprocessing image: {e}")
print("Error while processing the image:", e)
return None
# Generate response
def _generate_response(decoded_predictions, list_of_labels):
results = {}
for i, (imagenet_id, label, score) in enumerate(decoded_predictions):
if i == 0 and label not in list_of_labels:
return None
if score < 0.01:
break
if label in list_of_labels:
results[len(results) + 1] = {"label": label, "score": round(float(score), 2)}
return results
# Cat detection
def detect_cat(image_file, list_of_labels):
img_array = _preprocess_image(image_file)
prediction = model.predict(img_array)
decoded_predictions = decode_predictions(prediction, top=10)[0]
return _generate_response(decoded_predictions, list_of_labels)

View File

@ -1,6 +0,0 @@
*.md
/venv
.git
__pycache__
.pytest_cache
/tests

View File

@ -1,11 +0,0 @@
FROM python:3.11
WORKDIR /app
COPY . /app
RUN pip install --no-cache-dir -r requirements.txt
EXPOSE 5000
CMD ["python", "main.py"]

View File

@ -1,10 +0,0 @@
version: '3.3'
services:
cat-detection:
image: cat-detection
build:
context: ../
dockerfile: ./docker/Dockerfile
ports:
- "5000:5000"

9
docs.md Normal file
View File

@ -0,0 +1,9 @@
# Api
Port -> 5000
endpoint -> /detect-cat
Key -> 'Image'
Value -> {UPLOADED_FILE}

View File

@ -1,53 +0,0 @@
# Api
Port -> 5000
endpoint -> api/v1/detect-cat
Key -> 'Image'
Value -> {UPLOADED_FILE}
Flask Rest API application to cat recognition.
If request is valid then send response with results of recognition.
If key named 'Image' in body does not occur then returns 400 (BAD REQUEST).
Otherwise, returns 200 with results of recognition.
Format of response:
```json
{
"lang": "{users_lang}",
"results": {
"{filename}": {
"isCat": "{is_cat}",
"results": {
"1": "{result}",
"2": "{result}",
"3": "{result}",
"4": "{result}",
"5": "{result}",
"6": "{result}",
"7": "{result}",
"8": "{result}",
"9": "{result}",
"10": "{result}"
}
}
},
"errors": [
"{error_message}",
"{error_message}"
]
}
```
Format of result:
```json
{
"label": "{label}",
"score": "{score}"
}
```
Example response:
```json
```

View File

@ -1,30 +0,0 @@
import os
from jproperties import Properties
"""
Translator method.
If everything fine then returns translated labels.
Else throws an Exception and returns untranslated labels.
"""
def translate(to_translate, lang):
try:
config = Properties()
script_directory = os.path.dirname(os.path.abspath(__file__))
resources_path = os.path.join(script_directory, "./resources")
# Load properties file for given lang
with open(os.path.join(resources_path, f"./{lang}.properties"), 'rb') as config_file:
config.load(config_file, encoding='UTF-8')
# Translate labels for given to_translate dictionary
for index, label_info in to_translate.items():
label = label_info.get("label")
to_translate[index]["label"] = config.get(label).data
return to_translate, []
except Exception as e:
error_message = f"Error translating labels: {e}"
print(error_message)
return to_translate, error_message

116
main.py
View File

@ -1,108 +1,38 @@
from flask import Flask, request, Response, json
from cat_detection import detect_cat
from language_label_mapper import translate
from validator import validate
from flask_cors import CORS
"""
Flask Rest API application to cat recognition.
If request is valid then send response with results of recognition.
If key named 'Image' in body does not occurred then returns 400 (BAD REQUEST).
Otherwise returns 200 with results of recognition.
Format of response:
{
"lang": {users_lang},
"results": {
{filename}: {
"isCat": {is_cat},
"results": {
"1": {result}
"2": {result}
"3": {result}
...
"10" {result}
}
},
...
},
errors[
{error_message},
{error_message},
...
]
}
To see result format -> cat_detection.py
"""
from flask import Flask, request, jsonify, session
from cat_detection import is_cat
# Define flask app
app = Flask(__name__)
app.secret_key = 'secret_key'
CORS(app)
# Available cats
list_of_labels = [
'lynx',
'lion',
'tiger',
'cheetah',
'leopard',
'jaguar',
'tabby',
'Egyptian_cat',
'cougar',
'Persian_cat',
'Siamese_cat',
'snow_leopard',
'tiger_cat'
]
# Available languages
languages = {'pl', 'en'}
@app.route('/api/v1/detect-cat', methods=['POST'])
@app.route('/detect-cat', methods=['POST'])
def upload_file():
# Validate request
error_messages = validate(request)
# 'Key' in body should be named as 'image'. Type should be 'File' and in 'Value' we should upload image from disc.
file = request.files['image']
if file.filename == '':
return jsonify({'error': "File name is empty. Please name a file."}), 400
max_class, max_prob = is_cat(file)
# If any errors occurred, return 400 (BAD REQUEST)
if len(error_messages) > 0:
errors = json.dumps(
{
'errors': error_messages
}
)
return Response(errors, status=400, mimetype='application/json')
# Save result in session
session['result'] = max_class, max_prob
# Get files from request
files = request.files.getlist('image')
# Get user's language (Value in header 'Accept-Language'). Default value is English
lang = request.accept_languages.best_match(languages, default='en')
# Define JSON structure for results
results = {
'lang': lang,
'results': {},
'errors': []
# Tworzenie komunikatu na podstawie wyniku analizy zdjęcia
translator = {
281: "tabby cat",
282: "tiger cat",
283: "persian cat",
284: "siamese cat",
285: "egyptian cat"
}
if max_prob is not None:
result = f"The image is recognized as '{translator[max_class]}' with a probability of {round(max_prob * 100, 2)}%"
else:
result = f"The image is not recognized as a class within the range 281-285 ({max_class})"
# Generate results
for file in files:
predictions = detect_cat(file, list_of_labels)
if predictions is not None:
predictions, error_messages = translate(predictions, lang)
results['results'][file.filename] = {
'isCat': False if not predictions else True,
**({'predictions': predictions} if predictions is not None else {})
}
if len(error_messages) > 1:
results['errors'].append(error_messages)
# Send response with 200 (Success)
return Response(json.dumps(results), status=200, mimetype='application/json')
return jsonify({'result': result}), 200
if __name__ == '__main__':
app.run(host='0.0.0.0')
app.run(debug=True)

View File

@ -1,9 +0,0 @@
flask==3.0.0
numpy==1.26.3
pillow==10.2.0
keras==2.15.0
jproperties==2.1.1
tensorflow==2.15.0
werkzeug==3.0.1
pytest==7.4.4
flask-cors==4.0.0

View File

@ -1,14 +0,0 @@
# EN
lynx=lynx
lion=lion
tiger=tiger
cheetah=cheetah
leopard=leopard
jaguar=jaguar
tabby=tabby
Egyptian_cat=Egyptian cat
cougar=cougar
Persian_cat=Persian cat
Siamese_cat=Siamese cat
snow_leopard=snow leopard
tiger_cat=tiger cat

View File

@ -1,14 +0,0 @@
# PL
lynx=ryś
lion=lew
tiger=tygrys
cheetah=gepard
leopard=lampart
jaguar=jaguar
tabby=kot pręgowany
Egyptian_cat=kot egipski
cougar=puma
Persian_cat=kot perski
Siamese_cat=kot syjamski
snow_leopard=lampart śnieżny
tiger_cat=kot tygrysi

View File

@ -1,24 +0,0 @@
@echo off
chcp 65001 >nul
docker -v >nul 2>&1
if %errorlevel% neq 0 (
echo Docker is not installed.
exit /b 1
)
docker info >nul 2>&1
if %errorlevel% neq 0 (
echo Docker engine is not running.
exit /b 1
)
cd %~dp0
echo Building docker image...
docker-compose -f ../docker/docker-compose.yml build
if %errorlevel% neq 0 (
echo Building docker image failed.
exit /b 1
)
echo The image was built successfully.

View File

@ -1,27 +0,0 @@
#!/bin/bash
export LC_ALL=C.UTF-8
export LANG=C.UTF-8
docker -v > /dev/null 2>&1
if [ $? -ne 0 ]; then
echo -e "\033[31mDocker is not installed.\033[0m"
exit 1
fi
docker info > /dev/null 2>&1
if [ $? -ne 0 ]; then
echo -e "\033[31mDocker engine is not running.\033[0m"
exit 1
fi
cd ../docker
echo -e "\033[32mBuilding docker image...\033[0m"
docker-compose build cat-detection
if [ $? -ne 0 ]; then
echo -e "\033[31mBuilding docker image failed.\033[0m"
exit 1
fi
echo -e "\033[32mThe image was built successfully.\033[0m"

View File

@ -1,12 +0,0 @@
@echo off
echo Running unit tests.
cd %~dp0
pytest ../tests
if %ERRORLEVEL% equ 0 (
echo Tests passed successfully.
) else (
Tests failed.
echo Tests failed.
)

View File

@ -1,9 +0,0 @@
@echo off
chcp 65001 >nul
cd %~dp0
docker compose -f ../docker/docker-compose.yml up cat-detection -d
if %errorlevel% neq 0 (
echo Starting docker container failed.
exit /b 1
)

View File

@ -1,11 +0,0 @@
#!/bin/bash
export LC_ALL=C.UTF-8
export LANG=C.UTF-8
docker-compose -f ../docker/docker-compose.yml up cat-detection -d
if [ $? -ne 0 ]; then
echo -e "\033[31mStarting docker container failed.\033[0m"
exit 1
fi

9
test.py Normal file
View File

@ -0,0 +1,9 @@
import requests
url = 'http://127.0.0.1:5000/detect-cat'
files = {'image': (open('cat1.jpg', 'rb'))}
response = requests.post(url, files=files)
print(response.text)

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 360 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 159 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 180 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 216 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 326 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 221 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 281 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 469 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 255 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 345 KiB

View File

@ -1,19 +0,0 @@
import os
from werkzeug.datastructures import FileStorage
from main import app
def test_upload_file():
with app.test_client() as test_client:
script_directory = os.path.dirname(os.path.abspath(__file__))
image_path = os.path.join(script_directory, "./img/tiger_cat/cat1.jpg")
image = FileStorage(
stream=open(image_path, "rb"),
filename="cat1.jpg",
content_type="image/jpeg",
)
response = test_client.post('/api/v1/detect-cat', data={'image': image}, content_type='multipart/form-data')
assert response.status_code == 200

View File

@ -1,31 +0,0 @@
"""
Validation method.
If everything fine then returns empty list.
Else returns list of error messages.
"""
# Allowed extensions
allowed_extensions = {'jpg', 'jpeg', 'png'}
def validate(request):
errors = []
try:
images = request.files.getlist('image')
# Case 1 - > request has no 'Image' Key in body
if images is None:
raise KeyError("'Image' key not found in request.")
# Case 2 - > if some of the images has no filename
if not images or all(img.filename == '' for img in images):
raise ValueError("Value of 'Image' key is empty.")
# Case 3 -> if some of the images has wrong extension
for img in images:
if not img.filename.lower().endswith(('.png', '.jpg', '.jpeg')):
raise ValueError(f"Given file '{img.filename}' has no allowed extension. "
f"Allowed extensions: {allowed_extensions}.")
except Exception as e:
errors.append(e.args[0])
return errors

View File

Before

Width:  |  Height:  |  Size: 7.1 KiB

After

Width:  |  Height:  |  Size: 7.1 KiB