Browse Source

ImageCategory,AssignedLabelCaseFile,Group增加DevelopConfirmInfo状态,分配逻辑优化
更新说明文档部分图片

felix 8 months ago
parent
commit
b60d6b8b05

+ 13 - 0
AIPlatform.Protocol/Entities/AssignedLabelCaseFile.cs

@@ -111,10 +111,23 @@ namespace AIPlatform.Protocol.Entities
         /// </summary>
         public List<int> KeyFrameIndexList { get; set; }
 
+        /// <summary>
+        /// Gets or sets the video item
+        /// </summary>
+        public VideoItem VideoItem { get; set; }
+
+        /// <summary>
+        /// Gets or sets the develop confirm info
+        /// </summary>
+        public DevelopConfirmInfo DevelopConfirmInfo { get; set; }
+
+
         public AssignedLabelCaseFile()
         {
             LabeledUltrasoundFileIds = new Dictionary<long, DateTime>();
             KeyFrameIndexList = new List<int>();
+            VideoItem = new VideoItem();
+            DevelopConfirmInfo = new DevelopConfirmInfo();
         }
     }
 }

+ 11 - 0
AIPlatform.Protocol/Entities/AssignedLabelCaseGroup.cs

@@ -68,5 +68,16 @@ namespace AIPlatform.Protocol.Entities
         /// Gets or sets the questioned state.
         /// </summary>
         public RelabelState QuestionedState { get; set; }
+
+        /// <summary>
+        /// Gets or sets the develop confirm info
+        /// </summary>
+        public DevelopConfirmInfo DevelopConfirmInfo { get; set; }
+
+
+        public AssignedLabelCaseGroup()
+        {
+            DevelopConfirmInfo = new DevelopConfirmInfo();
+        }
     }
 }

+ 2 - 0
AIPlatform.Protocol/Entities/DevelopConfirmInfo.cs

@@ -20,6 +20,8 @@ namespace AIPlatform.Protocol.Entities
         public DevelopConfirmInfo()
         {
             Status = DevelopConfirmStatus.None;
+            Developer = new EntityBase();
+            ConfirmTime = DateTime.MinValue.ToUniversalTime();
         }
     }
 }

+ 6 - 0
AIPlatform.Protocol/Entities/ImageCategory.cs

@@ -175,6 +175,11 @@ namespace AIPlatform.Protocol.Entities
         /// </summary>
         public DifficultyLevel DifficultyLevel { get; set; }
 
+        /// <summary>
+        /// Gets or sets the develop confirm info
+        /// </summary>
+        public DevelopConfirmInfo DevelopConfirmInfo { get; set; }
+
         public ImageCategory()
         {
             Labelers = new List<EntityBase>();
@@ -184,6 +189,7 @@ namespace AIPlatform.Protocol.Entities
             GroupItem = new QuantityItem();
             FileItem = new QuantityItem();
             ModalItems = new List<ModalQuantityItem>();
+            DevelopConfirmInfo = new DevelopConfirmInfo();
         }
 
         //overrid Name to Dispaly

+ 6 - 0
aipdev/MainWindow.xaml

@@ -30,6 +30,12 @@
                 </Button.ToolTip>
                 <Image Source="Resources/Images/Import.png"></Image>
             </Button>
+            <Button x:Name="PendingAllocationButton" Margin="2" Click="OnPendingAllocationClick">
+                <Button.ToolTip>
+                    <local:BlackToolTip Text="待确认分配" Direction="Right"/>
+                </Button.ToolTip>
+                <Image Source="Resources/Images/PendingAllocation.png"></Image>
+            </Button>
             <Button x:Name="FolderManagementButton" Margin="2" Click="OnFolderManagementClick">
                 <Button.ToolTip>
                     <local:BlackToolTip Text="文件夹管理" Direction="Right"/>

+ 8 - 0
aipdev/MainWindow.xaml.cs

@@ -89,6 +89,8 @@ namespace aipdev
             ContentManager.RegisterContentPage(new DeveloperCenter());
             ContentManager.RegisterContentPage(new FolderManagement());
             ContentManager.RegisterContentPage(new FolderImageBrowser());
+            ContentManager.RegisterContentPage(new PendingAllocationManagement());
+            ContentManager.RegisterContentPage(new PendingAllocationImageBrowser());
             ContentManager.RegisterContentPage(new FolderQuestionedManagement());
             ContentManager.RegisterContentPage(new FolderQuestionedImageBrowser());
             ContentManager.RegisterContentPage(new ScriptManagement());
@@ -208,6 +210,10 @@ namespace aipdev
             ContentManager.Show("FolderManagement");
         }
 
+        private void OnPendingAllocationClick(object sender, RoutedEventArgs e)
+        {
+            ContentManager.Show("PendingAllocationManagement");
+        }
         private void OnFolderQuestionedManagementClick(object sender, RoutedEventArgs e)
         {
             ContentManager.Show("FolderQuestionedManagement");
@@ -301,5 +307,7 @@ namespace aipdev
             }
             Process.GetCurrentProcess().Kill();
         }
+
+
     }
 }

+ 119 - 0
aipdev/PendingAllocationImageBrowser.xaml

