Skip to content
Snippets Groups Projects
Commit 70d95282 authored by Antoine Gaudron-Desjardins's avatar Antoine Gaudron-Desjardins
Browse files

Merge branch 'main' into 'interface_admin'

# Conflicts:
#   backend/db/crud.py
parents 64d575d1 5ded1cae
No related branches found
No related tags found
1 merge request!53Interface admin
Pipeline #44460 passed
Showing with 161 additions and 106 deletions
...@@ -4,3 +4,4 @@ build/ ...@@ -4,3 +4,4 @@ build/
env/ env/
.env .env
__pycache__ __pycache__
cameras.py
\ No newline at end of file
...@@ -46,13 +46,9 @@ Navigate to [http://localhost:3000](http://localhost:3000) ...@@ -46,13 +46,9 @@ Navigate to [http://localhost:3000](http://localhost:3000)
# TODO # TODO
## Coté algo ## Coté algo
- Faire tournez le script de comptage que pendant les créneaux d'ouvertures du RU associé
- Mettre en place le script pour compter les caisses ouvertes
- Mettre en place le couplage des caméras (implique de pouvoir définir les masques proprement) dans le script de comptage de personnes
- Accéder à d'autre infos telle que l'API des cours sur demande à la DISI pour intégrer ça aux prédictions (ex: cours en promo complète juste implique plus d'attente) - Accéder à d'autre infos telle que l'API des cours sur demande à la DISI pour intégrer ça aux prédictions (ex: cours en promo complète juste implique plus d'attente)
## Coté dev ## Coté dev
- Améliorer l'interface utilisateur avec le timer
- Protéger l'interface OpenAPI et mettre en place une interface admin pour les news et potentiellement modération (avec authentification) - Protéger l'interface OpenAPI et mettre en place une interface admin pour les news et potentiellement modération (avec authentification)
- Permettre de définir les masques proprement et de manière à pouvoir généraliser à d'autre RU - Permettre de définir les masques proprement et de manière à pouvoir généraliser à d'autre RU
- Accorder la charte graphique si le service est intégré à d'autres appli (bordeau/blanc service de CS, charte graphique de VR) - Accorder la charte graphique si le service est intégré à d'autres appli (bordeau/blanc service de CS, charte graphique de VR)
......
restaurants = [
{
"restaurant": "local",
"a_factor": 30,
"b_factor": 120,
"cameras":
[
{
"IP": "",
"user": "",
"password": "",
"stream": "stream1",
"mask_points":
[
[
[70, 370],
[420, 720],
[1280, 720],
[1280, 250],
[930, 215],
[450, 550],
[130, 350]
]
],
"caisses":
[
{
"x1": 380,
"x2": 435,
"y1": 740,
"y2": 780
},
{
"x1": 300,
"x2": 350,
"y1": 830,
"y2": 880
}
]
}
]
}
]
...@@ -505,7 +505,6 @@ def update_collaborative_record(user: schemas.User, db: Session): ...@@ -505,7 +505,6 @@ def update_collaborative_record(user: schemas.User, db: Session):
if last_record.date >= datetime.combine(date, time_slot.open_time) and not last_record.waiting_time: if last_record.date >= datetime.combine(date, time_slot.open_time) and not last_record.waiting_time:
last_record.waiting_time = current_date - \ last_record.waiting_time = current_date - \
pytz.timezone("Europe/Paris").localize(last_record.date) pytz.timezone("Europe/Paris").localize(last_record.date)
print(last_record.waiting_time)
db.add(last_record) db.add(last_record)
db.commit() db.commit()
db.refresh(last_record) db.refresh(last_record)
...@@ -514,6 +513,12 @@ def update_collaborative_record(user: schemas.User, db: Session): ...@@ -514,6 +513,12 @@ def update_collaborative_record(user: schemas.User, db: Session):
raise HTTPException(status_code=406, detail="Client already registered") raise HTTPException(status_code=406, detail="Client already registered")
def cancel_collaborative_record(user_id: int, db: Session):
db.query(models.CollaborativeRecords).filter(models.CollaborativeRecords.user_id == user_id).delete()
db.commit()
return
def delete_collaborative_record(id: int, db: Session): def delete_collaborative_record(id: int, db: Session):
if id == 0: if id == 0:
db.query(models.CollaborativeRecords).delete() db.query(models.CollaborativeRecords).delete()
......
...@@ -12,7 +12,7 @@ class CollaborativeRecords(Base): ...@@ -12,7 +12,7 @@ class CollaborativeRecords(Base):
__tablename__ = "collection" __tablename__ = "collection"
id = Column(Integer, primary_key=True, index=True) id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, ForeignKey("users.id")) user_id = Column(Integer, ForeignKey("users.id", ondelete='SET NULL'))
place = Column(String(30)) place = Column(String(30))
date = Column(DateTime) date = Column(DateTime)
waiting_time = Column(Interval) waiting_time = Column(Interval)
......
...@@ -33,7 +33,7 @@ class RecordRead(BaseModel): ...@@ -33,7 +33,7 @@ class RecordRead(BaseModel):
class CollaborativeRecords(BaseModel): class CollaborativeRecords(BaseModel):
"""CollaborativeRecords schema""" """CollaborativeRecords schema"""
user_id: int = Field(..., title="Id of the user timed") user_id: Optional[int] = Field(default=None, title="Id of the user timed")
place: str = Field(..., title="Name of the RU corresponding the given record") place: str = Field(..., title="Name of the RU corresponding the given record")
date: datetime = Field(..., title="Date of the record") date: datetime = Field(..., title="Date of the record")
waiting_time: Optional[timedelta] = Field(default=None, title="Caculated waiting time for timed person") waiting_time: Optional[timedelta] = Field(default=None, title="Caculated waiting time for timed person")
......
...@@ -6,6 +6,7 @@ from fastapi.openapi.utils import get_openapi ...@@ -6,6 +6,7 @@ from fastapi.openapi.utils import get_openapi
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from dotenv import load_dotenv from dotenv import load_dotenv
from threading import Thread from threading import Thread
from asyncio import run
import os import os
from db import database, models, crud from db import database, models, crud
...@@ -35,7 +36,7 @@ app.add_middleware( ...@@ -35,7 +36,7 @@ app.add_middleware(
async def on_startup(): async def on_startup():
# Database creation # Database creation
models.Base.metadata.create_all(bind=database.engine) models.Base.metadata.create_all(bind=database.engine)
t = Thread(target=handle_cameras) t = Thread(target=run, args=(handle_cameras(),))
t.start() t.start()
......
...@@ -54,6 +54,16 @@ async def end_new_record(response: Response, connect_id: str = Cookie(...), db: ...@@ -54,6 +54,16 @@ async def end_new_record(response: Response, connect_id: str = Cookie(...), db:
return db_record return db_record
@router.post("/collection/cancel", response_model=None, tags=["data collection"])
async def cancel_new_record(response: Response, connect_id: str = Cookie(...), db: Session = Depends(get_db)):
user = crud.get_user(connect_id, db)
db_record = crud.cancel_collaborative_record(user.id, db)
if not user.username:
crud.delete_user(user.cookie, db)
response.delete_cookie(key="connect_id")
return db_record
@router.delete('/collection', response_model=None, tags=["data collection"]) @router.delete('/collection', response_model=None, tags=["data collection"])
async def delete_record(id: int, db: Session = Depends(get_db)): async def delete_record(id: int, db: Session = Depends(get_db)):
return crud.delete_collaborative_record(id, db) return crud.delete_collaborative_record(id, db)
...@@ -3,50 +3,47 @@ from datetime import datetime, timedelta ...@@ -3,50 +3,47 @@ from datetime import datetime, timedelta
import numpy as np import numpy as np
import keras import keras
from utils.preprocessing import fix_singular_shape, norm_by_imagenet from utils.preprocessing import fix_singular_shape, norm_by_imagenet
from dotenv import load_dotenv
import json import json
import os import time
from cameras import restaurants
from db import models from db import models
from db.database import SessionLocal from db.database import SessionLocal
from routers.websocket import manager from routers.websocket import manager
def handle_cameras(): async def handle_cameras():
model = keras.models.load_model('assets', compile=False) model = keras.models.load_model('assets', compile=False)
db = SessionLocal() db = SessionLocal()
load_dotenv() for restaurant in restaurants:
camera_number = int(os.getenv('CAM_NUMBER')) for camera in restaurant["cameras"]:
cameras = []
for i in range(camera_number):
camera = {}
camera["place"] = os.getenv(f'CAM_{i}_PLACE')
camera["IP"] = os.getenv(f'CAM_{i}_IP')
camera["user"] = os.getenv(f'CAM_{i}_USER')
camera["password"] = os.getenv(f'CAM_{i}_PASSWORD')
camera["stream"] = os.getenv(f'CAM_{i}_STREAM')
camera["a_factor"] = int(os.getenv(f'CAM_{i}_A_FACTOR'))
camera["b_factor"] = int(os.getenv(f'CAM_{i}_B_FACTOR'))
camera["framegap"] = int(os.getenv(f'CAM_{i}_FRAMEGAP'))
camera["count"] = 0
camera["cap"] = cv2.VideoCapture(
f'rtsp://{camera["user"]}:{camera["password"]}@{camera["IP"]}/{camera["stream"]}')
mask_length = int(os.getenv(f'CAM_{i}_POINTS_NB'))
mask_points = []
for j in range(mask_length):
point = os.getenv(f'CAM_{i}_POINT_{j}')
mask_points.append(list(map(int, point.split(','))))
mask = np.zeros((720, 1280, 3), dtype=np.float32) mask = np.zeros((720, 1280, 3), dtype=np.float32)
cv2.fillPoly(mask, [np.array(mask_points)], (255, 255, 255)) cv2.fillPoly(mask, np.array(camera["mask_points"]), (255, 255, 255))
camera["mask"] = mask camera["mask"] = mask
cameras.append(camera)
while True: while True:
for camera in cameras:
if camera['cap'].isOpened(): current_date = datetime.now()
ret, frame = camera['cap'].read() weekday, current_time = current_date.weekday(), current_date.time()
if ret and camera['count'] % camera['framegap'] == 0:
current_time = datetime.now() for restaurant in restaurants:
is_open = db.query(
models.OpeningHours).filter(
models.OpeningHours.place == restaurant["restaurant"],
models.OpeningHours.day == weekday,
models.OpeningHours.open_time <= current_time,
models.OpeningHours.close_time >= current_time).first() is not None
if is_open:
count_prediction = 0
open_checkouts = 0
cams_working = True
for camera in restaurant["cameras"]:
cap = cv2.VideoCapture(f'rtsp://{camera["user"]}:{camera["password"]}@{camera["IP"]}/{camera["stream"]}')
if cams_working and cap.isOpened():
_, frame = cap.read()
masked_img = cv2.bitwise_and( masked_img = cv2.bitwise_and(
frame.astype(np.float32), camera["mask"]) frame.astype(np.float32), camera["mask"])
treated_img = fix_singular_shape(masked_img, 16) treated_img = fix_singular_shape(masked_img, 16)
...@@ -56,22 +53,26 @@ def handle_cameras(): ...@@ -56,22 +53,26 @@ def handle_cameras():
np.array( np.array(
[treated_img]))), [treated_img]))),
axis=0) axis=0)
pred_map = np.squeeze(model.predict(input_image)) pred_map = np.squeeze(model.predict(input_image, verbose=0))
count_prediction = np.sum(pred_map) count_prediction += np.sum(pred_map)
for caisse in camera["caisses"]:
if np.sum(pred_map[caisse["x1"] // 2:caisse["x2"] // 2, caisse["y1"] // 2:caisse["y2"] // 2]) > 0.5:
open_checkouts += 1
else:
cams_working = False
cap.release()
if cams_working and open_checkouts:
waiting_time = timedelta( waiting_time = timedelta(
seconds=camera['b_factor'] + seconds=restaurant['b_factor'] +
int(count_prediction) * int(count_prediction *
camera['a_factor']) restaurant['a_factor'] / open_checkouts))
db_record = models.Records( db_record = models.Records(
place=camera['place'], place=restaurant['restaurant'],
date=current_time, date=current_date,
density=int(count_prediction), density=int(count_prediction),
waiting_time=waiting_time) waiting_time=waiting_time)
db.add(db_record) db.add(db_record)
db.commit() db.commit()
manager.broadcast(json.dumps({"type": "data"})) await manager.broadcast(json.dumps({"type": "data"}))
camera['count'] += 1 time.sleep(max(0, 60 - (datetime.now() - current_date).total_seconds()))
else:
camera["cap"] = cv2.VideoCapture(
f"rtsp://{camera['user']}:{camera['password']}@{camera['IP']}/{camera['stream']}")
print("tentative de reconnexion")
...@@ -12,43 +12,19 @@ ...@@ -12,43 +12,19 @@
overflow: hidden; overflow: hidden;
} }
#restaurant-start-button { .restaurant-button {
width: fit-content;
border: none;
border-radius: 5px;
padding: 0.1rem;
padding-left: 0.3rem;
padding-right: 0.3rem;
margin-top: 2rem;
}
.restaurant-button-disabled {
background-color: rgb(66, 75, 83);
color: #b9b9b9;
}
.restaurant-button-active {
background-color: rgb(33, 37, 41); background-color: rgb(33, 37, 41);
color: white; color: white;
} width: 7rem;
.restaurant-button-active:hover {
box-shadow: 0px 0px 5px white;
}
#restaurant-end-button {
width: fit-content;
background-color: #83000d;
color: white;
border: none; border: none;
border-radius: 5px; border-radius: 5px;
padding: 0.1rem; padding: 0.2rem;
padding-left: 0.3rem; padding-left: 0.3rem;
padding-right: 0.3rem; padding-right: 0.3rem;
margin-top: 2rem; margin-top: 2rem;
} }
#restaurant-end-button:hover { .restaurant-button:hover {
box-shadow: 0px 0px 5px rgb(33, 37, 41); box-shadow: 0px 0px 5px rgb(33, 37, 41);
} }
......
...@@ -12,7 +12,7 @@ import "../styles/restaurant.css"; ...@@ -12,7 +12,7 @@ import "../styles/restaurant.css";
export default function RestaurantPage({ selection, lastMessage }) { export default function RestaurantPage({ selection, lastMessage }) {
// const [started, setStarted] = useState(false); // const [started, setStarted] = useState(false);
// const [disabled, setDisabled] = useState(false); // const [timed, setTimed] = useState(false);
// const Start = () => { // const Start = () => {
// instance // instance
...@@ -21,7 +21,7 @@ export default function RestaurantPage({ selection, lastMessage }) { ...@@ -21,7 +21,7 @@ export default function RestaurantPage({ selection, lastMessage }) {
// setStarted(true); // setStarted(true);
// }) // })
// .catch((e) => { // .catch((e) => {
// setDisabled(true); // setTimed(true);
// alert( // alert(
// "Il semblerait que tu aies déjà renseigné un temps d'attente sur ce créneau. Merci de ta participation, n'hésite pas à te chronométrer de nouveau la prochaine fois !", // "Il semblerait que tu aies déjà renseigné un temps d'attente sur ce créneau. Merci de ta participation, n'hésite pas à te chronométrer de nouveau la prochaine fois !",
// ); // );
...@@ -34,10 +34,10 @@ export default function RestaurantPage({ selection, lastMessage }) { ...@@ -34,10 +34,10 @@ export default function RestaurantPage({ selection, lastMessage }) {
// .post("collection/stop") // .post("collection/stop")
// .then(() => { // .then(() => {
// setStarted(false); // setStarted(false);
// setDisabled(true); // setTimed(true);
// }) // })
// .catch((e) => { // .catch((e) => {
// setDisabled(true); // setTimed(true);
// alert( // alert(
// "Il semblerait que tu aies déjà renseigné un temps d'attente sur ce créneau. Merci de ta participation, n'hésite pas à te chronométrer de nouveau la prochaine fois !", // "Il semblerait que tu aies déjà renseigné un temps d'attente sur ce créneau. Merci de ta participation, n'hésite pas à te chronométrer de nouveau la prochaine fois !",
// ); // );
...@@ -45,6 +45,20 @@ export default function RestaurantPage({ selection, lastMessage }) { ...@@ -45,6 +45,20 @@ export default function RestaurantPage({ selection, lastMessage }) {
// }); // });
// }; // };
// const Cancel = () => {
// instance
// .post("collection/cancel")
// .then(() => {
// setStarted(false);
// setTimed(false);
// })
// .catch((e) => {
// setStarted(false);
// setTimed(false);
// console.log(e);
// });
// };
return ( return (
<> <>
{selection && ( {selection && (
...@@ -52,18 +66,26 @@ export default function RestaurantPage({ selection, lastMessage }) { ...@@ -52,18 +66,26 @@ export default function RestaurantPage({ selection, lastMessage }) {
<Comments place={selection.name} lastMessage={lastMessage} infos /> <Comments place={selection.name} lastMessage={lastMessage} infos />
<div className="restaurant-container" id="restaurant-main-page"> <div className="restaurant-container" id="restaurant-main-page">
<WaitingTime place={selection.name} lastMessage={lastMessage} /> <WaitingTime place={selection.name} lastMessage={lastMessage} />
<Graph place={selection.name} type="current" lastMessage={lastMessage} /> <Graph place={selection.name} lastMessage={lastMessage} />
{/* <button {/* <div style={{ fontSize: "1.2rem", paddingLeft: "10%", paddingRight: "10%" }}>
id={`restaurant-${started ? "end" : "start"}-button`} {timed
onClick={started ? End : Start} ? "Merci de ta participation, n'hésite pas à te chronométrer de nouveau la prochaine fois !"
className={disabled ? "restaurant-button-disabled" : "restaurant-button-active"} : "Aide nous à récupérer des données en te chronométrant dans la queue du self !"}
disabled={disabled} </div>
> {!timed && (
{started ? "Fini !!" : "Départ !!"} <>
</button> */} <button onClick={started ? End : Start} className="restaurant-button">
{started ? "Fini !" : "Démarrer !"}
</button>
{started && (
<button onClick={Cancel} className="restaurant-button">
Annuler
</button>
)}
</>
)} */}
</div> </div>
<Comments place={selection.name} lastMessage={lastMessage} /> <Comments place={selection.name} lastMessage={lastMessage} />
{/*<Graph place={selection.name} type="avg" />*/}
</div> </div>
)} )}
</> </>
......
photo_camera/1er_etage_milieu.jpg

107 KiB

photo_camera/1er_etage_milieu_2.jpg

140 KiB

photo_camera/1er_etage_milieu_retourne.jpg

91.7 KiB

photo_camera/2eme_etage_angle.jpg

92.4 KiB

photo_camera/2eme_etage_milieu.jpg

82.1 KiB

photo_camera/2eme_etage_milieu_2.jpg

82.1 KiB

0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment