add photils plugin

This commit is contained in:
Tobias Scheck
2020-06-06 12:39:26 +02:00
parent 05d86e89da
commit 33f91af1af
3 changed files with 500 additions and 0 deletions

View File

@@ -54,6 +54,7 @@ LabelsToTags|Yes|LMW|Apply tags based on color labels and ratings
OpenInExplorer|No|LMW|Open the selected images in the system file manager
passport_guide|Yes|LMW|Add passport cropping guide to darkroom crop tool
pdf_slideshow|No|LM|Export images to a PDF slideshow
[photils](https://github.com/scheckmedia/photils-dt)|No|LM|Automatic tag suggestions for your images
quicktag|Yes|LMW|Create shortcuts for quickly applying tags
rate_group|Yes|LMW|Apply or remove a star rating from grouped images
rename-tags|Yes|LMW|Change a tag name

416
contrib/photils.lua Normal file
View File

@@ -0,0 +1,416 @@
--[[ photils Auto Tagging plugin
copyright (c) 2020 Tobias Scheck
darktable is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
darktable is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with darktable. If not, see <http://www.gnu.org/licenses/>.
--]]
--[[
A darktable plugin which tries to predict keywords based on the selected image.
This plugin uses photils-cli to handle this task. Photils-cli is an application
which passes the image through a neural network and generates a feature vector.
This feature vector will be used to find similar images in a self-hosted online
database and the corresponding tags. The usage of the feature vector has this advantage
that at no time your selected image will leave the PC. Rather a representation
of the image is sent.
ADDITIONAL SOFTWARE NEEDED FOR THIS SCRIPT
* photils-cli - https://github.com/scheckmedia/photils-cli
USAGE
* require this script from your main lua file
To do this add this line to the file .config/darktable/luarc:
require "contrib/photils"
* Select an image
* Press "Get Tags"
* Select the tags you want from a list of suggestions
* Press "Attach .. Tags" to add the selected tags to your image
--]]
local dt = require "darktable"
local du = require "lib/dtutils"
local df = require "lib/dtutils.file"
local dtsys = require "lib/dtutils.system"
local MODULE_NAME = "photils"
du.check_min_api_version("5.0.0", MODULE_NAME)
local PS = dt.configuration.running_os == "windows" and "\\" or "/"
local gettext = dt.gettext
gettext.bindtextdomain(MODULE_NAME,
dt.configuration.config_dir .. PS .. "lua" .. PS .. "locale" .. PS)
local exporter = dt.new_format("jpeg")
exporter.quality = 80
exporter.max_height = 224
exporter.max_width = 224
-- helper functions
local function _(msgid)
return gettext.dgettext(MODULE_NAME, msgid)
end
local function num_keys(tbl)
local num = 0
for _ in pairs(tbl) do num = num + 1 end
return num
end
local function has_key(tbl, value)
for k, _ in pairs(tbl) do
if k == value then
return true
end
end
return false
end
local photils_installed = df.check_if_bin_exists("photils-cli")
--[[
local state object
maybe per_page is a preference variable but I think 10
is a good value for the ui
]]
local PHOTILS = {
tags = {},
page = 1,
per_page = 10,
selected_tags = {},
in_pagination = false,
tagged_image = ""
}
local GUI = {
container = dt.new_widget("box") {
orientation = "vertical",
sensitive = true,
dt.new_widget("button") {
label = _("Get Tags"),
sensitive = photils_installed,
clicked_callback = function() PHOTILS.on_tags_clicked() end
},
reset_callback = function() PHOTILS.on_reset(true) end
},
stack = dt.new_widget("stack"),
prev_button = dt.new_widget("button") {
label = "<",
sensitive = false,
clicked_callback = function()
PHOTILS.page = PHOTILS.page - 1
PHOTILS.paginate()
end
},
next_button = dt.new_widget("button") {
label = ">",
sensitive = false,
clicked_callback = function()
PHOTILS.page = PHOTILS.page + 1
PHOTILS.paginate()
end
},
tag_box = dt.new_widget("box") {orientation = "vertical"},
tag_view = dt.new_widget("box") {orientation = "vertical"},
page_label = dt.new_widget("label") {label = ""},
error_view = dt.new_widget("box") {orientation = "vertical"},
warning_label = dt.new_widget("label") {
label = ""
},
restart_required_label = dt.new_widget("label") {
label = _("requires a restart to be applied")
},
attach_button = dt.new_widget("button") {
label = "",
sensitive = false,
clicked_callback = function() PHOTILS.attach_tags() end
},
confidence_slider = dt.new_widget("slider") {
step = 1,
digits = 0,
value = 90,
hard_max = 100,
hard_min = 0,
soft_max = 100,
soft_min = 0,
label = _("Min Confidence Value")
},
warning = dt.new_widget("label")
}
function PHOTILS.image_changed()
local current_image = tostring(dt.gui.selection()[1])
if current_image ~= PHOTILS.tagged_image then
if PHOTILS.tagged_image ~= "" then
PHOTILS.tagged_image_has_changed()
end
PHOTILS.tagged_image = tostring(current_image)
end
end
function PHOTILS.tagged_image_has_changed()
GUI.warning.label = _(
"The suggested tags were not generated\n for the currently selected image!")
end
function PHOTILS.paginate()
PHOTILS.in_pagination = true
local num_pages = math.ceil(#PHOTILS.tags / PHOTILS.per_page)
GUI.page_label.label = string.format(_(" Page %s of %s "), PHOTILS.page,
num_pages)
if PHOTILS.page <= 1 then
PHOTILS.page = 1
GUI.prev_button.sensitive = false
else
GUI.prev_button.sensitive = true
end
if PHOTILS.page > num_pages - 1 then
PHOTILS.page = num_pages
GUI.next_button.sensitive = false
else
GUI.next_button.sensitive = true
end
local offset = ((PHOTILS.page - 1) * PHOTILS.per_page) + 1
local tag_index = 1
for i = offset, offset + PHOTILS.per_page - 1, 1 do
local tag = PHOTILS.tags[i]
GUI.tag_box[tag_index].value = has_key(PHOTILS.selected_tags, tag)
if tag then
GUI.tag_box[tag_index].label = tag
GUI.tag_box[tag_index].sensitive = true
else
GUI.tag_box[tag_index].label = ""
GUI.tag_box[tag_index].sensitive = false
end
tag_index = tag_index + 1
end
PHOTILS.in_pagination = false
end
function PHOTILS.attach_tags()
local image = dt.gui.selection()[1]
for tag, _ in pairs(PHOTILS.selected_tags) do
local dt_tag = dt.tags.create(tag)
dt.tags.attach(dt_tag, image)
end
dt.print(_("Tags successfully attached to image"))
end
function PHOTILS.get_tmp_file()
local tmp_file = os.tmpname()
if dt.configuration.running_os == "windows" then
tmp_file = dt.configuration.tmp_dir .. tmp_file -- windows os.tmpname() defaults to root directory
end
local f = io.open(tmp_file, "w")
if not f then
dt.print_log(string.format(_("Error writing to `%s`"), tmp_file))
os.remove(tmp_file)
return nil
end
return tmp_file
end
function PHOTILS.get_tags(image, with_export)
local tmp_file = PHOTILS.get_tmp_file()
local in_arg = df.sanitize_filename(tostring(image))
local out_arg = df.sanitize_filename(tmp_file)
local executable = photils_installed
if dt.configuration.running_os == "macos" then
executable = executable .. "/Contents/MacOS/photils-cli"
end
if with_export then
dt.print_log("use export to for prediction")
local export_file = PHOTILS.get_tmp_file()
exporter:write_image(image, export_file)
in_arg = df.sanitize_filename(tostring(export_file))
end
local command = executable .. " -c " .. " -i " .. in_arg .. " -o " .. out_arg
local ret = dtsys.external_command(command)
if ret > 0 then
dt.print_error(string.format("command %s returned error code %d", command, ret))
os.remove(tmp_file)
-- try to export the image and run tagging
if not with_export then
return PHOTILS.get_tags(image, true)
end
return false
end
for i = #PHOTILS.tags, 1, -1 do
PHOTILS.tags[i] = nil
end
for tag in io.lines(tmp_file) do
local splitted = du.split(tag, ":")
if 100 * tonumber(splitted[2]) >= GUI.confidence_slider.value then
PHOTILS.tags[#PHOTILS.tags + 1] = splitted[1]
end
end
dt.print(string.format(_("%s found %d tags for your image"), MODULE_NAME,
#PHOTILS.tags))
os.remove(tmp_file)
return true
end
function PHOTILS.on_tags_clicked()
PHOTILS.page = 1
GUI.warning.label = ""
PHOTILS.on_reset(false)
local images = dt.gui.selection()
if #images == 0 then
dt.print(_("No image selected."))
dt.control.sleep(2000)
else
if #images > 1 then
dt.print(_("This plugin can only handle a single image."))
dt.gui.selection({images[1]})
dt.control.sleep(2000)
end
if not PHOTILS.get_tags(images[1], true) then
local msg = string.format(_("%s failed, see terminal output for details"), MODULE_NAME)
GUI.warning_label.label = msg
GUI.stack.active = GUI.error_view
dt.print(msg)
return
end
if #PHOTILS.tags == 0 then
local msg = string.format(_("No tags where found"), MODULE_NAME)
GUI.warning_label.label = msg
GUI.stack.active = GUI.error_view
return
end
GUI.stack.active = GUI.tag_view
PHOTILS.paginate()
end
end
function PHOTILS.tag_selected(tag_button)
if PHOTILS.in_pagination then return end
if tag_button.value then
PHOTILS.selected_tags[tag_button.label] = tag_button.label
else
PHOTILS.selected_tags[tag_button.label] = nil
end
local num_selected = num_keys(PHOTILS.selected_tags)
if num_selected == 0 then
GUI.attach_button.label = ""
GUI.attach_button.sensitive = false
else
GUI.attach_button.label = string.format(_("Attach %d Tags"),
num_selected)
GUI.attach_button.sensitive = true
end
end
function PHOTILS.on_reset(with_view)
if with_view then GUI.stack.active = 1 end
for k, _ in pairs(PHOTILS.selected_tags) do
PHOTILS.selected_tags[k] = nil
end
for _, v in ipairs(GUI.tag_box) do
v.value = false
end
GUI.attach_button.label = ""
GUI.attach_button.sensitive = false
end
-- add a fix number of buttons
for _ = 1, PHOTILS.per_page, 1 do
local btn_tag = dt.new_widget("check_button") {
label = "",
sensitive = false,
clicked_callback = PHOTILS.tag_selected
}
table.insert(GUI.tag_box, btn_tag)
end
if not photils_installed then
GUI.warning_label.label = _("photils-cli not found")
dt.print_log(_("photils-cli not found"))
else
GUI.warning_label.label = _("Select an image, click \"Get Tags\" and get \nsuggestions for tags.")
end
GUI.pagination = dt.new_widget("box") {
orientation = "horizontal",
GUI.prev_button,
GUI.page_label,
GUI.next_button
}
table.insert(GUI.error_view, GUI.warning_label)
if not photils_installed then
table.insert(GUI.error_view, df.executable_path_widget({"photils-cli"}))
table.insert(GUI.error_view, GUI.restart_required_label)
end
table.insert(GUI.stack, GUI.error_view)
table.insert(GUI.stack, GUI.tag_view)
table.insert(GUI.tag_view, GUI.pagination)
table.insert(GUI.tag_view, GUI.tag_box)
table.insert(GUI.tag_view, GUI.attach_button)
table.insert(GUI.tag_view, GUI.warning)
table.insert(GUI.container, GUI.confidence_slider)
table.insert(GUI.container, GUI.stack)
GUI.stack.active = 1
local plugin_display_views = {
[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 100},
[dt.gui.views.darkroom] = {"DT_UI_CONTAINER_PANEL_LEFT_CENTER", 100}
}
-- dt.control.dispatch(PHOTILS.image_changed)
dt.register_event("mouse-over-image-changed",PHOTILS.image_changed)
dt.register_lib(MODULE_NAME,
"photils autotagger",
true,
true,
plugin_display_views,
GUI.container,
nil,
nil
)

View File

@@ -0,0 +1,83 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: Lua photils\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-05-12 13:12+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: de\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: ../../../contrib/photils.lua:97
msgid "Get Tags"
msgstr "Hole Stichwörter"
#: ../../../contrib/photils.lua:128
msgid "requires a restart to be applied"
msgstr "erfordert die Neustart um angewendet zu werden"
#: ../../../contrib/photils.lua:155
msgid ""
"The suggested tags were not generated\n"
" for the currently selected image!"
msgstr ""
"Die vorgeschlagenen Stichwörter wurden für das\n"
"aktuell ausgewählte Bild nicht generiert!"
#: ../../../contrib/photils.lua:161
#, lua-format
msgid " Page %s of %s "
msgstr " Seite %s von %s "
#: ../../../contrib/photils.lua:204
msgid "Tags successfully attached to image"
msgstr "Stichwörter erfolgreich an Bild angefügt"
#: ../../../contrib/photils.lua:215
#, lua-format
msgid "Error writing to `%s`"
msgstr "Fehler beim Schreiben in `%s`"
#: ../../../contrib/photils.lua:229
#, lua-format
msgid "%s failed, see terminal output for details"
msgstr "%s fehlgeschlagen, siehe Terminal-Ausgabe für Details"
#: ../../../contrib/photils.lua:243
#, lua-format
msgid "%s found %d tags for your image"
msgstr "%s gefunden %d-Stichwörter für Ihr Bild"
#: ../../../contrib/photils.lua:257
msgid "No image selected."
msgstr "Kein Bild ausgewählt."
#: ../../../contrib/photils.lua:262
msgid "This plugin can only handle a single image."
msgstr "Dieses Plugin kann nur ein einziges Bild verarbeiten."
#: ../../../contrib/photils.lua:293
#, lua-format
msgid "Attach %d Tags"
msgstr "%d-Stichwörter anhängen"
#: ../../../contrib/photils.lua:326 ../../../contrib/photils.lua:327
msgid "photils-cli not found"
msgstr "photils-cli nicht gefunden"
#: ../../../contrib/photils.lua:329
msgid ""
"Select an image, click \"Get Tags\" and get \n"
"suggestions for tags."
msgstr ""
"Wählen Sie ein Bild aus, drücken Sie \"Hole Stichwörter\"\n"
" und erhalten Sie Vorschläge für Stichwörter."