144 lines
6.5 KiB
PowerShell
144 lines
6.5 KiB
PowerShell
[CmdletBinding()]
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$ReferenceDirectory,
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$Checkpoint,
|
|
[string]$GodotPath,
|
|
[string]$Output = "user://render_camera_pose_sweep",
|
|
[double[]]$YawOffsets = @(-10.0, 0.0, 10.0),
|
|
[double[]]$PitchOffsets = @(-10.0, 0.0, 10.0),
|
|
[double]$CameraFov = 62.0,
|
|
[double[]]$CameraFovValues = @(),
|
|
[int]$ViewportWidth = 1280,
|
|
[int]$ViewportHeight = 900,
|
|
[double]$WaitSeconds = 8.0,
|
|
[double]$MeasureSeconds = 0.1,
|
|
[switch]$PlanOnly
|
|
)
|
|
|
|
$ErrorActionPreference = "Stop"
|
|
$repoRoot = Split-Path -Parent $PSScriptRoot
|
|
if (-not $GodotPath) {
|
|
$godotCommand = Get-Command godot -ErrorAction SilentlyContinue
|
|
if ($godotCommand) {
|
|
$GodotPath = $godotCommand.Source
|
|
} else {
|
|
$GodotPath = Join-Path $env:TEMP "godot-4.6.1-openwc\Godot_v4.6.1-stable_win64.exe"
|
|
}
|
|
}
|
|
if (-not (Test-Path -LiteralPath $GodotPath)) {
|
|
throw "Godot executable not found: $GodotPath"
|
|
}
|
|
if (-not (Test-Path -LiteralPath $ReferenceDirectory)) {
|
|
throw "Reference directory not found: $ReferenceDirectory"
|
|
}
|
|
|
|
$invariantCulture = [Globalization.CultureInfo]::InvariantCulture
|
|
$isJointFovSweep = $CameraFovValues.Count -gt 0
|
|
$effectiveCameraFovValues = if ($isJointFovSweep) { $CameraFovValues } else { @($CameraFov) }
|
|
$waitSecondsText = $WaitSeconds.ToString($invariantCulture)
|
|
$measureSecondsText = $MeasureSeconds.ToString($invariantCulture)
|
|
$absoluteOutput = if ($Output.StartsWith("user://")) {
|
|
Join-Path $env:APPDATA "Godot\app_userdata\OpenWC\$($Output.Substring(7))"
|
|
} else {
|
|
[IO.Path]::GetFullPath((Join-Path $repoRoot $Output))
|
|
}
|
|
$ranking = [Collections.Generic.List[object]]::new()
|
|
|
|
Push-Location $repoRoot
|
|
try {
|
|
foreach ($cameraFovValue in $effectiveCameraFovValues) {
|
|
foreach ($yawOffset in $YawOffsets) {
|
|
foreach ($pitchOffset in $PitchOffsets) {
|
|
$cameraFovText = $cameraFovValue.ToString("0.###", $invariantCulture)
|
|
$yawText = $yawOffset.ToString("0.###", $invariantCulture)
|
|
$pitchText = $pitchOffset.ToString("0.###", $invariantCulture)
|
|
$candidateName = "yaw_$($yawText.Replace('-', 'n').Replace('.', '_'))__pitch_$($pitchText.Replace('-', 'n').Replace('.', '_'))"
|
|
if ($isJointFovSweep) {
|
|
$candidateName = "fov_$($cameraFovText.Replace('.', '_'))__$candidateName"
|
|
}
|
|
$candidateOutput = "$Output/$candidateName"
|
|
$comparisonReport = Join-Path $absoluteOutput "$candidateName.json"
|
|
|
|
if ($PlanOnly) {
|
|
$ranking.Add([pscustomobject]@{
|
|
yaw_offset_degrees = $yawOffset
|
|
pitch_offset_degrees = $pitchOffset
|
|
camera_fov_degrees = $cameraFovValue
|
|
candidate_directory = $candidateOutput
|
|
})
|
|
continue
|
|
}
|
|
|
|
$captureArguments = @(
|
|
"--path", ".", "--script", "res://src/tools/capture_render_checkpoints.gd", "--",
|
|
"--output", $candidateOutput, "--only", $Checkpoint, "--single-pass",
|
|
"--camera-fov", $cameraFovText,
|
|
"--camera-yaw-offset", $yawText, "--camera-pitch-offset", $pitchText,
|
|
"--viewport-width", $ViewportWidth, "--viewport-height", $ViewportHeight,
|
|
"--wait", $waitSecondsText, "--measure", $measureSecondsText
|
|
)
|
|
$captureProcess = Start-Process -FilePath $GodotPath -ArgumentList $captureArguments -Wait -PassThru
|
|
if ($captureProcess.ExitCode -ne 0) {
|
|
throw "Capture failed for yaw=$yawText pitch=$pitchText"
|
|
}
|
|
|
|
$comparisonArguments = @(
|
|
"--headless", "--path", ".", "--script", "res://src/tools/compare_render_checkpoints.gd", "--",
|
|
"--reference", $ReferenceDirectory, "--candidate", $candidateOutput,
|
|
"--only", $Checkpoint, "--output", $comparisonReport
|
|
)
|
|
$comparisonProcess = Start-Process -FilePath $GodotPath -ArgumentList $comparisonArguments -Wait -PassThru -NoNewWindow
|
|
if ($comparisonProcess.ExitCode -notin @(0, 1)) {
|
|
throw "Comparison failed for yaw=$yawText pitch=$pitchText"
|
|
}
|
|
|
|
$comparison = Get-Content -Raw -LiteralPath $comparisonReport | ConvertFrom-Json
|
|
if ($comparison.compared_count -lt 1) {
|
|
throw "No paired image was compared for yaw=$yawText pitch=$pitchText"
|
|
}
|
|
$metricResults = @($comparison.results | Where-Object { $null -ne $_.mean_perceptual_error })
|
|
if ($metricResults.Count -ne $comparison.compared_count) {
|
|
$statuses = ($comparison.results.status | Sort-Object -Unique) -join ","
|
|
throw "Comparison produced no numeric metric for yaw=$yawText pitch=$pitchText statuses=$statuses"
|
|
}
|
|
$meanError = ($metricResults | Measure-Object -Property mean_perceptual_error -Average).Average
|
|
$changedRatio = ($metricResults | Measure-Object -Property changed_pixel_ratio -Average).Average
|
|
$ranking.Add([pscustomobject]@{
|
|
yaw_offset_degrees = $yawOffset
|
|
pitch_offset_degrees = $pitchOffset
|
|
camera_fov_degrees = $cameraFovValue
|
|
mean_perceptual_error = $meanError
|
|
changed_pixel_ratio = $changedRatio
|
|
candidate_directory = $candidateOutput
|
|
comparison_report = $comparisonReport
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
$rankedCandidates = if ($PlanOnly) {
|
|
@($ranking)
|
|
} else {
|
|
@($ranking | Sort-Object mean_perceptual_error, changed_pixel_ratio)
|
|
}
|
|
$rankingPath = Join-Path $absoluteOutput "ranking.json"
|
|
$rankingDocument = [ordered]@{
|
|
schema_version = 1
|
|
checkpoint = $Checkpoint
|
|
reference_directory = [IO.Path]::GetFullPath($ReferenceDirectory)
|
|
viewport = @($ViewportWidth, $ViewportHeight)
|
|
wait_seconds = $WaitSeconds
|
|
measure_seconds = $MeasureSeconds
|
|
candidates = $rankedCandidates
|
|
}
|
|
New-Item -ItemType Directory -Force -Path $absoluteOutput | Out-Null
|
|
$rankingDocument | ConvertTo-Json -Depth 6 | Set-Content -Encoding UTF8 -LiteralPath $rankingPath
|
|
$rankedCandidates | Format-Table camera_fov_degrees, yaw_offset_degrees, pitch_offset_degrees, mean_perceptual_error, changed_pixel_ratio
|
|
$completionKind = if ($PlanOnly) { "plan" } else { "ranking" }
|
|
Write-Output "Camera pose sweep $completionKind completed: $rankingPath"
|
|
} finally {
|
|
Pop-Location
|
|
}
|