Skip to content

Commit

Permalink
added button "Review ignored people" to review again ignored/hidden p…
Browse files Browse the repository at this point in the history
…eople. This button only appears when ignored/hidden people are found.

Signed-off-by: wronny <[email protected]>
  • Loading branch information
wronny committed Apr 30, 2024
1 parent d5478c5 commit de90639
Show file tree
Hide file tree
Showing 5 changed files with 260 additions and 2 deletions.
8 changes: 7 additions & 1 deletion appinfo/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,12 @@
'url' => '/clusters',
'verb' => 'GET'
],
// Get all clusters ignored clusters.
[
'name' => 'cluster#findIgnored',
'url' => '/clustersIgnored',
'verb' => 'GET'
],
// Change visibility to cluster
[
'name' => 'cluster#setVisibility',
Expand Down Expand Up @@ -208,4 +214,4 @@
'verb' => 'GET',
],

]];
]];
50 changes: 50 additions & 0 deletions lib/Controller/ClusterController.php
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,56 @@ public function findUnassigned(): DataResponse {
return new DataResponse($resp);
}

/**
* @NoAdminRequired
*
* @return DataResponse
*/
Public function findIgnored(): DataResponse {
sleep(1); // button "Review ignored people" should be created delayed (after "Review people found" button)
$userEnabled = $this->settingsService->getUserEnabled($this->userId);

$resp = array();
$resp['enabled'] = $userEnabled;
$resp['clusters'] = array();

if (!$userEnabled)
return new DataResponse($resp);

$modelId = $this->settingsService->getCurrentFaceModel();
$minClusterSize = $this->settingsService->getMinimumFacesInCluster();

$clusters = $this->personMapper->findIgnored($this->userId, $modelId);
foreach ($clusters as $cluster) {
$clusterSize = $this->personMapper->countClusterFaces($cluster->getId());
if ($clusterSize < $minClusterSize)
continue;

$personFaces = $this->faceMapper->findFromCluster($this->userId, $cluster->getId(), $modelId, 40);
$faces = [];
foreach ($personFaces as $personFace) {
$image = $this->imageMapper->find($this->userId, $personFace->getImage());

$file = $this->urlService->getFileNode($image->getFile());
if ($file === null) continue;

$face = [];
$face['thumbUrl'] = $this->urlService->getThumbUrl($personFace->getId(), 50);
$face['fileUrl'] = $this->urlService->getRedirectToFileUrl($file);

$faces[] = $face;
}

$entry = [];
$entry['count'] = $clusterSize;
$entry['id'] = $cluster->getId();
$entry['faces'] = $faces;
$resp['clusters'][] = $entry;
}

return new DataResponse($resp);
}

/**
* @NoAdminRequired
*
Expand Down
29 changes: 29 additions & 0 deletions lib/Db/PersonMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,35 @@ public function findUnassigned(string $userId, int $modelId): array {
return $this->findEntities($qb);
}

/**
* @param string $userId ID of the user
* @param int $modelId ID of the model
* @return Person[]
*/
public function findIgnored(string $userId, int $modelId): array {
$sub = $this->db->getQueryBuilder();
$sub->select(new Literal('1'))
->from('facerecog_faces', 'f')
->innerJoin('f', 'facerecog_images' ,'i', $sub->expr()->eq('f.image', 'i.id'))
->where($sub->expr()->eq('p.id', 'f.person'))
->andWhere($sub->expr()->eq('i.user', $sub->createParameter('user_id')))
->andWhere($sub->expr()->eq('i.model', $sub->createParameter('model_id')));

$qb = $this->db->getQueryBuilder();
$qb->select('id', 'is_valid')
->from($this->getTableName(), 'p')
->where('EXISTS (' . $sub->getSQL() . ')')
->andWhere($qb->expr()->eq('is_valid', $qb->createParameter('is_valid')))
->andWhere($qb->expr()->eq('is_visible', $qb->createParameter('is_visible')))
->andWhere($qb->expr()->isNull('name'))
->setParameter('user_id', $userId)
->setParameter('model_id', $modelId)
->setParameter('is_valid', true, IQueryBuilder::PARAM_BOOL)
->setParameter('is_visible', false, IQueryBuilder::PARAM_BOOL);

return $this->findEntities($qb);
}

