diff --git a/files.py b/files.py index e5d2a69..3c26b0c 100644 --- a/files.py +++ b/files.py @@ -32,7 +32,7 @@ from person import Refimg, Person, PersonRefimgLink from settings import Settings, SettingsIPath, SettingsSPath, SettingsRBPath from shared import SymlinkName from dups import Duplicates -from face import Face, FaceFileLink, FaceRefimgLink, FaceNoMatchOverride, FaceManualOverride +from face import Face, FaceFileLink, FaceRefimgLink, FaceOverrideType, FaceNoMatchOverride, FaceManualOverride # pylint: disable=no-member @@ -737,6 +737,7 @@ def view(id): face.no_match_override=fnmo mo = FaceManualOverride.query.filter(FaceManualOverride.face_id==face.id).first() if mo: + mo.type = FaceOverrideType.query.filter( FaceOverrideType.name== 'Manual match to existing person' ).first() face.manual_override=mo @@ -753,7 +754,8 @@ def view(id): st.SetMessage( msg, "warning" ) return redirect("/") else: - return render_template("viewer.html", current=int(id), eids=eids, objs=objs, OPT=OPT ) + NMO_data = FaceOverrideType.query.all() + return render_template("viewer.html", current=int(id), eids=eids, objs=objs, OPT=OPT, NMO_data=NMO_data ) ################################################################################## # /view/id -> grabs data from DB and views it (POST -> set state, redirect to GET) diff --git a/internal/js/view_support.js b/internal/js/view_support.js index a41da57..f854dd3 100644 --- a/internal/js/view_support.js +++ b/internal/js/view_support.js @@ -1,4 +1,3 @@ -// work out new width for canvas. depending on whether width > height of the // image, then use the width of the image (with the specified gap) otherwise // the height > width, so scale the new width based on height ratio of // image to window @@ -116,19 +115,20 @@ function DrawImg() // this face has an override so diff colour if( objs[current].faces[i].override ) - context.strokeStyle = 'blue' - else - context.strokeStyle = 'green' - - if( objs[current].faces[i].no_match_override) - DrawLabelOnFace( objs[current].faces[i].no_match_override.type ) - - if( objs[current].faces[i].who ) { - str=objs[current].faces[i].who - if( $('#distance').prop('checked') ) - str += "("+objs[current].faces[i].distance+")" - DrawLabelOnFace( str ) + context.strokeStyle = 'blue' + DrawLabelOnFace( objs[current].faces[i].override.who ) + } + else + { + context.strokeStyle = 'green' + if( objs[current].faces[i].who ) + { + str=objs[current].faces[i].who + if( $('#distance').prop('checked') ) + str += "("+objs[current].faces[i].distance+")" + DrawLabelOnFace( str ) + } } context.stroke(); } @@ -230,7 +230,7 @@ $(document).ready( function() { if( objs[current].faces[i].override ) { - item_list['remove_override']={ 'name': 'Remove override for this face', 'which_face': i, 'id': objs[current].faces[i].id } + item_list['remove_override_force_match']={ 'name': 'Remove override for this face', 'which_face': i, 'id': objs[current].faces[i].id } } else if( objs[current].faces[i].who ) { @@ -241,10 +241,9 @@ $(document).ready( function() { item_list['no_match_new_person']={ 'name': 'Add as reference image to NEW person', 'which_face': i, 'id': objs[current].faces[i].id } item_list['no_match_new_refimg']={ 'name': 'Add as reference image to EXISTING person', 'which_face': i, 'id': objs[current].faces[i].id } - item_list['no_match_override_match']={ 'name': 'Manually match to existing person', 'which_face': i, 'id': objs[current].faces[i].id } - item_list['no_match_no_face']={ 'name': 'Mark as not a face', 'which_face': i, 'id': objs[current].faces[i].id } - item_list['no_match_too_young']={ 'name': 'Mark as face too young', 'which_face': i, 'id': objs[current].faces[i].id } - item_list['no_match_ignore']={ 'name': 'Ignore this face', 'which_face': i, 'id': objs[current].faces[i].id } + for( var el in NMO ) { + item_list['NMO_'+el]={'type_id': NMO[el].type_id, 'name': 'Override: ' + NMO[el].name, 'which_face': i, 'id': objs[current].faces[i].id } + } } delete item_list['not_a_face'] $('#canvas').prop('menu_item', item_list ) @@ -264,17 +263,24 @@ $(document).ready( function() } ); // quick wrapper function to make calling this ajax code simpler in SearchForPerson -function OverrideForceMatch( person_id, face_id, face_pos ) +function OverrideForceMatch( person_id, face_id, face_pos, type_id ) { + // we have type_id passed in, so dig the NMO out, and use that below (its really just for name, but in case we change that in the DB) + for( el in NMO ) + { + if( NMO[el].type_id == type_id ) + { + fm_idx=el + break + } + } ofm='&person_id='+person_id+'&face_id='+face_id $.ajax({ type: 'POST', data: ofm, url: '/override_force_match', success: function(data) { - // on success, remember the original data, apply override values, close the dbox, force face drawing boxes on and redraw the face with the new override - if( objs[current].faces[face_pos].who ) - objs[current].faces[face_pos].old_who=objs[current].faces[face_pos].who - objs[current].faces[face_pos].old_distance=objs[current].faces[face_pos].distance - objs[current].faces[face_pos].who=data.person_tag - objs[current].faces[face_pos].distance="N/A" - objs[current].faces[face_pos].override=1 + objs[current].faces[face_pos].override={} + objs[current].faces[face_pos].override.who=data.person_tag + objs[current].faces[face_pos].override.distance='N/A' + objs[current].faces[face_pos].override.type_id=NMO[fm_idx].id + objs[current].faces[face_pos].override.type_name=NMO[fm_idx].name $('#dbox').modal('hide') $('#faces').prop('checked',true) @@ -286,7 +292,7 @@ function OverrideForceMatch( person_id, face_id, face_pos ) // function to facilitate adding a face match override to this "found" person // uses Ajax to the f/e to get any person matching #stext's content (via any name/tag) // and displays results in #search_person_results -function SearchForPerson(face_id, face_pos) +function SearchForPerson(face_id, face_pos, type_id) { // make URI safe who = encodeURIComponent( $('#stext').val() ) @@ -296,12 +302,8 @@ function SearchForPerson(face_id, face_pos) content='Click one of the link(s) below to manually connect this face as once-off connection to the person:

