added auto-rotate to settings, not used in job mgr yet

This commit is contained in:
2022-07-13 00:26:24 +10:00
parent 713d9d96a1
commit 3710b573ea
7 changed files with 23 additions and 9 deletions

2
BUGs
View File

@@ -1,3 +1,5 @@
### Next: 92 ### Next: 92
BUG-85: once we rebuild data from scratch, need to reset just clean out pa_user_state's BUG-85: once we rebuild data from scratch, need to reset just clean out pa_user_state's
BUG-91: face_recognition not working on many of Mandy's newer phone images BUG-91: face_recognition not working on many of Mandy's newer phone images
-- its when a photo is rotated -- can use:
exifautotran file.jpg, and then all should work (no loss), could then skip fix in thumbs too?

View File

@@ -15,7 +15,7 @@ RUN truncate -s0 /tmp/preseed.cfg && \
apt-get install -y tzdata apt-get install -y tzdata
## cleanup of files from setup ## cleanup of files from setup
RUN rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* RUN rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
RUN apt-get update && apt-get -y install python3-pip python3-psycopg2 libpq-dev gunicorn mediainfo cmake libgl1-mesa-glx libglib2.0-0 python3-ldap RUN apt-get update && apt-get -y install python3-pip python3-psycopg2 libpq-dev gunicorn mediainfo cmake libgl1-mesa-glx libglib2.0-0 python3-ldap libjpeg-turbo-progs
COPY requirements.txt requirements.txt COPY requirements.txt requirements.txt
RUN pip3 install -r requirements.txt RUN pip3 install -r requirements.txt
RUN pip3 install --upgrade pillow --user RUN pip3 install --upgrade pillow --user

3
TODO
View File

@@ -2,7 +2,8 @@
* on viewer: * on viewer:
- allow face to be used to: - allow face to be used to:
- create person - create person
[PARTIAL - person.py to go] - add to existing person [DONE] - add to existing person
--> still need to consider whether we trigger an AI search immediately
[DONE] - ignore/not a face/too young [DONE] - ignore/not a face/too young
[DONE] - redraw 'ignore's as a greyed out box? [DONE] - redraw 'ignore's as a greyed out box?
[DONE] - menu should only allow override IF we have put override on... [DONE] - menu should only allow override IF we have put override on...

View File

