diff --git a/TODO b/TODO index 99555aa..936415a 100644 --- a/TODO +++ b/TODO @@ -1,19 +1,17 @@ ## GENERAL - *** Need to double-check scheduled jobs running in PROD (can use new pa_job_manager.log) - - * remember last import dir, so you can just go straight back to it - - * when hitting back button to a search, it doesnt handle the post, etc. - $(document).ready(function() { - window.onpopstate = function() { - # this seems to work, but feels like no protection at all??? - # (what about back when it goes onto a POST of deleting a file!) - window.history.back() - }; - }); - -- maybe window.history.replace() is needed on unsafe URLs? + * optimise run_ai_on (and for that matter getfiledetails, etc.)... + - e.g. with last scan*, STORE: new files? + - if last scan no new files, dont getfiledetails, don't re-run ai job * 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? + * [DONE] order/ find face with largest size and at least show that as unmatched + - could also try to check it vs. other faces, if it matches more than say 10? we offer it up as a required ref img, then cut that face (with margin) out and use it is a new ref image / person + * on viewer: allow face to be used to create person, add to existing person, and allow 'ignore', mark as 'not a face', etc. -> all into DB + - so need face 'treatment' -> could be matched via face_refimg_link, but also could be 'ignore' or 'not a face', in each case we could exclude those faces from + matching for the future, and reporting on matches, etc. + - context-menu with rects on a canvas + https://stackoverflow.com/questions/31601393/create-context-menu-using-jquery-with-html-5-canvas + - also allow joblog search from the viewer for that file... * delete folder @@ -46,14 +44,15 @@ * comment your code -> only html files remaining - * from menu, we could try to get smart/fancy... say find face with largest size, check it vs. other faces, if it matches more than say 10? we offer it up as a required ref img, then cut that face (with margin) out and use it is a new ref image / person - - read that guys face matching / clustering / nearest neighbour examples, for a whole new AI capability + * read that guys face matching / clustering / nearest neighbour examples, for a whole new AI capability https://www.pyimagesearch.com/2018/07/09/face-clustering-with-python/ * fix up logging in general * support animated gifs in html5 canvas + * think about security - in job_mgr anywhere I can os.replace/remove NEED to protect, etc + ## DB * Dir can have date in the DB, so we can do Oldest/Newest dirs in Folder view diff --git a/ai.py b/ai.py index 66abcb0..363dd5b 100644 --- a/ai.py +++ b/ai.py @@ -91,7 +91,7 @@ def run_ai_on_storage(): @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() + faces=Face.query.join(FaceFileLink).join(FaceRefimgLink, isouter=True).filter(FaceRefimgLink.refimg_id==None).order_by(Face.h.desc()).limit(10).all() imgs={} for face in faces: face.locn=json.loads("["+face.locn+"]") @@ -102,6 +102,7 @@ def unmatched_faces(): 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() diff --git a/face.py b/face.py index d84ad3e..2fb73cf 100644 --- a/face.py +++ b/face.py @@ -2,6 +2,12 @@ from main import db, app, ma from sqlalchemy import Sequence from sqlalchemy.exc import SQLAlchemyError +# DEL ME SOON +from flask_login import login_required +from flask import render_template +import json + + # pylint: disable=no-member ################################################################################ @@ -17,6 +23,8 @@ class Face(db.Model): id = db.Column(db.Integer, db.Sequence('face_id_seq'), primary_key=True ) face = db.Column( db.LargeBinary ) locn = db.Column( db.String ) + w = db.Column( db.Integer ) + h = db.Column( db.Integer ) refimg_lnk = db.relationship("FaceRefimgLink", uselist=False, viewonly=True) facefile_lnk = db.relationship("FaceFileLink", uselist=False, viewonly=True) refimg =db.relationship("Refimg", secondary="face_refimg_link", uselist=False) @@ -53,4 +61,3 @@ class FaceRefimgLink(db.Model): def __repr__(self): return f"" + return f"" ################################################################################ @@ -85,14 +87,16 @@ class Options(PA): self.how_many=pref.how_many self.offset=pref.st_offset self.size=pref.size + self.root=pref.root + self.cwd=pref.cwd else: self.grouping="None" self.how_many="50" self.offset="0" self.size="128" + self.root='static/' + self.path_type + self.cwd=self.root - self.cwd='static/' + self.path_type - self.root=self.cwd # the above are defaults, if we are here, then we have current values, use them instead if they are set -- AI: searches dont set them so then we use those in the DB first if request.method=="POST": @@ -134,7 +138,7 @@ class Options(PA): pref=PA_PREF.query.filter(PA_PREF.pa_user_dn==current_user.dn,PA_PREF.path_type==self.path_type).first() if not pref: pref=PA_PREF( pa_user_dn=current_user.dn, path_type=self.path_type, noo=self.noo, grouping=self.grouping, how_many=self.how_many, - st_offset=self.offset, size=self.size, folders=self.folders) + st_offset=self.offset, size=self.size, folders=self.folders, root=self.root, cwd=self.cwd) else: pref.noo=self.noo pref.grouping=self.grouping @@ -142,10 +146,14 @@ class Options(PA): pref.st_offset=self.offset pref.size=self.size pref.folders=self.folders + pref.root = self.root + pref.cwd = self.cwd db.session.add(pref) db.session.commit() + return + ################################################################################ # /prefs -> GET only -> prints out list of all prefs (simple for now) ################################################################################ diff --git a/pa_job_manager.py b/pa_job_manager.py index 40d19e0..0b0d726 100644 --- a/pa_job_manager.py +++ b/pa_job_manager.py @@ -320,6 +320,8 @@ class Face(Base): id = Column(Integer, Sequence('face_id_seq'), primary_key=True ) face = Column( LargeBinary ) locn = Column(String) + w = Column(Integer) + h = Column(Integer) def __repr__(self): return f" + // do our own back button handling, TODO: 'dangerous' URLs, will be replaced with GETs first via history.replaceState() + window.onpopstate = function(e) { + window.history.back() + } function SetViewingOptionsForSearchForm() { if( $('#noo').length ) diff --git a/templates/faces.html b/templates/faces.html index 9e73ab9..bbcde68 100644 --- a/templates/faces.html +++ b/templates/faces.html @@ -6,42 +6,34 @@

Unmatched Faces

{% for f in faces %} -
+
- - - - - - - - - - ' + +
+
+ + -
{{f.id}} -
-
- + // when the document is ready, then DrawRefimg + $(function() { DrawRefimg( fig_{{f.id}}, im_{{f.id}}, c_{{f.id}}, orig_face_{{f.id}} ) }); + +
Face #{{f.id}} +
+ +
{% endfor %} diff --git a/templates/prefs.html b/templates/prefs.html index 63e5eb5..1539ab5 100644 --- a/templates/prefs.html +++ b/templates/prefs.html @@ -5,7 +5,7 @@ - + {% for pref in prefs %} @@ -18,6 +18,8 @@ + + {% endfor %}
PathNew or OldestHow ManyFolders?Group byThumb sizeFullscreenDB retrieve offset
PathNew or OldestHow ManyFolders?Group byThumb sizeFullscreenDB retrieve offsetRootcwd
{{pref.size}} {{pref.fullscreen}} {{pref.st_offset}}{{pref.root}}{{pref.cwd}}