Files
Denso/srcs/RobotNet10/RobotApp/RobotNet10.RobotApp.Client/Components/SLAM/MapEdit.razor
2026-07-03 16:31:37 +07:00

405 lines
22 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
@page "/map/{MapName}"
@rendermode InteractiveWebAssemblyNoPrerender
@implements IAsyncDisposable
@using MudBlazor
@using RobotNet10.RobotApp.Client.Shared.SLAM
@using Microsoft.JSInterop
@inject IJSRuntime JSRuntime
@inject ISnackbar Snackbar
<PageTitle>Map: @MapName</PageTitle>
<MudThemeProvider IsDarkMode />
<MudPopoverProvider />
<MudDialogProvider />
<MudSnackbarProvider />
<div class="map-edit-page">
@* Control Bar *@
<div class="control-bar">
<MudStack Row="true" Spacing="2" AlignItems="AlignItems.Center">
<MudTooltip Text="Back to Maps">
<MudIconButton Icon="@Icons.Material.Filled.ArrowBack"
Color="Color.Default"
Size="Size.Medium" Href="/maps" />
</MudTooltip>
<MudDivider Vertical="true" FlexItem="true" Class="my-1" />
<MudTooltip Text="Fit to View">
<MudIconButton Icon="@Icons.Material.Filled.FitScreen"
Color="Color.Primary"
Size="Size.Medium"
OnClick="FitViewAsync"
Disabled="@(!IsMapLoaded)" />
</MudTooltip>
</MudStack>
</div>
<div class="content-area">
@* Processing Overlay *@
<MudOverlay Visible="@MapInfo.IsProcessing" Absolute AutoClose="false" DarkBackground="true" ZIndex="1000">
<MudStack AlignItems="AlignItems.Center" Spacing="2">
<MudProgressCircular Indeterminate="true" Size="Size.Large" Color="Color.Primary" />
<MudText Typo="Typo.h6" Color="Color.Primary">Processing map...</MudText>
</MudStack>
</MudOverlay>
@* Info Bar *@
<div class="info-bar">
<MudText Typo="Typo.h6" Class="mb-2">Map Information</MudText>
<MudStack Spacing="1">
<div class="info-item">
<MudIcon Icon="@Icons.Material.Filled.Map" Size="Size.Small" Class="mr-1" />
<MudText Typo="Typo.body2"><strong>Name:</strong> @MapInfo.Name</MudText>
</div>
<div class="info-item">
<MudIcon Icon="@Icons.Material.Filled.CalendarToday" Size="Size.Small" Class="mr-1" />
<MudText Typo="Typo.body2"><strong>Created:</strong> @MapInfo.CreatedDate.ToString("yyyy-MM-dd HH:mm")</MudText>
</div>
<div class="info-item">
<MudIcon Icon="@Icons.Material.Filled.Straighten" Size="Size.Small" Class="mr-1" />
<MudText Typo="Typo.body2"><strong>Resolution:</strong> @MapInfo.Resolution.ToString("F3") m/p</MudText>
</div>
<div class="info-item">
<MudIcon Icon="@Icons.Material.Filled.AspectRatio" Size="Size.Small" Class="mr-1" />
<MudText Typo="Typo.body2"><strong>Size:</strong> @MapInfo.Width.ToString("F1") x @MapInfo.Height.ToString("F1") m</MudText>
</div>
<div class="info-item">
<MudIcon Icon="@Icons.Material.Filled.Timeline" Size="Size.Small" Class="mr-1" />
<MudText Typo="Typo.body2"><strong>Nodes:</strong> @MapInfo.TrajectoryNodeCount</MudText>
</div>
<div class="info-item info-item-origin">
<MudIcon Icon="@Icons.Material.Filled.MyLocation" Size="Size.Small" Class="mr-1" />
<MudText Typo="Typo.body2"><strong>Origin:</strong> (@MapInfo.OriginX.ToString("F3"), @MapInfo.OriginY.ToString("F3"))</MudText>
<MudSpacer />
<MudTooltip Text="Edit Origin">
<MudIconButton Icon="@Icons.Material.Filled.Edit"
Size="Size.Small"
Color="Color.Primary"
OnClick="OpenEditOriginDialog"
Disabled="@(!IsMapLoaded)" />
</MudTooltip>
</div>
</MudStack>
<MudDivider Class="my-2" />
<MudButton Variant="Variant.Outlined"
Color="Color.Primary"
StartIcon="@Icons.Material.Filled.Refresh"
OnClick="OpenRerenderDialog"
Disabled="@(!IsMapLoaded || MapInfo.IsProcessing)"
FullWidth="true"
Size="Size.Small">
Rerender Map
</MudButton>
<MudOverlay Visible="@IsLoading" Absolute AutoClose="false">
<MudProgressCircular Indeterminate="true" Size="Size.Small" />
</MudOverlay>
</div>
@* Map View - structure similar to MapLocalization *@
<div class="map-localization-container" @ref="containerRef" tabindex="1">
<div @ref="viewMovementRef" class="map-view-movement">
@* Map image with Y-flip (like map-canvas in MapLocalization) *@
<img @ref="mapImageRef"
src="@MapImageUrl"
alt="Map"
class="map-canvas" />
@* SVG overlay for origin marker (like map-editor in MapLocalization) *@
<svg @ref="mapContainerRef" class="map-editor" viewBox="0 0 0 0">
<defs>
<marker id="originvector" markerWidth="2.4" markerHeight="2.4" refX="0.1" refY="0.1">
<line x1="0" y1="0.1" x2="0.5" y2="0.1" stroke="red" stroke-width="0.02" />
<path d="M 0.5 0.15 L 0.6 0.1 L 0.5 0.05 Z" fill="red" stroke-width="0" />
<line x1="0.1" y1="0" x2="0.1" y2="0.5" stroke="blue" stroke-width="0.02" />
<path d="M 0.05 0.5 L 0.1 0.6 L 0.15 0.5 Z" fill="blue" stroke-width="0" />
</marker>
</defs>
@* Robot goal pose component *@
<GoalPose @ref="GoalPoseRef" />
@* Grid origin marker *@
<line class="origin" marker-end="url(#originvector)" />
</svg>
</div>
<MapMousePosition @ref="MapMousePositionRef" />
</div>
</div>
</div>
@* Edit Origin Dialog *@
<MudDialog @bind-Visible="_editOriginDialogVisible" Options="_editOriginDialogOptions">
<TitleContent>
<MudText Typo="Typo.h6">
<MudIcon Icon="@Icons.Material.Filled.MyLocation" Class="mr-2" />
Edit Map Origin
</MudText>
</TitleContent>
<DialogContent>
<MudText Typo="Typo.body2" Class="mb-3">
Set the new origin position from the selected goal pose on the map.
</MudText>
<MudStack Spacing="2">
<MudNumericField @bind-Value="_editOriginX" Label="X (meters)" Variant="Variant.Outlined" />
<MudNumericField @bind-Value="_editOriginY" Label="Y (meters)" Variant="Variant.Outlined" />
<MudNumericField @bind-Value="_editOriginYaw" Label="@(_editOriginYawUseRadian ? "Yaw (radians)" : "Yaw (degrees)")" Variant="Variant.Outlined"
Adornment="Adornment.End" AdornmentIcon="@Icons.Material.Filled.CompareArrows"
OnAdornmentClick="ToggleRadianDegree" AdornmentAriaLabel="Toggle radian/degree" />
</MudStack>
</DialogContent>
<DialogActions>
<MudButton OnClick="CloseEditOriginDialog">Cancel</MudButton>
<MudButton Color="Color.Primary" Variant="Variant.Filled" OnClick="ConfirmEditOrigin">Confirm</MudButton>
</DialogActions>
</MudDialog>
@* Rerender Map Dialog *@
<MudDialog @bind-Visible="_rerenderDialogVisible" Options="_rerenderDialogOptions">
<TitleContent>
<MudText Typo="Typo.h6">
<MudIcon Icon="@Icons.Material.Filled.Refresh" Class="mr-2" />
Rerender Map with Custom Config
</MudText>
</TitleContent>
<DialogContent>
<MudText Typo="Typo.body2" Class="mb-3">
Configure occupancy grid settings and rerender map image files (PNG, JPG, PGM).
</MudText>
<MudStack Spacing="2">
@* Merge Strategy *@
<MudText Typo="Typo.subtitle2" Color="Color.Primary">Merge Strategy</MudText>
<MudTooltip Placement="Placement.Right" Arrow="true">
<ChildContent>
<MudSelect T="SubmapMergeStrategyDto" @bind-Value="_rerenderConfig.MergeStrategy" Label="Merge Strategy" Variant="Variant.Outlined" Dense="true">
<MudSelectItem Value="SubmapMergeStrategyDto.LogOddsSum">Log-Odds Sum (Bayesian)</MudSelectItem>
<MudSelectItem Value="SubmapMergeStrategyDto.MaxProbability">Max Probability (Conservative)</MudSelectItem>
<MudSelectItem Value="SubmapMergeStrategyDto.PorterDuff">Porter-Duff (Cairo-style)</MudSelectItem>
</MudSelect>
</ChildContent>
<TooltipContent>
<MudText Typo="Typo.body2" Style="max-width: 300px;">
<strong>Submap merge strategy:</strong><br/>
• <b>Log-Odds Sum:</b> Sum Bayesian log-odds — clearer walls, less noise<br/>
• <b>Max Probability:</b> Take max probability — safer for navigation, thicker walls<br/>
• <b>Porter-Duff:</b> Cairo-style blending — matches the original C++ behavior; may blur in overlap areas
</MudText>
</TooltipContent>
</MudTooltip>
<MudTooltip Placement="Placement.Right" Arrow="true">
<ChildContent>
<MudNumericField @bind-Value="_rerenderConfig.LogOddsClamp" Label="Log-Odds Clamp (1-20)" Variant="Variant.Outlined" Min="1.0" Max="20.0" Step="0.5" />
</ChildContent>
<TooltipContent>
<MudText Typo="Typo.body2" Style="max-width: 280px;">
<strong>Log-odds clamp:</strong><br/>
• Low (1-5): smoother image, lower contrast<br/>
• High (10-20): sharper walls, higher contrast<br/>
• Default: 10 — balance between clarity and noise
</MudText>
</TooltipContent>
</MudTooltip>
<MudTooltip Placement="Placement.Right" Arrow="true">
<ChildContent>
<MudCheckBox @bind-Value="_rerenderConfig.UseLogOddsAverage" Label="Use Log-Odds Average" Dense="true" />
</ChildContent>
<TooltipContent>
<MudText Typo="Typo.body2" Style="max-width: 280px;">
<strong>Use log-odds average:</strong><br/>
• <b>On:</b> Divide log-odds by observation count — more uniform in overlap areas<br/>
• <b>Off:</b> Sum directly — areas scanned more often become darker/lighter
</MudText>
</TooltipContent>
</MudTooltip>
<MudDivider Class="my-1" />
@* Threshold Configuration *@
<MudText Typo="Typo.subtitle2" Color="Color.Primary">Threshold Configuration</MudText>
<MudTooltip Placement="Placement.Right" Arrow="true">
<ChildContent>
<MudNumericField @bind-Value="_rerenderConfig.FreeSpaceThreshold" Label="Free Space Threshold (0-255)" Variant="Variant.Outlined" Min="0" Max="255" />
</ChildContent>
<TooltipContent>
<MudText Typo="Typo.body2" Style="max-width: 280px;">
<strong>Free-space threshold:</strong><br/>
Pixels with texture value >= this threshold are marked as FREE (white).<br/>
• Low (50-80): more areas become free<br/>
• High (150-200): only very certain areas become free<br/>
• Default: 100
</MudText>
</TooltipContent>
</MudTooltip>
<MudTooltip Placement="Placement.Right" Arrow="true">
<ChildContent>
<MudNumericField @bind-Value="_rerenderConfig.OccupiedSpaceThreshold" Label="Occupied Space Threshold (0-255)" Variant="Variant.Outlined" Min="0" Max="255" />
</ChildContent>
<TooltipContent>
<MudText Typo="Typo.body2" Style="max-width: 280px;">
<strong>Occupied-space threshold:</strong><br/>
Pixels with alpha value > this threshold are marked as OCCUPIED (black).<br/>
• Low (1-10): thicker walls, more sensitive to obstacles<br/>
• High (50-100): only strong walls are drawn<br/>
• Default: 1 (most sensitive)
</MudText>
</TooltipContent>
</MudTooltip>
<MudDivider Class="my-1" />
@* Output Mode *@
<MudText Typo="Typo.subtitle2" Color="Color.Primary">Output Mode</MudText>
<MudTooltip Placement="Placement.Right" Arrow="true">
<ChildContent>
<MudCheckBox @bind-Value="_rerenderConfig.UseBinaryOutput" Label="Binary Output (0/100/-1)" Dense="true" />
</ChildContent>
<TooltipContent>
<MudText Typo="Typo.body2" Style="max-width: 280px;">
<strong>Binary output mode:</strong><br/>
• <b>On:</b> Only 3 values: 0 (white/free), 100 (black/wall), -1 (gray/unknown). Suitable for MCL/Navigation.<br/>
• <b>Off:</b> Gradient 0-100 values. Shows detailed occupancy probability.
</MudText>
</TooltipContent>
</MudTooltip>
<MudDivider Class="my-1" />
@* Wall Thinning *@
<MudText Typo="Typo.subtitle2" Color="Color.Primary">Wall Thinning (Post-processing)</MudText>
<MudTooltip Placement="Placement.Right" Arrow="true">
<ChildContent>
<MudCheckBox @bind-Value="_rerenderConfig.EnableWallThinning" Label="Enable Wall Thinning" Dense="true" />
</ChildContent>
<TooltipContent>
<MudText Typo="Typo.body2" Style="max-width: 280px;">
<strong>Wall thinning:</strong><br/>
Applies morphological erosion to reduce wall thickness.<br/>
• <b>On:</b> Thinner walls; the robot can pass narrow corridors more easily<br/>
• <b>Off:</b> Keep original wall thickness
</MudText>
</TooltipContent>
</MudTooltip>
<MudTooltip Placement="Placement.Right" Arrow="true">
<ChildContent>
<MudNumericField @bind-Value="_rerenderConfig.WallThinningIterations" Label="Thinning Iterations (1-5)" Variant="Variant.Outlined" Min="1" Max="5" Disabled="@(!_rerenderConfig.EnableWallThinning)" />
</ChildContent>
<TooltipContent>
<MudText Typo="Typo.body2" Style="max-width: 280px;">
<strong>Thinning iterations:</strong><br/>
Each iteration erodes ~1 pixel from the wall boundary.<br/>
• 1 iteration: slightly thinner (~1 pixel)<br/>
• 3-5 iterations: significantly thinner; walls may break/disconnect
</MudText>
</TooltipContent>
</MudTooltip>
<MudTooltip Placement="Placement.Right" Arrow="true">
<ChildContent>
<MudNumericField @bind-Value="_rerenderConfig.MinWallThicknessPixels" Label="Min Wall Thickness (pixels, 1-10)" Variant="Variant.Outlined" Min="1" Max="10" Disabled="@(!_rerenderConfig.EnableWallThinning)" />
</ChildContent>
<TooltipContent>
<MudText Typo="Typo.body2" Style="max-width: 280px;">
<strong>Minimum wall thickness:</strong><br/>
Do not erode if the wall is thinner than this value.<br/>
• 1 pixel: allows very thin walls (may break)<br/>
• 2-3 pixels: safer, preserves wall structure<br/>
• With 0.05 m resolution: 2 pixels ≈ 10 cm real wall thickness
</MudText>
</TooltipContent>
</MudTooltip>
<MudDivider Class="my-1" />
@* Ambiguous Cell Handling *@
<MudText Typo="Typo.subtitle2" Color="Color.Primary">Ambiguous Cell Handling</MudText>
<MudTooltip Placement="Placement.Right" Arrow="true">
<ChildContent>
<MudSelect T="sbyte" @bind-Value="_rerenderConfig.AmbiguousCellValue" Label="Ambiguous Cell Value" Variant="Variant.Outlined" Dense="true">
<MudSelectItem Value="@((sbyte)-1)">Unknown (-1)</MudSelectItem>
<MudSelectItem Value="@((sbyte)0)">Free (0)</MudSelectItem>
<MudSelectItem Value="@((sbyte)100)">Occupied (100)</MudSelectItem>
</MudSelect>
</ChildContent>
<TooltipContent>
<MudText Typo="Typo.body2" Style="max-width: 280px;">
<strong>Ambiguous cell value:</strong><br/>
Cells with probability in the ambiguous range are assigned this value.<br/>
• <b>Unknown (-1):</b> Gray; ignored by MCL — safest<br/>
• <b>Free (0):</b> White; robot may pass — riskier<br/>
• <b>Occupied (100):</b> Black; robot avoids — conservative
</MudText>
</TooltipContent>
</MudTooltip>
<MudTooltip Placement="Placement.Right" Arrow="true">
<ChildContent>
<MudNumericField @bind-Value="_rerenderConfig.AmbiguousRangeLower" Label="Ambiguous Range Lower (0-1)" Variant="Variant.Outlined" Min="0.0" Max="1.0" Step="0.05" />
</ChildContent>
<TooltipContent>
<MudText Typo="Typo.body2" Style="max-width: 280px;">
<strong>Ambiguous range lower:</strong><br/>
Probabilities below this value are considered FREE.<br/>
• 0.35 (default): 035% is free<br/>
• Lower to 0.2: stricter, fewer free areas<br/>
• Raise to 0.45: more areas become free
</MudText>
</TooltipContent>
</MudTooltip>
<MudTooltip Placement="Placement.Right" Arrow="true">
<ChildContent>
<MudNumericField @bind-Value="_rerenderConfig.AmbiguousRangeUpper" Label="Ambiguous Range Upper (0-1)" Variant="Variant.Outlined" Min="0.0" Max="1.0" Step="0.05" />
</ChildContent>
<TooltipContent>
<MudText Typo="Typo.body2" Style="max-width: 280px;">
<strong>Ambiguous range upper:</strong><br/>
Probabilities above this value are considered OCCUPIED.<br/>
• 0.65 (default): 65100% is wall<br/>
• Lower to 0.55: more walls (safer)<br/>
• Raise to 0.8: only very certain areas become walls
</MudText>
</TooltipContent>
</MudTooltip>
<MudDivider Class="my-1" />
@* Advanced Options *@
<MudText Typo="Typo.subtitle2" Color="Color.Primary">Advanced Options</MudText>
<MudTooltip Placement="Placement.Right" Arrow="true">
<ChildContent>
<MudCheckBox @bind-Value="_rerenderConfig.EnableMedianFilter" Label="Enable Median Filter" Dense="true" />
</ChildContent>
<TooltipContent>
<MudText Typo="Typo.body2" Style="max-width: 280px;">
<strong>Median filter:</strong><br/>
Reduces salt-and-pepper noise.<br/>
• <b>On:</b> Smoother image, removes isolated noisy pixels<br/>
• <b>Off:</b> Keeps original details, may contain noise
</MudText>
</TooltipContent>
</MudTooltip>
<MudTooltip Placement="Placement.Right" Arrow="true">
<ChildContent>
<MudNumericField @bind-Value="_rerenderConfig.MedianFilterKernelSize" Label="Median Filter Kernel Size (3,5,7)" Variant="Variant.Outlined" Min="3" Max="7" Step="2" Disabled="@(!_rerenderConfig.EnableMedianFilter)" />
</ChildContent>
<TooltipContent>
<MudText Typo="Typo.body2" Style="max-width: 280px;">
<strong>Filter kernel size:</strong><br/>
Neighborhood used to compute the median.<br/>
• 3x3: light filtering, preserves details<br/>
• 5x5: medium filtering<br/>
• 7x7: strong filtering, may blur wall edges
</MudText>
</TooltipContent>
</MudTooltip>
</MudStack>
</DialogContent>
<DialogActions>
<MudButton OnClick="CloseRerenderDialog">Cancel</MudButton>
<MudButton Color="Color.Primary" Variant="Variant.Filled" OnClick="ConfirmRerender">Rerender</MudButton>
</DialogActions>
</MudDialog>