Functions/GenXdev.AI.Queries/Save-FoundImageObjects.ps1
############################################################################### <# .SYNOPSIS Saves cropped object images from indexed image search results to files. .DESCRIPTION This function takes image search results and extracts individual detected object regions, saving them as separate image files. It can search for objects using various criteria including keywords, people, scenes, and metadata filters. The function processes images with AI-detected object boundaries and crops them to individual PNG files in the specified output directory. .PARAMETER Any Will match any of all the possible meta data types including descriptions, keywords, people, objects, scenes, picture types, style types, and moods. .PARAMETER DescriptionSearch The description text to look for, wildcards allowed. .PARAMETER Keywords The keywords to look for, wildcards allowed. .PARAMETER People People to look for, wildcards allowed. .PARAMETER Objects Objects to look for, wildcards allowed. .PARAMETER Scenes Scenes to look for, wildcards allowed. .PARAMETER PictureType Picture types to filter by, wildcards allowed. .PARAMETER StyleType Style types to filter by, wildcards allowed. .PARAMETER OverallMood Overall moods to filter by, wildcards allowed. .PARAMETER HasNudity Filter images that contain nudity. .PARAMETER NoNudity Filter images that do NOT contain nudity. .PARAMETER HasExplicitContent Filter images that contain explicit content. .PARAMETER NoExplicitContent Filter images that do NOT contain explicit content. .PARAMETER DatabaseFilePath Path to the SQLite database file. .PARAMETER PassThru Return image data as objects. .PARAMETER Language Language for descriptions and keywords. .PARAMETER ForceIndexRebuild Force rebuild of the image index database. .PARAMETER PathLike Array of directory path-like search strings to filter images by path (SQL LIKE patterns, e.g. '%\2024\%'). .PARAMETER InputObject Accepts search results from a previous -PassThru call to regenerate the view. .PARAMETER OutputDirectory Directory to save cropped object images. .PARAMETER SaveUnknownPersons Also save unknown persons detected as objects. .PARAMETER SessionOnly Use alternative settings stored in session for AI preferences like Language, Image collections, etc. .PARAMETER ClearSession Clear alternative settings stored in session for AI preferences like Language, Image collections, etc. .PARAMETER PreferencesDatabasePath Database path for preference data files. .PARAMETER SkipSession Dont use alternative settings stored in session for AI preferences like Language, Image collections, etc. .EXAMPLE Save-FoundImageObjects -Objects "car", "tree" -OutputDirectory "C:\CroppedObjects" .EXAMPLE saveimageObjects -Any "sunset" -SaveUnknownPersons #> function Save-FoundImageObjects { [CmdletBinding()] [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '')] [OutputType([Object[]], [System.Collections.Generic.List[Object]], [string])] [Alias("saveimageObjects")] param( ############################################################################### [Parameter( Position = 0, Mandatory = $false, HelpMessage = "Will match any of all the possible meta data types." )] [string[]] $Any = @(), ############################################################################### [Parameter( Mandatory = $false, HelpMessage = "The description text to look for, wildcards allowed." )] [string[]] $DescriptionSearch = @(), ############################################################################### [Parameter( Mandatory = $false, HelpMessage = "The keywords to look for, wildcards allowed." )] [string[]] $Keywords = @(), ############################################################################### [Parameter( Mandatory = $false, HelpMessage = "People to look for, wildcards allowed." )] [string[]] $People = @(), ############################################################################### [Parameter( Mandatory = $false, HelpMessage = "Objects to look for, wildcards allowed." )] [string[]] $Objects = @(), ############################################################################### [Parameter( Mandatory = $false, HelpMessage = "Scenes to look for, wildcards allowed." )] [string[]] $Scenes = @(), ############################################################################### [Parameter( Mandatory = $false, HelpMessage = "Picture types to filter by, wildcards allowed." )] [string[]] $PictureType = @(), ############################################################################### [Parameter( Mandatory = $false, HelpMessage = "Style types to filter by, wildcards allowed." )] [string[]] $StyleType = @(), ############################################################################### [Parameter( Mandatory = $false, HelpMessage = "Overall moods to filter by, wildcards allowed." )] [string[]] $OverallMood = @(), ############################################################################### [Parameter( Mandatory = $false, HelpMessage = "Path to the SQLite database file." )] [string] $DatabaseFilePath, ############################################################################### [Parameter( Mandatory = $false, HelpMessage = "Title for the image gallery." )] [string] $Title, ############################################################################### [Parameter( Mandatory = $false, HelpMessage = "Description for the image gallery." )] [string] $Description, ############################################################################### [Parameter( Mandatory = $false, HelpMessage = "Language for descriptions and keywords." )] [ValidateSet( "Afrikaans", "Akan", "Albanian", "Amharic", "Arabic", "Armenian", "Azerbaijani", "Basque", "Belarusian", "Bemba", "Bengali", "Bihari", "Bosnian", "Breton", "Bulgarian", "Cambodian", "Catalan", "Cherokee", "Chichewa", "Chinese (Simplified)", "Chinese (Traditional)", "Corsican", "Croatian", "Czech", "Danish", "Dutch", "English", "Esperanto", "Estonian", "Ewe", "Faroese", "Filipino", "Finnish", "French", "Frisian", "Ga", "Galician", "Georgian", "German", "Greek", "Guarani", "Gujarati", "Haitian Creole", "Hausa", "Hawaiian", "Hebrew", "Hindi", "Hungarian", "Icelandic", "Igbo", "Indonesian", "Interlingua", "Irish", "Italian", "Japanese", "Javanese", "Kannada", "Kazakh", "Kinyarwanda", "Kirundi", "Kongo", "Korean", "Krio (Sierra Leone)", "Kurdish", "Kurdish (Soranî)", "Kyrgyz", "Laothian", "Latin", "Latvian", "Lingala", "Lithuanian", "Lozi", "Luganda", "Luo", "Macedonian", "Malagasy", "Malay", "Malayalam", "Maltese", "Maori", "Marathi", "Mauritian Creole", "Moldavian", "Mongolian", "Montenegrin", "Nepali", "Nigerian Pidgin", "Northern Sotho", "Norwegian", "Norwegian (Nynorsk)", "Occitan", "Oriya", "Oromo", "Pashto", "Persian", "Polish", "Portuguese (Brazil)", "Portuguese (Portugal)", "Punjabi", "Quechua", "Romanian", "Romansh", "Runyakitara", "Russian", "Scots Gaelic", "Serbian", "Serbo-Croatian", "Sesotho", "Setswana", "Seychellois Creole", "Shona", "Sindhi", "Sinhalese", "Slovak", "Slovenian", "Somali", "Spanish", "Spanish (Latin American)", "Sundanese", "Swahili", "Swedish", "Tajik", "Tamil", "Tatar", "Telugu", "Thai", "Tigrinya", "Tonga", "Tshiluba", "Tumbuka", "Turkish", "Turkmen", "Twi", "Uighur", "Ukrainian", "Urdu", "Uzbek", "Vietnamese", "Welsh", "Wolof", "Xhosa", "Yiddish", "Yoruba", "Zulu")] [string] $Language, ############################################################################### [Parameter( Mandatory = $false, HelpMessage = ("Array of directory path-like search strings to " + "filter images by path (SQL LIKE patterns, e.g. '%\\2024\\%')") )] [string[]] $PathLike = @(), ############################################################################### [Parameter( Mandatory = $false, ValueFromPipeline = $true, HelpMessage = ("Accepts search results from a previous -PassThru " + "call to regenerate the view.") )] [System.Object[]] $InputObject, ############################################################################### [Parameter( Mandatory = $false, HelpMessage = "Directory to save cropped Object images." )] [string] $OutputDirectory = ".\", ############################################################################### [Parameter( Mandatory = $false, HelpMessage = "Database path for preference data files" )] [string] $PreferencesDatabasePath, ############################################################################### [Parameter( Mandatory = $false, HelpMessage = "Filter images that contain nudity." )] [switch] $HasNudity, ############################################################################### [Parameter( Mandatory = $false, HelpMessage = "Filter images that do NOT contain nudity." )] [switch] $NoNudity, ############################################################################### [Parameter( Mandatory = $false, HelpMessage = "Filter images that contain explicit content." )] [switch] $HasExplicitContent, ############################################################################### [Parameter( Mandatory = $false, HelpMessage = "Filter images that do NOT contain explicit content." )] [switch] $NoExplicitContent, ############################################################################### [Parameter( Mandatory = $false, HelpMessage = "Force rebuild of the image index database." )] [switch] $ForceIndexRebuild, ############################################################################### [Parameter( Mandatory = $false, HelpMessage = "Also save unknown persons detected as objects." )] [switch] $SaveUnknownPersons, ############################################################################### [Parameter( Mandatory = $false, HelpMessage = ("Use alternative settings stored in session for AI " + "preferences like Language, Image collections, etc") )] [switch] $SessionOnly, ############################################################################### [Parameter( Mandatory = $false, HelpMessage = ("Clear alternative settings stored in session for AI " + "preferences like Language, Image collections, etc") )] [switch] $ClearSession, ############################################################################### [Parameter( Mandatory = $false, HelpMessage = ("Dont use alternative settings stored in session " + "for AI preferences like Language, Image collections, etc") )] [Alias("FromPreferences")] [switch] $SkipSession ############################################################################### ) ############################################################################### begin { # copy parameters for the ai meta language function to resolve language $params = GenXdev.Helpers\Copy-IdenticalParamValues ` -BoundParameters $PSBoundParameters ` -FunctionName "GenXdev.AI\Get-AIMetaLanguage" ` -DefaultValues (Microsoft.PowerShell.Utility\Get-Variable ` -Scope Local ` -ErrorAction SilentlyContinue) # resolve the language parameter using ai meta language function $language = GenXdev.AI\Get-AIMetaLanguage @params # initialize information tracking object for processing statistics $info = @{ resultCount = 0 } # process the any parameter if provided to expand search criteria if ($null -ne $Any -and $Any.Length -gt 0) { # transform each entry in any parameter to include wildcards if needed $any = @($Any | Microsoft.PowerShell.Core\ForEach-Object { # trim whitespace from the entry $entry = $_.Trim() # add wildcards if no wildcard characters are present if ($entry.IndexOfAny([char[]]@('*', '?')) -lt 0) { "*$entry*" } else { $_ } }) # merge any parameter values with existing search criteria arrays $descriptionSearch = $null -ne $DescriptionSearch ? ($DescriptionSearch + $Any) : $Any $keywords = $null -ne $Keywords ? ($Keywords + $Any) : $Any $people = $null -ne $People ? ($People + $Any) : $Any $objects = $null -ne $Objects ? ($Objects + $Any) : $Any $scenes = $null -ne $Scenes ? ($Scenes + $Any) : $Any $pictureType = $null -ne $PictureType ? ($PictureType + $Any) : $Any $styleType = $null -ne $StyleType ? ($StyleType + $Any) : $Any $overallMood = $null -ne $OverallMood ? ($OverallMood + $Any) : $Any } } ############################################################################### process { # ensure the output directory exists by expanding the path $outputDirectory = GenXdev.FileSystem\Expand-Path $OutputDirectory ` -CreateDirectory Microsoft.PowerShell.Utility\Write-Verbose ( "using output directory: $outputDirectory") # define internal function to save image objects from processed images function saveImage { param ($InputObject) # process each image object in the pipeline $InputObject | Microsoft.PowerShell.Core\ForEach-Object { # get current image object and validate it has required data $image = $_ if ($null -eq $image -or -not $image.path) { return } Microsoft.PowerShell.Utility\Write-Verbose ( "processing image: $($image.path)") # extract object detection data if available $objects = $null if ($image.objects -and $image.objects.objects) { $objects = $image.objects.objects } # track coordinates of saved object rectangles to avoid duplicates $savedObjectRects = @() # process detected objects if any exist if ($objects) { # get the full path to the source image file $imgPath = $image.path try { # load the source image using system drawing classes $imgObj = [System.Drawing.Image]::FromFile($imgPath) try { # extract base filename without extension for output naming $imgBase = [System.IO.Path]::GetFileNameWithoutExtension( $imgPath) # initialize object index counter for unique naming $objectIdx = 0 # iterate through each detected object to crop and save foreach ($obj in $objects) { # calculate safe bounding rectangle coordinates within image bounds $x_min = [Math]::Max(0, [Math]::Min($obj.x_min, $imgObj.Width - 1)) $y_min = [Math]::Max(0, [Math]::Min($obj.y_min, $imgObj.Height - 1)) $x_max = [Math]::Max($x_min + 1, [Math]::Min($obj.x_max, $imgObj.Width)) $y_max = [Math]::Max($y_min + 1, [Math]::Min($obj.y_max, $imgObj.Height)) # calculate width and height of the crop rectangle $width = $x_max - $x_min $height = $y_max - $y_min # skip invalid rectangles with zero or negative dimensions if ($width -le 0 -or $height -le 0) { continue } # create rectangle objects for cropping operation $cropRect = [System.Drawing.Rectangle]::new( $x_min, $y_min, $width, $height) # create new bitmap to hold the cropped object $croppedBitmap = [System.Drawing.Bitmap]::new( $width, $height) # create graphics context for drawing the cropped region $croppedGraphics = [System.Drawing.Graphics]::FromImage( $croppedBitmap) # define destination rectangle for the cropped image $destRect = [System.Drawing.Rectangle]::new( 0, 0, $width, $height) # draw the cropped portion of source image to new bitmap $null = $croppedGraphics.DrawImage($imgObj, $destRect, $cropRect, [System.Drawing.GraphicsUnit]::Pixel) # dispose graphics context to free resources $croppedGraphics.Dispose() # generate sanitized label for the detected object $objectLabel = if ($obj.label) { $obj.label } else { "object$objectIdx" } # remove invalid filename characters from object label $objectLabel = $objectLabel ` -replace '[^\w\-_]', '_' # construct output filename with object information $outFile = Microsoft.PowerShell.Management\Join-Path ` $OutputDirectory ` ("${imgBase}_${objectLabel}_${objectIdx}.png") Microsoft.PowerShell.Utility\Write-Verbose ( "saving object to: $outFile") # save the cropped bitmap as png file $croppedBitmap.Save($outFile, [System.Drawing.Imaging.ImageFormat]::Png) # dispose bitmap to free memory $croppedBitmap.Dispose() # record saved object coordinates for overlap checking $savedObjectRects += @{ x_min = $x_min y_min = $y_min x_max = $x_max y_max = $y_max } # increment object index for next iteration $objectIdx++ } # process unknown persons if the switch is enabled if ($SaveUnknownPersons -and $image.people -and $image.people.predictions) { Microsoft.PowerShell.Utility\Write-Verbose ( "processing unknown persons") # initialize person index counter for unique naming $personIdx = 0 # iterate through detected person predictions foreach ($person in $image.people.predictions) { try { # calculate safe person bounding rectangle coordinates $x_min = [Math]::Max(0, [Math]::Min($person.x_min, $imgObj.Width - 1)) $y_min = [Math]::Max(0, [Math]::Min($person.y_min, $imgObj.Height - 1)) $x_max = [Math]::Max($x_min + 1, [Math]::Min($person.x_max, $imgObj.Width)) $y_max = [Math]::Max($y_min + 1, [Math]::Min($person.y_max, $imgObj.Height)) # calculate person rectangle dimensions $width = $x_max - $x_min $height = $y_max - $y_min # skip invalid person rectangles if ($width -le 0 -or $height -le 0) { continue } # check for overlap with previously saved objects $overlap = $false foreach ($rect in $savedObjectRects) { # test for rectangle intersection using bounds checking if ((($x_min -le $rect.x_max) -and ($x_max -ge $rect.x_min)) -and (($y_min -le $rect.y_max) -and ($y_max -ge $rect.y_min))) { $overlap = $true break } } # skip persons that overlap with saved objects if ($overlap) { continue } # create crop rectangle for person detection $cropRect = [System.Drawing.Rectangle]::new( $x_min, $y_min, $width, $height) # create bitmap for cropped person image $croppedBitmap = [System.Drawing.Bitmap]::new( $width, $height) # create graphics context for person cropping $croppedGraphics = [System.Drawing.Graphics]::FromImage( $croppedBitmap) # define destination rectangle for person crop $destRect = [System.Drawing.Rectangle]::new( 0, 0, $width, $height) # draw cropped person region to new bitmap $null = $croppedGraphics.DrawImage($imgObj, $destRect, $cropRect, [System.Drawing.GraphicsUnit]::Pixel) # dispose graphics context $croppedGraphics.Dispose() # construct filename for unknown person image $outFile = Microsoft.PowerShell.Management\Join-Path ` $OutputDirectory ` ("${imgBase}_unknownperson_${personIdx}.png") Microsoft.PowerShell.Utility\Write-Verbose ( "saving unknown person to: $outFile") # save person bitmap as png file $croppedBitmap.Save($outFile, [System.Drawing.Imaging.ImageFormat]::Png) # dispose person bitmap $croppedBitmap.Dispose() # increment person index counter $personIdx++ } catch { # log warning for person processing failures Microsoft.PowerShell.Utility\Write-Warning ( "failed to crop/save unknown person " + "for $($imgPath): $_") } } } } finally { # ensure source image object is disposed to free memory if ($null -ne $imgObj) { $imgObj.Dispose() } } } catch { # log warning for general image processing failures Microsoft.PowerShell.Utility\Write-Warning ( "failed to crop/save objects for $($imgPath): $_") } } # increment result counter for statistics tracking $info.resultCount++ # output the processed image object to pipeline Microsoft.PowerShell.Utility\Write-Output $image } } # process input based on whether explicit input objects are provided if ($null -ne $InputObject) { Microsoft.PowerShell.Utility\Write-Verbose ( "processing provided input objects") # process each input object through the save image function $InputObject | Microsoft.PowerShell.Core\ForEach-Object { saveImage $_ } } else { Microsoft.PowerShell.Utility\Write-Verbose ( "searching for indexed images") # copy parameters for find-indexedimage function call $params = GenXdev.Helpers\Copy-IdenticalParamValues ` -BoundParameters $PSBoundParameters ` -FunctionName "GenXdev.AI\Find-IndexedImage" ` -DefaultValues (Microsoft.PowerShell.Utility\Get-Variable ` -Scope Local ` -ErrorAction SilentlyContinue) # find indexed images and process each through save image function GenXdev.AI\Find-IndexedImage @params | Microsoft.PowerShell.Core\ForEach-Object { saveImage $_ } } } ############################################################################### end { # output processing statistics to verbose stream Microsoft.PowerShell.Utility\Write-Verbose ( "processed $($info.resultCount) images") } } ############################################################################### |