@@ -0,0 +1,119 @@
+<UserControl x:Class="aipdev.PendingAllocationImageBrowser"
+             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
+             xmlns:local="clr-namespace:aipdev"
+             xmlns:extensions="clr-namespace:aipdev.Extensions"
+             mc:Ignorable="d" 
+             d:DesignHeight="450" d:DesignWidth="800">
+    <UserControl.Resources>
+        <Style TargetType="{x:Type TextBox}" x:Key="ReadOnlyTextBoxStyle">
+            <Style.Setters>
+                <Setter Property="IsReadOnly" Value="True"/>
+                <Setter Property="Background" Value="Transparent"/>
+                <Setter Property="Foreground" Value="White"/>
+                <Setter Property="BorderThickness" Value="0"/>
+                <Setter Property="HorizontalAlignment" Value="Center"/>
+                <Setter Property="VerticalAlignment" Value="Center"/>
+            </Style.Setters>
+        </Style>
+    </UserControl.Resources>
+    <Grid>
+        <Grid.RowDefinitions>
+            <RowDefinition Height="48"/>
+            <RowDefinition Height="*"/>
+        </Grid.RowDefinitions>
+        <Border Grid.Row="0" BorderThickness="0.5" BorderBrush="Gray">
+            <Grid>
+                <Grid.ColumnDefinitions>
+                    <ColumnDefinition Width="0.4*"/>
+                    <ColumnDefinition Width="0.4*"/>
+                    <ColumnDefinition Width="Auto"/>
+                    <ColumnDefinition Width="Auto"/>
+                    <ColumnDefinition Width="Auto"/>
+                    <ColumnDefinition Width="Auto"/>
+                </Grid.ColumnDefinitions>
+                <TextBlock x:Name="FolderName" Grid.Column="0" VerticalAlignment="Center" Margin="4" FontWeight="Bold" FontSize="20" TextTrimming="CharacterEllipsis"/>
+                <TextBox Grid.Column="1" x:Name="txtFileName" Height="32" Margin="5,0" FontSize="14" VerticalContentAlignment="Center" HorizontalContentAlignment="Center" Padding="0"
+                         PreviewKeyDown="txtFileName_PreviewKeyDown">
+                    <TextBox.Resources>
+                        <VisualBrush x:Key="HelpBrush" TileMode="None" Opacity="0.3" Stretch="None" AlignmentX="Center">
+                            <VisualBrush.Visual>
+                                <TextBlock Text="图像名称" FontSize="14"/>
+                            </VisualBrush.Visual>
+                        </VisualBrush>
+                    </TextBox.Resources>
+                    <TextBox.Style>
+                        <Style TargetType="TextBox">
+                            <Style.Triggers>
+                                <Trigger Property="Text" Value="{x:Null}">
+                                    <Setter Property="Background" Value="{StaticResource HelpBrush}"/>
+                                </Trigger>
+                                <Trigger Property="Text" Value="">
+                                    <Setter Property="Background" Value="{StaticResource HelpBrush}"/>
+                                </Trigger>
+                            </Style.Triggers>
+                        </Style>
+                    </TextBox.Style>
+                </TextBox>
+                <Button x:Name="btnSearch" Content="搜索" Grid.Column="2" HorizontalAlignment="Left" Margin="2,0" Style="{StaticResource btn-primary}" Click="OnSearchFiles"/>
+                <Button x:Name="btnClearAndSearch" Content="清空搜索" Grid.Column="3" HorizontalAlignment="Left" Margin="2,0" Style="{StaticResource btn-primary}" Click="OnClearAndSearchFiles"/>
+                <CheckBox x:Name="chbIsConfirmed" Content="待处理" Grid.Column="4" FontSize="14" Margin="2,0" HorizontalAlignment="Left" Click="OnConfirmedFilesClick" />
+                <Button Style="{StaticResource btn-danger}" Grid.Column="5" Content="X" VerticalAlignment="Center" HorizontalAlignment="Right" Margin="4,0" Click="OnCloseClick"/>
+            </Grid>
+        </Border>
+        <ListBox Grid.Row="1" x:Name="FolderImages">
+            <ListBox.ItemsPanel>
+                <ItemsPanelTemplate>
+                    <WrapPanel/>
+                </ItemsPanelTemplate>
+            </ListBox.ItemsPanel>
+            <ListBox.ItemContainerStyle>
+                <Style TargetType="ListBoxItem">
+                    <Setter Property="ToolTip" Value="{Binding Conclusions}"/>
+                    <Setter Property="Template">
+                        <Setter.Value>
+                            <ControlTemplate>
+                                <Button OverridesDefaultStyle="True" x:Name="ImageButton" Width="520" Height="520" Margin="8" Click="OnImageClick">
+                                    <Button.Template>
+                                        <ControlTemplate>
+                                            <Border x:Name="ImageBorder" BorderThickness="0.5" BorderBrush="Gray" CornerRadius="4" Background="Black">
+                                                <Grid>
+                                                    <Grid.RowDefinitions>
+                                                        <RowDefinition Height="*"/>
+                                                        <RowDefinition Height="Auto"/>
+                                                    </Grid.RowDefinitions>
+                                                    <Image Source="{Binding PreviewImage}" Stretch="Uniform" VerticalAlignment="Center" HorizontalAlignment="Center" Margin="2"/>
+                                                    <!--<Image Source="Resources/Images/Question.png" VerticalAlignment="Top" HorizontalAlignment="Right" Width="20" Height="20"
+                                                           Visibility="{Binding HasQuestioned, Converter={extensions:BoolToVisibilityConverter}}" />-->
+                                                    <TextBlock Foreground="Red" Text="{Binding DeveloperFolderFile.Index, Mode=OneWay}" VerticalAlignment="Top" HorizontalAlignment="Left" Margin="5,0,0,0"/>
+                                                    <GroupBox Margin="4" MinHeight="50" Header="标注结果" Foreground="White" BorderBrush="Gray" BorderThickness="0.5" Grid.Row="1">
+                                                        <TextBlock Foreground="White" Text="{Binding TrimmingConclusions, Mode=OneWay}" VerticalAlignment="Center" HorizontalAlignment="Left" Margin="5,0,0,0" TextTrimming="CharacterEllipsis"/>
+                                                    </GroupBox>
+                                                </Grid>
+                                            </Border>
+                                            <ControlTemplate.Triggers>
+                                                <Trigger Property="IsMouseOver" Value="True">
+                                                    <Setter TargetName="ImageBorder" Property="BorderBrush" Value="DeepSkyBlue"/>
+                                                    <Setter TargetName="ImageBorder" Property="BorderThickness" Value="2"/>
+                                                </Trigger>
+                                            </ControlTemplate.Triggers>
+                                        </ControlTemplate>
+                                    </Button.Template>
+                                    <!--<Button.ContextMenu>
+                                        <ContextMenu>
+                                            <MenuItem Header="复制..." IsEnabled="{Binding CanCopy}" Click="OnCopyFileClick"/>
+                                            <MenuItem Header="删除..." IsEnabled="{Binding CanDelete}" Click="OnDeleteFileClick"/>
+                                        </ContextMenu>
+                                    </Button.ContextMenu>-->
+                                </Button>
+                            </ControlTemplate>
+                        </Setter.Value>
+                    </Setter>
+                </Style>
+            </ListBox.ItemContainerStyle>
+        </ListBox>
+        <local:WaitingSpinner Grid.Column="0" Grid.ColumnSpan="2" x:Name="Waiting"/>
+    </Grid>
+</UserControl>

+ 472 - 0
aipdev/PendingAllocationImageBrowser.xaml.cs