' for( var key in data ) { var person = data[key]; - /* - content+= ''+person.firstname+' '+person.surname+' ('+person.tag+')
' - */ content+= ''+person.firstname+' '+person.surname+' ('+person.tag+')
' + +face_id+','+face_pos+','+type_id+')">'+person.firstname+' '+person.surname+' ('+person.tag+')
' } $('#search_person_results').html( content ) } @@ -309,16 +311,12 @@ function SearchForPerson(face_id, face_pos) return false } -function RemoveOverride() +function RemoveOverrideForceMatch(face_pos) { d='&face_id='+objs[current].faces[face_pos].id+'&person_tag='+objs[current].faces[face_pos].who+ '&file_eid='+current - $.ajax({ type: 'POST', data: d, url: '/remove_override', + $.ajax({ type: 'POST', data: d, url: '/remove_override_force_match', success: function(data) { - if( objs[current].faces[face_pos].old_who ) - objs[current].faces[face_pos].who=objs[current].faces[face_pos].old_who - else - delete objs[current].faces[face_pos].who delete objs[current].faces[face_pos].override $('#dbox').modal('hide') DrawImg() @@ -328,6 +326,37 @@ function RemoveOverride() return false } +function RemoveOverrideNoMatch(face_pos, type_id) +{ + d='&face_id='+objs[current].faces[face_pos].id+'&type_id='+type_id + $.ajax({ type: 'POST', data: d, url: '/remove_override_no_match', + success: function(data) { + delete objs[current].faces[face_pos].override + $('#dbox').modal('hide') + DrawImg() + return false + } + } ) + return false +} + +function AddNoMatchOverride(type_id, face_id, face_pos, type_id) +{ + d='&type_id='+type_id+'&face_id='+face_id + $.ajax({ type: 'POST', data: d, url: '/add_no_match_override', + success: function(data) { + objs[current].faces[face_pos].override={} + objs[current].faces[face_pos].override.who=NMO[type_id].name + objs[current].faces[face_pos].override.distance='N/A' + objs[current].faces[face_pos].override.type_id=type_id + objs[current].faces[face_pos].override.type_name=NMO[type_id].name + $('#dbox').modal('hide') + $('#faces').prop('checked',true) + DrawImg() + } + } ) +} + // function that is called when we click on a face in the viewer and we want to // potentially override the non-match / match... it shows the face, and then @@ -344,42 +373,60 @@ function FaceDBox(key, item) } } ) div+='

