refimgs now contain face, orig_w, orig_h and face_locns. This is done via json.* to allow arrays to be saved/loaded back into face_locn - not useful for refimg as there has to be only 1, but tested for images where there will be many faces. This commit has a fair few changes. So overall, no more refimg menus/creation. You now create a person (the add button is hidden until you save), when you save you go back to the person you created rather than the list of persons. From there you can click add ref img, and it will create a thumbnail, and draw a green box around the face locations based on the data. Persons can have many refimgs, and they will all work the same, be formatted prettily no matter how many you have. Each refimg "tab" not only has the thumbnail, but also a red X click to delete button that will remove all refimg data and connection to the person table too. This all works/is tested.

This commit is contained in:
2021-07-11 22:35:59 +10:00
parent ce81898a07
commit f394e39c2f
3 changed files with 131 additions and 71 deletions

22
TODO
View File

@@ -1,20 +1,21 @@
## GENERAL
* refimg
- remove AI menu from top-level -> make a sub-of Person, and just have Match or AI
- store the face locations? (good for debugging later, and we calc them as part of GenFace anyway) ...
(really, doing this properly, we shoudl keep the face locns for ALL images)...
---> may well mean in a week or so, we move to the new DB structure, and START FORM SCRATCH)
* allow rotate of image (permanently on FS, so its right everywhere)
* improve photo browser -> view file, rather than just allowing browser to show image
* face locations:
START FORM SCRATCH so all images have face_locn data
right now GenThumb is in shared, and does width, height as well --> in person.py BUT need this for pa_job_manager
* allow for threshold/settings to be tweaked from the GUI
- it would be good to then say, just run the scanner against this image or maybe this DIR, to see how it IDs ppl
---> settings for default value
---> override table to do per file combos?
* refimg
- remove AI menu from top-level -> make a sub-of Person, and just have Match or AI
* fix up logging in general
* comment your code
* more OO goodness :)
@@ -33,9 +34,6 @@
*** Need to use thread-safe sessions per Thread, half-assed version did not work
- would it be quicker/smarter to use md5 hash matching on import (and if
so, not re-do face* ) ???
need a manual button to restart a job in the GUI,
(based on file-level optims, just run the job as new and it will optim over already done parts and continue)
@@ -67,7 +65,6 @@
need to copy into here the jquery/fa files so we don't need internet to function
- for that matter run lightspeed against all this
timelineview? (I think maybe sunburst for large amounts of files, then maybe something more timeline-series for drilling in?)
(vertical timeline, date has thumbnails (small) horizontally along
a page, etc.?
@@ -81,3 +78,8 @@
* exif processing?
* location stuff - test a new photo from my camera out
-- image is in dir, need to look at exifread output
### FUTURE:
* can emby use nfo for images (for AI/tags?)
-NO sadly

View File

@@ -10,6 +10,7 @@ from werkzeug import secure_filename
from shared import GenFace, GenThumb
from face import FaceRefimgLink
import os
import json
# pylint: disable=no-member
@@ -21,6 +22,9 @@ class Refimg(db.Model):
id = db.Column(db.Integer, db.Sequence('refimg_id_seq'), primary_key=True )
fname = db.Column(db.String(256), unique=True, nullable=False)
face = db.Column(db.LargeBinary, unique=True, nullable=False)
orig_w = db.Column(db.Integer)
orig_h = db.Column(db.Integer)
face_locn = db.Column(db.String)
thumbnail = db.Column(db.String, unique=True, nullable=False)
created_on = db.Column(db.Float)
@@ -41,7 +45,7 @@ class Person(db.Model):
tag = db.Column(db.String(48), unique=False, nullable=False)
surname = db.Column(db.String(48), unique=False, nullable=False)
firstname = db.Column(db.String(48), unique=False, nullable=False)
refimg = db.relationship('Refimg', secondary=PersonRefimgLink.__table__)
refimg = db.relationship('Refimg', secondary=PersonRefimgLink.__table__, order_by=Refimg.id)
def __repr__(self):
return "<tag: {}, firstname: {}, surname: {}, refimg: {}>".format(self.tag,self.firstname, self.surname, self.refimg)
@@ -94,7 +98,7 @@ def new_person():
db.session.add(person)
db.session.commit()
st.SetMessage( "Created new Person ({})".format(person.tag) )
return redirect( '/persons' )
return redirect( f'/person/{person.id}' )
except SQLAlchemyError as e:
st.SetAlert( "danger" )
st.SetMessage( "<b>Failed to add Person:</b>&nbsp;{}".format(e.orig) )
@@ -141,6 +145,8 @@ def person(id):
return render_template("person.html", form=form, page_title=page_title)
else:
person = Person.query.get(id)
for r in person.refimg:
r.face_locn=json.loads(r.face_locn)
form = PersonForm(request.values, obj=person)
return render_template("person.html", person=person, form=form, page_title = page_title)
@@ -164,8 +170,9 @@ def add_refimg():
fname = f"/tmp/{fname}"
f.save( fname )
refimg.thumbnail = GenThumb( fname )
refimg.face = GenFace( fname )
refimg.thumbnail, refimg.orig_w, refimg.orig_h = GenThumb( fname )
refimg.face, face_locn = GenFace( fname )
refimg.face_locn = json.dumps(face_locn)
os.remove(fname)
person.refimg.append(refimg)
db.session.add(person)

View File

@@ -1,5 +1,36 @@
{% extends "base.html" %} {% block main_content %}
<div class="container-fluid">
<script>
// Define this once and before it will be called, hence at the top of this file
function DrawRefimg(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);
// 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">
<h3 class="offset-lg-3">{{page_title}}</h3>
<form id="pfm" class="form form-inline" action="" method="POST">
{% for field in form %}
@@ -14,23 +45,42 @@
{% endfor %}
<div class="form-row col-lg-12">
<span class="col-lg-3"><center>Reference Images:</center></span>
{% for ref_img in person.refimg %}
{% for refimg in person.refimg %}
{% set offset="" %}
{% if (loop.index % 10) == 0 %}
{% set offset= "offset-lg-3" %}
{% endif %}
<div id="RI{{ref_img.id}}" class="px-0 col-lg-1 w-100 {{offset}}">
<input type="hidden" id="ref-img-id-{{ref_img.id}}" name="ref-img-id-{{ref_img.id}}" value="1"></input>
<div id="RI{{refimg.id}}" class="px-0 col-lg-1 w-100 {{offset}}">
<center>
<input type="hidden" id="ref-img-id-{{refimg.id}}" name="ref-img-id-{{refimg.id}}" value="1"></input>
<figure style="border: 1px solid #5bc0de; border-radius: 3px;" class="figure my-auto h-100 w-100">
<div style="position:relative">
<center><img height="128" class="thumb" src="data:image/jpeg;base64,{{ref_img.thumbnail}}"></img></center>
<canvas id="c_{{refimg.id}}" height="128"></canvas>
<script>
var im_{{refimg.id}}=new Image();
im_{{refimg.id}}.src="data:image/jpeg;base64,{{refimg.thumbnail}}";
// store this stuff in an javascript Object to use when document is ready event is triggered
var orig_face_{{refimg.id}}=new Object;
orig_face_{{refimg.id}}.x = {{refimg.face_locn[0][3]}}
orig_face_{{refimg.id}}.y = {{refimg.face_locn[0][0]}}
orig_face_{{refimg.id}}.w = {{refimg.face_locn[0][1]}}-{{refimg.face_locn[0][3]}}
orig_face_{{refimg.id}}.h = {{refimg.face_locn[0][2]}}-{{refimg.face_locn[0][0]}}
orig_face_{{refimg.id}}.orig_w = {{refimg.orig_w}}
orig_face_{{refimg.id}}.orig_h = {{refimg.orig_h}}
// when the document is ready, then DrawRefimg
$(function() { DrawRefimg(im_{{refimg.id}}, c_{{refimg.id}}, orig_face_{{refimg.id}} ) });
</script>
<div style="position:absolute; top: 2; right: 2;">
<button type="button" style="font-size:12px" class="btn btn-danger"
onClick="DelImg({{ref_img.id}})">X</button>
onClick="DelImg({{refimg.id}})">X</button>
</div>
<figcaption class="figure-caption text-center text-wrap text-break">{{ref_img.fname}}</figcaption>
<figcaption class="figure-caption text-center text-wrap text-break">{{refimg.fname}}</figcaption>
</div>
</figure>
</center>
</div id="/RI*">
{% endfor %}
</div class="form-row col-lg-12">
@@ -44,6 +94,7 @@
{% endif %}
</div class="form-row">
</form>
{% if person.id %}
<form id="new_ri" class="form" action="{{url_for('add_refimg')}}" method="POST" enctype="multipart/form-data">
<input type="hidden" name="person_id" value="{{person.id}}"></input>
<label class="btn btn-success offset-lg-3 col-lg-2">
@@ -51,17 +102,17 @@
<input name="refimg_file" type="file" onChange="$('#new_ri').submit()" style="display:none;" id="new_file_chooser">
</label>
</form>
</div class="row">
</div class="container">
{% endif %}
</div class="container">
{% endblock main_content %}
{% block script_content %}
<script>
function DelImg(ri_num)
{
$('#RI'+ri_num).remove()
$('#pfm').submit()
$('#RI'+ri_num).remove()
$('#pfm').submit()
}
</script>
{% endblock script_content %}