@@ -0,0 +1,472 @@
+using AIPlatform.Protocol.Model;
+using AIPlatform.Protocol.Utilities;
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+
+namespace aipdev
+{
+    /// <summary>
+    /// Interaction logic for PendingAllocationImageBrowser.xaml
+    /// </summary>
+    public partial class PendingAllocationImageBrowser : UserControl, IContentPage
+    {
+        private ScrollViewer _scrollViewer;
+
+        private string _keyword;
+
+        private int _pageSize { get; set; } = 45;
+
+        private int _totalPages { get; set; }
+
+        private int _preloadedPage { get; set; }
+
+        private bool _scrollDown { get; set; }
+
+        private bool _isLoadPreviousImages { get; set; }
+
+        private bool _isLoadNextImages { get; set; }
+
+        private double _oldVerticalOffset { get; set; }
+
+        private int _browseImageIndex { get; set; }
+
+        private int _browsePage { get; set; }
+
+        private int _imageCount { get; set; }
+
+        private int _groupCount { get; set; }
+
+        private ImageFolderExtend _folder;
+
+        private readonly ObservableCollection<FolderImageEx> _images = new ObservableCollection<FolderImageEx>();
+
+        public string PageName { get; set; }
+
+        public bool IsHomePage { get; set; }
+
+        public bool IsModalPage { get; set; }
+
+        private bool _stopLoading { get; set; }
+
+        private bool _isPendingConfirmed { get; set; }
+
+        public PendingAllocationImageBrowser()
+        {
+            InitializeComponent();
+            PageName = "PendingAllocationImageBrowser";
+            IsHomePage = false;
+            IsModalPage = true;
+            FolderImages.ItemsSource = _images;
+        }
+
+        private T FindVisualChild<T>(DependencyObject element) where T : class
+        {
+            try
+            {
+                while (element != null)
+                {
+                    if (element is T)
+                        return element as T;
+                    element = VisualTreeHelper.GetChild(element, 0);
+                }
+            }
+            catch (Exception)
+            {
+                //DoNothing
+            }
+
+            return null;
+        }
+
+        private void OnFolderImagesScrollChanged(object sender, ScrollChangedEventArgs e)
+        {
+            var scrollViewer = (ScrollViewer)sender;
+            if (IsOnScrollViewBottom(scrollViewer))
+            {
+                if (_preloadedPage < _totalPages)
+                {
+                    try
+                    {
+                        Task.Run(async () =>
+                        {
+                            await LoadImagesAsync(_preloadedPage).ConfigureAwait(false);
+                        });
+                    }
+                    catch (Exception ex)
+                    {
+                        MessageBox.Show(Application.Current.MainWindow, $"加载文件夹图像失败:{ex.Translate()}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
+                    }
+                }
+            }
+        }
+
+        private bool IsOnScrollViewBottom(ScrollViewer view)
+        {
+            var onBottom = false;
+            var verticalOffset = view.VerticalOffset;
+            var scrollableHeight = view.ScrollableHeight;
+            var extentHeight = view.ExtentHeight;
+            _scrollDown = verticalOffset >= _oldVerticalOffset;
+
+            #region 滚动条从上往下翻看,加载之面页数
+
+            if (_scrollDown && verticalOffset / extentHeight > 0.7 && _stopLoading && _preloadedPage < _totalPages - 1)
+            {
+                _isLoadNextImages = true;
+                _isLoadPreviousImages = false;
+                onBottom = true;
+                verticalOffset = 2;
+                _preloadedPage++;
+            }
+
+            #endregion 滚动条从上往下翻看,加载之面页数
+
+            #region 有上一次的浏览记录时,滚动条从下往上翻看,加载之前页数
+
+            else if (_browsePage > 0 && !_scrollDown && verticalOffset / extentHeight < 0.3 && _stopLoading && _preloadedPage > 0)
+            {
+                _isLoadPreviousImages = true;
+                _isLoadNextImages = false;
+                onBottom = true;
+                verticalOffset = scrollableHeight;
+                _preloadedPage--;
+            }
+
+            #endregion 有上一次的浏览记录时,滚动条从下往上翻看,加载之前页数
+
+            _oldVerticalOffset = verticalOffset;
+            return onBottom;
+        }
+
+        public void Close()
+        {
+            ClearImages();
+        }
+
+        private string GetConclusions(List<LabelConclusion> labelConclusions, ref string trimmingConclusions)
+        {
+            var conclusions = string.Empty;
+            for (int i = 0; i < labelConclusions.Count; i++)
+            {
+                var item = labelConclusions[i];
+                conclusions += $"{item.Title} {item.Content}";
+                if (i < labelConclusions.Count - 1)
+                    conclusions += Environment.NewLine;
+
+                if (i < 5)
+                {
+                    trimmingConclusions = conclusions;
+                }
+                else if (i == 5)
+                {
+                    trimmingConclusions += "......";
+                }
+            }
+            return conclusions;
+        }
+
+        private async Task LoadImagesAsync(int page = 0)
+        {
+            try
+            {
+                Dispatcher.Invoke(() =>
+                {
+                    ContentManager.ShowLoading();
+                });
+                _stopLoading = false;
+                if (_folder is ImageFolderExtend imageFolder)
+                {
+                    var labeledFileInfos = await DeveloperManager.Shared.GetLatestLabeledFileInfosAsync(imageFolder.Id, _keyword, true, _isPendingConfirmed, page, _pageSize);
+                    if (_isLoadPreviousImages)
+                    {
+                        labeledFileInfos = labeledFileInfos.OrderByDescending(x => x).ToList();
+                    }
+                    foreach (var fileInfo in labeledFileInfos)
+                    {
+                        var labeledUltrasoundFile = fileInfo.LabeledUltrasoundFile;
+                        if (_images.Any(x => x.LabeledUltrasoundFile.Id == labeledUltrasoundFile.Id))
+                        {
+                            continue;
+                        }
+                        var previewImage = new BitmapImage();
+                        previewImage.BeginInit();
+                        previewImage.StreamSource = new MemoryStream(fileInfo.ImageData);
+                        previewImage.EndInit();
+                        previewImage.Freeze();
+                        var trimmingConclusions = string.Empty;
+                        var conclusions = GetConclusions(fileInfo.LabelConclusions, ref trimmingConclusions);
+                        var folderImageEx = new FolderImageEx(labeledUltrasoundFile, previewImage, false, fileInfo.LabeledResult, conclusions, trimmingConclusions, page, imageFolder.Id, fileInfo.HasQuestioned);
+                        if (_stopLoading)
+                        {
+                            break;
+                        }
+                        if (_isLoadPreviousImages)
+                        {
+                            Dispatcher.Invoke(() =>
+                            {
+                                _images.Insert(0, folderImageEx);
+                            });
+                        }
+                        else
+                        {
+                            Dispatcher.Invoke(() =>
+                            {
+                                _images.Add(folderImageEx);
+                            });
+                        }
+                    }
+                }
+                _stopLoading = true;
+
+                Dispatcher.Invoke(() =>
+                {
+                    if (_scrollViewer == null)
+                    {
+                        _scrollViewer = FindVisualChild<ScrollViewer>(FolderImages);
+                        if (_scrollViewer != null)
+                        {
+                            if (_browseImageIndex == 0)
+                            {
+                                _scrollViewer.ScrollToTop();
+                            }
+                            else
+                            {
+                                var rowHeight = _scrollViewer.ExtentHeight / Math.Ceiling((double)_images.Count / 3);
+                                var count = _browseImageIndex % _pageSize;
+                                var verticalOffset = Math.Floor((double)count / 3) * rowHeight;
+                                _scrollViewer.ScrollToVerticalOffset(verticalOffset);
+                            }
+                            _scrollViewer.ScrollChanged += OnFolderImagesScrollChanged;
+                        }
+                    }
+                    else if (_isLoadPreviousImages)
+                    {
+                        var rowHeight = _scrollViewer.ExtentHeight / Math.Ceiling((double)_images.Count / 3);
+                        var verticalOffset = _pageSize / 3 * rowHeight + _scrollViewer.VerticalOffset;
+                        _scrollViewer.ScrollToVerticalOffset(verticalOffset);
+                    }
+                });
+            }
+            catch (Exception ex)
+            {
+                Dispatcher.Invoke(() =>
+                {
+                    MessageBox.Show(Application.Current.MainWindow, $"加载文件夹图像失败:{ex.Translate()}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
+                });
+            }
+            finally
+            {
+                Dispatcher.Invoke(() =>
+                {
+                    ContentManager.HideLoading();
+                });
+            }
+        }
+
+        private void ClearImages()
+        {
+            Reset();
+
+            _folder = null;
+            FolderName.Text = string.Empty;
+            FolderName.ToolTip = string.Empty;
+            _keyword = txtFileName.Text = string.Empty;
+            _totalPages = 0;
+            _imageCount = 0;
+            _groupCount = 0;
+            chbIsConfirmed.IsChecked = false;
+            _isPendingConfirmed = false;
+        }
+
+        public async void OnShow(object arg)
+        {
+            try
+            {
+                if (arg is ImageFolderExtend imageFolder)
+                {
+                    _folder = imageFolder;
+                    FolderName.Text = _folder.Name;
+                    FolderName.ToolTip = _folder.Name;
+                    _imageCount = _folder.ImageCount;
+                    _groupCount = _folder.GroupCount;
+                    _totalPages = (int)Math.Ceiling((double)_groupCount / _pageSize);
+
+                    await LoadImagesAsync(_preloadedPage).ConfigureAwait(false);
+                }
+                else
+                {
+                    throw new InvalidOperationException("无效的文件夹路径。");
+                }
+            }
+            catch (Exception ex)
+            {
+                MessageBox.Show(Application.Current.MainWindow, $"加载文件夹图像失败:{ex.Translate()}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
+            }
+        }
+
+        public void OnHide()
+        {
+            ClearImages();
+        }
+
+        private async void OnImageClick(object sender, RoutedEventArgs e)
+        {
+            FolderImageEx folderImageEx = null;
+            if (sender is MenuItem menuItem)
+            {
+                folderImageEx = (FolderImageEx)menuItem.DataContext;
+            }
+            else if (sender is Button button)
+            {
+                folderImageEx = (FolderImageEx)button.DataContext;
+            }
+
+            if (folderImageEx != null)
+            {
+                if (folderImageEx.LabeledUltrasoundFile != null)
+                {
+                    var lstFileId = _images.Select(x => x.LabeledUltrasoundFile.UltrasoundFileId).ToList();
+                    var lstLabeledFileId = _images.Select(x => x.LabeledUltrasoundFile.Id).ToList();
+                    var index = lstFileId.IndexOf(folderImageEx.LabeledUltrasoundFile.UltrasoundFileId);
+                    var folderImageViewer = new FolderQuestionedImageViewer(folderImageEx.SourceFolderId, index, folderImageEx.LabeledUltrasoundFile, lstFileId, lstLabeledFileId) { Owner = Application.Current.MainWindow };
+                    folderImageViewer.ShowDialog();
+                }
+            }
+        }
+
+        private void OnCloseClick(object sender, RoutedEventArgs e)
+        {
+            ClearImages();
+            this.Hide();
+        }
+
+        private void OnSearchFiles(object sender, RoutedEventArgs e)
+        {
+            _keyword = txtFileName.Text;
+            Reset();
+            Task.Run(async () =>
+            {
+                await LoadImagesAsync().ConfigureAwait(false);
+            });
+        }
+
+        private void Reset()
+        {
+            _scrollViewer ??= FindVisualChild<ScrollViewer>(FolderImages);
+            if (_scrollViewer != null)
+            {
+                //#region 保存历史查看位置
+                //if (_folder is DeveloperImageFolder developerImageFolder)
+                //{
+                //    try
+                //    {
+                //        var rowHeight = _scrollViewer.ExtentHeight / Math.Ceiling((double)_images.Count / 3);
+                //        var currRows = Math.Ceiling(_scrollViewer.VerticalOffset / rowHeight);
+                //        _browseImageIndex = (int)currRows * 3;
+                //        _browsePage = _images[_browseImageIndex].Page;
+                //        await DeveloperManager.Shared.UpdateDeveloperFolderBrowseHistoricalInfo(_folder.Id, _browseImageIndex, _browsePage);
+                //    }
+                //    catch (Exception ex)
+                //    {
+                //        MessageBox.Show(Application.Current.MainWindow, $"保存文件夹浏览记录失败:{ex.Translate()}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
+                //    }
+                //}
+                //#endregion
+
+                _scrollViewer.ScrollChanged -= OnFolderImagesScrollChanged;
+            }
+            _stopLoading = true;
+            _scrollViewer = null;
+            _images.Clear();
+            _preloadedPage = 0;
+            _isLoadPreviousImages = false;
+            _isLoadNextImages = false;
+            _scrollDown = false;
+            _oldVerticalOffset = 0;
+            _browseImageIndex = 0;
+            _browsePage = 0;
+        }
+
+        private void OnClearAndSearchFiles(object sender, RoutedEventArgs e)
+        {
+            _keyword = txtFileName.Text = string.Empty;
+            OnSearchFiles(sender, e);
+        }
+
+        private void txtFileName_PreviewKeyDown(object sender, KeyEventArgs e)
+        {
+            if (e.Key == Key.Enter)
+            {
+                OnSearchFiles(sender, e);
+            }
+        }
+
+        private async void OnSuspectedMislabelingFileClick(object sender, RoutedEventArgs e)
+        {
+            var menuItem = (MenuItem)sender;
+            var folderImageEx = (FolderImageEx)menuItem.DataContext;
+            try
+            {
+                CopyImageWindow copyImageWindow;
+                if (folderImageEx.DeveloperFolderFile != null)
+                {
+                    copyImageWindow = new CopyImageWindow(folderImageEx.DeveloperFolderFile.ImageCategoryId) { Owner = Application.Current.MainWindow };
+                }
+                else
+                {
+                    copyImageWindow = new CopyImageWindow(folderImageEx.LabeledUltrasoundFile.ImageCategoryId) { Owner = Application.Current.MainWindow };
+                }
+                copyImageWindow.ShowDialog();
+                var selectedFolder = copyImageWindow.SelectedFolder;
+                if (selectedFolder != null)
+                {
+                    Dispatcher.Invoke(() =>
+                    {
+                        ContentManager.ShowLoading();
+                    });
+                    try
+                    {
+                        if (folderImageEx.DeveloperFolderFile != null)
+                        {
+                            await DeveloperManager.Shared.CopyDeveloperFolderGroupAsync(selectedFolder.Id, folderImageEx.SourceFolderId, folderImageEx.DeveloperFolderFile.UltrasoundGroupId);
+                        }
+                        else
+                        {
+                            await DeveloperManager.Shared.CopyFolderGroupAsync(selectedFolder.Id, folderImageEx.SourceFolderId, folderImageEx.LabeledUltrasoundFile.UltrasoundGroupId);
+                        }
+                    }
+                    catch (Exception ex)
+                    {
+                        MessageBox.Show(Application.Current.MainWindow, $"复制图片失败:{ex.Translate()}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
+                    }
+                    finally
+                    {
+                        Dispatcher.Invoke(() =>
+                        {
+                            ContentManager.HideLoading();
+                        });
+                    }
+                }
+            }
+            catch (Exception ex)
+            {
+                MessageBox.Show(Application.Current.MainWindow, $"获取文件夹列表失败:{ex.Translate()}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
+            }
+        }
+
+        private void OnConfirmedFilesClick(object sender, RoutedEventArgs e)
+        {
+            _isPendingConfirmed = (bool)chbIsConfirmed.IsChecked;
+            OnSearchFiles(sender, e);
+        }
+    }
+}

+ 166 - 0
aipdev/PendingAllocationManagement.xaml

@@ -0,0 +1,166 @@
+<UserControl x:Class="aipdev.PendingAllocationManagement"
+             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
+             xmlns:local="clr-namespace:aipdev" 
+             xmlns:extensions="clr-namespace:aipdev.Extensions"
+             mc:Ignorable="d" 
+             d:DesignHeight="450" d:DesignWidth="800">
+
+    <Grid>
+        <Grid.RowDefinitions>
+            <RowDefinition Height="50"/>
+            <RowDefinition Height="*"/>
+        </Grid.RowDefinitions>
+        <Button Grid.Row="0" HorizontalAlignment="Left" Margin="2" VerticalAlignment="Center" Content="刷新" Click="OnRefreshServerFoldersClick"/>
+        <TreeView x:Name="ServerFolders" Grid.Row="1" Grid.Column="0">
+            <TreeView.Resources>
+                <Style x:Key="ExpandCollapseToggleStyle" TargetType="ToggleButton">
+                    <Setter Property="Focusable" Value="False"/>
+                    <Setter Property="Background" Value="Transparent"/>
+                    <Setter Property="BorderBrush" Value="#FF565656"/>
+                    <Setter Property="Template">
+                        <Setter.Value>
+                            <ControlTemplate TargetType="ToggleButton">
+                                <Grid>
+                                    <!-- 画折叠样式 -->
+                                    <Border x:Name="CollapsePath" Height="14" Width="10" SnapsToDevicePixels="True" Background="{TemplateBinding Background}">
+                                        <!--<Rectangle x:Name="CollapsePath" Width="1" Height="5" Stroke="{TemplateBinding Background}" SnapsToDevicePixels="true"/>-->
+                                        <Path Data="M 0,0 8,7 0,14" Stretch="Fill"
+                                              Stroke="{TemplateBinding BorderBrush}" StrokeThickness="1.5"
+                                              Height="14" Width="8"
+                                              VerticalAlignment="Center" 
+                                              HorizontalAlignment="Center"
+                                              Margin="0,0,0,0">
+                                            <Path.LayoutTransform>
+                                                <TransformGroup>
+                                                    <ScaleTransform/>
+                                                    <SkewTransform/>
+                                                    <RotateTransform Angle="0"/>
+                                                    <TranslateTransform/>
+                                                </TransformGroup>
+                                            </Path.LayoutTransform>
+                                        </Path>
+                                    </Border>
+                                    <!-- 画展开折叠样式 -->
+                                    <Border x:Name="ExpandPath" Height="10" Width="14" SnapsToDevicePixels="True" Background="{TemplateBinding Background}" Visibility="Collapsed">
+                                        <!--<Rectangle x:Name="ExpandPath" Width="1" Height="5" Stroke="{TemplateBinding Background}" SnapsToDevicePixels="true"/>-->
+                                        <Path Data="M 0,0 8,7 0,14" Stretch="Fill"
+                                              Stroke="{TemplateBinding BorderBrush}" StrokeThickness="1.5"
+                                              Height="14" Width="8"
+                                              VerticalAlignment="Center" 
+                                              HorizontalAlignment="Center"
+                                              Margin="0,0,0,0">
+                                            <Path.LayoutTransform>
+                                                <TransformGroup>
+                                                    <ScaleTransform/>
+                                                    <SkewTransform/>
+                                                    <RotateTransform Angle="90"/>
+                                                    <TranslateTransform/>
+                                                </TransformGroup>
+                                            </Path.LayoutTransform>
+                                        </Path>
+                                    </Border>
+                                </Grid>
+                                <ControlTemplate.Triggers>
+                                    <!-- 通过IsChecked判断折叠还是展开 -->
+                                    <Trigger Property="IsChecked" Value="True">
+                                        <Setter Property="Visibility" TargetName="CollapsePath" Value="Collapsed"/>
+                                        <Setter Property="Visibility" TargetName="ExpandPath" Value="Visible"/>
+                                    </Trigger>
+                                </ControlTemplate.Triggers>
+                            </ControlTemplate>
+                        </Setter.Value>
+                    </Setter>
+                </Style>
+
+                <HierarchicalDataTemplate DataType= "{x:Type local:ImageFolderExtend}" ItemsSource = "{Binding Path=Children}">
+                    <Grid>
+                        <Grid.ColumnDefinitions>
+                            <ColumnDefinition Width="24"/>
+                            <ColumnDefinition Width="*"/>
+                        </Grid.ColumnDefinitions>
+                        <Image Grid.Column="0" VerticalAlignment="Center" HorizontalAlignment="Center" 
+                                       Source="{Binding Path=DataContext,RelativeSource={RelativeSource Mode=Self}, Converter={extensions:FolderLevelToImageConverter}}" Width="20" Height="20"/>
+                        <TextBlock Grid.Column="1" VerticalAlignment="Center" Text="{Binding Description}" Foreground="Black" Margin="2,0,0,0" FontSize="14px"/>
+                        <Grid.ContextMenu>
+                            <ContextMenu Visibility="{Binding HasMenu, Converter={extensions:BoolToVisibilityConverter}}">
+                                <MenuItem Header="查看..." IsEnabled="{Binding IsEnabled}" Click="OnOpenFolderClick"/>
+                            </ContextMenu>
+                        </Grid.ContextMenu>
+                    </Grid>
+                </HierarchicalDataTemplate>
+            </TreeView.Resources>
+            <TreeView.ItemContainerStyle>
+                <Style TargetType="{x:Type TreeViewItem}">
+                    <Setter Property="IsExpanded" Value="True" />
+                    <Setter Property="Background" Value="Transparent"/>
+                    <Setter Property="Padding" Value="1,0,0,0"/>
+                    <Setter Property="Template">
+                        <Setter.Value>
+                            <ControlTemplate TargetType="{x:Type TreeViewItem}">
+                                <Grid>
+                                    <Grid.ColumnDefinitions>
+                                        <ColumnDefinition MinWidth="20" Width="Auto"/>
+                                        <ColumnDefinition Width="Auto"/>
+                                        <ColumnDefinition Width="*"/>
+                                    </Grid.ColumnDefinitions>
+                                    <Grid.RowDefinitions>
+                                        <RowDefinition Height="Auto"/>
+                                        <RowDefinition/>
+                                    </Grid.RowDefinitions>
+                                    <Border Name="FocusBd" Margin="-100,0,0,0" Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="3" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Padding="{TemplateBinding Padding}" SnapsToDevicePixels="True"></Border>
+                                    <Border Name="Bd" Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="3" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Padding="{TemplateBinding Padding}" SnapsToDevicePixels="True">
+                                        <Grid Margin="2">
+                                            <Grid.ColumnDefinitions>
+                                                <ColumnDefinition Width="Auto"/>
+                                                <ColumnDefinition Width="Auto"/>
+                                                <ColumnDefinition Width="*"/>
+                                            </Grid.ColumnDefinitions>
+                                            <ToggleButton Width="18" Grid.Column="0" Margin="-1,0,0,0" x:Name="Expander" Style="{StaticResource ExpandCollapseToggleStyle}" IsChecked="{Binding Path=IsExpanded, RelativeSource={RelativeSource TemplatedParent}}" ClickMode="Press"/>
+                                            <ContentPresenter Grid.Column="2" x:Name="PART_Header" ContentSource="Header" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" MinWidth="20"/>
+                                        </Grid>
+                                    </Border>
+                                    <ItemsPresenter x:Name="ItemsHost" Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="2"/>
+                                </Grid>
+                                <ControlTemplate.Triggers>
+                                    <Trigger Property="IsExpanded" Value="False">
+                                        <Setter TargetName="ItemsHost" Property="Visibility" Value="Collapsed"/>
+                                    </Trigger>
+                                    <Trigger Property="HasItems" Value="False">
+                                        <Setter TargetName="Expander" Property="Visibility" Value="Collapsed"/>
+                                    </Trigger>
+                                    <MultiTrigger>
+                                        <MultiTrigger.Conditions>
+                                            <Condition Property="HasHeader" Value="False"/>
+                                            <Condition Property="Width" Value="Auto"/>
+                                        </MultiTrigger.Conditions>
+                                        <Setter TargetName="PART_Header" Property="MinWidth" Value="75"/>
+                                    </MultiTrigger>
+                                    <MultiTrigger>
+                                        <MultiTrigger.Conditions>
+                                            <Condition Property="HasHeader" Value="False"/>
+                                            <Condition Property="Height" Value="Auto"/>
+                                        </MultiTrigger.Conditions>
+                                        <Setter TargetName="PART_Header" Property="MinHeight" Value="19"/>
+                                    </MultiTrigger>
+                                    <Trigger Property="IsSelected" Value="True">
+                                        <Setter TargetName="FocusBd" Property="Background" Value="#C4D5FF"/>
+                                        <Setter TargetName="Bd" Property="Background" Value="#C4D5FF"/>
+                                    </Trigger>
+                                    <Trigger Property="IsEnabled" Value="False">
+                                        <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
+                                    </Trigger>
+                                </ControlTemplate.Triggers>
+                            </ControlTemplate>
+                        </Setter.Value>
+                    </Setter>
+                    <EventSetter Event="MouseDoubleClick" Handler="OnNodeDoubleClickHandler"></EventSetter>
+                    <EventSetter Event="MouseDown" Handler="OnNodeMouseDown"></EventSetter>
+                </Style>
+            </TreeView.ItemContainerStyle>
+        </TreeView>
+    </Grid>
+
+</UserControl>

+ 214 - 0
aipdev/PendingAllocationManagement.xaml.cs

@@ -0,0 +1,214 @@
+using AIPlatform.Protocol.Entities;
+using AIPlatform.Protocol.Utilities;
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+
+namespace aipdev
+{
+    /// <summary>
+    /// Interaction logic for PendingAllocationManagement.xaml
+    /// </summary>
+    public partial class PendingAllocationManagement : UserControl, IContentPage
+    {
+        private readonly ObservableCollection<ImageFolderExtend> _serverFolders = new ObservableCollection<ImageFolderExtend>();
+
+        public string PageName { get; set; }
+
+        public bool IsHomePage { get; set; }
+
+        public bool IsModalPage { get; set; }
+
+        public List<ImageCategory> _imageCategories { get; set; }
+
+        public List<ImageCategory> _imageBatches { get; set; }
+
+        public PendingAllocationManagement()
+        {
+            InitializeComponent();
+            PageName = "PendingAllocationManagement";
+            IsHomePage = false;
+            IsModalPage = false;
+            ServerFolders.ItemsSource = _serverFolders;
+        }
+
+        public async void OnShow(object arg)
+        {
+            await LoadFoldersAsync().ConfigureAwait(false);
+        }
+
+        public void OnHide()
+        {
+            ClearFolders();
+        }
+
+        public void Close()
+        {
+            ClearFolders();
+        }
+
+        private void ClearFolders()
+        {
+            _serverFolders.Clear();
+        }
+
+        private async Task LoadFoldersAsync()
+        {
+            try
+            {
+                Dispatcher.Invoke(() =>
+                {
+                    ContentManager.ShowLoading();
+                });
+                await LoadServerFoldersAsync().ConfigureAwait(false);
+            }
+            catch (Exception ex)
+            {
+                Dispatcher.Invoke(() =>
+                {
+                    MessageBox.Show(Application.Current.MainWindow, $"加载文件夹列表失败:{ex.Translate()}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
+                });
+            }
+            finally
+            {
+                Dispatcher.Invoke(() =>
+                {
+                    ContentManager.HideLoading();
+                });
+            }
+        }
+
+        private async Task LoadServerFoldersAsync()
+        {
+            _imageCategories = new List<ImageCategory>();
+
+            #region Organizations
+            Dispatcher.Invoke(() =>
+            {
+                _serverFolders.Clear();
+            });
+            var organizations = await DeveloperManager.Shared.GetOrganizationsAsync().ConfigureAwait(false);
+            if (organizations != null)
+            {
+                foreach (var org in organizations)
+                {
+                    var orgExtend = new ImageFolderExtend
+                    {
+                        Id = org.Id,
+                        Name = org.Name,
+                        Description = org.Name,
+                        Level = CategoryLevel.Organization,
+                        HasMenu = false,
+                    };
+                    #region ImageCategories
+                    var categories = await DeveloperManager.Shared.GetImageCategoriesAsync(org.Id).ConfigureAwait(false);
+                    if (categories != null)
+                    {
+                        foreach (var categ in categories)
+                        {
+                            if (categ.Developers.Any(x => x.Id == DeveloperManager.Shared.Session.AccountId))
+                            {
+                                if (categ.DevelopConfirmInfo.Status != DevelopConfirmStatus.WaitForConfirm)
+                                {
+                                    continue;
+                                }
+                                var quantity = categ.FileItem.Quantity;
+                                var desc = $"{categ.Name}";
+                                var groupQty = categ.GroupItem.Quantity;
+                                var categExtend = new ImageFolderExtend
+                                {
+                                    Id = categ.Id,
+                                    Name = categ.Name,
+                                    Description = desc,
+                                    Level = categ.Level,
+                                    ImageCount = quantity.Seen,
+                                    GroupCount = groupQty.Seen,
+                                    HasMenu = true,
+                                    OrganizationId = org.Id,
+                                    ImageCategoryId = categ.Id,
+                                    IsEnabled = true,
+                                    Info = $"组:{quantity.Seen},文件:{quantity.Seen}",
+                                };
+                                await AddItemChildAsync(categExtend, categ, categ.Id).ConfigureAwait(false);
+                                orgExtend.Children.Add(categExtend);
+                                _imageCategories.Add(categ);
+                            }
+                        }
+                    }
+                    #endregion ImageCategories
+                    Dispatcher.Invoke(() =>
+                    {
+                        _serverFolders.Add(orgExtend);
+                    });
+                }
+            }
+            #endregion Organizations
+        }
+
+        private async Task AddItemChildAsync(ImageFolderExtend folderExtend, ImageCategory category, long imageCategoryId)
+        {
+            var categories = await DeveloperManager.Shared.GetImageCategoriesAsync(category.Id).ConfigureAwait(false);
+            if (categories != null)
+            {
+                foreach (var item in categories)
+                {
+                    if (item.DevelopConfirmInfo.Status != DevelopConfirmStatus.WaitForConfirm)
+                    {
+                        continue;
+                    }
+                    var quantity = item.FileItem.Quantity;
+                    var desc = $"{item.Name}";
+                    if (item.Level == CategoryLevel.SameBatchLabelCase)
+                    {
+                        desc += $" - 文件:总计:{quantity.Total}";
+                    }
+                    var groupQty = item.GroupItem.Quantity;
+                    var caseExtend = new ImageFolderExtend
+                    {
+                        Id = item.Id,
+                        Name = item.Name,
+                        Description = desc,
+                        Level = item.Level,
+                        ImageCount = quantity.Seen,
+                        GroupCount = groupQty.Seen,
+                        HasMenu = true,
+                        OrganizationId = item.OrganizationId,
+                        ImageCategoryId = imageCategoryId,
+                        IsEnabled = true,
+                        Info = $"组:{quantity.Seen},文件:{quantity.Seen}",
+                    };
+                    await AddItemChildAsync(caseExtend, item, imageCategoryId).ConfigureAwait(false);
+                    folderExtend.Children.Add(caseExtend);
+                }
+            }
+        }
+
+        private async void OnRefreshServerFoldersClick(object sender, RoutedEventArgs e)
+        {
+            await LoadFoldersAsync().ConfigureAwait(false);
+        }
+
+        private void OnOpenFolderClick(object sender, RoutedEventArgs e)
+        {
+            var menuItem = (MenuItem)sender;
+            var folder = (ImageFolderExtend)menuItem.DataContext;
+            ContentManager.Show("PendingAllocationImageBrowser", folder);
+        }
+
+        private void OnNodeDoubleClickHandler(object sender, System.Windows.Input.MouseButtonEventArgs e)
+        {
+        }
+
+        private void OnNodeMouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
+        {
+            var treeViewItem = (TreeViewItem)sender;
+            treeViewItem.IsSelected = true;
+            treeViewItem.Focus();
+            e.Handled = true;
+        }
+    }
+}

BIN
aipdev/Resources/Images/PendingAllocation.png


+ 2 - 38
aipdev/aipdev.csproj

@@ -16,44 +16,7 @@
     <OutputPath>..\Bin\aipdev</OutputPath>
   </PropertyGroup>
 
-  <ItemGroup>
-    <None Remove="App.ico" />
-    <None Remove="Resources\Images\About.png" />
-    <None Remove="Resources\Images\Back.png" />
-    <None Remove="Resources\Images\CNTK.png" />
-    <None Remove="Resources\Images\Files.png" />
-    <None Remove="Resources\Images\Folder.png" />
-    <None Remove="Resources\Images\Home.png" />
-    <None Remove="Resources\Images\Left.png" />
-    <None Remove="Resources\Images\Next.png" />
-    <None Remove="Resources\Images\Next2.png" />
-    <None Remove="Resources\Images\Next3.png" />
-    <None Remove="Resources\Images\Pause.png" />
-    <None Remove="Resources\Images\Play.png" />
-    <None Remove="Resources\Images\Play2.png" />
-    <None Remove="Resources\Images\Previous.png" />
-    <None Remove="Resources\Images\Previous2.png" />
-    <None Remove="Resources\Images\Previous3.png" />
-    <None Remove="Resources\Images\ProjectFolder.png" />
-    <None Remove="Resources\Images\Python.png" />
-    <None Remove="Resources\Images\Pytorch.png" />
-    <None Remove="Resources\Images\Question.png" />
-    <None Remove="Resources\Images\QuestionFolder.png" />
-    <None Remove="Resources\Images\Right.png" />
-    <None Remove="Resources\Images\Script.png" />
-    <None Remove="Resources\Images\Splash.png" />
-	<None Remove="Resources\Images\yaml.png" />
-	<None Remove="Resources\Images\dll.png" />
-	<None Remove="Resources\Images\netron.png" />
-    <None Remove="Resources\Images\Task.png" />
-	<None Remove="Resources\Images\Tasks.png" />
-    <None Remove="Resources\Images\TensorFlow.png" />
-    <None Remove="Resources\Images\User.png" />
-	<None Remove="Resources\Images\Image.png" />
-	<None Remove="Resources\Images\Import.png" />
-	<None Remove="Resources\Images\Warn.png" />
-	<None Remove="Resources\Images\New.png" />
-  </ItemGroup>
+
 
   <ItemGroup>
     <PackageReference Include="AvalonEdit" Version="6.0.1" />
@@ -87,6 +50,7 @@
 	<Resource Include="Resources\Images\Next2.png" />
 	<Resource Include="Resources\Images\Next3.png" />
 	<Resource Include="Resources\Images\Pause.png" />
+	<Resource Include="Resources\Images\PendingAllocation.png" />
 	<Resource Include="Resources\Images\Play.png" />
 	<Resource Include="Resources\Images\Play2.png" />
 	<Resource Include="Resources\Images\Previous.png" />

+ 2 - 0
aipmgr/Pages/ImageCategories.razor

@@ -64,6 +64,8 @@
                 </div>
             </Template>
         </TableColumn>
+        <TableColumn @bind-Field="@context.IsSupportedSkipFrame" Width="90" />
+        <TableColumn @bind-Field="@context.DifficultyLevel" Width="90"/>
         <TableColumn @bind-Field="@context.CreateTime" Width="130" FormatString="yyyy-MM-dd HH:mm:ss" />
         <TableColumn @bind-Field="@context.UpdateTime" Width="130" FormatString="yyyy-MM-dd HH:mm:ss" />
         @*<TableColumn @bind-Field="@context.Id" Width="250" Text="操作" TextWrap="true">

+ 73 - 70
aipsvr/Services/AdminService.cs

@@ -3699,6 +3699,7 @@ namespace aipsvr.Services
                         InstitutionId = institutionId,
                         Mode = firstAssignedLabelCase.Mode,
                         CaseIndex = assignedCaseCount,
+                        DevelopConfirmInfo = firstAssignedLabelCase.DevelopConfirmInfo,
                     };
                     await assignedLabelCaseDataManager.CreateAssignedLabelCaseAsync(assignedLabelCase);
 
@@ -3718,6 +3719,7 @@ namespace aipsvr.Services
                             AssignedLabelCaseId = assignedLabelCase.Id,
                             UltrasoundGroupId = sourceGroup.UltrasoundGroupId,
                             Index = count.Group,
+                            DevelopConfirmInfo = sourceGroup.DevelopConfirmInfo,
                         };
                         await assignedLabelCaseDataManager.CreateAssignedLabelCaseGroupAsync(assignedLabelCaseGroup);
 
@@ -3731,6 +3733,9 @@ namespace aipsvr.Services
                                 UltrasoundFileId = sourceFile.UltrasoundFileId,
                                 Index = sourceFile.Index,
                                 IsVideo = sourceFile.IsVideo,
+                                KeyFrameIndexList = sourceFile.KeyFrameIndexList,
+                                VideoItem = sourceFile.VideoItem,
+                                DevelopConfirmInfo = sourceFile.DevelopConfirmInfo
                             };
                             await assignedLabelCaseDataManager.CreateAssignedLabelCaseFileAsync(assignedLabelCaseFile);
 
@@ -3819,7 +3824,7 @@ namespace aipsvr.Services
                                     actualAddedGoldStandardCount++;
                                 }
                             }
-                            #endregion
+                            #endregion Add GoldStandardAssignedFile
 
                             var progress = (int)((double)assignIndex / assignTotal * 100);
                             OperationManager.SetProgress(operationId, progress);
@@ -3901,7 +3906,7 @@ namespace aipsvr.Services
                                     actualAddedSelfCheckCount++;
                                 }
                             }
