update file to use new pylint settings, added types and using docstrings in goolge format with partial openapi spec

This commit is contained in:
2023-06-18 22:02:33 +10:00
parent 2767d7872d
commit b636ac08b8
4 changed files with 348 additions and 188 deletions

159
ai.py
View File

@@ -1,90 +1,140 @@
from wtforms import SubmitField, StringField, HiddenField, validators, Form
from flask_wtf import FlaskForm
""" file containing all functions to handle routes relating to AI functionality """
# pylint: disable=singleton-comparison
from flask import request, render_template, redirect, make_response
from main import db, app, ma
from sqlalchemy import Sequence
from sqlalchemy.exc import SQLAlchemyError
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 flask_login import login_required
from PIL import Image
import io
import base64
from job import Job, JobExtra, Joblog, NewJob
from main import db, app
from path import PathType
from files import Entry, File
from job import JobExtra, NewJob
from face import Face, FaceFileLink, FaceRefimgLink, FaceNoMatchOverride, FaceForceMatchOverride
# pylint: disable=no-member
################################################################################
# /ai_stats -> placholder for some sort of stats
################################################################################
@app.route("/ai_stats", methods=["GET"])
@login_required
def ai_stats():
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" )
""" route to handle URL: /ai_stats
---
responses:
200:
description: renders ai_stats.html to display counts of how many matches for each person we have
"""
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]
fstats["files_with_a_face"] = db.session.execute( "select count(distinct file_eid) as count from face_file_link" ).first()[0]
sql="select count(distinct ffl.file_eid) as count from face_file_link ffl, face_refimg_link frl where frl.face_id = ffl.face_id"
fstats["files_with_a_match"] = db.session.execute( sql ).first()[0]
sql={"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" }
fstats["files_with_missing_matches"] = db.session.execute( sql ).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]
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]
sql="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"
fstats["all_unmatched_faces"] = db.session.execute( sql).first()[0]
return render_template("ai_stats.html", page_title='AI Statistics', stats=stats, num_stats=num_stats, fstats=fstats )
return render_template("ai_stats.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():
""" route to handle URL: /run_ai_on
this route creates a job for the job manager to scan for face(s) with AI on the
files/dirs passed in as form variables named eid-X, where X=0, 1, 2, etc. and
each eid-X contains an eid from the database for the dir/file entry
jobextras created containing entry ids (eid-0, eid-1,
and person=all|dad, etc. Room to consider threshold, algo, etc.
---
responses:
302:
description: redirects to /jobs page showing all jobs (including this new one)
"""
jex=[]
for el in request.form:
jex.append( JobExtra( name=f"{el}", value=request.form[el] ) )
job=NewJob( "run_ai_on", num_files=0, wait_for=None, jex=jex, desc="Look for face(s) in selected file(s)" )
jex.append( JobExtra( name=el, value=request.form[el] ) )
NewJob( "run_ai_on", num_files=0, wait_for=None, jex=jex, desc="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():
""" route to handle URL: /run_ai_on_import
this route creates a job for the job manager to scan for the all faces with AI on
all the files in the import dir
---
responses:
302:
description: redirects to /jobs page showing all jobs (including this new one)
"""
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", num_files=0, wait_for=None, jex=jex, desc="Look for face(s) in import path(s)")
ptype=PathType.query.filter(PathType.name=="Import").first()
jex.append( JobExtra( name="person", value="all" ) )
jex.append( JobExtra( name="path_type", value=ptype.id ) )
NewJob( "run_ai_on_path", num_files=0, wait_for=None, jex=jex, desc="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():
""" route to handle URL: /run_ai_on_storage
this route creates a job for the job manager to scan for the all faces with AI on
all the files in the storage dir
---
responses:
302:
description: redirects to /jobs page showing all jobs (including this new one)
"""
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", num_files=0, wait_for=None, jex=jex, desc="Look for face(s) in storage path(s)")
ptype=PathType.query.filter(PathType.name=="Storage").first()
jex.append( JobExtra( name="person", value="all" ) )
jex.append( JobExtra( name="path_type", value=ptype.id ) )
NewJob( "run_ai_on_path", num_files=0, wait_for=None, jex=jex, desc="Look for face(s) in storage path(s)")
return redirect("/jobs")
################################################################################
@app.route("/unmatched_faces", methods=["GET"])
@login_required
def unmatched_faces():
""" route to handle URL: /unmatched_faces
---
responses:
200:
description: renders faces.html to show up to 10 faces that AI has found, that have no matching person
"""
# 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={}
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()
for face in faces:
f = Entry.query.join(File).join(FaceFileLink).filter(FaceFileLink.face_id==face.id).first()
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
@@ -96,7 +146,7 @@ def unmatched_faces():
im = Image.open(f.FullPathOnFS())
region = im.crop((x, y, x2, y2))
img_bytearray = io.BytesIO()
region.save(img_bytearray, format='JPEG')
region.save(img_bytearray, format="JPEG")
img_bytearray = img_bytearray.getvalue()
face.img = base64.b64encode(img_bytearray)
face.img = str(face.img)[2:-1]
@@ -104,14 +154,29 @@ def unmatched_faces():
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"])
################################################################################
@app.route("/get_face_from_image/<face_id:int>", methods=["POST"])
@login_required
def get_face_from_image(face_id):
""" route to handle URL: /get_face_from_image/<face_id:int>
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)
---
responses:
200:
description: Base64-encoded image of face AI found returned successfully
content:
text/plain:
schema:
type: string
format: binary
description: Base64-encoded image data
"""
face=Face.query.get(face_id)
f = Entry.query.join(File).join(FaceFileLink).filter(FaceFileLink.face_id==face_id).first()
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
@@ -120,7 +185,7 @@ def get_face_from_image(face_id):
im = Image.open(f.FullPathOnFS())
region = im.crop((x, y, x2, y2))
img_bytearray = io.BytesIO()
region.save(img_bytearray, format='JPEG')
region.save(img_bytearray, format="JPEG")
img_bytearray = img_bytearray.getvalue()
face_img = base64.b64encode(img_bytearray)
face_img = str(face_img)[2:-1]