From f394e39c2fec912167e83bc33cbb366cd442293e Mon Sep 17 00:00:00 2001 From: Damien De Paoli Date: Sun, 11 Jul 2021 22:35:59 +1000 Subject: [PATCH] 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. --- TODO | 22 +++--- person.py | 15 +++- templates/person.html | 165 +++++++++++++++++++++++++++--------------- 3 files changed, 131 insertions(+), 71 deletions(-) diff --git a/TODO b/TODO index ffda7fe..10e373e 100644 --- a/TODO +++ b/TODO @@ -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 + diff --git a/person.py b/person.py index 4bf7d16..369d1d2 100644 --- a/person.py +++ b/person.py @@ -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 "".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( "Failed to add Person: {}".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) diff --git a/templates/person.html b/templates/person.html index a46ec94..827f052 100644 --- a/templates/person.html +++ b/templates/person.html @@ -1,67 +1,118 @@ {% extends "base.html" %} {% block main_content %} -
-

{{page_title}}

-
- {% for field in form %} - {% if field.type == 'HiddenField' or field.type == 'CSRFTokenField' %} - {{field}}
- {% elif field.type != 'SubmitField' %} -
- {{ field.label( class="col-lg-3" ) }} - {{ field( class="form-control col" ) }} -
- {% endif %} - {% endfor %} -
-
Reference Images:
- {% for ref_img in person.refimg %} - {% set offset="" %} - {% if (loop.index % 10) == 0 %} - {% set offset= "offset-lg-3" %} - {% endif %} -
- -
-
-
-
- -
-
{{ref_img.fname}}
-
-
-
- {% endfor %} -
-
-
-
-
- {{ form.save( id="save", class="btn btn-primary offset-lg-3 col-lg-2" )}} - {% if 'Edit' in page_title %} - {{ form.delete( class="btn btn-outline-danger col-lg-2" )}} - {% endif %} -
-
-
- - -
-
- + + +
+

{{page_title}}

+
+ {% for field in form %} + {% if field.type == 'HiddenField' or field.type == 'CSRFTokenField' %} + {{field}}
+ {% elif field.type != 'SubmitField' %} +
+ {{ field.label( class="col-lg-3" ) }} + {{ field( class="form-control col" ) }} +
+ {% endif %} + {% endfor %} +
+
Reference Images:
+ {% for refimg in person.refimg %} + {% set offset="" %} + {% if (loop.index % 10) == 0 %} + {% set offset= "offset-lg-3" %} + {% endif %} +
+
+ +
+
+ + +
+ +
+
{{refimg.fname}}
+
+
+
+
+ {% endfor %} +
+
+
+
+
+ {{ form.save( id="save", class="btn btn-primary offset-lg-3 col-lg-2" )}} + {% if 'Edit' in page_title %} + {{ form.delete( class="btn btn-outline-danger col-lg-2" )}} + {% endif %} +
+
+ {% if person.id %} +
+ + +
+ {% endif %} +
{% endblock main_content %} {% block script_content %} {% endblock script_content %} -