-                            #endregion
+                            #endregion Add SelfCheckAssignedFile
 
                             var progress = (int)((double)assignIndex / assignTotal * 100);
                             OperationManager.SetProgress(operationId, progress);
@@ -3917,7 +3922,7 @@ namespace aipsvr.Services
                     assignedLabelCase.Count = count;
                     await assignedLabelCaseDataManager.UpdateAssignCaseAsync(assignedLabelCase);
 
-                    #endregion
+                    #endregion Update AssignedLabelCase
 
                     #region Update SameBatchLabelCase
 
@@ -3934,7 +3939,7 @@ namespace aipsvr.Services
                         AssignRestCount = firstLabelerInfo.AssignRestCount,
                     });
 
-                    #endregion Update sameBatchLabelCase
+                    #endregion Update SameBatchLabelCase
 
                     #region Create LabelerGroup
 
@@ -3964,7 +3969,7 @@ namespace aipsvr.Services
                         labelerGroup.SelfCheckAssignedCount += actualAddedSelfCheckCount;
                         await labelerGroupDataManager.UpdateLabelerGroupAsync(labelerGroup);
                     }
-                    #endregion
+                    #endregion Create LabelerGroup
                 }
 
                 #region Update SameBatchLabelCase
@@ -3972,14 +3977,14 @@ namespace aipsvr.Services
                 sameBatchLabelCase.State = SameBatchLabelCaseState.LabelerAssigned;
                 await sameBatchLabelCaseDataManager.UpdateSameBatchLabelCaseAsync(sameBatchLabelCase);
 
