Functions/GenXdev.AI.DeepStack/Compare-ImageFaces.ps1

################################################################################
<#
.SYNOPSIS
Compares faces in two different images and returns their similarity using
DeepStack.
 
.DESCRIPTION
This function compares faces between two images to determine similarity. It
uses a local DeepStack face match API running on a configurable port and
returns a similarity score between 0.0 and 1.0. This is typically used for
matching identity documents with pictures of a person or verifying if two
photos show the same person.
 
.PARAMETER ImagePath1
The local path to the first image file to compare. This parameter accepts any
valid file path that can be resolved by the system.
 
.PARAMETER ImagePath2
The local path to the second image file to compare. This parameter accepts any
valid file path that can be resolved by the system.
 
.PARAMETER NoDockerInitialize
Skip Docker initialization when this switch is used. This is typically used
when already called by parent function to avoid duplicate initialization.
 
.PARAMETER Force
Force rebuild of Docker container and remove existing data when this switch
is used. This is useful for troubleshooting or updating the DeepStack image.
 
.PARAMETER UseGPU
Use GPU-accelerated version when this switch is used. This requires an
NVIDIA GPU with proper Docker GPU support configured.
 
.PARAMETER ContainerName
The name for the Docker container. This allows multiple DeepStack instances
or custom naming conventions. Default is "deepstack_face_recognition".
 
.PARAMETER VolumeName
The name for the Docker volume for persistent storage. This ensures face data
persists between container restarts. Default is "deepstack_face_data".
 
.PARAMETER ServicePort
The port number for the DeepStack service. Must be between 1 and 65535.
Default is 5000.
 
.PARAMETER HealthCheckTimeout
Maximum time in seconds to wait for service health check. Must be between 10
and 300 seconds. Default is 60.
 
.PARAMETER HealthCheckInterval
Interval in seconds between health check attempts. Must be between 1 and 10
seconds. Default is 3.
 
.PARAMETER ImageName
Custom Docker image name to use instead of the default DeepStack image. This
allows using custom or updated DeepStack images.
 
.PARAMETER FacesPath
The path inside the container where faces are stored. This should match the
DeepStack configuration. Default is "/datastore".
 
