From 9870ddbe861f74381bd8d55d6751d6dc15dc755f Mon Sep 17 00:00:00 2001 From: Piter Dias Date: Wed, 15 Apr 2020 14:03:58 -0300 Subject: [PATCH 1/2] =?UTF-8?q?face=5Frecognition.lua=20script=20face=20so?= =?UTF-8?q?me=20stability=20and=20performance=20issues=20on=20an=20i5-6600?= =?UTF-8?q?,=2032=20GB=20and=20GTX=201060=206GB:=20=20=20=20=201.=20Taking?= =?UTF-8?q?=20to=20much=20time=20=E2=80=93=20almost=20a=20day=20for=203000?= =?UTF-8?q?=20images=20=20=20=20=202.=20Crashing=20due=20to=20lines=20malf?= =?UTF-8?q?ormation=20in=20facerecognition.txt=20=E2=80=93=20some=20times?= =?UTF-8?q?=20face=5Frecognition=20Python=20script=20concatenates=20two=20?= =?UTF-8?q?output=20lines=20in=20just=20once,=20what=20is=20not=20expected?= =?UTF-8?q?=20by=20face=5Frecognition.lua=20script=20=20=20=20=203.=20Cras?= =?UTF-8?q?hing=20the=20whole=20system=20in=20Linux=20=E2=80=93=20I=20keep?= =?UTF-8?q?=20receiving=20=E2=80=9Cbash:=20fork:=20resource=20temporarily?= =?UTF-8?q?=20unavailable=E2=80=9D=20trying=20to=20process=20a=20too=20big?= =?UTF-8?q?=20(starting=20from=20some=20hundreds)=20image=20list.=20Some?= =?UTF-8?q?=20other=20applications=20starts=20to=20crash=20at=20this=20poi?= =?UTF-8?q?nt.=20It=20seems=20related=20to=20the=20attach=20tag=20call=20b?= =?UTF-8?q?ecause=20commenting=20this=20instruction=20(for=20debugging=20p?= =?UTF-8?q?urposes)=20eliminates=20the=20crashes.=20After=20debugging=20th?= =?UTF-8?q?e=20Lua=20script=20and=20checking=20Lua=20and=20SQL=20darktable?= =?UTF-8?q?=20out=20logs,=20I=20decided=20to=20make=20the=20following=20ch?= =?UTF-8?q?anges:=20=20=20=20=201.=20Remove=20an=20inner=20loop=20in=20the?= =?UTF-8?q?=20processing=20results=20step=20=E2=80=93=20helps=20preventing?= =?UTF-8?q?=20issue=201=20=20=20=20=202.=20Remove=20a=20loop=20in=20the=20?= =?UTF-8?q?export=20step=20=E2=80=93=20helps=20preventing=20issue=201=20?= =?UTF-8?q?=20=20=20=203.=20Remove=20duplicate=20tags=20(when=20there=20ar?= =?UTF-8?q?e=20more=20more=20than=20a=20reference=20face=20for=20each=20pe?= =?UTF-8?q?rson)=20while=20facerecognition.txt=20is=20read=20=E2=80=93=20h?= =?UTF-8?q?elps=20preventing=20issue=201=20and=203=20=20=20=20=204.=20Make?= =?UTF-8?q?=20a=20sanity=20check=20with=20processing=20images,=20skip=20an?= =?UTF-8?q?y=20malformation=20data=20=E2=80=93=20helps=20preventing=20issu?= =?UTF-8?q?e=202=20=20=20=20=205.=20Reduces=20the=20sqlite=20access=20by?= =?UTF-8?q?=20caching=20tags=20that=20where=20already=20created=20for=20a?= =?UTF-8?q?=20previous=20image=20=E2=80=93=20helps=20preventing=20issue=20?= =?UTF-8?q?3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Those changes allowed may thousands (more than 30K) of images to be processed in half a day instead of several days. --- contrib/face_recognition.lua | 140 ++++++++++++++++++----------------- 1 file changed, 73 insertions(+), 67 deletions(-) diff --git a/contrib/face_recognition.lua b/contrib/face_recognition.lua index fe1e9f6..64fefea 100644 --- a/contrib/face_recognition.lua +++ b/contrib/face_recognition.lua @@ -63,7 +63,7 @@ du.check_min_api_version("5.0.0", "face_recognition") gettext.bindtextdomain("face_recognition", dt.configuration.config_dir.."/lua/locale/") local function _(msgid) - return gettext.dgettext("face_recognition", msgid) + return gettext.dgettext("face_recognition", msgid) end -- preferences @@ -92,8 +92,10 @@ local function build_image_table(images) end for _,img in ipairs(images) do - image_table[img] = tmp_dir .. df.get_basename(img.filename) .. file_extension - cnt = cnt + 1 + if img ~= nil then + image_table[tmp_dir .. df.get_basename(img.filename) .. file_extension] = img + cnt = cnt + 1 + end end return image_table, cnt @@ -103,17 +105,12 @@ local function stop_job(job) job.valid = false end -local function do_export(img_tbl) +local function do_export(img_tbl, images) local exporter = nil local upsize = false - local upscale = false local ff = fc.export_format.value local height = dt.preferences.read(MODULE, "max_height", "integer") local width = dt.preferences.read(MODULE, "max_width", "integer") - local images = 0 - for k,v in pairs(img_tbl) do - images = images + 1 - end -- get the export format parameters if string.match(ff, "JPEG") then @@ -134,7 +131,7 @@ local function do_export(img_tbl) local exp_cnt = 0 local percent_step = 1.0 / images job.percent = 0.0 - for img,export in pairs(img_tbl) do + for export,img in pairs(img_tbl) do exp_cnt = exp_cnt + 1 dt.print(string.format(_("Exporting image %i of %i images"), exp_cnt, images)) exporter:write_image(img, export, upsize) @@ -188,7 +185,7 @@ local function ignoreByTag (image, ignoreTags) end end end - + return ignoreImage end @@ -221,25 +218,25 @@ local function face_recognition () if nrCores < 1 then nrCores = -1 end - + -- Split ignore tags (if any) ignoreTags = {} for tag in string.gmatch(ignoreTagString, '([^,]+)') do table.insert (ignoreTags, tag) dt.print_log ("Face recognition: Ignore tag: " .. tag) end - + -- list of exported images local image_table, cnt = build_image_table(dt.gui.action_images) if cnt > 0 then - local success = do_export(image_table) + local success = do_export(image_table, cnt) if success then -- do the face recognition local img_list = {} - for img,v in pairs(image_table) do + for v,_ in pairs(image_table) do table.insert (img_list, v) end @@ -248,7 +245,7 @@ local function face_recognition () dt.print_log ("Face recognition: Path to unknown images: " .. path) os.setlocale("C") local tolerance = dt.preferences.read(MODULE, "tolerance", "float") - + local command = bin_path .. " --cpus " .. nrCores .. " --tolerance " .. tolerance .. " " .. knownPath .. " " .. path .. " > " .. OUTPUT os.setlocale() dt.print_log("Face recognition: Running command: " .. command) @@ -258,61 +255,77 @@ local function face_recognition () -- Open output file local f = io.open(OUTPUT, "rb") - + if not f then dt.print(_("Face recognition failed")) else dt.print(_("Face recognition finished")) f:close () end - + -- Read output dt.print(_("processing results...")) local result = {} - for line in io.lines(OUTPUT) do - if not string.match(line, "^WARNING:") then + local tags_list = {} + local tag_object = {} + for line in io.lines(OUTPUT) do + if not string.match(line, "^WARNING:") and line ~= "" and line ~= nil then local file, tag = string.match (line, "(.*),(.*)$") tag = string.gsub (tag, "%d*$", "") dt.print_log ("File:"..file .." Tag:".. tag) - if result[file] ~= nil then - table.insert (result[file], tag) + tag_object = {} + if result[file] == nil then + tag_object[tag] = true + result[file] = tag_object else - result[file] = {tag} + tag_object = result[file] + tag_object[tag] = true + result[file] = tag_object end end end - + -- Attach tags + local result_index = 0 for file,tags in pairs(result) do + result_index = result_index +1 -- Find image in table - for img,file2 in pairs(image_table) do - if file == file2 then - for _,t in ipairs (tags) do - -- Check if image is ignored - if ignoreByTag (img, ignoreTags) then - dt.print_log("Face recognition: Ignoring image with ID " .. img.id) - else - -- Check of unrecognized unknown_person - if t == "unknown_person" then - t = unknownTag - end - -- Check of unrecognized no_persons_found - if t == "no_persons_found" then - t = nonpersonsfoundTag - end - if t ~= "" and t ~= nil then - dt.print_log ("ImgId:" .. img.id .. " Tag:".. t) - -- Create tag if it does not exists - local tag = dt.tags.create (t) - img:attach_tag (tag) + img = image_table[file] + if img == nil then + dt.print_log("Face recognition: Ignoring face recognition entry: " .. file) + else + for t,_ in pairs (tags) do + -- Check if image is ignored + if ignoreByTag (img, ignoreTags) then + dt.print_log("Face recognition: Ignoring image with ID " .. img.id) + else + -- Check of unrecognized unknown_person + if t == "unknown_person" then + t = unknownTag + end + -- Check of unrecognized no_persons_found + if t == "no_persons_found" then + t = nonpersonsfoundTag + end + if t ~= "" and t ~= nil then + dt.print_log ("ImgId:" .. img.id .. " Tag:".. t) + -- Create tag if it does not exist + if tags_list[t] == nil then + tag = dt.tags.create (t) + tags_list[t] = tag + else + tag = tags_list[t] end + img:attach_tag (tag) end end end end end - dt.print(_("face recognition complete")) cleanup(img_list) + dt.print_log("img_list cleaned-up") + dt.print_log("face recognition complete") + dt.print(_("face recognition complete")) else dt.print(_("image export failed")) return @@ -321,8 +334,6 @@ local function face_recognition () dt.print(_("no images selected")) return end - - end -- build the interface @@ -376,7 +387,7 @@ fc.known_image_path = dt.new_widget("file_chooser_button"){ is_directory = true, changed_callback = function(this) dt.preferences.write(MODULE, "known_image_path", "directory", this.value) - end +end } fc.export_format = dt.new_widget("combobox"){ @@ -403,9 +414,9 @@ fc.height = dt.new_widget("entry"){ fc.execute = dt.new_widget("button"){ label = "detect faces", - clicked_callback = function(this) + clicked_callback = function(this) face_recognition() - end +end } local widgets = { @@ -422,14 +433,14 @@ local widgets = { if dt.configuration.running_os == "windows" or dt.configuration.running_os == "macos" then table.insert(widgets, df.executable_path_widget({"face_recognition"})) end -table.insert(widgets, dt.new_widget("section_label"){ label = _("processing options")}) -table.insert(widgets, fc.tolerance) -table.insert(widgets, fc.num_cores) -table.insert(widgets, fc.export_format) -table.insert(widgets, dt.new_widget("box"){ - orientation = "horizontal", - dt.new_widget("label"){ label = _("width ")}, - fc.width, + table.insert(widgets, dt.new_widget("section_label"){ label = _("processing options")}) + table.insert(widgets, fc.tolerance) + table.insert(widgets, fc.num_cores) + table.insert(widgets, fc.export_format) + table.insert(widgets, dt.new_widget("box"){ + orientation = "horizontal", + dt.new_widget("label"){ label = _("width ")}, + fc.width, }) table.insert(widgets, dt.new_widget("box"){ orientation = "horizontal", @@ -439,18 +450,13 @@ table.insert(widgets, dt.new_widget("box"){ table.insert(widgets, fc.execute) fc.widget = dt.new_widget("box"){ - orientation = vertical, - reset_callback = function(this) + orientation = vertical, + reset_callback = function(this) reset_preferences() - end, - table.unpack(widgets), + end, + table.unpack(widgets), } ---fc.tolerance.value = dt.preferences.read(MODULE, "tolerance", "float") - --- Register ---dt.register_storage("module_face_recognition", _("Face recognition"), show_status, face_recognition) - dt.register_lib( "face_recognition", -- Module name _("face recognition"), -- Visible name From 8c75b9316dad27bdf7e06b0a801628527b7cffc9 Mon Sep 17 00:00:00 2001 From: Piter Dias Date: Thu, 16 Apr 2020 01:46:45 -0300 Subject: [PATCH 2/2] Rollback some indentation changes --- contrib/face_recognition.lua | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/contrib/face_recognition.lua b/contrib/face_recognition.lua index 64fefea..de98f0b 100644 --- a/contrib/face_recognition.lua +++ b/contrib/face_recognition.lua @@ -387,7 +387,7 @@ fc.known_image_path = dt.new_widget("file_chooser_button"){ is_directory = true, changed_callback = function(this) dt.preferences.write(MODULE, "known_image_path", "directory", this.value) -end + end } fc.export_format = dt.new_widget("combobox"){ @@ -414,9 +414,9 @@ fc.height = dt.new_widget("entry"){ fc.execute = dt.new_widget("button"){ label = "detect faces", - clicked_callback = function(this) + clicked_callback = function(this) face_recognition() -end + end } local widgets = { @@ -433,14 +433,14 @@ local widgets = { if dt.configuration.running_os == "windows" or dt.configuration.running_os == "macos" then table.insert(widgets, df.executable_path_widget({"face_recognition"})) end - table.insert(widgets, dt.new_widget("section_label"){ label = _("processing options")}) - table.insert(widgets, fc.tolerance) - table.insert(widgets, fc.num_cores) - table.insert(widgets, fc.export_format) - table.insert(widgets, dt.new_widget("box"){ - orientation = "horizontal", - dt.new_widget("label"){ label = _("width ")}, - fc.width, +table.insert(widgets, dt.new_widget("section_label"){ label = _("processing options")}) +table.insert(widgets, fc.tolerance) +table.insert(widgets, fc.num_cores) +table.insert(widgets, fc.export_format) +table.insert(widgets, dt.new_widget("box"){ + orientation = "horizontal", + dt.new_widget("label"){ label = _("width ")}, + fc.width, }) table.insert(widgets, dt.new_widget("box"){ orientation = "horizontal", @@ -450,11 +450,11 @@ table.insert(widgets, dt.new_widget("box"){ table.insert(widgets, fc.execute) fc.widget = dt.new_widget("box"){ - orientation = vertical, - reset_callback = function(this) + orientation = vertical, + reset_callback = function(this) reset_preferences() - end, - table.unpack(widgets), + end, + table.unpack(widgets), } dt.register_lib(