-                #endregion
+                #endregion Update SameBatchLabelCase
 
                 #region Update ImageCase Labelers
 
                 var users = sameBatchLabelCase.Labelers.Select(x => new EntityBase { Id = x.Id, Name = x.Name }).ToList();
                 await imageCategoryDataManager.UpdateImageCategoryLabelersAsync(sameBatchLabelCase.ImageCaseId, users);
 
-                #endregion
+                #endregion Update ImageCase Labelers
 
                 OperationManager.SetProgress(operationId, 100);
                 return operationId;
@@ -4562,7 +4567,7 @@ namespace aipsvr.Services
                     assignCount.File = 0;
                     var assignUltrasoundModels = new List<AssignUltrasoundModel>();
                     var modalItems = new List<ModalQuantityItem>();
-                    bool hasVideo = false;
+                    Dictionary<long, DevelopConfirmStatus> developConfirmStatusInGroupDict = new Dictionary<long, DevelopConfirmStatus>();
                     foreach (var group in assignUltrasoundGroups)
                     {
                         var assignUltrasoundModel = new AssignUltrasoundModel
@@ -4584,15 +4589,22 @@ namespace aipsvr.Services
                                     };
                                     modalItems.Add(modalItem);
                                 }
-                                if (ultrasoundFile.IsVideo)
+                                if (ultrasoundFile.IsVideo && videoItem.FrameMode == FrameMode.EqualInterval)
                                 {
-                                    hasVideo = true;
+                                    if (!developConfirmStatusInGroupDict.ContainsKey(group.Id))
+                                    {
+                                        developConfirmStatusInGroupDict.Add(group.Id, DevelopConfirmStatus.WaitForConfirm);
+                                    }
                                 }
                                 modalItem.Quantity.Total++;
                                 modalItem.Quantity.UnLabeled++;
                                 assignCount.File++;
                                 assignUltrasoundModel.FileFrameCount += ultrasoundFile.FrameCount;
                             }