.EXAMPLE
Compare-ImageFaces -ImagePath1 "C:\Users\YourName\photo1.jpg" `
                   -ImagePath2 "C:\Users\YourName\photo2.jpg"
 
Compares faces between two images using default settings.
 
.EXAMPLE
cfaces "C:\docs\id_photo.jpg" "C:\photos\person.jpg" -UseGPU
 
Compares faces using GPU acceleration for identity verification with alias and
positional parameters.
 
.NOTES
DeepStack API Documentation: POST /v1/vision/face/match endpoint for face
comparison.
Example: curl -X POST -F "image1=@person1.jpg" -F "image2=@person2.jpg"
http://localhost:5000/v1/vision/face/match
#>

function Compare-ImageFaces {

    [CmdletBinding()]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '')]
    [Alias("comparefaces")]

    param(
        ###############################################################################
        [Parameter(
            Position = 0,
            Mandatory = $true,
            HelpMessage = "The local path to the first image file to compare"
        )]
        [ValidateNotNullOrEmpty()]
        [string] $ImagePath1,
        ###############################################################################
        [Parameter(
            Position = 1,
            Mandatory = $true,
            HelpMessage = "The local path to the second image file to compare"
        )]
        [ValidateNotNullOrEmpty()]
        [string] $ImagePath2,
        ###############################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = ("Skip Docker initialization (used 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 (requires NVIDIA " +
                          "GPU)")
        )]
        [switch] $UseGPU,
        ###############################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = "The name for the Docker container"
        )]
        [ValidateNotNullOrEmpty()]
        [string] $ContainerName = "deepstack_face_recognition",
        ###############################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = ("The name for the Docker volume for persistent " +
                          "storage")
        )]
        [ValidateNotNullOrEmpty()]
        [string] $VolumeName = "deepstack_face_data",
        ###############################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = "The port number for the DeepStack service"
        )]
        [ValidateRange(1, 65535)]
        [int] $ServicePort = 5000,
        ###############################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = ("Maximum time in seconds to wait for service " +
                          "health check")
        )]
        [ValidateRange(10, 300)]
        [int] $HealthCheckTimeout = 60,
        ###############################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = ("Interval in seconds between health check " +
                          "attempts")
        )]
        [ValidateRange(1, 10)]
        [int] $HealthCheckInterval = 3,
        ###############################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = "Custom Docker image name to use"
        )]
        [ValidateNotNullOrEmpty()]
        [string] $ImageName,
        ###############################################################################
        [Parameter(
            Mandatory = $false,
            HelpMessage = ("The path inside the container where faces are " +
                          "stored")
        )]
        [ValidateNotNullOrEmpty()]
        [string] $FacesPath = "/datastore"
        ###############################################################################
    )

    begin {

        # use script-scoped variables set by ensuredeepstack, with fallback
        # defaults
        if (-not $script:ApiBaseUrl) {

            $noDockerInitialize = $false
        }

        # ensure that the deepstack face match service is running
        if (-not $NoDockerInitialize) {

            Microsoft.PowerShell.Utility\Write-Verbose `
                ("Ensuring DeepStack face match service is available")

            # copy parameter values for the ensuredeepstack function call
            $ensureParams = GenXdev.Helpers\Copy-IdenticalParamValues `
                -BoundParameters $PSBoundParameters `
                -FunctionName 'EnsureDeepStack' `
                -DefaultValues (Microsoft.PowerShell.Utility\Get-Variable `
                    -Scope Local `
                    -ErrorAction SilentlyContinue)

            # initialize deepstack docker container if needed
            $null = GenXdev.AI\EnsureDeepStack @ensureParams

        }
        else {

            Microsoft.PowerShell.Utility\Write-Verbose `
                "Skipping Docker initialization as requested"
        }

        Microsoft.PowerShell.Utility\Write-Verbose `
            "Using DeepStack face match API at: $script:ApiBaseUrl"

        ###############################################################################
        <#
        .SYNOPSIS
        Processes the face match result from DeepStack API.
 
        .DESCRIPTION
        This internal helper function processes the response from DeepStack face
        match API and formats it into a standardized result object with success
        status, similarity score, and match percentage.
 
        .PARAMETER MatchData
        The raw response data from the DeepStack face match API endpoint.
 
        .EXAMPLE
        $result = Format-FaceMatchResult -MatchData $response
        #>

        function Format-FaceMatchResult {

            param(
                ###################################################################
                [Parameter(
                    Position = 0,
                    Mandatory = $true,
                    HelpMessage = "The raw response data from DeepStack API"
                )]
                $MatchData
                ###################################################################
            )

            # check if match data is valid and successful
            if (-not $MatchData -or -not $MatchData.success) {

                Microsoft.PowerShell.Utility\Write-Verbose `
                    "No successful face match data received"

                return @{
                    success = $false
                    similarity = 0.0
                    message = "Face match failed"
                }
            }

            # extract similarity score from the response data
            $similarity = if ($MatchData.similarity) {

                $MatchData.similarity

            } else {

                0.0
            }

            Microsoft.PowerShell.Utility\Write-Verbose `
                "Face similarity score: $similarity"

            # return formatted result object with all relevant metrics
            return @{
                success = $true
                similarity = $similarity
                confidence = $similarity
                match_percentage = [math]::Round($similarity * 100, 2)
            }
        }
    }

    process {

        try {

            # expand and validate both image paths to absolute paths
            $imagePath1 = GenXdev.FileSystem\Expand-Path $ImagePath1

            $imagePath2 = GenXdev.FileSystem\Expand-Path $ImagePath2

            Microsoft.PowerShell.Utility\Write-Verbose `
                "Comparing images: $imagePath1 and $imagePath2"

            # validate that both files are valid image files
            $null = GenXdev.AI\Test-ImageFile -Path $imagePath1

            $null = GenXdev.AI\Test-ImageFile -Path $imagePath2

            # construct the api endpoint uri for deepstack face match service
            $uri = "$($script:ApiBaseUrl)/v1/vision/face/match"

            Microsoft.PowerShell.Utility\Write-Verbose "Sending request to: $uri"

            # create form data for deepstack api (it expects multipart form data)
            $form = @{
                image1 = Microsoft.PowerShell.Management\Get-Item $imagePath1
                image2 = Microsoft.PowerShell.Management\Get-Item $imagePath2
            }

            # send the request to the deepstack face match api
            Microsoft.PowerShell.Utility\Write-Verbose `
                "Sending image data to DeepStack face match API"

            $response = Microsoft.PowerShell.Utility\Invoke-RestMethod `
                -Uri $uri `
                -Method Post `
                -Form $form `
                -TimeoutSec 30 `
                -ErrorAction Stop

            Microsoft.PowerShell.Utility\Write-Verbose `
                ("API Response: " +
                 "$($response | `
                    Microsoft.PowerShell.Utility\ConvertTo-Json -Depth 3)"
)

            # process the response from deepstack using the helper function
            $matchResult = Format-FaceMatchResult -MatchData $response

            Microsoft.PowerShell.Utility\Write-Output $matchResult

        }
        catch [System.Net.WebException] {

            Microsoft.PowerShell.Utility\Write-Error `
                "Network error during face comparison: $_"
        }
        catch [System.TimeoutException] {

            Microsoft.PowerShell.Utility\Write-Error `
                ("Timeout during face comparison between $imagePath1 and " +
                 "$imagePath2")
        }
        catch {
            Microsoft.PowerShell.Utility\Write-Error `
                ("Failed to compare faces between $imagePath1 and " +
                 "$imagePath2`: $_")
        }
    }

    end {

    }
}
################################################################################