Functions/GenXdev.AI.Queries/Invoke-ImageFacesUpdate.ps1
################################################################################ <# .SYNOPSIS Updates face recognition metadata for image files in a specified directory. .DESCRIPTION This function processes images in a specified directory to identify and analyze faces using AI recognition technology. It creates or updates metadata files containing face information for each image. The metadata is stored in a separate file with the same name as the image but with a ':people.json' suffix. .PARAMETER ImageDirectory The directory path containing images to process. Can be relative or absolute. Default is the current directory. .PARAMETER Recurse If specified, processes images in the specified directory and all subdirectories. .PARAMETER OnlyNew If specified, only processes images that don't already have face metadata files. .PARAMETER RetryFailed If specified, retries processing previously failed images (empty metadata files). .PARAMETER NoDockerInitialize Skip Docker initialization when this switch is used. Used when already called by parent function. .PARAMETER Force Force rebuild of Docker container and remove existing data when this switch is used. .PARAMETER UseGPU Use GPU-accelerated version when this switch is used. Requires an NVIDIA GPU. .PARAMETER ContainerName The name for the Docker container. Default is "deepstack_face_recognition". .PARAMETER VolumeName The name for the Docker volume for persistent storage. Default is "deepstack_face_data". .PARAMETER ServicePort The port number for the DeepStack service. Default is 5000. .PARAMETER HealthCheckTimeout Maximum time in seconds to wait for service health check. Default is 60. .PARAMETER HealthCheckInterval Interval in seconds between health check attempts. Default is 3. .PARAMETER ImageName Custom Docker image name to use instead of the default DeepStack image. .PARAMETER FacesPath The path inside the container where faces are stored. Default is "/datastore". .EXAMPLE Invoke-ImageFacesUpdate -ImageDirectory "C:\Photos" -Recurse .EXAMPLE facerecognition "C:\Photos" -RetryFailed -OnlyNew #> ############################################################################### function Invoke-ImageFacesUpdate { [CmdletBinding()] [Alias("facerecognition")] param( ####################################################################### [Parameter( Position = 0, Mandatory = $false, HelpMessage = "The directory path containing images to process" )] [string] $ImageDirectory = ".\", ####################################################################### [Parameter( Position = 1, Mandatory = $false, HelpMessage = "Custom Docker image name to use instead of default" )] [ValidateNotNullOrEmpty()] [string] $ImageName, ####################################################################### [Parameter( Position = 2, Mandatory = $false, HelpMessage = "The name for the Docker container" )] [ValidateNotNullOrEmpty()] [string] $ContainerName = "deepstack_face_recognition", ####################################################################### [Parameter( Position = 3, Mandatory = $false, HelpMessage = "The name for the Docker volume for persistent storage" )] [ValidateNotNullOrEmpty()] [string] $VolumeName = "deepstack_face_data", ####################################################################### [Parameter( Position = 4, Mandatory = $false, HelpMessage = "The path inside the container where faces are stored" )] [ValidateNotNullOrEmpty()] [string] $FacesPath = "/datastore", ####################################################################### [Parameter( Position = 5, Mandatory = $false, HelpMessage = "The port number for the DeepStack service" )] [ValidateRange(1, 65535)] [int] $ServicePort = 5000, ####################################################################### [Parameter( Position = 6, Mandatory = $false, HelpMessage = ("Maximum time in seconds to wait for service " + "health check") )] [ValidateRange(10, 300)] [int] $HealthCheckTimeout = 60, ####################################################################### [Parameter( Position = 7, Mandatory = $false, HelpMessage = "Interval in seconds between health check attempts" )] [ValidateRange(1, 10)] [int] $HealthCheckInterval = 3, ####################################################################### [Parameter( Mandatory = $false, HelpMessage = ("Process images in specified directory and all " + "subdirectories") )] [switch] $Recurse, ####################################################################### [Parameter( Mandatory = $false, HelpMessage = ("Only process images that don't already have face " + "metadata files") )] [switch] $OnlyNew, ####################################################################### [Parameter( Mandatory = $false, HelpMessage = ("Retry processing previously failed images with " + "empty metadata files") )] [switch] $RetryFailed, ####################################################################### [Parameter( Mandatory = $false, HelpMessage = ("Skip Docker initialization when already called by " + "parent function") )] [switch] $NoDockerInitialize, ####################################################################### [Parameter( Mandatory = $false, HelpMessage = ("Force rebuild of Docker container and remove " + "existing data") )] [Alias("ForceRebuild")] [switch] $Force, ####################################################################### [Parameter( Mandatory = $false, HelpMessage = ("Use GPU-accelerated version which requires an " + "NVIDIA GPU") )] [switch] $UseGPU ####################################################################### ) begin { # convert the possibly relative path to an absolute path for reliable access $path = GenXdev.FileSystem\Expand-Path $ImageDirectory # ensure the target directory exists before proceeding with any operations if (-not [System.IO.Directory]::Exists($path)) { Microsoft.PowerShell.Utility\Write-Host ("The directory '$path' " + "does not exist.") return } Microsoft.PowerShell.Utility\Write-Verbose ("Processing images in " + "directory: $path") } process { # retrieve all supported image files from the specified directory # applying recursion only if the -Recurse switch was provided Microsoft.PowerShell.Management\Get-ChildItem ` -Path "$path\*.jpg", "$path\*.jpeg", "$path\*.png" ` -Recurse:$Recurse ` -File ` -ErrorAction SilentlyContinue | Microsoft.PowerShell.Core\ForEach-Object { # store the full path to the current image for better readability $image = $PSItem.FullName # if retry mode is active, handle previously failed images if ($RetryFailed) { if ([System.IO.File]::Exists("$($image):people.json")) { # read existing metadata to check for empty or invalid content $content = [System.IO.File]::ReadAllText( "$($image):people.json") # check if metadata file contains no faces or invalid data if ($content.StartsWith("{}") -or $content -eq ('{"predictions":null,"count":1,' + '"faces":[""]}')) { $content = "{}" } } } Microsoft.PowerShell.Utility\Write-Verbose ("Processing image: " + "$image") # remove read-only attribute if present to ensure file modification if ($PSItem.Attributes -band [System.IO.FileAttributes]::ReadOnly) { $PSItem.Attributes = $PSItem.Attributes -bxor [System.IO.FileAttributes]::ReadOnly } # check if a metadata file already exists for this image $metadataFilePath = "$($image):people.json" $fileExists = [System.IO.File]::Exists($metadataFilePath) # read existing content or use empty JSON object as default $content = if ($fileExists) { [System.IO.File]::ReadAllText($metadataFilePath) } else { "{}" } # determine if image should be processed based on options $shouldProcess = ( (-not $OnlyNew) -or (-not $fileExists) -or ($content -eq "{}") -or (-not $content.Contains("predictions")) ) if ($shouldProcess) { # create an empty metadata file as placeholder if needed if (-not $fileExists) { $null = [System.IO.File]::WriteAllText($metadataFilePath, "{}") Microsoft.PowerShell.Utility\Write-Verbose ( "Created new metadata file for: $image") } # obtain face recognition data using ai recognition technology $faceData = GenXdev.AI\Get-ImageDetectedFaces ` -ImagePath $image ` -NoDockerInitialize:$NoDockerInitialize ` -ConfidenceThreshold 0.6 # process the returned face data into standardized format $processedData = if ($faceData -and $faceData.success -and $faceData.predictions) { $predictions = $faceData.predictions # extract unique face names from predictions data $faceNames = $predictions | Microsoft.PowerShell.Core\ForEach-Object { $name = $_.userid $lastUnderscoreIndex = $name.LastIndexOf("_") # remove timestamp suffix if present in face name if ($lastUnderscoreIndex -gt 0) { $name.Substring(0, $lastUnderscoreIndex) } else { $name } } | Microsoft.PowerShell.Utility\Sort-Object -Unique # create standardized data structure for face metadata @{ success = $true count = $faceNames.Count faces = $faceNames predictions = $predictions } } else { # create empty structure when no faces are detected @{ success = $true count = 0 faces = @() predictions = @() } } # convert processed data to json format for storage $faces = $processedData | Microsoft.PowerShell.Utility\ConvertTo-Json ` -Depth 20 ` -WarningAction SilentlyContinue Microsoft.PowerShell.Utility\Write-Verbose ( "Received face analysis for: $image") try { # reformat json to ensure consistent compressed format $newContent = ($faces | Microsoft.PowerShell.Utility\ConvertFrom-Json | Microsoft.PowerShell.Utility\ConvertTo-Json ` -Compress ` -Depth 20 ` -WarningAction SilentlyContinue) # handle invalid json response by resetting to empty object if ($newContent -eq ('{"predictions":null,"count":1,' + '"faces":[""]}')) { $newContent = '{}' } # save the processed face data to metadata file [System.IO.File]::WriteAllText($metadataFilePath, $newContent) Microsoft.PowerShell.Utility\Write-Verbose ( "Successfully saved face metadata for: $image") } catch { # log any errors that occur during metadata processing Microsoft.PowerShell.Utility\Write-Warning ( "$PSItem`r`n$faces") } } } } end { } } ################################################################################ |