+                            if (!developConfirmStatusInGroupDict.ContainsKey(group.Id))
+                            {
+                                developConfirmStatusInGroupDict.Add(group.Id, DevelopConfirmStatus.None);
+                            }
                         }
                         assignUltrasoundModels.Add(assignUltrasoundModel);
                     }
@@ -4601,15 +4613,10 @@ namespace aipsvr.Services
 
                     #region Create SameBatchLabelCase & AssignedLabelCase & AssignedLabelCaseFile & LabelerGroup
 
-                    DevelopConfirmInfo developConfirmInfo = new DevelopConfirmInfo();
-                    switch (videoItem.FrameMode)
+                    DevelopConfirmInfo totalDevelopConfirmInfo = new DevelopConfirmInfo();
+                    if (developConfirmStatusInGroupDict.ContainsValue(DevelopConfirmStatus.WaitForConfirm))
                     {
-                        case FrameMode.EqualInterval:
-                            if (hasVideo)
-                            {
-                                developConfirmInfo.Status = DevelopConfirmStatus.WaitForConfirm;
-                            }
-                            break;
+                        totalDevelopConfirmInfo.Status = DevelopConfirmStatus.WaitForConfirm;
                     }
                     var sameBatchLabelCase = new SameBatchLabelCase
                     {
@@ -4623,7 +4630,7 @@ namespace aipsvr.Services
                         AssignCount = assignCount,
                         State = SameBatchLabelCaseState.LabelerAssigned,
                         VideoItem = videoItem,
-                        DevelopConfirmInfo = developConfirmInfo,
+                        DevelopConfirmInfo = totalDevelopConfirmInfo,
                     };
 
                     #region Create ImageCategory
