first pass of allowing to scan for all files in import and storage paths <- works, and showing all unknown faces to handle them somehow - very rudimentary

This commit is contained in:
2022-01-10 01:20:20 +11:00
parent 27dadacd5c
commit bf04c862d6
6 changed files with 146 additions and 35 deletions

2
TODO
View File

@@ -3,8 +3,6 @@
* move all unsorted photos/* -> import/ * move all unsorted photos/* -> import/
* add an option on the person menu to run_ai_on all photos (or at least import/storage)
* per file you could select an unknown face and add it as a ref img to an existing person, or make a new person and attach? * per file you could select an unknown face and add it as a ref img to an existing person, or make a new person and attach?
* when search, have a way to hide deleted files * when search, have a way to hide deleted files

63
ai.py
View File

@@ -5,9 +5,14 @@ from main import db, app, ma
from sqlalchemy import Sequence from sqlalchemy import Sequence
from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.exc import SQLAlchemyError
from status import st, Status from status import st, Status
from files import Entry, File from path import Path, PathType
from files import Entry, Dir, File, PathDirLink
from person import Refimg, Person, PersonRefimgLink from person import Refimg, Person, PersonRefimgLink
from flask_login import login_required, current_user from flask_login import login_required, current_user
from PIL import Image
import io
import base64
import json
from job import Job, JobExtra, Joblog, NewJob from job import Job, JobExtra, Joblog, NewJob
from face import Face, FaceFileLink, FaceRefimgLink from face import Face, FaceFileLink, FaceRefimgLink
@@ -52,3 +57,59 @@ def run_ai_on():
job=NewJob( "run_ai_on", 0, None, jex ) job=NewJob( "run_ai_on", 0, None, jex )
st.SetMessage( f"Created&nbsp;<a href=/job/{job.id}>Job #{job.id}</a>&nbsp;to Look for face(s) in selected file(s)") st.SetMessage( f"Created&nbsp;<a href=/job/{job.id}>Job #{job.id}</a>&nbsp;to Look for face(s) in selected file(s)")
return render_template("base.html") return render_template("base.html")
@app.route("/run_ai_on_import")
@login_required
def run_ai_on_import():
jex=[]
jex.append( JobExtra( name=f"person", value="all" ) )
paths=Path.query.join(PathType).filter(PathType.name=='Import').all()
path_cnt=0
for p in paths:
d = Dir.query.join(PathDirLink).filter(PathDirLink.path_id==p.id).filter(Dir.rel_path=='').first()
jex.append( JobExtra( name=f"eid-{path_cnt}", value=f"{d.eid}" ) )
path_cnt+=1
print(jex)
job=NewJob( "run_ai_on", 0, None, jex )
st.SetMessage( f"Created&nbsp;<a href=/job/{job.id}>Job #{job.id}</a>&nbsp;to Look for face(s) in import path(s)")
return render_template("base.html")
@app.route("/run_ai_on_storage")
@login_required
def run_ai_on_storage():
jex=[]
jex.append( JobExtra( name=f"person", value="all" ) )
paths=Path.query.join(PathType).filter(PathType.name=='Storage').all()
path_cnt=0
for p in paths:
d = Dir.query.join(PathDirLink).filter(PathDirLink.path_id==p.id).filter(Dir.rel_path=='').first()
jex.append( JobExtra( name=f"eid-{path_cnt}", value=f"{d.eid}" ) )
path_cnt+=1
print(jex)
job=NewJob( "run_ai_on", 0, None, jex )
st.SetMessage( f"Created&nbsp;<a href=/job/{job.id}>Job #{job.id}</a>&nbsp;to Look for face(s) in storage path(s)")
return render_template("base.html")
@app.route("/unmatched_faces")
@login_required
def unmatched_faces():
faces=Face.query.join(FaceFileLink).join(FaceRefimgLink, isouter=True).filter(FaceRefimgLink.refimg_id==None).limit(10).all()
imgs={}
for face in faces:
face.locn=json.loads("["+face.locn+"]")
f = Entry.query.join(File).join(FaceFileLink).filter(FaceFileLink.face_id==face.id).first()
face.file_eid=f.id
face.url=f.FullPathOnFS()
x=face.locn[0][3]*0.95
y=face.locn[0][0]*0.95
x2=face.locn[0][1]*1.05
y2=face.locn[0][2]*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)

29
internal/js/face.js Normal file
View File

@@ -0,0 +1,29 @@
// Define this once and before it will be called, hence at the top of this file
function DrawRefimg(fig, img, canvas, orig_face )
{
// FIXME: should get this from shared.py, not sure why this doesnt work at present
thumbsize=256
context=canvas.getContext('2d')
// another call to this func will occur on load, so skip this one
if( img.width == 0 )
return
// only set canvas.width once we have valid img dimensions
canvas.width=img.width/2
// actually draw the pixel images to the canvas at the right size
context.drawImage(img, 0, 0, img.width/(img.height/canvas.height), canvas.height);
fig.width(canvas.width)
// draw rectangle on face
context.beginPath();
new_x=(orig_face.x/orig_face.orig_w)*img.width/(img.height/canvas.height)
new_y=(orig_face.y/orig_face.orig_h)*thumbsize/(img.height/canvas.height)
new_w=(orig_face.w/orig_face.orig_w)*img.width/(img.height/canvas.height)
new_h=(orig_face.h/orig_face.orig_h)*thumbsize/(img.height/canvas.height)
context.rect(new_x, new_y, new_w, new_h)
context.lineWidth = 2;
context.strokeStyle = 'green';
context.stroke();
}

View File

@@ -74,7 +74,10 @@
<div class="dropdown-menu" aria-labelledby="PersonMenu"> <div class="dropdown-menu" aria-labelledby="PersonMenu">
<a class="dropdown-item" href="{{url_for('new_person')}}">Create Person</a> <a class="dropdown-item" href="{{url_for('new_person')}}">Create Person</a>
<a class="dropdown-item" href="{{url_for('persons')}}">Show People</a> <a class="dropdown-item" href="{{url_for('persons')}}">Show People</a>
<a class="dropdown-item" href="{{url_for('aistats')}}">View stats of matched faces</a> <a class="dropdown-item" href="{{url_for('aistats')}}">View stats of matches</a>
<a class="dropdown-item" href="{{url_for('run_ai_on_import')}}">Match in import path(s) </a>
<a class="dropdown-item" href="{{url_for('run_ai_on_storage')}}">Match in storage path(s) </a>
<a class="dropdown-item" href="{{url_for('unmatched_faces')}}">Show unmatched</a>
</div> </div>
</div class="nav-item dropdown"> </div class="nav-item dropdown">
<div class="nav-item dropdown"> <div class="nav-item dropdown">

50
templates/faces.html Normal file
View File

@@ -0,0 +1,50 @@
{% extends "base.html" %}
{% block main_content %}
<script src="{{ url_for( 'internal', filename='js/face.js')}}"></script>
<div class="container-fluid">
<h3>Unmatched Faces</h3>
<div class="row mt-3">
{% for f in faces %}
<div id="F{{f.id}}" class="col-2 px-0">
<form id="_fm" method="POST" action="/view/{{f.file_eid}}">
<input type="hidden" name="eids" value="{{f.file_eid}},">
<input type="hidden" name="noo" value="newest">
<input type="hidden" name="cwd" value="/">
<input type="hidden" name="root" value="/">
<input type="hidden" name="size" value="128">
<input type="hidden" name="grouping" value="1">
<input type="hidden" name="offset" value="0">
<input type="hidden" name="folders" value="false">
<input type="hidden" name="how_many" value="1">
<input type="hidden" name="orig_url" value="{{request.path}}">'
<figure id="fig_{{f.id}}">
<div style="position:relative">
<canvas id="c_{{f.id}}" height="128"></canvas>
<script>
var im_{{f.id}}=new Image();
im_{{f.id}}.src="data:image/jpeg;base64,{{f.img}}";
fig_{{f.id}}=$('#fig_{{f.id}}')
// store this stuff in an javascript Object to use when document is ready event is triggered
var orig_face_{{f.id}}=new Object;
orig_face_{{f.id}}.x = {{f.locn[0][3]}}
orig_face_{{f.id}}.y = {{f.locn[0][0]}}
orig_face_{{f.id}}.w = {{f.locn[0][1]}}-{{f.locn[0][3]}}
orig_face_{{f.id}}.h = {{f.locn[0][2]}}-{{f.locn[0][0]}}
orig_face_{{f.id}}.orig_w = orig_face_{{f.id}}.w
orig_face_{{f.id}}.orig_h = orig_face_{{f.id}}.h
// when the document is ready, then DrawRefimg
$(function() { DrawRefimg( fig_{{f.id}}, im_{{f.id}}, c_{{f.id}}, orig_face_{{f.id}} ) });
</script>
<figcaption>{{f.id}}</figcation>
</div>
</figure>
<button>Go</button>
</form>
</div id="/F*">
{% endfor %}
</div class="row">
</div class="container-fluid">
{% endblock main_content %}

View File

@@ -1,35 +1,5 @@
{% extends "base.html" %} {% block main_content %} {% extends "base.html" %} {% block main_content %}
<script> <script src="{{ url_for( 'internal', filename='js/face.js')}}"></script>
// Define this once and before it will be called, hence at the top of this file
function DrawRefimg(fig, img, canvas, orig_face )
{
// FIXME: should get this from shared.py, not sure why this doesnt work at present
thumbsize=256
context=canvas.getContext('2d')
// another call to this func will occur on load, so skip this one
if( img.width == 0 )
return
// only set canvas.width once we have valid img dimensions
canvas.width=img.width/2
// actually draw the pixel images to the canvas at the right size
context.drawImage(img, 0, 0, img.width/(img.height/canvas.height), canvas.height);
fig.width(canvas.width)
// draw rectangle on face
context.beginPath();
new_x=(orig_face.x/orig_face.orig_w)*img.width/(img.height/canvas.height)
new_y=(orig_face.y/orig_face.orig_h)*thumbsize/(img.height/canvas.height)
new_w=(orig_face.w/orig_face.orig_w)*img.width/(img.height/canvas.height)
new_h=(orig_face.h/orig_face.orig_h)*thumbsize/(img.height/canvas.height)
context.rect(new_x, new_y, new_w, new_h)
context.lineWidth = 2;
context.strokeStyle = 'green';
context.stroke();
}
</script>
<div class="container-fluid"> <div class="container-fluid">
<div class="row col-12"><h3 class="offset-3">{{page_title}}</h3></div> <div class="row col-12"><h3 class="offset-3">{{page_title}}</h3></div>