/**
* @param string $userId ID of the user
* @param int $modelId ID of the model
Expand Down
95 changes: 94 additions & 1 deletion src/dialogs.js
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,99 @@ const FrDialogs = {
});
},

assignIgnored: function (faces, callback) {
return $.when(this._getMessageTemplate()).then(function ($tmpl) {
var dialogName = 'fr-assign-dialog';
var dialogId = '#' + dialogName;
var $dlg = $tmpl.octemplate({
dialog_name: dialogName,
title: t('facerecognition', 'Add name'),
message: t('facerecognition', 'Please assign a name to this person.'),
type: 'none'
});

$dlg.append($('<br/>'));

var div = $('<div/>').attr('style', 'text-align: center');
$dlg.append(div);

for (var face of faces) {
if (face['fileUrl'] !== undefined) {
div.append($('<a href="' + face['fileUrl'] + '" target="_blank"><img class="face-preview-dialog" src="' + face['thumbUrl'] + '" width="50" height="50"/></a>'));
} else {
div.append($('<img class="face-preview-dialog" src="' + face['thumbUrl'] + '" width="50" height="50"/>'));
}
}

var input = $('<input/>').attr('type', 'text').attr('id', dialogName + '-input').attr('placeholder', t('facerecognition', 'Please assign a name to this person.'));
$dlg.append(input);

$('body').append($dlg);

// wrap callback in _.once():
// only call callback once and not twice (button handler and close
// event) but call it for the close event, if ESC or the x is hit
if (callback !== undefined) {
callback = _.once(callback);
}

var buttonlist = [{
text: t('facerecognition', 'Keep ignored'),
click: function () {
$(dialogId).ocdialog('close');
if (callback !== undefined) {
callback(true, null);
}
},
}, {
text: t('facerecognition', 'Save'),
click: function () {
$(dialogId).ocdialog('close');
if (callback !== undefined) {
callback(true, input.val().trim());
}
},
defaultButton: true
}];

$(dialogId).ocdialog({
closeOnEscape: true,
modal: true,
buttons: buttonlist,
close: function () {
// callback is already fired if Yes/No is clicked directly
if (callback !== undefined) {
callback(false, '');
}
}
});

new AutoComplete({
input: document.getElementById(dialogName + "-input"),
lookup (query) {
return new Promise(resolve => {
$.get(OC.generateUrl('/apps/facerecognition/autocomplete/' + query)).done(function (names) {
resolve(names);
});
});
},
silent: true,
highlight: false
});

$(dialogId + "-input").keydown(function(event) {
// It only prevents the that change the image when you press arrow keys.
event.stopPropagation();
if (event.key === "Enter") {
// It only prevents the that change the image when you press enter.
event.preventDefault();
}
});

input.focus();
});
},

_getMessageTemplate: function () {
var defer = $.Deferred();
if (!this.$messageTemplate) {
Expand All @@ -390,4 +483,4 @@ const FrDialogs = {
return defer.promise();
}

}
}
80 changes: 80 additions & 0 deletions src/personal.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ var Persons = function (baseUrl) {
this._clustersByName = [];

this._unassignedClusters = [];
this._ignoredClusters = [];

this._enabled = false;
this._loaded = false;
Expand Down Expand Up @@ -142,12 +143,29 @@ Persons.prototype = {
});
return deferred.promise();
},
loadIgnoredClusters: function(){
this._ignoredClusters = [];
var deferred = $.Deferred();
var self = this;
$.get(this._baseUrl+'/clustersIgnored').done(function (clusters) {
self._ignoredClusters = clusters.clusters.sort(function(a, b) {
return b.count - a.count;
});
deferred.resolve();
}).fail(function () {
deferred.reject();
});
return deferred.promise();
},
getClustersByName: function () {
return this._clustersByName;
},
getUnassignedClusters: function () {
return this._unassignedClusters;
},
getIgnoredClusters: function () {
return this._ignoredClusters;
},
getNamedClusterById: function (clusterId) {
var ret = undefined;
for (var cluster of this._clustersByName) {
Expand Down Expand Up @@ -257,6 +275,18 @@ View.prototype = {
}
});
},
searchIgnoredClusters: function () {
var self = this;
self._persons.loadIgnoredClusters().done(function () {
if (self._persons.getIgnoredClusters().length > 0) {
var button = $("<button id='show-ignored-clusters' type='button' class='primary'>" + t('facerecognition', 'Review ignored people') + "</button>");
$('#optional-buttons-div').append(button);
button.click(function () {
self.renameIgnoredClusterDialog();
});
}
});
},
renameUnassignedClusterDialog: function () {
var self = this;
var unassignedClusters = this._persons.getUnassignedClusters();
Expand Down Expand Up @@ -295,6 +325,44 @@ View.prototype = {
}
);
},
renameIgnoredClusterDialog: function () {
var self = this;
var ignoredClusters = this._persons.getIgnoredClusters();
var cluster = ignoredClusters.shift();
if (cluster === undefined) {
self.renderContent();
if (self._persons.mustReload())
self.reload();
return;
}
FrDialogs.assignIgnored(cluster.faces,
function(result, name) {
if (result === true) {
if (name !== null) {
if (name.length > 0) {
self._persons.renameCluster(cluster.id, name).done(function () {
self.renameIgnoredClusterDialog();
}).fail(function () {
OC.Notification.showTemporary(t('facerecognition', 'There was an error renaming this person'));
});
} else {
self.renameIgnoredClusterDialog();
}
} else {
self._persons.setClusterVisibility(cluster.id, false).done(function () {
self.renameIgnoredClusterDialog();
}).fail(function () {
OC.Notification.showTemporary(t('facerecognition', 'There was an error ignoring this person'));
});
}
} else {
// Cancelled
if (self._persons.mustReload())
self.reload();
}
}
);
},
renderContent: function () {
var context = {
loaded: this._persons.isLoaded(),
Expand All @@ -303,6 +371,7 @@ View.prototype = {
enableDescription: t('facerecognition', 'Analyze my images and group my loved ones with similar faces'),
loadingMsg: t('facerecognition', 'Looking for your recognized friends'),
showMoreButton: t('facerecognition', 'Review face groups'),
showIgnoredButton: t('facerecognition', 'Review ignored people'),
emptyMsg: t('facerecognition', 'The analysis is disabled'),
emptyHint: t('facerecognition', 'Enable it to find your loved ones'),
renameHint: t('facerecognition', 'Rename'),
Expand Down Expand Up @@ -480,6 +549,16 @@ View.prototype = {
});
});

$('#facerecognition #show-ignored-clusters').click(function () {
$(this).css("cursor", "wait");
var person = self._persons.getActivePerson();
self._persons.loadClustersByName(person.name).done(function () {
self.renderContent();
}).fail(function () {
OC.Notification.showTemporary(t('facerecognition', 'There was an error when trying to find photos of your friend'));
});
});

$('#facerecognition .icon-back').click(function () {
self._persons.unsetActive();
self.renderContent();
Expand Down Expand Up @@ -550,6 +629,7 @@ if (personName !== undefined) {
persons.load().done(function () {
view.renderContent();
view.searchUnassignedClusters();
view.searchIgnoredClusters();
}).fail(function () {
OC.Notification.showTemporary(t('facerecognition', 'There was an error trying to show your friends'));
});
Expand Down

0 comments on commit de90639

Please sign in to comment.