@@ -4637,6 +4644,7 @@ namespace aipsvr.Services
                         LabelPackageContentId = imageCategory.LabelPackageContentId,
                         SameBatchLabelCaseId = sameBatchLabelCase.Id,
                         Labelers = labelerInfos.Select(x => new EntityBase { Id = x.Id, Name = x.Name }).ToList(),
+                        DevelopConfirmInfo = totalDevelopConfirmInfo
                     };
                     imageCase.GroupItem.Quantity.Total = assignCount.Group;
                     imageCase.GroupItem.Quantity.UnLabeled = assignCount.Group;
@@ -4685,7 +4693,7 @@ namespace aipsvr.Services
                             Mode = mode,
                             CaseIndex = caseIndex,
                             VideoItem = videoItem,
-                            DevelopConfirmInfo = developConfirmInfo,
+                            DevelopConfirmInfo = totalDevelopConfirmInfo,
                         };
                         await caseDataManager.CreateAssignedLabelCaseAsync(assignedLabelCase);
 
@@ -4707,12 +4715,15 @@ namespace aipsvr.Services
                         {
                             var assignUltrasoundModel = assignUltrasoundModels[eachStartIndex];
                             var ultrasoundGroup = assignUltrasoundModel.Group;
+                            var developConfirmInfoInGroup = new DevelopConfirmInfo();
+                            developConfirmInfoInGroup.Status = developConfirmStatusInGroupDict[ultrasoundGroup.Id];
                             var assignedLabelCaseGroup = new AssignedLabelCaseGroup
                             {
                                 ImageCategoryId = imageCategoryId,
                                 AssignedLabelCaseId = assignedLabelCase.Id,
                                 UltrasoundGroupId = ultrasoundGroup.Id,
                                 Index = count.Group,
+                                DevelopConfirmInfo = developConfirmInfoInGroup,
                             };
                             await caseDataManager.CreateAssignedLabelCaseGroupAsync(assignedLabelCaseGroup);
 
@@ -4747,12 +4758,14 @@ namespace aipsvr.Services
                             var requiredFrame = 0;
                             foreach (var ultrasoundFile in assignUltrasoundModel.Files)
                             {
+                                var developConfirmInfoInFile = new DevelopConfirmInfo();
                                 List<int> keyFrameIndexList = new List<int>();
                                 if (ultrasoundFile.IsVideo)
                                 {
                                     switch (videoItem.FrameMode)
                                     {
                                         case FrameMode.EqualInterval:
+                                            developConfirmInfoInFile.Status = DevelopConfirmStatus.WaitForConfirm;
                                             var result = opencvSimUtils.VideoFrameSimilarity(ultrasoundFile.Source.FileFullName, 1, (float)videoItem.SimilarityThreshold, videoItem.IntervalFrame, out var frameCount, out var keyFrameIndexListFromAI);
                                             if (result)
                                             {
@@ -4814,6 +4827,8 @@ namespace aipsvr.Services
                                     IsLabeled = false,
                                     IsVideo = ultrasoundFile.IsVideo,
                                     KeyFrameIndexList = keyFrameIndexList,
+                                    VideoItem = videoItem,
+                                    DevelopConfirmInfo = developConfirmInfoInFile,
                                 };
                                 await caseDataManager.CreateAssignedLabelCaseFileAsync(assignedLabelCaseFile);
                                 count.File++;
@@ -4850,8 +4865,31 @@ namespace aipsvr.Services
                             var progress = (int)((double)assignIndex / assignTotal * 100);
                             OperationManager.SetProgress(operationId, progress);
                         }
-
+                        switch (imageCategory.DevelopConfirmInfo.Status)
+                        {
+                            case DevelopConfirmStatus.None:
+                            case DevelopConfirmStatus.HasConfirmed:
+                                if (totalDevelopConfirmInfo.Status == DevelopConfirmStatus.WaitForConfirm)
+                                {
+                                    imageCategory.DevelopConfirmInfo.Status = DevelopConfirmStatus.WaitForConfirm;
+                                    imageCategory.DevelopConfirmInfo.Developer = new EntityBase();
+                                    imageCategory.DevelopConfirmInfo.ConfirmTime = DateTime.MinValue.ToUniversalTime();
+                                }
+                                break;
+                        }
                         await imageCategoryDataManager.UpdateImageCategoryAsync(imageCategory);
+                        switch (imageBatch.DevelopConfirmInfo.Status)
+                        {
+                            case DevelopConfirmStatus.None:
+                            case DevelopConfirmStatus.HasConfirmed:
+                                if (totalDevelopConfirmInfo.Status == DevelopConfirmStatus.WaitForConfirm)
+                                {
+                                    imageBatch.DevelopConfirmInfo.Status = DevelopConfirmStatus.WaitForConfirm;
+                                    imageBatch.DevelopConfirmInfo.Developer = new EntityBase();
+                                    imageBatch.DevelopConfirmInfo.ConfirmTime = DateTime.MinValue.ToUniversalTime();
+                                }
+                                break;
+                        }
                         await imageCategoryDataManager.UpdateImageCategoryAsync(imageBatch);
 
                         #endregion Add eachCount
@@ -5087,57 +5125,22 @@ namespace aipsvr.Services
                         var labelerGroup = await labelerGroupDataManager.GetLabelerGroupAsync(labelerInfo.Id, labelerInfo.InstitutionId, imageCategory.Id);
                         if (labelerGroup == null)
                         {
-                            if (hasVideo)
+                            labelerGroup = new LabelerGroup
                             {
-                                if (videoItem.FrameMode == FrameMode.MaxInterval)
-                                {
-                                    labelerGroup = new LabelerGroup
-                                    {
-                                        Labeler = new EntityBase { Id = labelerInfo.Id, Name = labelerInfo.Name },
-                                        ImageCategoryId = imageCategory.Id,
-                                        OrganizationId = imageCategory.OrganizationId,
-                                        LabelCaseQuantityItem = new AssignedCaseQuantityItem
-                                        {
-                                            Total = assignFrameTotal,
-                                            Remaining = requiredFrameTotal,
-                                            Required = requiredFrameTotal
-                                        },
-                                        GoldStandardAssignedCount = actualAddedGoldStandardCount,
-                                        SelfCheckAssignedCount = actualAddedSelfCheckCount,
-                                    };
-                                }
-                                else
+                                Labeler = new EntityBase { Id = labelerInfo.Id, Name = labelerInfo.Name },
+                                ImageCategoryId = imageCategory.Id,
+                                OrganizationId = imageCategory.OrganizationId,
+                                LabelCaseQuantityItem = new AssignedCaseQuantityItem
                                 {
-                                    labelerGroup = new LabelerGroup
-                                    {
-                                        Labeler = new EntityBase { Id = labelerInfo.Id, Name = labelerInfo.Name },
-                                        ImageCategoryId = imageCategory.Id,
-                                        OrganizationId = imageCategory.OrganizationId,
-                                        LabelCaseQuantityItem = new AssignedCaseQuantityItem
-                                        {
-                                            Total = assignFrameTotal,
-                                        },
-                                        GoldStandardAssignedCount = actualAddedGoldStandardCount,
-                                        SelfCheckAssignedCount = actualAddedSelfCheckCount,
-                                    };
-                                }
-                            }
-                            else
+                                    Total = assignFrameTotal,
+                                },
+                                GoldStandardAssignedCount = actualAddedGoldStandardCount,
+                                SelfCheckAssignedCount = actualAddedSelfCheckCount,
+                            };
+                            if (!developConfirmStatusInGroupDict.ContainsValue(DevelopConfirmStatus.WaitForConfirm))
                             {
-                                labelerGroup = new LabelerGroup
-                                {
-                                    Labeler = new EntityBase { Id = labelerInfo.Id, Name = labelerInfo.Name },
-                                    ImageCategoryId = imageCategory.Id,
-                                    OrganizationId = imageCategory.OrganizationId,
-                                    LabelCaseQuantityItem = new AssignedCaseQuantityItem
-                                    {
-                                        Total = assignFrameTotal,
-                                        Remaining = requiredFrameTotal,
-                                        Required = requiredFrameTotal,
-                                    },
-                                    GoldStandardAssignedCount = actualAddedGoldStandardCount,
-                                    SelfCheckAssignedCount = actualAddedSelfCheckCount,
-                                };
+                                labelerGroup.LabelCaseQuantityItem.Remaining += requiredFrameTotal;
+                                labelerGroup.LabelCaseQuantityItem.Required += requiredFrameTotal;
                             }
                             await labelerGroupDataManager.CreateLabelerGroupAsync(labelerGroup);
                         }
@@ -5146,7 +5149,7 @@ namespace aipsvr.Services
                             labelerGroup.LabelCaseQuantityItem.Total += assignFrameTotal;
                             labelerGroup.GoldStandardAssignedCount += actualAddedGoldStandardCount;
                             labelerGroup.SelfCheckAssignedCount += actualAddedSelfCheckCount;
-                            if (!hasVideo || videoItem.FrameMode == FrameMode.MaxInterval)
+                            if (!developConfirmStatusInGroupDict.ContainsValue(DevelopConfirmStatus.WaitForConfirm))
                             {
                                 labelerGroup.LabelCaseQuantityItem.Remaining += requiredFrameTotal;
                                 labelerGroup.LabelCaseQuantityItem.Required += requiredFrameTotal;

+ 3 - 3
aipsvr/Services/DatabaseManagers/MongoDb/MongoDbImageCategoryDataManager.cs

@@ -19,7 +19,6 @@ namespace aipsvr.Services.DatabaseManagers.MongoDb
             await MongoDbClient.Instance.ImageCategories.InsertOneAsync(ImageCategory);
         }
 
-
         /// <summary>
         /// Delete a image category from the system.
         /// </summary>
@@ -55,7 +54,8 @@ namespace aipsvr.Services.DatabaseManagers.MongoDb
                 .Set(x => x.GroupItem, imageCategory.GroupItem)
                 .Set(x => x.FileItem, imageCategory.FileItem)
                 .Set(x => x.ModalItems, imageCategory.ModalItems)
-                .Set(x => x.DifficultyLevel, imageCategory.DifficultyLevel);
+                .Set(x => x.DifficultyLevel, imageCategory.DifficultyLevel)
+                .Set(x => x.DevelopConfirmInfo, imageCategory.DevelopConfirmInfo);
             await MongoDbClient.Instance.ImageCategories.FindOneAndUpdateAsync(x => x.Id == imageCategory.Id, update);
         }
 
