commit 1571c398a96a701507c45a1cf945a53192eedc04 Author: LI Ligeng Date: Fri Mar 28 14:31:53 2025 +0800 update code for v1.1 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..1ff0c42 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,63 @@ +############################################################################### +# Set default behavior to automatically normalize line endings. +############################################################################### +* text=auto + +############################################################################### +# Set default behavior for command prompt diff. +# +# This is need for earlier builds of msysgit that does not have it on by +# default for csharp files. +# Note: This is only used by command line +############################################################################### +#*.cs diff=csharp + +############################################################################### +# Set the merge driver for project and solution files +# +# Merging from the command prompt will add diff markers to the files if there +# are conflicts (Merging from VS is not affected by the settings below, in VS +# the diff markers are never inserted). Diff markers may cause the following +# file extensions to fail to load in VS. An alternative would be to treat +# these files as binary and thus will always conflict and require user +# intervention with every merge. To do so, just uncomment the entries below +############################################################################### +#*.sln merge=binary +#*.csproj merge=binary +#*.vbproj merge=binary +#*.vcxproj merge=binary +#*.vcproj merge=binary +#*.dbproj merge=binary +#*.fsproj merge=binary +#*.lsproj merge=binary +#*.wixproj merge=binary +#*.modelproj merge=binary +#*.sqlproj merge=binary +#*.wwaproj merge=binary + +############################################################################### +# behavior for image files +# +# image files are treated as binary by default. +############################################################################### +#*.jpg binary +#*.png binary +#*.gif binary + +############################################################################### +# diff behavior for common document formats +# +# Convert binary document formats to text before diffing them. This feature +# is only available from the command line. Turn it on by uncommenting the +# entries below. +############################################################################### +#*.doc diff=astextplain +#*.DOC diff=astextplain +#*.docx diff=astextplain +#*.DOCX diff=astextplain +#*.dot diff=astextplain +#*.DOT diff=astextplain +#*.pdf diff=astextplain +#*.PDF diff=astextplain +#*.rtf diff=astextplain +#*.RTF diff=astextplain diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..60c7829 --- /dev/null +++ b/.gitignore @@ -0,0 +1,370 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +SiemensCereflowSetup/*.exe + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Oo]ut/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +#*.pubxml +#*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd +/SiemensCereflow/Properties/launchSettings.json +/SiemensCereflow/cereflow_command_line +/*/astroke_cmd +/*/iBrain_cmd +/MdpC3/Properties/launchSettings.json diff --git a/Astroke/App.xaml b/Astroke/App.xaml new file mode 100644 index 0000000..9f9a99f --- /dev/null +++ b/Astroke/App.xaml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Astroke/App.xaml.cs b/Astroke/App.xaml.cs new file mode 100644 index 0000000..b2ca9a5 --- /dev/null +++ b/Astroke/App.xaml.cs @@ -0,0 +1,65 @@ +using System.Windows; +using Astroke.ViewModels; +using Astroke.Views.Controls; +using JetBrains.Annotations; +using Livia.Models; +using Livia.Models.Data; +using Livia.Properties; +using Livia.Utility.DependencyInjection; +using Livia.ViewModels; +using Livia.Views; +using Livia.Views.Utility; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Velopack; + +namespace Astroke; + +/// +/// Interaction logic for App.xaml +/// +public partial class App +{ + + private readonly ILogger _logger; + private readonly IServiceProvider _serviceProvider; + private readonly IWarningSystem _warningSystem; + [UsedImplicitly] private Mutex? _mutex; + + public App() + { + ServiceConfigurations.AppName = "astroke"; + VelopackApp.Build().Run(); + ServiceProviderFactory.Init(sc => sc.AddSingleton()); + + InitializeComponent(); + _serviceProvider = ServiceProviderFactory.ServiceProvider; + _logger = ActivatorUtilities.GetServiceOrCreateInstance>(ServiceProviderFactory.ServiceProvider); + _warningSystem = ActivatorUtilities.GetServiceOrCreateInstance(ServiceProviderFactory.ServiceProvider); + _logger.LogInformation("App Starting"); + SettingsLogger.Init(); + + AppHelper helper = ActivatorUtilities.GetServiceOrCreateInstance(ServiceProviderFactory.ServiceProvider); + + Dispatcher.UnhandledException += helper.OnDispatcherUnhandledException; + helper.UpdateSettings(); + helper.LoadStrings(); + } + + private void OnStartup(object sender, StartupEventArgs eventArgs) + { + _mutex = new Mutex(true, "Livia", out bool isNewInstance); + if (!isNewInstance) + { + _warningSystem.ShowDialog(WarningWindowKind.Warning, true, "InstanceIsRunningError"); + Current.Shutdown(); + } + + MainWindow mainWindow = ActivatorUtilities.CreateInstance(_serviceProvider); + mainWindow.AddMainControl(new AStrokeControl(ActivatorUtilities.GetServiceOrCreateInstance(ServiceProviderFactory.ServiceProvider))); + MainWindow = mainWindow; + mainWindow.Show(); + + _logger.LogInformation("App Started"); + } +} \ No newline at end of file diff --git a/Astroke/AssemblyInfo.cs b/Astroke/AssemblyInfo.cs new file mode 100644 index 0000000..8b5504e --- /dev/null +++ b/Astroke/AssemblyInfo.cs @@ -0,0 +1,10 @@ +using System.Windows; + +[assembly: ThemeInfo( + ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located + //(used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) +)] diff --git a/Astroke/Astroke.csproj b/Astroke/Astroke.csproj new file mode 100644 index 0000000..81a9215 --- /dev/null +++ b/Astroke/Astroke.csproj @@ -0,0 +1,39 @@ + + + WinExe + net8.0-windows + enable + enable + true + Resources\Images\astroke.ico + aStroke + true + + + + + + + + Astroke.Program + + + + PreserveNewest + + + PreserveNewest + + + + + + + + Code + + + Code + + + \ No newline at end of file diff --git a/Astroke/Program.cs b/Astroke/Program.cs new file mode 100644 index 0000000..a48c5b4 --- /dev/null +++ b/Astroke/Program.cs @@ -0,0 +1,31 @@ +using System.Windows; +using JetBrains.Annotations; +using Velopack; + +namespace Astroke; + +// from https://github.com/velopack/velopack/blob/master/samples/VeloWpfSample/Program.cs +[UsedImplicitly] +public class Program +{ + + [STAThread] + public static void Main(string[] args) + { + try + { + //TODO::multi language + VelopackApp.Build().WithFirstRun(_ => MessageBox.Show("软件安装成功。后续请使用桌面快捷方式访问本软件。")).Run(); + + // We can now launch the WPF application as normal. + App app = new(); + app.InitializeComponent(); + app.Run(); + + } + catch (Exception ex) + { + MessageBox.Show($"Unhandled exception: {ex}"); + } + } +} \ No newline at end of file diff --git a/Astroke/Resources/Images/astroke.ico b/Astroke/Resources/Images/astroke.ico new file mode 100644 index 0000000..860cecd Binary files /dev/null and b/Astroke/Resources/Images/astroke.ico differ diff --git a/Astroke/Resources/Images/placeholder.png b/Astroke/Resources/Images/placeholder.png new file mode 100644 index 0000000..3bc235e Binary files /dev/null and b/Astroke/Resources/Images/placeholder.png differ diff --git a/Astroke/Resources/Strings/Strings.xaml b/Astroke/Resources/Strings/Strings.xaml new file mode 100644 index 0000000..3443680 --- /dev/null +++ b/Astroke/Resources/Strings/Strings.xaml @@ -0,0 +1,24 @@ + + + 欢迎使用aStroke + aStroke + 请上传MD-pCASL或SD-pCASL、3D T2 FLAIR、DWI (4mm层厚) 序列,所有序列使用同一StudyInstanceUID + + CBF + 弥散受限 + ATT + aCBV + 前循环ASPECTS + 尾状核、内囊、豆状核、脑岛存在CBF<10或ADC<620扣1分;M1-M6区域CBF<10或ADC<620体积大于等于该区域三分之一扣除1分。 + CBF<10或ADC<620中脑和桥脑,扣除2分;大脑后动脉供血区左、大脑后动脉供血区右、小脑左,小脑右、丘脑左、丘脑右区域扣除1分。 + 后循环ASPECTS + + 注册人/企业名称: 安影科技(北京)有限公司 +生产地址: 北京市大兴区宝参南街16号院3号楼(3A)201单元205、206、207室 +售后服务单位: 安影科技(北京)有限公司 +联系方式: 010-69465675 + + \ No newline at end of file diff --git a/Astroke/Resources/Strings/en.xaml b/Astroke/Resources/Strings/en.xaml new file mode 100644 index 0000000..0472e99 --- /dev/null +++ b/Astroke/Resources/Strings/en.xaml @@ -0,0 +1,12 @@ + + + Welcome to aStroke + aStroke + + Ischemic + Restricted Diffusion + Collateral Circulation + ASPECTS + \ No newline at end of file diff --git a/Astroke/ViewModels/AStrokeControlViewModel.cs b/Astroke/ViewModels/AStrokeControlViewModel.cs new file mode 100644 index 0000000..a2ef69d --- /dev/null +++ b/Astroke/ViewModels/AStrokeControlViewModel.cs @@ -0,0 +1,207 @@ +using System.IO; +using System.Windows; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Messaging; +using Livia.Models; +using Livia.Utility; +using Livia.Utility.DependencyInjection; +using Livia.ViewModels; +using Microsoft.Extensions.DependencyInjection; + +namespace Astroke.ViewModels; + +public class AStrokeControlViewModel : ObservableRecipient, ILiviaControlViewModel, IRecipient, IRecipient +{ + public ImageRotationViewerControlViewModel ArterialTerritoriesImageRotationViewerControlViewModel { get; } + public ImageRotationViewerControlViewModel AspectsImageRotationViewerControlViewModel { get; } + + public PerfusionDataGridGroupControlViewModel PerfusionDataGridGroupViewModel { get; } + + public AStrokeRoiSummaryControlViewModel LeftAStrokeRoiSummaryControlViewModel { get; } + public AStrokeRoiSummaryControlViewModel RightAStrokeRoiSummaryControlViewModel { get; } + public RoiSummaryControlViewModel LeftRestrictedDiffusionRoiSummaryControlViewModel { get; } + public RoiSummaryControlViewModel RightRestrictedDiffusionRoiSummaryControlViewModel { get; } + public RoiSummaryControlViewModel LeftAttRoiSummaryControlViewModel { get; } + public RoiSummaryControlViewModel RightAttRoiSummaryControlViewModel { get; } + public RoiSummaryControlViewModel LeftAcbvRoiSummaryControlViewModel { get; } + public RoiSummaryControlViewModel RightAcbvRoiSummaryControlViewModel { get; } + public AspectsScoreRoiSummaryControlViewModel LeftAnteriorAspectsScoreRoiSummaryControlViewModel { get; } + public AspectsScoreRoiSummaryControlViewModel RightAnteriorAspectsScoreRoiSummaryControlViewModel { get; } + public AspectsScoreRoiSummaryControlViewModel PosteriorAspectsScoreRoiSummaryControlViewModel { get; } + public MosaicImageGroupControlViewModel MosaicImageGroupViewModel { get; } + //TODO::temp fix I do not like this + public bool AttTabVisible { get => _attTabVisible; set => SetProperty(ref _attTabVisible, value); } + public IDataBlockLoader DataBlockLoader { get; } + public int RoiTabSelectedIndex { get => _roiTabSelectedIndex; set => SetProperty(ref _roiTabSelectedIndex, value); } + + private int _roiTabSelectedIndex; + private bool _attTabVisible = true; + + public AStrokeControlViewModel(IDataBlockLoader dataBlockLoader) + { + WeakReferenceMessenger.Default.RegisterAll(this); + DataBlockLoader = dataBlockLoader; + + ArterialTerritoriesImageRotationViewerControlViewModel = ActivatorUtilities.CreateInstance(ServiceProviderFactory.ServiceProvider); + ArterialTerritoriesImageRotationViewerControlViewModel.DisplayName = (string)Application.Current.TryFindResource("ArterialTerritoriesRotationViewerName"); + ArterialTerritoriesImageRotationViewerControlViewModel.AtlasMaskLoadPath = @"atlas\AnImage_ArterialTerritories\mask"; + ArterialTerritoriesImageRotationViewerControlViewModel.StructLoadPath = "structure"; + ArterialTerritoriesImageRotationViewerControlViewModel.StructDataType = ImageRotationViewerDataType.Dicom2D; + ArterialTerritoriesImageRotationViewerControlViewModel.AtlasId = "AnImage_ArterialTerritories"; + ArterialTerritoriesImageRotationViewerControlViewModel.ImageIndexSyncKey = "structure"; + dataBlockLoader.AddViewModel(ArterialTerritoriesImageRotationViewerControlViewModel); + + AspectsImageRotationViewerControlViewModel = ActivatorUtilities.CreateInstance(ServiceProviderFactory.ServiceProvider); + AspectsImageRotationViewerControlViewModel.DisplayName = (string)Application.Current.TryFindResource("AspectsRotationViewerName"); + AspectsImageRotationViewerControlViewModel.AtlasMaskLoadPath = @"atlas\AnImage_ASPECTS\mask"; + AspectsImageRotationViewerControlViewModel.StructLoadPath = "structure"; + AspectsImageRotationViewerControlViewModel.StructDataType = ImageRotationViewerDataType.Dicom2D; + AspectsImageRotationViewerControlViewModel.AtlasId = "AnImage_ASPECTS"; + AspectsImageRotationViewerControlViewModel.ImageIndexSyncKey = "structure"; + dataBlockLoader.AddViewModel(AspectsImageRotationViewerControlViewModel); + + PerfusionDataGridControlViewModel cerebrumCerebellumPerfusionDataGridControlViewModel = ActivatorUtilities.CreateInstance(ServiceProviderFactory.ServiceProvider); + cerebrumCerebellumPerfusionDataGridControlViewModel.LoadPath = Path.Combine("atlas", "AnImage_CerebrumCerebellum"); + cerebrumCerebellumPerfusionDataGridControlViewModel.GridTabName = (string)Application.Current.TryFindResource("CerebrumCerebellumPerfusionDataGridName"); + + PerfusionDataGridControlViewModel arterialTerritoriesPerfusionDataGridControlViewModel = ActivatorUtilities.CreateInstance(ServiceProviderFactory.ServiceProvider); + arterialTerritoriesPerfusionDataGridControlViewModel.LoadPath = Path.Combine("atlas", "AnImage_ArterialTerritories"); + arterialTerritoriesPerfusionDataGridControlViewModel.GridTabName = (string)Application.Current.TryFindResource("ArterialTerritoriesPerfusionDataGridName"); + + PerfusionDataGridControlViewModel aspectsPerfusionDataGridControlViewModel = ActivatorUtilities.CreateInstance(ServiceProviderFactory.ServiceProvider); + aspectsPerfusionDataGridControlViewModel.LoadPath = Path.Combine("atlas", "AnImage_ASPECTS"); + aspectsPerfusionDataGridControlViewModel.GridTabName = (string)Application.Current.TryFindResource("AspectsPerfusionDataGridName"); + + PerfusionDataGridGroupViewModel = ActivatorUtilities.CreateInstance(ServiceProviderFactory.ServiceProvider); + PerfusionDataGridGroupViewModel.PerfusionDataGridViewModelCollection.Add(cerebrumCerebellumPerfusionDataGridControlViewModel); + PerfusionDataGridGroupViewModel.PerfusionDataGridViewModelCollection.Add(arterialTerritoriesPerfusionDataGridControlViewModel); + PerfusionDataGridGroupViewModel.PerfusionDataGridViewModelCollection.Add(aspectsPerfusionDataGridControlViewModel); + dataBlockLoader.AddViewModel(PerfusionDataGridGroupViewModel); + + LeftAStrokeRoiSummaryControlViewModel = ActivatorUtilities.CreateInstance(ServiceProviderFactory.ServiceProvider); + LeftAStrokeRoiSummaryControlViewModel.LoadPath = Path.Combine("roi", "cbf_location_l.json"); + LeftAStrokeRoiSummaryControlViewModel.RoiTabId = "cbf"; + dataBlockLoader.AddViewModel(LeftAStrokeRoiSummaryControlViewModel); + + RightAStrokeRoiSummaryControlViewModel = ActivatorUtilities.CreateInstance(ServiceProviderFactory.ServiceProvider); + RightAStrokeRoiSummaryControlViewModel.LoadPath = Path.Combine("roi", "cbf_location_r.json"); + RightAStrokeRoiSummaryControlViewModel.RoiTabId = "cbf"; + dataBlockLoader.AddViewModel(RightAStrokeRoiSummaryControlViewModel); + + //This is useless but prevents ReSharper form complaining. This function may be needed in the future. + RoiExpanderGroupControlViewModel.DefaultDeselectRoiSet.Add(""); + + LeftRestrictedDiffusionRoiSummaryControlViewModel = ActivatorUtilities.CreateInstance(ServiceProviderFactory.ServiceProvider); + LeftRestrictedDiffusionRoiSummaryControlViewModel.LoadPath = Path.Combine("roi", "restricted_diffusion_location_l.json"); + LeftRestrictedDiffusionRoiSummaryControlViewModel.RoiTabId = "restricted_diffusion"; + dataBlockLoader.AddViewModel(LeftRestrictedDiffusionRoiSummaryControlViewModel); + + RightRestrictedDiffusionRoiSummaryControlViewModel = ActivatorUtilities.CreateInstance(ServiceProviderFactory.ServiceProvider); + RightRestrictedDiffusionRoiSummaryControlViewModel.LoadPath = Path.Combine("roi", "restricted_diffusion_location_r.json"); + RightRestrictedDiffusionRoiSummaryControlViewModel.RoiTabId = "restricted_diffusion"; + dataBlockLoader.AddViewModel(RightRestrictedDiffusionRoiSummaryControlViewModel); + + LeftAnteriorAspectsScoreRoiSummaryControlViewModel = ActivatorUtilities.CreateInstance(ServiceProviderFactory.ServiceProvider); + LeftAnteriorAspectsScoreRoiSummaryControlViewModel.LoadPath = Path.Combine("roi", "anterior_aspects_l.json"); + LeftAnteriorAspectsScoreRoiSummaryControlViewModel.RoiTabId = "anterior"; + dataBlockLoader.AddViewModel(LeftAnteriorAspectsScoreRoiSummaryControlViewModel); + + RightAnteriorAspectsScoreRoiSummaryControlViewModel = ActivatorUtilities.CreateInstance(ServiceProviderFactory.ServiceProvider); + RightAnteriorAspectsScoreRoiSummaryControlViewModel.LoadPath = Path.Combine("roi", "anterior_aspects_r.json"); + RightAnteriorAspectsScoreRoiSummaryControlViewModel.RoiTabId = "anterior"; + dataBlockLoader.AddViewModel(RightAnteriorAspectsScoreRoiSummaryControlViewModel); + + PosteriorAspectsScoreRoiSummaryControlViewModel = ActivatorUtilities.CreateInstance(ServiceProviderFactory.ServiceProvider); + PosteriorAspectsScoreRoiSummaryControlViewModel.LoadPath = Path.Combine("roi", "posterior_aspects.json"); + PosteriorAspectsScoreRoiSummaryControlViewModel.IsTwoSided = true; + PosteriorAspectsScoreRoiSummaryControlViewModel.RoiTabId = "posterior"; + dataBlockLoader.AddViewModel(PosteriorAspectsScoreRoiSummaryControlViewModel); + + LeftAttRoiSummaryControlViewModel = ActivatorUtilities.CreateInstance(ServiceProviderFactory.ServiceProvider); + LeftAttRoiSummaryControlViewModel.LoadPath = Path.Combine("roi", "att_location_l.json"); + LeftAttRoiSummaryControlViewModel.RoiTabId = "att"; + dataBlockLoader.AddViewModel(LeftAttRoiSummaryControlViewModel); + + RightAttRoiSummaryControlViewModel = ActivatorUtilities.CreateInstance(ServiceProviderFactory.ServiceProvider); + RightAttRoiSummaryControlViewModel.LoadPath = Path.Combine("roi", "att_location_r.json"); + RightAttRoiSummaryControlViewModel.RoiTabId = "att"; + dataBlockLoader.AddViewModel(RightAttRoiSummaryControlViewModel); + + LeftAcbvRoiSummaryControlViewModel = ActivatorUtilities.CreateInstance(ServiceProviderFactory.ServiceProvider); + LeftAcbvRoiSummaryControlViewModel.LoadPath = Path.Combine("roi", "acbv_location_l.json"); + LeftAcbvRoiSummaryControlViewModel.RoiTabId = "acbv"; + dataBlockLoader.AddViewModel(LeftAcbvRoiSummaryControlViewModel); + + RightAcbvRoiSummaryControlViewModel = ActivatorUtilities.CreateInstance(ServiceProviderFactory.ServiceProvider); + RightAcbvRoiSummaryControlViewModel.LoadPath = Path.Combine("roi", "acbv_location_r.json"); + RightAcbvRoiSummaryControlViewModel.RoiTabId = "acbv"; + dataBlockLoader.AddViewModel(RightAcbvRoiSummaryControlViewModel); + + MosaicImageGroupViewModel = ActivatorUtilities.CreateInstance(ServiceProviderFactory.ServiceProvider); + dataBlockLoader.AddDeferredLoadingModuleViewModel(MosaicImageGroupViewModel); + + dataBlockLoader.Init(); + + RoiSummaryControlViewModel.RoiTabIdToTabIndexDictionary = new Dictionary + { + { "cbf", 1 }, { "restricted_diffusion", 2 }, { "att", 3 }, { "acbv", 4 }, {"anterior", 5}, {"posterior", 6} + }; + } + + public List GenerateReportModuleGroups() + { + List atlasItems = + [ + new ReportModuleItemViewModel( + (string)Application.Current.TryFindResource("ArterialTerritoriesRotationViewerName"), + "AnImage_ArterialTerritories"), + new ReportModuleItemViewModel((string)Application.Current.TryFindResource("AspectsRotationViewerName"), + "AnImage_ASPECTS") + ]; + + //TODO::maybe cache it when loading data is better. No need to check colorMap + List colorImageItems = (from viewModel in MosaicImageGroupViewModel.MosaicImageCollection where viewModel.UseColorBar select new ReportModuleItemViewModel(viewModel.DisplayName, viewModel.DisplayName)).ToList(); + + + List result = + [ + new ReportModuleExpanderViewModel((string)Application.Current.TryFindResource("ReportModuleAtlas"), + "regional_perfusion", atlasItems), + new ReportModuleExpanderViewModel((string)Application.Current.TryFindResource("ReportModuleColorImage"), + "param_color_img", colorImageItems), + new ReportModuleExpanderViewModel("CBF", "cbf", + LeftAStrokeRoiSummaryControlViewModel.GenerateReportModuleList() + .Concat(RightAStrokeRoiSummaryControlViewModel.GenerateReportModuleList())), + new ReportModuleExpanderViewModel((string)Application.Current.TryFindResource("RoiTabHeader2"), + "restricted_diffusion", + LeftRestrictedDiffusionRoiSummaryControlViewModel.GenerateReportModuleList() + .Concat(RightRestrictedDiffusionRoiSummaryControlViewModel.GenerateReportModuleList())), + new ReportModuleExpanderViewModel("ATT", "att", + LeftAttRoiSummaryControlViewModel.GenerateReportModuleList() + .Concat(RightAttRoiSummaryControlViewModel.GenerateReportModuleList())), + new ReportModuleExpanderViewModel("aCBV", "acbv", + LeftAcbvRoiSummaryControlViewModel.GenerateReportModuleList() + .Concat(RightAcbvRoiSummaryControlViewModel.GenerateReportModuleList())), + new ReportModuleExpanderViewModel((string)Application.Current.TryFindResource("RoiTabHeader5"), + "anterior_aspects", + LeftAnteriorAspectsScoreRoiSummaryControlViewModel.GenerateReportModuleList() + .Concat(RightAnteriorAspectsScoreRoiSummaryControlViewModel.GenerateReportModuleList())), + new ReportModuleExpanderViewModel((string)Application.Current.TryFindResource("RoiTabHeader6"), + "posterior_aspects", PosteriorAspectsScoreRoiSummaryControlViewModel.GenerateReportModuleList()) + ]; + return result; + } + + public void Receive(DataLoadedMessage message) + { + RoiTabSelectedIndex = 1; + WeakReferenceMessenger.Default.Send(new RoiTabChangedMessage(RoiTabSelectedIndex)); + AttTabVisible = LeftAttRoiSummaryControlViewModel.Visible || RightAttRoiSummaryControlViewModel.Visible; + } + + public void Receive(PerfusionDataGridChangeParameterMessage message) + { + if (message.Value.Item2) + RoiTabSelectedIndex = 0; + } +} \ No newline at end of file diff --git a/Astroke/ViewModels/AStrokeRoiSummaryControlViewModel.cs b/Astroke/ViewModels/AStrokeRoiSummaryControlViewModel.cs new file mode 100644 index 0000000..508fbd2 --- /dev/null +++ b/Astroke/ViewModels/AStrokeRoiSummaryControlViewModel.cs @@ -0,0 +1,31 @@ +using Livia.Models; +using Livia.ViewModels; +using Microsoft.Extensions.Logging; + +namespace Astroke.ViewModels; + +public class AStrokeRoiSummaryControlViewModel(ILogger logger, IRoiLegendManager roiLegendManager) + : RoiSummaryControlViewModel(logger, roiLegendManager) +{ + public float Ratio1 { get => _ratio1; private set => SetProperty(ref _ratio1, value); } + public float Ratio2 { get => _ratio2; private set => SetProperty(ref _ratio2, value); } + public bool ShowDiagnosis { get => _showDiagnosis; private set => SetProperty(ref _showDiagnosis, value); } + + + private float _ratio1; + private float _ratio2; + private bool _showDiagnosis; + //private const float ShowDiagnosisThreshold = 1.8f; + + protected override void UpdateDiagnosis() + { + float iscVolume = SelectedIsCRoiExpanderGroupViewModel?.SelectedVolume ?? 0; + float ispVolume = SelectedIsPRoiExpanderGroupViewModel?.SelectedVolume ?? 0; + + Ratio1 = ispVolume / iscVolume; + Ratio2 = 100 * ispVolume / (ispVolume + iscVolume); + + //ShowDiagnosis = Ratio1 > ShowDiagnosisThreshold && iscVolume > 0; + ShowDiagnosis = false; + } +} \ No newline at end of file diff --git a/Astroke/ViewModels/AspectsScoreRoiSummaryControlViewModel.cs b/Astroke/ViewModels/AspectsScoreRoiSummaryControlViewModel.cs new file mode 100644 index 0000000..ef4165a --- /dev/null +++ b/Astroke/ViewModels/AspectsScoreRoiSummaryControlViewModel.cs @@ -0,0 +1,39 @@ +using Livia.Models; +using Livia.ViewModels; +using Microsoft.Extensions.Logging; + +namespace Astroke.ViewModels; + +public class AspectsScoreRoiSummaryControlViewModel(ILogger logger, IRoiLegendManager roiLegendManager) + : RoiSummaryControlViewModel(logger, roiLegendManager) +{ + + public int CbfIscScore { get => _cbfIscScore; private set => SetProperty(ref _cbfIscScore, value); } + public int AdcIscScore { get => _adcIscScore; private set => SetProperty(ref _adcIscScore, value); } + + private int _cbfIscScore; + private int _adcIscScore; + private const int MaxScore = 10; + private const float Threshold = 100.0f / 3; + + protected override void UpdateDiagnosis() + { + CbfIscScore = CalculateScores(GetSelectedRoiExpanderGroupControlViewModel("CBF-IC")?.RoiViewModelCollection.Items); + AdcIscScore = CalculateScores(GetSelectedRoiExpanderGroupControlViewModel("ADC-IC")?.RoiViewModelCollection.Items); + } + + + private static int CalculateScores(IEnumerable? roiCollection) + { + if (roiCollection == null) return MaxScore; + + int score = MaxScore - roiCollection.Where(roiExpanderControlViewModel => roiExpanderControlViewModel.IsChecked) + .SelectMany(roiExpanderControlViewModel => roiExpanderControlViewModel.Locations) + .GroupBy(roiLocationDataViewModel => roiLocationDataViewModel.Name, roiLocationDataViewModel => roiLocationDataViewModel) + .Where(group => !group.Key.StartsWith("M") || group.Select(x => x.RegionPercentage).Sum() > Threshold) + .Sum(group => group.First().Weight); + + //Just in case + return Math.Clamp(score, 0, MaxScore); + } +} \ No newline at end of file diff --git a/Astroke/Views/Controls/AStrokeControl.xaml b/Astroke/Views/Controls/AStrokeControl.xaml new file mode 100644 index 0000000..7741281 --- /dev/null +++ b/Astroke/Views/Controls/AStrokeControl.xaml @@ -0,0 +1,283 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Astroke/Views/Controls/AStrokeControl.xaml.cs b/Astroke/Views/Controls/AStrokeControl.xaml.cs new file mode 100644 index 0000000..739615a --- /dev/null +++ b/Astroke/Views/Controls/AStrokeControl.xaml.cs @@ -0,0 +1,29 @@ +using CommunityToolkit.Mvvm.Messaging; +using System.Windows.Controls; +using Livia.Utility; + +namespace Astroke.Views.Controls; + +/// +/// Interaction logic for AStrokeControl.xaml +/// +public partial class AStrokeControl +{ + public AStrokeControl(object viewModel) + { + InitializeComponent(); + DataContext = viewModel; + } + + private void TabSelectionChanged(object sender, SelectionChangedEventArgs e) + { + if (sender is not TabControl tab) + return; + + //This is also fired on start up, ignore those calls. + if (!tab.IsLoaded) + return; + + WeakReferenceMessenger.Default.Send(new RoiTabChangedMessage(tab.SelectedIndex)); + } +} \ No newline at end of file diff --git a/Astroke/Views/Controls/AStrokeRoiSummaryControl.xaml b/Astroke/Views/Controls/AStrokeRoiSummaryControl.xaml new file mode 100644 index 0000000..80786cd --- /dev/null +++ b/Astroke/Views/Controls/AStrokeRoiSummaryControl.xaml @@ -0,0 +1,148 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Astroke/Views/Controls/AStrokeRoiSummaryControl.xaml.cs b/Astroke/Views/Controls/AStrokeRoiSummaryControl.xaml.cs new file mode 100644 index 0000000..f40120e --- /dev/null +++ b/Astroke/Views/Controls/AStrokeRoiSummaryControl.xaml.cs @@ -0,0 +1,12 @@ +namespace Astroke.Views.Controls; + +/// +/// Interaction logic for AStrokeRoiSummaryControl.xaml +/// +public partial class AStrokeRoiSummaryControl +{ + public AStrokeRoiSummaryControl() + { + InitializeComponent(); + } +} \ No newline at end of file diff --git a/Astroke/Views/Controls/AspectsScoreRoiSummaryControl.xaml b/Astroke/Views/Controls/AspectsScoreRoiSummaryControl.xaml new file mode 100644 index 0000000..a4bd39d --- /dev/null +++ b/Astroke/Views/Controls/AspectsScoreRoiSummaryControl.xaml @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Astroke/Views/Controls/AspectsScoreRoiSummaryControl.xaml.cs b/Astroke/Views/Controls/AspectsScoreRoiSummaryControl.xaml.cs new file mode 100644 index 0000000..250ecc6 --- /dev/null +++ b/Astroke/Views/Controls/AspectsScoreRoiSummaryControl.xaml.cs @@ -0,0 +1,12 @@ +namespace Astroke.Views.Controls; + +/// +/// Interaction logic for AspectsScoreRoiSummaryControl.xaml +/// +public partial class AspectsScoreRoiSummaryControl +{ + public AspectsScoreRoiSummaryControl() + { + InitializeComponent(); + } +} \ No newline at end of file diff --git a/CereFlow/App.xaml b/CereFlow/App.xaml new file mode 100644 index 0000000..e9e1a75 --- /dev/null +++ b/CereFlow/App.xaml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/CereFlow/App.xaml.cs b/CereFlow/App.xaml.cs new file mode 100644 index 0000000..f1dafec --- /dev/null +++ b/CereFlow/App.xaml.cs @@ -0,0 +1,85 @@ +using System.IO; +using System.Windows; +using CereFlow.ViewModels; +using CereFlow.Views.Controls; +using JetBrains.Annotations; +using Livia.Models; +using Livia.Models.Data; +using Livia.Properties; +using Livia.Utility.DependencyInjection; +using Livia.ViewModels; +using Livia.Views; +using Livia.Views.Utility; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Velopack; + +namespace CereFlow; + +/// +/// Interaction logic for App.xaml +/// +public partial class App +{ + + private readonly ILogger _logger; + private readonly IServiceProvider _serviceProvider; + private readonly IWarningSystem _warningSystem; + private readonly IProcessManager _processManager; + [UsedImplicitly] private Mutex? _mutex; + + public App() + { + ServiceConfigurations.AppName = "cereflow"; + VelopackApp.Build().Run(); + ServiceProviderFactory.Init(sc => sc.AddSingleton()); + + InitializeComponent(); + _serviceProvider = ServiceProviderFactory.ServiceProvider; + _logger = ActivatorUtilities.GetServiceOrCreateInstance>(ServiceProviderFactory.ServiceProvider); + _warningSystem = ActivatorUtilities.GetServiceOrCreateInstance(ServiceProviderFactory.ServiceProvider); + _processManager = ActivatorUtilities.GetServiceOrCreateInstance(ServiceProviderFactory.ServiceProvider); + _logger.LogInformation("App Starting"); + SettingsLogger.Init(); + + AppHelper helper = ActivatorUtilities.GetServiceOrCreateInstance(ServiceProviderFactory.ServiceProvider); + + Dispatcher.UnhandledException += helper.OnDispatcherUnhandledException; + helper.UpdateSettings(); + helper.LoadStrings(); + } + + private void OnStartup(object sender, StartupEventArgs eventArgs) + { + _mutex = new Mutex(true, "Livia", out bool isNewInstance); + if (!isNewInstance) + { + _warningSystem.ShowDialog(WarningWindowKind.Warning, true, "InstanceIsRunningError"); + Current.Shutdown(); + } + + if (File.Exists(Path.Join(Directory.GetCurrentDirectory(), "astroke_cmd", "astroke_back-sqlite", "astroke_back-sqlite.exe"))) + { + bool success = _processManager.Start([ + new ServerFileLocation("astroke_back-sqlite.exe", Path.Join(Directory.GetCurrentDirectory(), "astroke_cmd", "astroke_back-sqlite")), + new ServerFileLocation("data-storage-sqlite.exe", Path.Join(Directory.GetCurrentDirectory(), "astroke_cmd", "data-storage-sqlite")) + ]); + if (!success) + { + _warningSystem.ShowDialog(WarningWindowKind.Error, true, "SystemCrashing"); + _logger.LogError("Cannot start process"); + Current.Shutdown(); + return; + } + Settings.Default.ServerAddress = "127.0.0.1"; + Settings.Default.ServerPort = 8080; + } + + MainWindow mainWindow = ActivatorUtilities.CreateInstance(_serviceProvider); + mainWindow.AddMainControl(new CereFlowControl(ActivatorUtilities.GetServiceOrCreateInstance(ServiceProviderFactory.ServiceProvider))); + MainWindow = mainWindow; + mainWindow.Show(); + + _logger.LogInformation("App Started"); + } +} \ No newline at end of file diff --git a/CereFlow/AssemblyInfo.cs b/CereFlow/AssemblyInfo.cs new file mode 100644 index 0000000..8b5504e --- /dev/null +++ b/CereFlow/AssemblyInfo.cs @@ -0,0 +1,10 @@ +using System.Windows; + +[assembly: ThemeInfo( + ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located + //(used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) +)] diff --git a/CereFlow/CereFlow.csproj b/CereFlow/CereFlow.csproj new file mode 100644 index 0000000..964c25f --- /dev/null +++ b/CereFlow/CereFlow.csproj @@ -0,0 +1,38 @@ + + + WinExe + net8.0-windows + enable + enable + true + Resources\Images\cereflow.ico + Cereflow + true + + + + + PreserveNewest + + + + + + + + + + CereFlow.Program + + + + PreserveNewest + + + PreserveNewest + + + + + + \ No newline at end of file diff --git a/CereFlow/Program.cs b/CereFlow/Program.cs new file mode 100644 index 0000000..862fa94 --- /dev/null +++ b/CereFlow/Program.cs @@ -0,0 +1,31 @@ +using JetBrains.Annotations; +using System.Windows; +using Velopack; + +namespace CereFlow; + +// from https://github.com/velopack/velopack/blob/master/samples/VeloWpfSample/Program.cs +[UsedImplicitly] +public class Program +{ + + [STAThread] + public static void Main(string[] args) + { + try + { + //TODO::multi language + VelopackApp.Build().WithFirstRun(_ => MessageBox.Show("软件安装成功。后续请使用桌面快捷方式访问本软件。")).Run(); + + // We can now launch the WPF application as normal. + App app = new(); + app.InitializeComponent(); + app.Run(); + + } + catch (Exception ex) + { + MessageBox.Show($"Unhandled exception: {ex}"); + } + } +} \ No newline at end of file diff --git a/CereFlow/Resources/Images/cereflow.ico b/CereFlow/Resources/Images/cereflow.ico new file mode 100644 index 0000000..0b67133 Binary files /dev/null and b/CereFlow/Resources/Images/cereflow.ico differ diff --git a/CereFlow/Resources/Images/placeholder.png b/CereFlow/Resources/Images/placeholder.png new file mode 100644 index 0000000..4eeea9b Binary files /dev/null and b/CereFlow/Resources/Images/placeholder.png differ diff --git a/CereFlow/Resources/Strings/Strings.xaml b/CereFlow/Resources/Strings/Strings.xaml new file mode 100644 index 0000000..31a0648 --- /dev/null +++ b/CereFlow/Resources/Strings/Strings.xaml @@ -0,0 +1,23 @@ + + + 欢迎使用CereFlow + CereFlow + 请上传MD-pCASL或SD-pCASL、3D T2 FLAIR序列,所有序列使用同一StudyInstanceUID + + CBF + ATT + aCBV + 软件名称:医学图像处理软件 +规格型号:CereFlow +发布版本:V1 +完整版本:V1.0.0.0 + + 注册人/企业名称: 安影科技(北京)有限公司 +生产地址: 北京市大兴区宝参南街16号院3号楼(3A)201单元205、206、207室 +售后服务单位: 安影科技(北京)有限公司 +联系方式: 010-69465675 + \ No newline at end of file diff --git a/CereFlow/ViewModels/CereFlowControlViewModel.cs b/CereFlow/ViewModels/CereFlowControlViewModel.cs new file mode 100644 index 0000000..bc79112 --- /dev/null +++ b/CereFlow/ViewModels/CereFlowControlViewModel.cs @@ -0,0 +1,163 @@ +using System.IO; +using System.Windows; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Messaging; +using Livia.Models; +using Livia.Utility; +using Livia.Utility.DependencyInjection; +using Livia.ViewModels; +using Microsoft.Extensions.DependencyInjection; + +namespace CereFlow.ViewModels; + +public class CereFlowControlViewModel : ObservableRecipient, ILiviaControlViewModel, IRecipient, IRecipient +{ + public ImageRotationViewerControlViewModel BrainLobesImageRotationViewerControlViewModel { get; } + public ImageRotationViewerControlViewModel LimbicSystemImageRotationViewerControlViewModel { get; } + public PerfusionDataGridGroupControlViewModel PerfusionDataGridGroupViewModel { get; } + + public RoiSummaryControlViewModel LeftAnteriorRoiSummaryControlViewModel { get; } + public RoiSummaryControlViewModel RightAnteriorRoiSummaryControlViewModel { get; } + public RoiSummaryControlViewModel LeftAttRoiSummaryControlViewModel { get; } + public RoiSummaryControlViewModel RightAttRoiSummaryControlViewModel { get; } + public RoiSummaryControlViewModel LeftAcbvRoiSummaryControlViewModel { get; } + public RoiSummaryControlViewModel RightAcbvRoiSummaryControlViewModel { get; } + + public MosaicImageGroupControlViewModel MosaicImageGroupViewModel { get; } + //TODO::temp fix I do not like this + public bool AttTabVisible { get => _attTabVisible; set => SetProperty(ref _attTabVisible, value); } + public IDataBlockLoader DataBlockLoader { get; } + public int RoiTabSelectedIndex { get => _roiTabSelectedIndex; set => SetProperty(ref _roiTabSelectedIndex, value); } + + private int _roiTabSelectedIndex; + private bool _attTabVisible = true; + + public CereFlowControlViewModel(IDataBlockLoader dataBlockLoader) + { + WeakReferenceMessenger.Default.RegisterAll(this); + DataBlockLoader = dataBlockLoader; + + BrainLobesImageRotationViewerControlViewModel = ActivatorUtilities.CreateInstance(ServiceProviderFactory.ServiceProvider); + BrainLobesImageRotationViewerControlViewModel.DisplayName = (string)Application.Current.TryFindResource("BrainLobesRotationViewerName"); + BrainLobesImageRotationViewerControlViewModel.AtlasMaskLoadPath = @"atlas\AnImage_BrainLobes\mask"; + BrainLobesImageRotationViewerControlViewModel.StructLoadPath = "structure"; + BrainLobesImageRotationViewerControlViewModel.StructDataType = ImageRotationViewerDataType.Dicom2D; + BrainLobesImageRotationViewerControlViewModel.AtlasId = "AnImage_BrainLobes"; + BrainLobesImageRotationViewerControlViewModel.ImageIndexSyncKey = "structure"; + dataBlockLoader.AddViewModel(BrainLobesImageRotationViewerControlViewModel); + + LimbicSystemImageRotationViewerControlViewModel = ActivatorUtilities.CreateInstance(ServiceProviderFactory.ServiceProvider); + LimbicSystemImageRotationViewerControlViewModel.DisplayName = (string)Application.Current.TryFindResource("LimbicSystemRotationViewerName"); + LimbicSystemImageRotationViewerControlViewModel.AtlasMaskLoadPath = @"atlas\LimbicSystem\mask"; + LimbicSystemImageRotationViewerControlViewModel.StructLoadPath = "structure"; + LimbicSystemImageRotationViewerControlViewModel.StructDataType = ImageRotationViewerDataType.Dicom2D; + LimbicSystemImageRotationViewerControlViewModel.AtlasId = "LimbicSystem"; + LimbicSystemImageRotationViewerControlViewModel.ImageIndexSyncKey = "structure"; + dataBlockLoader.AddViewModel(LimbicSystemImageRotationViewerControlViewModel); + + PerfusionDataGridControlViewModel cerebrumCerebellumPerfusionDataGridControlViewModel = ActivatorUtilities.CreateInstance(ServiceProviderFactory.ServiceProvider); + cerebrumCerebellumPerfusionDataGridControlViewModel.LoadPath = Path.Combine("atlas", "AnImage_CerebrumCerebellum"); + cerebrumCerebellumPerfusionDataGridControlViewModel.GridTabName = (string)Application.Current.TryFindResource("CerebrumCerebellumPerfusionDataGridName"); + + PerfusionDataGridControlViewModel brainLobesPerfusionDataGridControlViewModel = ActivatorUtilities.CreateInstance(ServiceProviderFactory.ServiceProvider); + brainLobesPerfusionDataGridControlViewModel.LoadPath = Path.Combine("atlas", "AnImage_BrainLobes"); + brainLobesPerfusionDataGridControlViewModel.GridTabName = (string)Application.Current.TryFindResource("BrainLobesPerfusionDataGridName"); + + PerfusionDataGridControlViewModel limbicSystemPerfusionDataGridControlViewModel = ActivatorUtilities.CreateInstance(ServiceProviderFactory.ServiceProvider); + limbicSystemPerfusionDataGridControlViewModel.LoadPath = Path.Combine("atlas", "LimbicSystem"); + limbicSystemPerfusionDataGridControlViewModel.GridTabName = (string)Application.Current.TryFindResource("LimbicSystemPerfusionDataGridName"); + + PerfusionDataGridGroupViewModel = ActivatorUtilities.CreateInstance(ServiceProviderFactory.ServiceProvider); + PerfusionDataGridGroupViewModel.PerfusionDataGridViewModelCollection.Add(cerebrumCerebellumPerfusionDataGridControlViewModel); + PerfusionDataGridGroupViewModel.PerfusionDataGridViewModelCollection.Add(brainLobesPerfusionDataGridControlViewModel); + PerfusionDataGridGroupViewModel.PerfusionDataGridViewModelCollection.Add(limbicSystemPerfusionDataGridControlViewModel); + dataBlockLoader.AddViewModel(PerfusionDataGridGroupViewModel); + + LeftAnteriorRoiSummaryControlViewModel = ActivatorUtilities.CreateInstance(ServiceProviderFactory.ServiceProvider); + LeftAnteriorRoiSummaryControlViewModel.LoadPath = Path.Combine("roi", "cbf_location_l.json"); + LeftAnteriorRoiSummaryControlViewModel.RoiTabId = "cbf"; + dataBlockLoader.AddViewModel(LeftAnteriorRoiSummaryControlViewModel); + + RightAnteriorRoiSummaryControlViewModel = ActivatorUtilities.CreateInstance(ServiceProviderFactory.ServiceProvider); + RightAnteriorRoiSummaryControlViewModel.LoadPath = Path.Combine("roi", "cbf_location_r.json"); + RightAnteriorRoiSummaryControlViewModel.RoiTabId = "cbf"; + dataBlockLoader.AddViewModel(RightAnteriorRoiSummaryControlViewModel); + + LeftAttRoiSummaryControlViewModel = ActivatorUtilities.CreateInstance(ServiceProviderFactory.ServiceProvider); + LeftAttRoiSummaryControlViewModel.LoadPath = Path.Combine("roi", "att_location_l.json"); + LeftAttRoiSummaryControlViewModel.RoiTabId = "att"; + dataBlockLoader.AddViewModel(LeftAttRoiSummaryControlViewModel); + + RightAttRoiSummaryControlViewModel = ActivatorUtilities.CreateInstance(ServiceProviderFactory.ServiceProvider); + RightAttRoiSummaryControlViewModel.LoadPath = Path.Combine("roi", "att_location_r.json"); + RightAttRoiSummaryControlViewModel.RoiTabId = "att"; + dataBlockLoader.AddViewModel(RightAttRoiSummaryControlViewModel); + + LeftAcbvRoiSummaryControlViewModel = ActivatorUtilities.CreateInstance(ServiceProviderFactory.ServiceProvider); + LeftAcbvRoiSummaryControlViewModel.LoadPath = Path.Combine("roi", "acbv_location_l.json"); + LeftAcbvRoiSummaryControlViewModel.RoiTabId = "acbv"; + dataBlockLoader.AddViewModel(LeftAcbvRoiSummaryControlViewModel); + + RightAcbvRoiSummaryControlViewModel = ActivatorUtilities.CreateInstance(ServiceProviderFactory.ServiceProvider); + RightAcbvRoiSummaryControlViewModel.LoadPath = Path.Combine("roi", "acbv_location_r.json"); + RightAcbvRoiSummaryControlViewModel.RoiTabId = "acbv"; + dataBlockLoader.AddViewModel(RightAcbvRoiSummaryControlViewModel); + + MosaicImageGroupViewModel = ActivatorUtilities.CreateInstance(ServiceProviderFactory.ServiceProvider); + dataBlockLoader.AddDeferredLoadingModuleViewModel(MosaicImageGroupViewModel); + + dataBlockLoader.Init(); + + RoiSummaryControlViewModel.RoiTabIdToTabIndexDictionary = new Dictionary + { + { "cbf", 1 }, { "att", 2 }, { "acbv", 3 } + }; + } + + public List GenerateReportModuleGroups() + { + List atlasItems = + [ + new ReportModuleItemViewModel((string)Application.Current.TryFindResource("CerebrumCerebellumPerfusionDataGridName"), + "AnImage_CerebrumCerebellum"), + new ReportModuleItemViewModel((string)Application.Current.TryFindResource("BrainLobesRotationViewerName"), + "AnImage_BrainLobes"), + new ReportModuleItemViewModel((string)Application.Current.TryFindResource("LimbicSystemRotationViewerName"), + "LimbicSystem") + ]; + + //TODO::maybe cache it when loading data is better. No need to check colorMap + List colorImageItems = (from viewModel in MosaicImageGroupViewModel.MosaicImageCollection where viewModel.UseColorBar select new ReportModuleItemViewModel(viewModel.DisplayName, viewModel.DisplayName)).ToList(); + + List result = + [ + new ReportModuleExpanderViewModel((string)Application.Current.TryFindResource("ReportModuleAtlas"), + "regional_perfusion", atlasItems), + new ReportModuleExpanderViewModel((string)Application.Current.TryFindResource("ReportModuleColorImage"), + "param_color_img", colorImageItems), + new ReportModuleExpanderViewModel("CBF", "cbf", + LeftAnteriorRoiSummaryControlViewModel.GenerateReportModuleList() + .Concat(RightAnteriorRoiSummaryControlViewModel.GenerateReportModuleList())), + new ReportModuleExpanderViewModel("ATT", "att", + LeftAttRoiSummaryControlViewModel.GenerateReportModuleList() + .Concat(RightAttRoiSummaryControlViewModel.GenerateReportModuleList())), + new ReportModuleExpanderViewModel("aCBV", "acbv", + LeftAcbvRoiSummaryControlViewModel.GenerateReportModuleList() + .Concat(RightAcbvRoiSummaryControlViewModel.GenerateReportModuleList())) + ]; + return result; + } + + public void Receive(DataLoadedMessage message) + { + RoiTabSelectedIndex = 1; + WeakReferenceMessenger.Default.Send(new RoiTabChangedMessage(RoiTabSelectedIndex)); + AttTabVisible = LeftAttRoiSummaryControlViewModel.Visible || RightAttRoiSummaryControlViewModel.Visible; + } + + public void Receive(PerfusionDataGridChangeParameterMessage message) + { + if (message.Value.Item2) + RoiTabSelectedIndex = 0; + } +} \ No newline at end of file diff --git a/CereFlow/Views/Controls/CereFlowControl.xaml b/CereFlow/Views/Controls/CereFlowControl.xaml new file mode 100644 index 0000000..4193b9d --- /dev/null +++ b/CereFlow/Views/Controls/CereFlowControl.xaml @@ -0,0 +1,182 @@ + + + + + + + + + + + + + + + + + + + + + + +