@@ -1,4 +1,4 @@
from wtforms import SubmitField, StringField, IntegerField, FloatField, HiddenField, validators, Form, SelectField from wtforms import SubmitField, StringField, IntegerField, FloatField, HiddenField, validators, Form, SelectField, BooleanField
from flask_wtf import FlaskForm from flask_wtf import FlaskForm
from flask import request, render_template, redirect, url_for from flask import request, render_template, redirect, url_for
from main import db, app, ma from main import db, app, ma
@@ -30,6 +30,7 @@ class Settings(db.Model):
import_path = db.Column(db.String) import_path = db.Column(db.String)
storage_path = db.Column(db.String) storage_path = db.Column(db.String)
recycle_bin_path = db.Column(db.String) recycle_bin_path = db.Column(db.String)
auto_rotate = db.Column(db.Boolean)
default_refimg_model = db.Column(db.Integer,db.ForeignKey('ai_model.id'), unique=True, nullable=False) default_refimg_model = db.Column(db.Integer,db.ForeignKey('ai_model.id'), unique=True, nullable=False)
default_scan_model = db.Column(db.Integer,db.ForeignKey('ai_model.id'), unique=True, nullable=False) default_scan_model = db.Column(db.Integer,db.ForeignKey('ai_model.id'), unique=True, nullable=False)
default_threshold = db.Column(db.Integer) default_threshold = db.Column(db.Integer)
@@ -41,7 +42,7 @@ class Settings(db.Model):
job_archive_age = db.Column(db.Integer) job_archive_age = db.Column(db.Integer)
def __repr__(self): def __repr__(self):
return f"<id: {self.id}, import_path: {self.import_path}, storage_path: {self.storage_path}, recycle_bin_path: {self.recycle_bin_path}, default_refimg_model: {self.default_refimg_model}, default_scan_model: {self.default_scan_model}, default_threshold: {self.default_threshold}, face_size_limit: {self.face_size_limit}, scheduled_import_scan:{self.scheduled_import_scan}, scheduled_storage_scan: {self.scheduled_storage_scan}, scheduled_bin_cleanup: {self.scheduled_bin_cleanup}, bin_cleanup_file_age: {self.bin_cleanup_file_age}, job_archive_age: {self.job_archive_age}>" return f"<id: {self.id}, import_path: {self.import_path}, storage_path: {self.storage_path}, recycle_bin_path: {self.recycle_bin_path}, auto_rotate: {self.auto_rotate}, default_refimg_model: {self.default_refimg_model}, default_scan_model: {self.default_scan_model}, default_threshold: {self.default_threshold}, face_size_limit: {self.face_size_limit}, scheduled_import_scan:{self.scheduled_import_scan}, scheduled_storage_scan: {self.scheduled_storage_scan}, scheduled_bin_cleanup: {self.scheduled_bin_cleanup}, bin_cleanup_file_age: {self.bin_cleanup_file_age}, job_archive_age: {self.job_archive_age}>"
################################################################################ ################################################################################
# Helper class that inherits a .dump() method to turn class Settings into json / useful in jinja2 # Helper class that inherits a .dump() method to turn class Settings into json / useful in jinja2
@@ -63,6 +64,7 @@ class SettingsForm(FlaskForm):
import_path = StringField('Path(s) to import from:', [validators.DataRequired()]) import_path = StringField('Path(s) to import from:', [validators.DataRequired()])
storage_path = StringField('Path to store sorted images to:', [validators.DataRequired()]) storage_path = StringField('Path to store sorted images to:', [validators.DataRequired()])
recycle_bin_path = StringField('Path to temporarily store deleted images in:', [validators.DataRequired()]) recycle_bin_path = StringField('Path to temporarily store deleted images in:', [validators.DataRequired()])
auto_rotate = BooleanField('Automatically rotate jpegs based on exif', [validators.AnyOf([True, False])])
default_refimg_model = SelectField( 'Default model to use for reference images', choices=[(c.id, c.name) for c in AIModel.query.order_by('id')] ) default_refimg_model = SelectField( 'Default model to use for reference images', choices=[(c.id, c.name) for c in AIModel.query.order_by('id')] )
default_scan_model = SelectField( 'Default model to use for all scanned images', choices=[(c.id, c.name) for c in AIModel.query.order_by('id')] ) default_scan_model = SelectField( 'Default model to use for all scanned images', choices=[(c.id, c.name) for c in AIModel.query.order_by('id')] )
default_threshold = StringField('Face Distance threshold (below is a match):', [validators.DataRequired()]) default_threshold = StringField('Face Distance threshold (below is a match):', [validators.DataRequired()])
@@ -87,6 +89,7 @@ def settings():
HELP['import_path']="Path(s) to import files from. If starting with /, then used literally, otherwise base path is prepended" HELP['import_path']="Path(s) to import files from. If starting with /, then used literally, otherwise base path is prepended"
HELP['storage_path']="Path(s) to store sorted files to. If starting with /, then used literally, otherwise base path is prepended" HELP['storage_path']="Path(s) to store sorted files to. If starting with /, then used literally, otherwise base path is prepended"
HELP['recycle_bin_path']="Path where deleted files are moved to. If starting with /, then used literally, otherwise base path is prepended" HELP['recycle_bin_path']="Path where deleted files are moved to. If starting with /, then used literally, otherwise base path is prepended"
HELP['auto_rotate']="Automatically rotate jpegs based on exif to orient them so that AI matching will work. NOTE: this actually changes/rewrites the file - as it is a simple rotate, it is down without losing quality/content"
HELP['default_refimg_model']="Default face recognition model used for reference images - cnn is slower/more accurate, hog is faster/less accurate - we scan (small) refimg once, so cnn is okay" HELP['default_refimg_model']="Default face recognition model used for reference images - cnn is slower/more accurate, hog is faster/less accurate - we scan (small) refimg once, so cnn is okay"
HELP['default_scan_model']="Default face recognition model used for scanned images - cnn is slower/more accurate, hog is faster/less accurate - we scan (large) scanned images lots, so cnn NEEDS gpu/mem" HELP['default_scan_model']="Default face recognition model used for scanned images - cnn is slower/more accurate, hog is faster/less accurate - we scan (large) scanned images lots, so cnn NEEDS gpu/mem"
HELP['default_threshold']="The distance below which a face is considered a match. The default is usually 0.6, we are trying for about 0.55 with kids. YMMV" HELP['default_threshold']="The distance below which a face is considered a match. The default is usually 0.6, we are trying for about 0.55 with kids. YMMV"
@@ -106,6 +109,10 @@ def settings():
s.import_path = request.form['import_path'] s.import_path = request.form['import_path']
s.storage_path = request.form['storage_path'] s.storage_path = request.form['storage_path']
s.recycle_bin_path = request.form['recycle_bin_path'] s.recycle_bin_path = request.form['recycle_bin_path']
if 'auto_rotate' in request.form:
s.auto_rotate = True
else:
s.auto_rotate = False
s.default_refimg_model = request.form['default_refimg_model'] s.default_refimg_model = request.form['default_refimg_model']
s.default_scan_model = request.form['default_scan_model'] s.default_scan_model = request.form['default_scan_model']
s.default_threshold = request.form['default_threshold'] s.default_threshold = request.form['default_threshold']

