-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathAddaxProcess.lua
More file actions
191 lines (166 loc) · 9.03 KB
/
AddaxProcess.lua
File metadata and controls
191 lines (166 loc) · 9.03 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
local LrApplication = import 'LrApplication'
local LrTasks = import 'LrTasks'
local LrDialogs = import 'LrDialogs'
local LrExportSession = import 'LrExportSession'
local LrPathUtils = import 'LrPathUtils'
local LrFileUtils = import 'LrFileUtils'
local LrErrors = import 'LrErrors'
local LrFunctionContext = import 'LrFunctionContext'
local LrPrefs = import 'LrPrefs'
local LrLogger = import 'LrLogger'
local LrProgressScope = import 'LrProgressScope'
-- External modules
local AddaxKeywords = require 'AddaxKeywords'
local logger = import 'LrLogger'('AddaxProcess')
local prefs = LrPrefs.prefsForPlugin()
local catalog = LrApplication.activeCatalog()
-- --- LOGGING HELPERS ---
--- Logs professional status messages to the Desktop DebugLog.
-- @param msg The message to log.
local function debugLog(msg)
if prefs.logging then
local desktop = LrPathUtils.getStandardFilePath('desktop')
local logPath = LrPathUtils.child(desktop, "Addax_DebugLog.txt")
local f = io.open(logPath, "a")
if f then
f:write(os.date("%H:%M:%S") .. " - " .. tostring(msg) .. "\n")
f:close()
end
end
end
-- --- REPORT MANAGEMENT ---
--- Saves the Addax-AI results JSON to the user-specified destination.
-- @param outputJson Path to the generated results JSON.
-- @param samplePhotoPaths List of original paths to determine the destination folder.
-- @param reportDestType The user preference for destination.
-- @param reportCustomPath The custom directory path if selected.
local function saveReport(outputJson, samplePhotoPaths, reportDestType, reportCustomPath)
if LrFileUtils.exists(outputJson) and reportDestType ~= "none" then
local baseName = "addax_report_" .. os.date("%Y%m%d_%H%M%S") .. ".json"
local finalDest = nil
if reportDestType == "same" and #samplePhotoPaths > 0 then
local parentFolder = LrPathUtils.parent(samplePhotoPaths[1])
if parentFolder then finalDest = LrPathUtils.child(parentFolder, baseName) end
elseif reportDestType == "custom" and reportCustomPath ~= "" then
if LrFileUtils.exists(reportCustomPath) then finalDest = LrPathUtils.child(reportCustomPath, baseName) end
end
if finalDest then
LrFileUtils.copy(outputJson, finalDest)
debugLog("Analysis report saved to: " .. finalDest)
end
end
end
-- --- MAIN WORKFLOW HANDLER ---
import 'LrTasks'.startAsyncTask(function()
LrFunctionContext.callWithContext("AddaxAnalysis", function(context)
-- Identify user selection
local targetPhotos = catalog:getTargetPhotos()
if #targetPhotos == 0 then return end
debugLog("Initializing new analysis session...")
-- Initialize session state
local pathMap = {}
local prefs = LrPrefs.prefsForPlugin()
local addaxPath = prefs.addaxPath or '/Applications/AddaxAI_files'
local modelPath = prefs.modelPath or ""
local exportRes = tonumber(prefs.exportRes) or 2048
local exportQuality = tonumber(prefs.exportQuality) or 60
local keywordThreshold = (tonumber(prefs.keywordThreshold) or 90) / 100
local reportDestType = prefs.reportDestType or "none"
local reportCustomPath = prefs.reportCustomPath or ""
local excludeListRaw = prefs.excludeList or "person, vehicle"
-- Build exclusion table for the keyword phase
local excludes = {}
for item in excludeListRaw:gmatch("([^,%s]+)") do excludes[item:lower()] = true end
-- Setup clean temporary workspace
local tempDir = LrPathUtils.child(LrPathUtils.getStandardFilePath('temp'), "AddaxLR_Session")
if LrFileUtils.exists(tempDir) then LrFileUtils.delete(tempDir) end
LrFileUtils.createAllDirectories(tempDir)
local progress = import 'LrProgressScope' { title = "Addax-AI Analysis", functionContext = context }
progress:setCaption("Phase 1: Generating image previews...")
-- Collect photos for export (Lightroom exports JPEGs for both images and videos)
local photosToProcess = {}
for _, photo in ipairs(targetPhotos) do
table.insert(photosToProcess, photo)
end
-- Perform export and build the preview-to-catalog mapping
if #photosToProcess > 0 then
local exportSettings = {
LR_format = "JPEG", LR_export_destinationType = "specificFolder", LR_export_destinationPathPrefix = tempDir,
LR_export_useSubfolder = false, LR_overwritePolicy = "overwrite", LR_size_doConstrain = true,
LR_size_maxSide = exportRes, LR_jpeg_quality = exportQuality / 100, LR_metadata_filterMode = "all",
}
local exportSession = LrExportSession({ photosToExport = photosToProcess, exportSettings = exportSettings })
for i, rendition in exportSession:renditions() do
if progress:isCanceled() then return end
local status = rendition:waitForRender()
if status then
local exportedName = LrPathUtils.leafName(rendition.destinationPath)
local originalPath = rendition.photo:getRawMetadata('path')
pathMap[exportedName] = originalPath
end
progress:setPortionComplete(i / #targetPhotos * 0.33)
end
end
-- Configure Python environment and script paths
local isWin = (LrPathUtils.getStandardFilePath('appData'):find(":") ~= nil)
local pythonExe = LrPathUtils.child(addaxPath, isWin and "envs\\env-pytorch\\python.exe" or "envs/env-pytorch/bin/python")
local scriptPath = LrPathUtils.child(_PLUGIN.path, "scripts/addax_bridge.py")
local outputJson = LrPathUtils.child(tempDir, "results.json")
local pythonDebugLog = LrPathUtils.child(LrPathUtils.getStandardFilePath('desktop'), "Addax_PythonDebug.txt")
-- Build the search path for the Python sub-process
local ctPath = LrPathUtils.child(addaxPath, "cameratraps")
local mdPath = LrPathUtils.child(ctPath, "megadetector")
local aiPath = LrPathUtils.child(addaxPath, "AddaxAI")
local yoloPath = LrPathUtils.child(addaxPath, "yolov5_versions/yolov5_new/yolov5")
local sep = isWin and ";" or ":"
local pyPath = string.format("%s%s%s%s%s%s%s%s%s", yoloPath, sep, mdPath, sep, ctPath, sep, aiPath, sep, addaxPath)
local cmd = string.format('"%s" "%s" --addax-path "%s" --model-path "%s" --image-dir "%s" --output-json "%s" --exclude "%s" > "%s" 2>&1',
pythonExe, scriptPath, addaxPath, modelPath, tempDir, outputJson, excludeListRaw, pythonDebugLog)
-- Execute the Addax-AI bridge
progress:setCaption("Phase 2: Addax-AI core processing...")
if isWin then
local script = LrPathUtils.child(tempDir, "run.bat")
local f = io.open(script, "w")
if f then
f:write('@echo off\r\n')
f:write(string.format('set PYTHONPATH=%s;%%PYTHONPATH%%\r\n', pyPath))
f:write(cmd .. '\r\n')
f:close()
end
import 'LrTasks'.execute('cmd.exe /c "' .. script .. '"')
else
-- Pre-set PYTHONPATH and execute via AppleScript for best macOS compatibility
local fullCmd = string.format('export PYTHONPATH="%s":$PYTHONPATH && %s', pyPath, cmd)
local appleScript = string.format('osascript -e \'do shell script "%s"\'', fullCmd:gsub('"', '\\"'))
import 'LrTasks'.execute(appleScript)
end
-- Safeguard the analysis results before any cleanup or import failures
local samplePaths = {}
for _, path in pairs(pathMap) do table.insert(samplePaths, path) end
saveReport(outputJson, samplePaths, reportDestType, reportCustomPath)
-- Final verification of Python output
if not LrFileUtils.exists(outputJson) then
local errorMsg = "Core engine failed to generate results."
if LrFileUtils.exists(pythonDebugLog) then
local content = LrFileUtils.readFile(pythonDebugLog)
if content and content ~= "" then errorMsg = content end
end
progress:done()
LrDialogs.message("Addax-AI Error", "Analysis Failure:\n\n" .. errorMsg, "critical")
LrFileUtils.delete(tempDir)
return
end
-- Final Phase: Keyword Synchronization
progress:setCaption("Phase 3: Synchronizing with catalog...")
local count = 0
catalog:withWriteAccessDo("Addax AI Synchronization", function(context)
count = AddaxKeywords.synchronize(catalog, LrPathUtils.child(tempDir, "keywords.txt"), pathMap, excludes, keywordThreshold)
end)
-- Summary and Cleanup
LrDialogs.message("Addax-AI", string.format("Processing complete!\n\nMatched %d photo(s).", count), "info")
debugLog("Cleaning up session data...")
LrFileUtils.delete(tempDir)
debugLog("Session finished.")
progress:done()
end)
end)