Enhance Face Recognition Workflow with Remote Capabilities and Consent Management
All checks were successful
Publish FaceAI Container / publish (push) Successful in 5m20s

- Updated `run_face_encoder.bat` to include remote execution parameters for SSH and SCP.
- Refactored `run_face_encoder.ps1` to accept remote execution parameters and handle remote file operations.
- Modified `FaceAiUploadPanel.vue` to introduce consent management UI and error handling for race availability.
- Enhanced `useFaceAiHome.js` to manage consent acceptance and integrate cookie handling for biometric data processing notice.
- Updated `HomeView.vue` to streamline the upload panel and integrate consent handling logic.

Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
MaddoScientisto 2026-04-24 19:33:38 +02:00
commit 6e37aa16c8
6 changed files with 1163 additions and 129 deletions

View file

@ -1,21 +1,21 @@
# --- Selezione livello multicore ---
Write-Host ""
Write-Host "Seleziona il livello di multicore per l'elaborazione CPU:"
Write-Host " 1 = 1/8 dei core"
Write-Host " 2 = 1/4 dei core"
Write-Host " 3 = 1/2 dei core (predefinito)"
Write-Host " 4 = 3/4 dei core"
Write-Host " 5 = n-2 core"
Write-Host ""
$multicoreChoice = Read-Host "Inserisci il livello (1-5) oppure premi Invio per usare il predefinito (3)"
param(
[Parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[string]$RemoteUser,
[Parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[string]$RemoteHost,
[Parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[string]$RemotePort,
[Parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[string]$SshExe,
[Parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[string]$ScpExe
)
if ($multicoreChoice -match '^[1-5]$') {
$multicore = [int]$multicoreChoice
} else {
$multicore = -1
}
# --- Modern folder picker (IFileOpenDialog, Vista+) ---
Add-Type -TypeDefinition @'
using System;
using System.Runtime.InteropServices;
@ -87,63 +87,369 @@ public class ModernFolderPicker {
}
'@
$inputPath = [ModernFolderPicker]::Show("Select the folder containing images to encode")
Add-Type -AssemblyName System.Windows.Forms
$remoteRoots = @(
'/mnt/nas12/nas2/RUS',
'/mnt/da1/foto'
)
$encoderDir = Join-Path $PSScriptRoot 'face_encoder_cpu'
$outputDir = Join-Path $encoderDir 'output'
if (-not $inputPath) {
Write-Host "No folder selected. Exiting."
exit 0
function ConvertTo-PosixSingleQuoted {
param(
[Parameter(Mandatory = $true)]
[string]$Value
)
return "'" + $Value.Replace("'", "'`"'`"'") + "'"
}
$inputFolder = Get-Item -LiteralPath $inputPath -ErrorAction Stop
$raceName = $inputFolder.Name
$safeRaceName = ($raceName -replace '[<>:"/\\|?*]', ' ').Trim()
$safeRaceName = $safeRaceName -replace '\s+', '_'
if (-not $safeRaceName) {
$safeRaceName = 'race'
function Normalize-RemoteRelativePath {
param(
[Parameter(Mandatory = $true)]
[string]$InputPath
)
$normalized = $InputPath.Trim()
$normalized = $normalized -replace '\\', '/'
$normalized = $normalized -replace '^/mnt/nas12/nas2/RUS/?', ''
$normalized = $normalized -replace '^/mnt/da1/foto/?', ''
$normalized = $normalized -replace '/+', '/'
$normalized = $normalized.Trim('/')
if (-not $normalized) {
throw 'Il percorso remoto non puo essere vuoto.'
}
$segments = $normalized -split '/'
foreach ($segment in $segments) {
if ([string]::IsNullOrWhiteSpace($segment)) {
throw 'Il percorso remoto contiene segmenti vuoti non validi.'
}
if ($segment -in @('.', '..')) {
throw 'Il percorso remoto non puo contenere . o ...'
}
}
return ($segments -join '/')
}
$timestamp = Get-Date -Format 'yyyyMMdd_HHmmss'
$encoderDir = Join-Path $PSScriptRoot "face_encoder_cpu"
$outputDir = Join-Path $encoderDir "output"
$outputFile = Join-Path $outputDir ("face_encodings_{0}_{1}.pkl" -f $timestamp, $safeRaceName)
$logFile = Join-Path $outputDir ("encoder_log_{0}_{1}.txt" -f $timestamp, $safeRaceName)
function Join-RemotePath {
param(
[Parameter(Mandatory = $true)]
[string]$Root,
[Parameter(Mandatory = $true)]
[string]$RelativePath,
[string]$LeafName
)
New-Item -ItemType Directory -Path $outputDir -Force | Out-Null
$parts = @($Root.TrimEnd('/'))
if ($RelativePath) {
$parts += $RelativePath.Trim('/')
}
if ($LeafName) {
$parts += $LeafName
}
# --- Build argument list ---
$encoderExe = Join-Path $encoderDir "face_encoder_cpu.exe"
$encoderArgs = [System.Collections.Generic.List[string]]::new()
$encoderArgs.Add("-i")
$encoderArgs.Add($inputFolder.FullName)
$encoderArgs.Add("-r")
$encoderArgs.Add("-o")
$encoderArgs.Add($outputFile)
$encoderArgs.Add("-l")
$encoderArgs.Add($logFile)
if ($multicore -ge 0) {
$encoderArgs.Add("-m")
$encoderArgs.Add([string]$multicore)
return ($parts -join '/')
}
# --- Run encoder ---
Write-Host "Input folder : $($inputFolder.FullName)"
Write-Host "Race name : $raceName"
Write-Host "Multicore : $(if ($multicore -ge 0) { $multicore } else { 'default (3)' })"
Write-Host "Output file : $outputFile"
Write-Host "Log file : $logFile"
Write-Host "Command : $encoderExe $encoderArgs"
Write-Host ""
function Get-MulticoreSetting {
Write-Host ''
Write-Host "Seleziona il livello di multicore per l'elaborazione CPU:"
Write-Host ' 1 = 1/8 dei core'
Write-Host ' 2 = 1/4 dei core'
Write-Host ' 3 = 1/2 dei core (predefinito)'
Write-Host ' 4 = 3/4 dei core'
Write-Host ' 5 = n-2 core'
Write-Host ''
& $encoderExe @encoderArgs
$multicoreChoice = Read-Host 'Inserisci il livello (1-5) oppure premi Invio per usare il predefinito (3)'
if ($multicoreChoice -match '^[1-5]$') {
return [int]$multicoreChoice
}
$encoderExitCode = $LASTEXITCODE
if ($encoderExitCode -eq 0 -and (Test-Path -LiteralPath $outputFile)) {
Start-Process explorer.exe "/select,`"$outputFile`""
} elseif ($encoderExitCode -eq 0) {
Write-Warning "Encoding completed, but the expected output file was not found: $outputFile"
return -1
}
exit $encoderExitCode
function Show-PklFilePicker {
param(
[Parameter(Mandatory = $true)]
[string]$InitialDirectory
)
$dialog = New-Object System.Windows.Forms.OpenFileDialog
$dialog.Title = 'Seleziona il file PKL da caricare'
$dialog.Filter = 'Pickle files (*.pkl)|*.pkl|Tutti i file (*.*)|*.*'
$dialog.CheckFileExists = $true
$dialog.Multiselect = $false
if (Test-Path -LiteralPath $InitialDirectory) {
$dialog.InitialDirectory = (Resolve-Path -LiteralPath $InitialDirectory).Path
}
if ($dialog.ShowDialog() -ne [System.Windows.Forms.DialogResult]::OK) {
return $null
}
return $dialog.FileName
}
function Invoke-RemoteShellCommand {
param(
[Parameter(Mandatory = $true)]
[string]$Payload,
[switch]$AllocateTty,
[switch]$Quiet
)
$remoteCommand = 'sh -c ' + (ConvertTo-PosixSingleQuoted $Payload)
$sshArgs = [System.Collections.Generic.List[string]]::new()
if ($AllocateTty) {
$sshArgs.Add('-tt')
}
$sshArgs.Add('-p')
$sshArgs.Add($remotePort)
$sshArgs.Add('-o')
$sshArgs.Add('PreferredAuthentications=password')
$sshArgs.Add('-o')
$sshArgs.Add('PubkeyAuthentication=no')
$sshArgs.Add('-o')
$sshArgs.Add('StrictHostKeyChecking=accept-new')
$sshArgs.Add("$remoteUser@$remoteHost")
$sshArgs.Add($remoteCommand)
if ($Quiet) {
& $sshExe @sshArgs | Out-Null
} else {
& $sshExe @sshArgs
}
return $LASTEXITCODE
}
function Test-RemoteFileExists {
param(
[Parameter(Mandatory = $true)]
[string]$RemotePath
)
$payload = 'test -e ' + (ConvertTo-PosixSingleQuoted $RemotePath)
$exitCode = Invoke-RemoteShellCommand -Payload $payload -Quiet
if ($exitCode -eq 0) {
return $true
}
if ($exitCode -eq 1) {
return $false
}
throw "Impossibile verificare l'esistenza del file remoto: $RemotePath"
}
function Test-RemoteDirectoryExists {
param(
[Parameter(Mandatory = $true)]
[string]$RemotePath
)
$payload = 'test -d ' + (ConvertTo-PosixSingleQuoted $RemotePath)
$exitCode = Invoke-RemoteShellCommand -Payload $payload -Quiet
if ($exitCode -eq 0) {
return $true
}
if ($exitCode -eq 1) {
return $false
}
throw "Impossibile verificare il percorso remoto: $RemotePath"
}
function Ensure-RemoteDirectory {
param(
[Parameter(Mandatory = $true)]
[string]$RemoteDirectory
)
$payload = 'mkdir -p ' + (ConvertTo-PosixSingleQuoted $RemoteDirectory)
$exitCode = Invoke-RemoteShellCommand -Payload $payload
if ($exitCode -ne 0) {
throw "Creazione cartella remota non riuscita: $RemoteDirectory"
}
}
function Upload-FileToRemoteTarget {
param(
[Parameter(Mandatory = $true)]
[string]$LocalFile,
[Parameter(Mandatory = $true)]
[string]$RemoteFile,
[bool]$Overwrite
)
$remoteDirectory = Split-Path -Path $RemoteFile -Parent
if ([string]::IsNullOrWhiteSpace($remoteDirectory) -or $remoteDirectory -eq '/') {
throw 'Il percorso remoto destinazione non e valido.'
}
if (Test-RemoteDirectoryExists -RemotePath $RemoteFile) {
throw "La destinazione remota e una cartella, non un file: $RemoteFile"
}
Ensure-RemoteDirectory -RemoteDirectory $remoteDirectory
if ((-not $Overwrite) -and (Test-RemoteFileExists -RemotePath $RemoteFile)) {
Write-Host "Salto upload per file esistente: $RemoteFile"
return
}
$scpArgs = @(
'-P', $remotePort,
'-o', 'PreferredAuthentications=password',
'-o', 'PubkeyAuthentication=no',
'-o', 'StrictHostKeyChecking=accept-new',
$LocalFile,
("{0}@{1}:{2}" -f $remoteUser, $remoteHost, (ConvertTo-PosixSingleQuoted $RemoteFile))
)
& $scpExe @scpArgs
if ($LASTEXITCODE -ne 0) {
throw "Caricamento non riuscito verso $RemoteFile"
}
}
function Invoke-FaceEncoding {
$multicore = Get-MulticoreSetting
$inputPath = [ModernFolderPicker]::Show('Select the folder containing images to encode')
if (-not $inputPath) {
Write-Host 'Nessuna cartella selezionata. Uscita.'
return 0
}
$inputFolder = Get-Item -LiteralPath $inputPath -ErrorAction Stop
$raceName = $inputFolder.Name
$safeRaceName = ($raceName -replace '[<>:"/\\|?*]', ' ').Trim()
$safeRaceName = $safeRaceName -replace '\s+', '_'
if (-not $safeRaceName) {
$safeRaceName = 'race'
}
$timestamp = Get-Date -Format 'yyyyMMdd_HHmmss'
$outputFile = Join-Path $outputDir ("face_encodings_{0}_{1}.pkl" -f $timestamp, $safeRaceName)
$logFile = Join-Path $outputDir ("encoder_log_{0}_{1}.txt" -f $timestamp, $safeRaceName)
New-Item -ItemType Directory -Path $outputDir -Force | Out-Null
$encoderExe = Join-Path $encoderDir 'face_encoder_cpu.exe'
$encoderArgs = [System.Collections.Generic.List[string]]::new()
$encoderArgs.Add('-i')
$encoderArgs.Add($inputFolder.FullName)
$encoderArgs.Add('-r')
$encoderArgs.Add('-o')
$encoderArgs.Add($outputFile)
$encoderArgs.Add('-l')
$encoderArgs.Add($logFile)
if ($multicore -ge 0) {
$encoderArgs.Add('-m')
$encoderArgs.Add([string]$multicore)
}
Write-Host "Input folder : $($inputFolder.FullName)"
Write-Host "Race name : $raceName"
Write-Host "Multicore : $(if ($multicore -ge 0) { $multicore } else { 'default (3)' })"
Write-Host "Output file : $outputFile"
Write-Host "Log file : $logFile"
Write-Host "Command : $encoderExe $encoderArgs"
Write-Host ''
& $encoderExe @encoderArgs
$encoderExitCode = $LASTEXITCODE
if ($encoderExitCode -eq 0 -and (Test-Path -LiteralPath $outputFile)) {
Start-Process explorer.exe "/select,`"$outputFile`""
} elseif ($encoderExitCode -eq 0) {
Write-Warning "Encoding completed, but the expected output file was not found: $outputFile"
}
return $encoderExitCode
}
function Invoke-PklUpload {
New-Item -ItemType Directory -Path $outputDir -Force | Out-Null
$selectedFile = Show-PklFilePicker -InitialDirectory $outputDir
if (-not $selectedFile) {
Write-Host 'Nessun file selezionato. Uscita.'
return 0
}
$fileInfo = Get-Item -LiteralPath $selectedFile -ErrorAction Stop
$relativeInput = Read-Host 'Inserisci il percorso relativo sotto RUS/foto (esempio: 2026/04.APRILE/ISOLOTTO)'
$relativePath = Normalize-RemoteRelativePath -InputPath $relativeInput
$targets = foreach ($root in $remoteRoots) {
[pscustomobject]@{
Root = $root
RemoteFile = Join-RemotePath -Root $root -RelativePath $relativePath -LeafName $fileInfo.Name
}
}
Write-Host ''
Write-Host 'Destinazioni remote:'
foreach ($target in $targets) {
Write-Host (" - {0}" -f $target.RemoteFile)
}
Write-Host ''
$existingTargets = @($targets | Where-Object { Test-RemoteFileExists -RemotePath $_.RemoteFile })
$overwrite = $false
if ($existingTargets.Count -gt 0) {
Write-Host 'Il file esiste gia nelle seguenti destinazioni:'
foreach ($target in $existingTargets) {
Write-Host (" - {0}" -f $target.RemoteFile)
}
$choice = Read-Host 'Vuoi sovrascrivere i file esistenti? (s/N)'
if ($choice -match '^(s|si|y|yes)$') {
$overwrite = $true
}
}
foreach ($target in $targets) {
Upload-FileToRemoteTarget -LocalFile $fileInfo.FullName -RemoteFile $target.RemoteFile -Overwrite:$overwrite
}
Write-Host ''
Write-Host 'Caricamento completato.'
return 0
}
Write-Host ''
Write-Host 'Seleziona un''opzione:'
Write-Host ' 1. Elaborazione riconoscimento facciale'
Write-Host ' 2. Caricamento'
Write-Host ''
$mode = $null
while ($mode -notin @('1', '2')) {
$mode = Read-Host 'Inserisci 1 o 2'
}
try {
if ($mode -eq '1') {
$exitCode = Invoke-FaceEncoding
} else {
$exitCode = Invoke-PklUpload
}
exit $exitCode
} catch {
Write-Error $_
exit 1
}