405 lines
22 KiB
Plaintext
405 lines
22 KiB
Plaintext
@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): 0–35% 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): 65–100% 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>
|