132 lines
6.5 KiB
Python
132 lines
6.5 KiB
Python
from wtforms import SubmitField, StringField, HiddenField, validators, Form
|
|
from flask_wtf import FlaskForm
|
|
from flask import request, render_template, redirect
|
|
from main import db, app, ma
|
|
from sqlalchemy import Sequence
|
|
from sqlalchemy.exc import SQLAlchemyError
|
|
from status import st, Status
|
|
from path import Path, PathType
|
|
from files import Entry, Dir, File, PathDirLink
|
|
from person import Refimg, Person, PersonRefimgLink
|
|
from flask_login import login_required, current_user
|
|
from PIL import Image
|
|
import io
|
|
import base64
|
|
|
|
from job import Job, JobExtra, Joblog, NewJob
|
|
from face import Face, FaceFileLink, FaceRefimgLink, FaceNoMatchOverride, FaceForceMatchOverride
|
|
|
|
# pylint: disable=no-member
|
|
|
|
################################################################################
|
|
# /aistats -> placholder for some sort of stats
|
|
################################################################################
|
|
@app.route("/aistats", methods=["GET"])
|
|
@login_required
|
|
def aistats():
|
|
stats = db.session.execute( "select p.tag, count(f.id) from person p, face f, face_file_link ffl, face_refimg_link frl, person_refimg_link prl where p.id = prl.person_id and prl.refimg_id = frl.refimg_id and frl.face_id = ffl.face_id and ffl.face_id = f.id group by p.tag order by 2 desc" )
|
|
cnt_res = db.session.execute( "select count(1) from ( select p.tag from person p, face f, face_file_link ffl, face_refimg_link frl, person_refimg_link prl where p.id = prl.person_id and prl.refimg_id = frl.refimg_id and frl.face_id = ffl.face_id and ffl.face_id = f.id group by p.tag ) as foo" )
|
|
num_stats=cnt_res.first()[0]
|
|
|
|
fstats={}
|
|
fstats['files_with_a_face'] = db.session.execute( "select count(distinct file_eid) as count from face_file_link" ).first()[0]
|
|
fstats['files_with_a_match'] = db.session.execute( "select count(distinct ffl.file_eid) as count from face_file_link ffl, face_refimg_link frl where frl.face_id = ffl.face_id" ).first()[0]
|
|
fstats['files_with_missing_matches'] = db.session.execute( "select count(distinct ffl.file_eid) from face f left join face_refimg_link frl on f.id = frl.face_id join face_file_link ffl on f.id = ffl.face_id where frl.refimg_id is null" ).first()[0]
|
|
|
|
# files_with_no_matches?
|
|
|
|
fstats['all_faces'] = db.session.execute( "select count(distinct face_id) as count from face_file_link" ).first()[0]
|
|
fstats['all_matched_faces'] = db.session.execute( "select count(distinct face_id) as count from face_refimg_link" ).first()[0]
|
|
fstats['all_unmatched_faces'] = db.session.execute( "select count(f.id) from face f left join face_refimg_link frl on f.id = frl.face_id where frl.refimg_id is null" ).first()[0]
|
|
|
|
return render_template("aistats.html", page_title='AI Statistics', stats=stats, num_stats=num_stats, fstats=fstats )
|
|
|
|
|
|
################################################################################
|
|
# /run_ai_on -> creates a job, with extras containing entry ids (eid-0, eid-1,
|
|
# etc.) and person=all|dad, etc. Room to consider threshold, algo, etc.
|
|
################################################################################
|
|
@app.route("/run_ai_on", methods=["POST"])
|
|
@login_required
|
|
def run_ai_on():
|
|
jex=[]
|
|
for el in request.form:
|
|
jex.append( JobExtra( name=f"{el}", value=request.form[el] ) )
|
|
job=NewJob( "run_ai_on", 0, None, jex )
|
|
st.SetMessage( f"Created <a href=/job/{job.id}>Job #{job.id}</a> to Look for face(s) in selected file(s)")
|
|
return redirect("/jobs")
|
|
|
|
@app.route("/run_ai_on_import", methods=["GET"])
|
|
@login_required
|
|
def run_ai_on_import():
|
|
jex=[]
|
|
|
|
ptype=PathType.query.filter(PathType.name=='Import').first()
|
|
jex.append( JobExtra( name=f"person", value="all" ) )
|
|
jex.append( JobExtra( name=f"path_type", value=ptype.id ) )
|
|
job=NewJob( "run_ai_on_path", 0, None, jex )
|
|
st.SetMessage( f"Created <a href=/job/{job.id}>Job #{job.id}</a> to Look for face(s) in import path(s)")
|
|
return redirect("/jobs")
|
|
|
|
@app.route("/run_ai_on_storage", methods=["GET"])
|
|
@login_required
|
|
def run_ai_on_storage():
|
|
jex=[]
|
|
ptype=PathType.query.filter(PathType.name=='Storage').first()
|
|
jex.append( JobExtra( name=f"person", value="all" ) )
|
|
jex.append( JobExtra( name=f"path_type", value=ptype.id ) )
|
|
job=NewJob( "run_ai_on_path", 0, None, jex )
|
|
st.SetMessage( f"Created <a href=/job/{job.id}>Job #{job.id}</a> to Look for face(s) in storage path(s)")
|
|
return redirect("/jobs")
|
|
|
|
@app.route("/unmatched_faces", methods=["GET"])
|
|
@login_required
|
|
def unmatched_faces():
|
|
# get overrides and exclude them as they have been processed already
|
|
fnmo_ids = [id[0] for id in FaceNoMatchOverride.query.with_entities(FaceNoMatchOverride.face_id).all()]
|
|
fmo_ids = [id[0] for id in FaceForceMatchOverride.query.with_entities(FaceForceMatchOverride.face_id).all()]
|
|
faces=Face.query.join(FaceFileLink).join(FaceRefimgLink, isouter=True).filter(FaceRefimgLink.refimg_id==None).filter(Face.id.not_in(fnmo_ids+fmo_ids)).order_by(Face.h.desc()).limit(10).all()
|
|
imgs={}
|
|
for face in faces:
|
|
f = Entry.query.join(File).join(FaceFileLink).filter(FaceFileLink.face_id==face.id).first()
|
|
face.file_eid=f.id
|
|
face.url=f.FullPathOnFS()
|
|
face.filename=f.name
|
|
x=face.face_left*0.95
|
|
y=face.face_top*0.95
|
|
x2=face.face_right*1.05
|
|
y2=face.face_bottom*1.05
|
|
|
|
im = Image.open(f.FullPathOnFS())
|
|
region = im.crop((x, y, x2, y2))
|
|
img_bytearray = io.BytesIO()
|
|
region.save(img_bytearray, format='JPEG')
|
|
img_bytearray = img_bytearray.getvalue()
|
|
face.img = base64.b64encode(img_bytearray)
|
|
face.img = str(face.img)[2:-1]
|
|
|
|
return render_template("faces.html", faces=faces)
|
|
|
|
|
|
# this is called in Ajax, when we manually override a face that is currently unmatched load
|
|
# the original full image, find the current face's coords, grab pixels 10% larger and return
|
|
# it so we can show it in the dbox, and be able to pass it around for refimg creation (if needed)
|
|
@app.route("/get_face_from_image/<face_id>", methods=["POST"])
|
|
@login_required
|
|
def get_face_from_image(face_id):
|
|
face=Face.query.get(face_id)
|
|
f = Entry.query.join(File).join(FaceFileLink).filter(FaceFileLink.face_id==face_id).first()
|
|
x=face.face_left*0.95
|
|
y=face.face_top*0.95
|
|
x2=face.face_right*1.05
|
|
y2=face.face_bottom*1.05
|
|
|
|
im = Image.open(f.FullPathOnFS())
|
|
region = im.crop((x, y, x2, y2))
|
|
img_bytearray = io.BytesIO()
|
|
region.save(img_bytearray, format='JPEG')
|
|
img_bytearray = img_bytearray.getvalue()
|
|
face_img = base64.b64encode(img_bytearray)
|
|
face_img = str(face_img)[2:-1]
|
|
return face_img
|