View File

@@ -140,6 +140,7 @@ def GenThumb(fname):
def GenFace(fname, model): def GenFace(fname, model):
img = face_recognition.load_image_file(fname) img = face_recognition.load_image_file(fname)
location = face_recognition.face_locations(img, model=model) location = face_recognition.face_locations(img, model=model)
print( f"locn={location}" )
encodings = face_recognition.face_encodings(img, known_face_locations=location) encodings = face_recognition.face_encodings(img, known_face_locations=location)
if len(encodings) and len(location): if len(encodings) and len(location):
return encodings[0].tobytes(), location[0] return encodings[0].tobytes(), location[0]

View File

@@ -7,6 +7,7 @@ insert into AI_MODEL values ( 2, 'cnn', 'more accurate / much slower' );
create table SETTINGS( create table SETTINGS(
ID integer, BASE_PATH varchar, IMPORT_PATH varchar, STORAGE_PATH varchar, RECYCLE_BIN_PATH varchar, ID integer, BASE_PATH varchar, IMPORT_PATH varchar, STORAGE_PATH varchar, RECYCLE_BIN_PATH varchar,
AUTO_ROTATE Boolean,
DEFAULT_REFIMG_MODEL integer, DEFAULT_SCAN_MODEL integer, DEFAULT_THRESHOLD float, DEFAULT_REFIMG_MODEL integer, DEFAULT_SCAN_MODEL integer, DEFAULT_THRESHOLD float,
FACE_SIZE_LIMIT integer, FACE_SIZE_LIMIT integer,
SCHEDULED_IMPORT_SCAN integer, SCHEDULED_STORAGE_SCAN integer, SCHEDULED_IMPORT_SCAN integer, SCHEDULED_STORAGE_SCAN integer,
@@ -166,7 +167,7 @@ insert into PERSON values ( (select nextval('PERSON_ID_SEQ')), 'mum', 'Mandy', '
insert into PERSON values ( (select nextval('PERSON_ID_SEQ')), 'cam', 'Cameron', 'De Paoli' ); insert into PERSON values ( (select nextval('PERSON_ID_SEQ')), 'cam', 'Cameron', 'De Paoli' );
insert into PERSON values ( (select nextval('PERSON_ID_SEQ')), 'mich', 'Michelle', 'De Paoli' ); insert into PERSON values ( (select nextval('PERSON_ID_SEQ')), 'mich', 'Michelle', 'De Paoli' );
-- DEV(ddp): -- DEV(ddp):
insert into SETTINGS ( id, base_path, import_path, storage_path, recycle_bin_path, default_refimg_model, default_scan_model, default_threshold, face_size_limit, scheduled_import_scan, scheduled_storage_scan, scheduled_bin_cleanup, bin_cleanup_file_age, job_archive_age ) values ( (select nextval('SETTINGS_ID_SEQ')), '/home/ddp/src/photoassistant/', 'images_to_process/', 'photos/', '.pa_bin/', 1, 1, '0.55', 43, 1, 1, 7, 30, 3 ); insert into SETTINGS ( id, base_path, import_path, storage_path, recycle_bin_path, auto_rotate, default_refimg_model, default_scan_model, default_threshold, face_size_limit, scheduled_import_scan, scheduled_storage_scan, scheduled_bin_cleanup, bin_cleanup_file_age, job_archive_age ) values ( (select nextval('SETTINGS_ID_SEQ')), '/home/ddp/src/photoassistant/', 'images_to_process/', 'photos/', '.pa_bin/', true, 1, 1, '0.55', 43, 1, 1, 7, 30, 3 );
-- DEV(cam): -- DEV(cam):
--insert into SETTINGS ( id, base_path, import_path, storage_path, recycle_bin_path, default_refimg_model, default_scan_model, default_threshold, face_size_limit, scheduled_import_scan, scheduled_storage_scan, scheduled_bin_cleanup, bin_cleanup_file_age, job_archive_age ) values ( (select nextval('SETTINGS_ID_SEQ')), 'c:/Users/cam/Desktop/code/python/photoassistant/', 'c:\images_to_process', 'photos/', '.pa_bin/', 1, 1, '0.55', 43, 1, 1, 7, 30, 3 ); --insert into SETTINGS ( id, base_path, import_path, storage_path, recycle_bin_path, default_refimg_model, default_scan_model, default_threshold, face_size_limit, scheduled_import_scan, scheduled_storage_scan, scheduled_bin_cleanup, bin_cleanup_file_age, job_archive_age ) values ( (select nextval('SETTINGS_ID_SEQ')), 'c:/Users/cam/Desktop/code/python/photoassistant/', 'c:\images_to_process', 'photos/', '.pa_bin/', 1, 1, '0.55', 43, 1, 1, 7, 30, 3 );
-- PROD: -- PROD:

View File

@@ -8,17 +8,19 @@
{{field}} {{field}}
{% elif field.type != 'SubmitField' %} {% elif field.type != 'SubmitField' %}
<div class="input-group"> <div class="input-group">
{{ field.label( class="input-group-text col-3 justify-content-end", title=HELP[field.name] ) }} {{ field.label( class="input-group-text col-4 justify-content-end", title=HELP[field.name] ) }}
{% if field.type == 'SelectField' %} {% if field.type == 'SelectField' %}
{{ field( class="form-select col-9" ) }} {{ field( class="form-select col-8" ) }}
{% elif field.type == 'BooleanField' %}
{{ field( class="form-check form-check-input mt-2" ) }}
{% else %} {% else %}
{{ field( class="form-control col-9" ) }} {{ field( class="form-control col-8" ) }}
{% endif %} {% endif %}
</div class=""> </div class="">
{% endif %} {% endif %}
{% endfor %} {% endfor %}
<div class="form-row col-12"> <div class="form-row col-12">
{{form.submit(class="btn btn-primary offset-2 col-2 mt-4" )}} {{form.submit(class="btn btn-primary offset-4 col-2 mt-4" )}}
</div class="row"> </div class="row">
</div class="form"> </div class="form">
</div class="row"> </div class="row">