' - if ( key == 'remove_override' ) + if ( key == 'remove_override_force_match' ) { - div+='
remove this override (force match to: ' + objs[current].faces[face_pos].who + ')' + if( objs[current].faces[face_pos].override.type_name == 'Manual match to existing person' ) + div+='
remove this override (force match to: ' + objs[current].faces[face_pos].override.who + ')
' + else + div+='
remove this override (no match)
' div+='
' div+='' - div+='' + div+='' + else + div+='onClick="RemoveOverrideNoMatch(' +face_pos+','+objs[current].faces[face_pos].override.type_id+ ')">Remove' div+='
' } if ( key == 'no_match_new_person' ) { - div+='
create new person' + div+='
create new person NOT YET' } - if ( key == 'no_match_new_refimg' || key == 'no_match_override_match' ) + if ( key == 'no_match_new_refimg' ) { - div+='
search for existing person:
' - div+= - ` -
- -
-
-
` - } + div+='
search for existing person: NOT YET
' + } if ( key == 'wrong_person' ) { div+='
wrong person, so mark this as the wrong person/refimg connection, for face#' + item[key]['which_face'] div+='
face db id: ' + item[key]['id'] } - if ( key == 'no_match_no_face' || key == 'no_match_too_young' || key == 'no_match_ignore' ) + if( /NMO_/.test(key) ) { - div+='
just track this against face#' + item[key]['which_face'] - div+='
face db id: ' + item[key]['id'] - } + if( item[key].name == 'Override: Manual match to existing person' ) + { + div+='
search for existing person:
' + div+= + ` +
+ +
+
+
` + } + else + { + type_id=item[key].type_id + face_id=item[key].id + div+='
' + div+='' + div+='' + div+='
' + } + } $('#dbox-title').html(item[key]['name']) $('#dbox-content').html(div) $('#dbox').modal('show') diff --git a/person.py b/person.py index 5b602ec..72cc5fd 100644 --- a/person.py +++ b/person.py @@ -270,17 +270,17 @@ def override_force_match(): resp={} resp['person_tag']=p.tag return resp - + ################################################################################ -# /remove_override -> POST +# /remove_override_force_match -> POST ################################################################################ -@app.route("/remove_override", methods=["POST"]) +@app.route("/remove_override_force_match", methods=["POST"]) @login_required -def remove_override(): +def remove_override_force_match(): face_id = request.form['face_id'] person_tag = request.form['person_tag'] file_eid = request.form['file_eid'] - print( f"Remove override with face_id={face_id} to person_tag={person_tag}" ) + print( f"Remove override force match of face_id={face_id} to person_tag={person_tag}" ) FaceManualOverride.query.filter( FaceManualOverride.face_id==face_id ).delete() db.session.commit() @@ -288,3 +288,47 @@ def remove_override(): # this will reply to the Ajax / POST, and cause the page to re-draw with new face override resp={} return resp + +################################################################################ +# /remove_override_no_match -> POST +################################################################################ +@app.route("/remove_override_no_match", methods=["POST"]) +@login_required +def remove_override_no_match(): + face_id = request.form['face_id'] + type_id = request.form['type_id'] + print( f"Remove override of no match (type_id={type_id}) for face_id={face_id}" ) + + FaceNoMatchOverride.query.filter( FaceNoMatchOverride.face_id==face_id, FaceNoMatchOverride.type_id==type_id ).delete() + db.session.commit() + + # this will reply to the Ajax / POST, and cause the page to re-draw with new face override + resp={} + return resp + + +################################################################################ +# /add_no_match_override -> POST +################################################################################ +@app.route("/add_no_match_override", methods=["POST"]) +@login_required +def add_no_match_override(): + face_id = request.form['face_id'] + f = Face.query.get(face_id); + if not f: + raise Exception("could not find face to add override too!") + + type_id = request.form['type_id'] + t = FaceOverrideType.query.get(type_id); + if not t: + raise Exception("could not find override_type to add override for!") + + nmo = FaceNoMatchOverride( face_id=f.id, face=f.face, type_id=t.id ) + db.session.add( nmo ) + db.session.commit() + + print( f"Placing an override of NO Match for face_id {face_id}" ) + # this will reply to the Ajax / POST, and cause the page to re-draw with new face override to person_tag + resp={} + resp['type']=t.name + return resp diff --git a/tables.sql b/tables.sql index 10029c3..8c98913 100644 --- a/tables.sql +++ b/tables.sql @@ -95,10 +95,10 @@ create table FACE_REFIMG_LINK( FACE_ID integer, REFIMG_ID integer, FACE_DISTANCE create sequence FACE_OVERRIDE_TYPE_ID_SEQ; create sequence FACE_OVERRIDE_ID_SEQ; create table FACE_OVERRIDE_TYPE ( ID integer, NAME varchar unique, constraint PK_FACE_OVERRIDE_TYPE_ID primary key(ID) ); +insert into FACE_OVERRIDE_TYPE values ( (select nextval('FACE_OVERRIDE_TYPE_ID_SEQ')), 'Manual match to existing person' ); insert into FACE_OVERRIDE_TYPE values ( (select nextval('FACE_OVERRIDE_TYPE_ID_SEQ')), 'Not a face' ); insert into FACE_OVERRIDE_TYPE values ( (select nextval('FACE_OVERRIDE_TYPE_ID_SEQ')), 'Too young' ); insert into FACE_OVERRIDE_TYPE values ( (select nextval('FACE_OVERRIDE_TYPE_ID_SEQ')), 'Ignore face' ); -insert into FACE_OVERRIDE_TYPE values ( (select nextval('FACE_OVERRIDE_TYPE_ID_SEQ')), 'Manual match' ); -- keep non-redundant FACE because, when we rebuild data we may have a null FACE_ID, but still want to connect to this override -- from a previous AI pass... (would happen if we delete a file and then reimport/scan it), OR, more likely we change (say) a threshold, etc. diff --git a/templates/viewer.html b/templates/viewer.html index 72da1c9..399d41c 100644 --- a/templates/viewer.html +++ b/templates/viewer.html @@ -23,6 +23,7 @@ var throbber=0 var objs=[] + var NMO=[] var current={{current}} var eids="{{eids}}" var eid_lst=eids.split(",") @@ -49,22 +50,33 @@ data['distance']="{{face.refimg_lnk.face_distance|round(2)}}" {% endif %} {% if face.no_match_override %} - data['override']=1 - data['no_match_override'] = { - 'face_id' : '{{face.no_match_override.face_id}}', - 'type' : '{{face.no_match_override.type.name}}', + data['override'] = { + 'face_id' : '{{face.no_match_override.face_id}}', + 'type_id' : '{{face.no_match_override.type.id}}', + 'type_name': '{{face.no_match_override.type.name}}', + 'who' : '{{face.no_match_override.type.name}}', + 'distance' : 'N/A' } {% endif %} {% if face.manual_override %} - data['override']=1 - data['who']='{{face.manual_override.person.tag}}' - data['distance']="N/A" + data['override'] = { + 'face_id' : '{{face.manual_override.face_id}}', + 'type_id' : '{{face.manual_override.type.id}}', + 'type_name': '{{face.manual_override.type.name}}', + 'who' : '{{face.manual_override.person.tag}}', + 'distance' : 'N/A' + } {% endif %} e.faces.push( data ) {% endfor %} objs[{{id}}]=e {% endfor %} + {% for el in NMO_data %} + NMO[{{el.id}}] = { 'type_id': {{el.id}}, 'name': '{{el.name}}' } + {% endfor %} + + function PrettyFname(fname) {