@@ -275,4 +275,4 @@ namespace aipsvr.Services.DatabaseManagers.MongoDb
             return await MongoDbClient.Instance.ImageCategories.Find(x => x.IsDeleted == false && x.LabelPackageContentId == labelPackageContentId && x.HasChildren).ToListAsync();
         }
     }
-}
+}

+ 5 - 3
document/标注数据管理平台操作指南.docx

@@ -93,10 +93,12 @@
  自校验比例:大类下自校验数量占总数的比例;(注意,金标准与自校验生成时不可重复)
  支持跳帧:在同一个用例下,标注端是否支持跳页标注,但在提交到最后一页时会判断整个用例是否全部标注完成;
  视频标注模式:
- 最间隔标注帧--提交整组视频时检查标注帧最少间隔帧数
+ 最间隔标注帧--提交整组视频时检查标注帧最少间隔帧数
  等距间隔标注帧(从0开始)--提交整组视频时检查是否等距标注
- 间隔帧数:设置默认检查帧数
- 
+ 间隔帧数:设置默认检查帧数(最小为1)
+ 相似度阈值(0-1):仅当视频标注模式为等距间隔标注帧,才启用,阈值越高,图像必标帧越多。
+ 难度等级:共分为5级,数字越大,难度越大。
+                                         
  
   标注数据管理
    用于上传数据,查看图像大类、批次及用例图像。