AIDiagnosisService.cs 59 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239
  1. using WingServerCommon.Service;
  2. using WingInterfaceLibrary.Interface;
  3. using WingInterfaceLibrary.Request;
  4. using WingInterfaceLibrary.Enum;
  5. using WingServerCommon.Config;
  6. using WingServerCommon.Config.Parameters;
  7. using WingAIDiagnosisService.Carotid.Utilities;
  8. using Vinno.vCloud.Common.Vid2;
  9. using WingAIDiagnosisService.Manage;
  10. using WingAIDiagnosisService.Carotid;
  11. using Newtonsoft.Json;
  12. using AI.DiagSystem;
  13. using WingServerCommon.Log;
  14. using System.IO;
  15. using System;
  16. using System.Threading.Tasks;
  17. using System.Collections.Generic;
  18. using System.Linq;
  19. using AI.Common;
  20. using System.Threading;
  21. using AI.Common.Log;
  22. using SkiaSharp;
  23. using System.Net.Http;
  24. using System.Net;
  25. using WingInterfaceLibrary.Request.Storage;
  26. using WingInterfaceLibrary.Result.AIDiagnosis;
  27. using WingInterfaceLibrary.Request.AIDiagnosis;
  28. using WingInterfaceLibrary.DTO.Record;
  29. using WingInterfaceLibrary.DTO.Comment;
  30. using Emgu.CV;
  31. using Emgu.CV.CvEnum;
  32. using Emgu.CV.Structure;
  33. using System.Net.Http.Headers;
  34. using WingAIDiagnosisService.Common;
  35. using WingInterfaceLibrary.Interface.DBInterface;
  36. using WingInterfaceLibrary.Request.DBRequest;
  37. using WingInterfaceLibrary.DTO.DiagnosisResult;
  38. using WingInterfaceLibrary.Request.RemedicalAISelected;
  39. using ContourModifyUtils;
  40. namespace WingAIDiagnosisService.Service
  41. {
  42. /// <summary>
  43. /// AI诊断服务
  44. /// </summary>
  45. public partial class AIDiagnosisService : JsonRpcService, IAIDiagnosisService
  46. {
  47. private EnumPerformance _workerLevel = EnumPerformance.Medium;
  48. private int _batchImageSize = 16;
  49. private int _sleepTime = 400;
  50. private readonly string _tempFolder = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "DiagnosisTemp");
  51. private AIDiagSystem _diagSystem;
  52. private AILesionCompareUtils _lesionCompareUtil;
  53. private RecognizeCarotidType _recognizeCarotidType = new RecognizeCarotidType();
  54. //识别左右颈是否初始化成功
  55. private static bool _recognizeCarotidTypeCanBeUse = false;
  56. static HttpClient _httpClient = new HttpClient();
  57. private IAuthenticationService _authenticationService;
  58. private IStorageService _storageService;
  59. private readonly string _carotidName = "CarotidPlaqueDetect";
  60. private int _contourInterval = 10;
  61. private IDiagnosisResultDBService _diagnosisResultDBService;
  62. /// <summary>
  63. /// Init service
  64. /// </summary>
  65. public override void Load(JsonRpcClientPool jsonRpcClientPool)
  66. {
  67. base.Load(jsonRpcClientPool);
  68. _authenticationService = GetProxy<IAuthenticationService>();
  69. _storageService = GetProxy<IStorageService>();
  70. _workerLevel = (EnumPerformance)ConfigurationManager.GetParammeter<IntParameter>("AI", "WorkerLevel").Value;
  71. _batchImageSize = ConfigurationManager.GetParammeter<IntParameter>("AI", "BatchImageSize").Value;
  72. _contourInterval = ConfigurationManager.GetParammeter<IntParameter>("AI", "ContourInterval").Value;
  73. _httpClient.Timeout = TimeSpan.FromMinutes(15);
  74. _diagnosisResultDBService = GetProxy<IDiagnosisResultDBService>();
  75. InitAISystem();
  76. //初始化识别左右颈
  77. _recognizeCarotidTypeCanBeUse = _recognizeCarotidType.Initialization();
  78. var keyPointDistanceThreshold = ConfigurationManager.GetParammeter<IntParameter>("AI", "ContourInterval").Value;
  79. var dragStartPointCatchDistanceThreshold = ConfigurationManager.GetParammeter<IntParameter>("AI", "ContourInterval").Value;
  80. ContourModifyHelper.KeyPointDistanceThreshold = keyPointDistanceThreshold;
  81. ContourModifyHelper.DragStartPointCatchDistanceThreshold = dragStartPointCatchDistanceThreshold;
  82. }
  83. private void InitAISystem()
  84. {
  85. var modeFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "AIDiagnosis\\Networks");
  86. _diagSystem = new AIDiagSystem(_workerLevel, modeFilePath, EnumInferWorkName.Default, false, true, true);
  87. if (_diagSystem != null)
  88. {
  89. _diagSystem.NotifyError += AIdiagSystem_NotifyError;
  90. _diagSystem.NotifyLog += AIdiagSystem_NotifyLog;
  91. }
  92. _lesionCompareUtil = new AILesionCompareUtils();
  93. }
  94. /// <summary>
  95. /// 查询杏聆荟支持的AI模块
  96. /// </summary>
  97. /// <param name="request">查询杏聆荟支持的AI模块请求实体</param>
  98. /// <returns></returns>
  99. /// <show>false</show>
  100. public async Task<IList<string>> FindDiagnosisModulesAsync(TokenRequest request)
  101. {
  102. try
  103. {
  104. var moduleNames = AIDiagSystem.GetValidModuleNamesForVclound() ?? new List<EnumAIModuleNames>();
  105. var resultData = moduleNames.Select(x => x.ToString()).ToList();
  106. if (!resultData.Contains(_carotidName))
  107. {
  108. resultData.Remove("CarotidArtery");
  109. resultData.Add(_carotidName);
  110. }
  111. return await Task.FromResult(resultData.Distinct().ToList());
  112. }
  113. catch (Exception)
  114. {
  115. return new List<string>();
  116. }
  117. }
  118. /// <summary>
  119. /// 图像诊断
  120. /// </summary>
  121. /// <param name="request">图像诊断请求实体</param>
  122. /// <returns>图像诊断结果</returns>
  123. /// <show>false</show>
  124. public async Task<DiagnosisImageResult> DiagnosisImageAsync(DiagnosisImageRequest request)
  125. {
  126. try
  127. {
  128. var relationCode = request.RelationCode;
  129. var fileUrl = request.FileToken;
  130. var localFile = "";
  131. if (!string.IsNullOrWhiteSpace(request.DiskPath))
  132. {
  133. localFile = request.DiskPath;
  134. }
  135. else
  136. {
  137. localFile = await DownloadAsync(fileUrl);
  138. }
  139. if (File.Exists(localFile))
  140. {
  141. if (_diagSystem == null)
  142. {
  143. InitAISystem();
  144. }
  145. using (var imageData = new VinnoImageData(localFile, OperationMode.Open))
  146. {
  147. if (DiagnosisHelper.IsCarotid(imageData))
  148. {
  149. if (imageData.ImageCount > DiagnosisHelper.CarotidMinImageCounts)
  150. {
  151. //颈动脉
  152. var result = await CarotidDiagnosis(imageData);
  153. var resultData = new DiagnosisImageResult()
  154. {
  155. DiagnosisOrgans = new List<DiagnosisOrganEnum>() { DiagnosisOrganEnum.CarotidArtery },
  156. CarotidResult = result
  157. };
  158. return resultData;
  159. }
  160. }
  161. else
  162. {
  163. //乳腺、肝脏
  164. var results = NormalDiagnosis(imageData);
  165. //AI实体有很多参数仅支持get,为了方便后续反序列化,db入库前先做个转换
  166. var diagnosisPerImages = results.Select(x => new AIDiagnosisPerImageModel(x.Key, x.Value, 1)).ToList();
  167. var organs = GetDiagnosisOrgans(diagnosisPerImages);
  168. if (organs.Contains(DiagnosisOrganEnum.Breast))
  169. {
  170. var breastCount = diagnosisPerImages.Where(a => a.PriorityScore > 0).Count();
  171. var totalCount = results.Count;
  172. var breastRate = breastCount * 1.0 / totalCount * 1.0;
  173. if (breastRate <= 1 / 3.0)
  174. {
  175. return new DiagnosisImageResult
  176. {
  177. DiagnosisConclusion = DiagnosisConclusionEnum.Unrecognized,
  178. DiagnosisResult = null,
  179. DiagnosisOrgans = new List<DiagnosisOrganEnum>(),
  180. };
  181. }
  182. }
  183. await AddDiagnosisResultInfosAsync(relationCode, fileUrl, diagnosisPerImages);
  184. //轮廓线分隔处理
  185. diagnosisPerImages = results.Select(x => new AIDiagnosisPerImageModel(x.Key, x.Value, _contourInterval)).ToList();
  186. var diagnosisResult = Newtonsoft.Json.JsonConvert.DeserializeObject<List<AIDiagnosisPerImageDTO>>(Newtonsoft.Json.JsonConvert.SerializeObject(diagnosisPerImages));
  187. var resultData = new DiagnosisImageResult()
  188. {
  189. DiagnosisConclusion = (DiagnosisConclusionEnum)GetDiagnosisConclusion(diagnosisPerImages),
  190. DiagnosisResult = diagnosisResult,
  191. DiagnosisOrgans = organs,
  192. };
  193. return resultData;
  194. }
  195. }
  196. }
  197. }
  198. catch (Exception ex)
  199. {
  200. Logger.WriteLineWarn($"DiagnosisImageAsync err {ex}");
  201. }
  202. return new DiagnosisImageResult();
  203. }
  204. /// <summary>
  205. /// 生成AI报告
  206. /// </summary>
  207. /// <param name="request">生成AI报告请求实体</param>
  208. /// <returns></returns>
  209. /// <show>false</show>
  210. public async Task<DiagnosisReportResult> DiagnosisReportAsync(DiagnosisReportRequest request)
  211. {
  212. try
  213. {
  214. if (request.Organ == DiagnosisOrganEnum.CarotidArtery)
  215. {
  216. var result = await GetCarotidAIMeasureResult(request);
  217. return result;
  218. }
  219. else
  220. {
  221. var manager = await CreateDiagnosisManagerAsync(request);
  222. var reportPerImages = manager.GetReportResults();
  223. var diagnosisConclusion = GetDiagnosisConclusion(reportPerImages);
  224. var diagnosisOrgans = GetDiagnosisOrgans(reportPerImages);
  225. var diagnosisResult = new List<DiagnosisPerImageDTO>();
  226. foreach (var item in reportPerImages)
  227. {
  228. var aiFileToken = await UploadFileAsync(item.AILocalImagePath, Path.GetFileName(item.AILocalImagePath));
  229. var perImageJson = Newtonsoft.Json.JsonConvert.SerializeObject(item);
  230. var perImageResult = Newtonsoft.Json.JsonConvert.DeserializeObject<DiagnosisPerImageDTO>(perImageJson);
  231. perImageResult.RemedicalCode = item.RemedicalCode;
  232. perImageResult.DataType = (RemedicalFileDataTypeEnum)item.DataType;
  233. perImageResult.Pixel = item.Pixel;
  234. perImageResult.AIFileToken = aiFileToken;
  235. perImageResult.PerImageJson = perImageJson;
  236. var conclusion = GetDiagnosisConclusion(new List<DiagnosisPerImageModel> { item });
  237. var organs = GetDiagnosisOrgans(new List<DiagnosisPerImageModel> { item });
  238. perImageResult.DiagnosisConclusion = (DiagnosisConclusionEnum)conclusion;
  239. perImageResult.DiagnosisOrgans = organs;
  240. diagnosisResult.Add(perImageResult);
  241. }
  242. return new DiagnosisReportResult
  243. {
  244. DiagnosisConclusion = (DiagnosisConclusionEnum)diagnosisConclusion,
  245. DiagnosisResult = diagnosisResult,
  246. };
  247. }
  248. }
  249. catch (Exception ex)
  250. {
  251. Logger.WriteLineWarn($"AIService DiagnosisReport err, {ex}");
  252. }
  253. return new DiagnosisReportResult();
  254. }
  255. /// <summary>
  256. /// 查询AI诊断结论
  257. /// </summary>
  258. /// <param name="request">查询AI诊断结论请求实体</param>
  259. /// <returns></returns>
  260. /// <show>false</show>
  261. public async Task<GetDiagnosisConclusionResult> GetDiagnosisConclusionAsync(GetDiagnosisConclusionRequest request)
  262. {
  263. var preImage = Newtonsoft.Json.JsonConvert.DeserializeObject<DiagnosisPerImageModel>(request.PerImageJson);
  264. var conclusion = GetDiagnosisConclusion(new List<DiagnosisPerImageModel> { preImage });
  265. var organs = GetDiagnosisOrgans(new List<DiagnosisPerImageModel> { preImage });
  266. var resultData = new GetDiagnosisConclusionResult
  267. {
  268. DiagnosisConclusion = (DiagnosisConclusionEnum)conclusion,
  269. DiagnosisOrgans = organs,
  270. };
  271. return await Task.FromResult(resultData);
  272. }
  273. /// <summary>
  274. /// 查询AI相关枚举集合
  275. /// </summary>
  276. /// <param name="request">查询AI相关枚举集合请求实体</param>
  277. /// <returns>AI相关枚举集合</returns>
  278. /// <value></value>
  279. /// <errorCodes></errorCodes>
  280. public async Task<GetDiagnosisEnumItemsResult> GetDiagnosisEnumItemsAsync(GetDiagnosisEnumItemsRequest request)
  281. {
  282. var diagnosisItems = new List<EnumItemDTO>();
  283. //病灶
  284. diagnosisItems.Add(GetEnumItem(typeof(DiagnosisBreastLabelEnum), "Breast", new List<string> { "BIRads1" }));
  285. diagnosisItems.Add(GetEnumItem(typeof(DiagnosisLiverLabelEnum), "Liver", new List<string> { "BIRads1" }));
  286. diagnosisItems.Add(GetEnumItem(typeof(AIThyroidLabelEnum), "Thyroid", new List<string> { "TIRADS0" }));
  287. //病灶特性描述
  288. diagnosisItems.Add(GetEnumItem(typeof(EnumDesShapeValue)));
  289. diagnosisItems.Add(GetEnumItem(typeof(EnumDesOrientationValue)));
  290. diagnosisItems.Add(GetEnumItem(typeof(EnumDesEchoPatternValue)));
  291. diagnosisItems.Add(GetEnumItem(typeof(EnumDesLesionBoundaryValue)));
  292. diagnosisItems.Add(GetEnumItem(typeof(EnumDesMarginValue)));
  293. diagnosisItems.Add(GetEnumItem(typeof(EnumCalcificationsValue), "Calcification"));
  294. diagnosisItems.Add(GetEnumItem(typeof(EnumDesLiverShapeValue)));
  295. diagnosisItems.Add(GetEnumItem(typeof(EnumDesLiverBoundaryValue)));
  296. diagnosisItems.Add(GetEnumItem(typeof(EnumDesLiverEchoTextureValue)));
  297. diagnosisItems.Add(GetEnumItem(typeof(EnumDesThyroidEchoPatternValue)));
  298. diagnosisItems.Add(GetEnumItem(typeof(EnumDesThyroidShapeValue)));
  299. diagnosisItems.Add(GetEnumItem(typeof(EnumDesThyroidMarginValue)));
  300. diagnosisItems.Add(GetEnumItem(typeof(EnumDesThyroidEchogenicFociValue)));
  301. //局灶和弥漫区分
  302. diagnosisItems.Add(GetEnumItem(typeof(DiagnosisBreastLabelEnum), "BreastLocalLesion", includeFields: new List<string> { "Lipomyoma", "BIRads2", "BIRads3" }));
  303. diagnosisItems.Add(GetEnumItem(typeof(DiagnosisBreastLabelEnum), "BreastDiffuseLesion", includeFields: new List<string> { "BIRads4A", "BIRads4B", "BIRads4C", "BIRads5" }));
  304. diagnosisItems.Add(GetEnumItem(typeof(DiagnosisLiverLabelEnum), "LiverLocalLesion", includeFields: new List<string> { "Hyperechoic", "HHE", "CYST", "PossibleCancer" }));
  305. diagnosisItems.Add(GetEnumItem(typeof(DiagnosisLiverLabelEnum), "LiverDiffuseLesion", includeFields: new List<string> { "FattyLiver", "DiffuseLesions", "Cirrhosis", "PCLD" }));
  306. diagnosisItems.Add(GetEnumItem(typeof(AIThyroidLabelEnum), "ThyroidLocalLesion", includeFields: new List<string> { "TIRADS2", "TIRADS3", "TIRADS4a", "TIRADS4b", "TIRADS4c", "TIRADS5" }));
  307. diagnosisItems.Add(GetEnumItem(typeof(AIThyroidLabelEnum), "ThyroidDiffuseLesion", includeFields: new List<string> { "DiffuseDisease" }));
  308. var resultData = new GetDiagnosisEnumItemsResult { Source = diagnosisItems };
  309. return await Task.FromResult(resultData);
  310. }
  311. private EnumItemDTO GetEnumItem(Type enumType, string keyCode = "", List<string> excludeFields = null, List<string> includeFields = null)
  312. {
  313. keyCode = !string.IsNullOrWhiteSpace(keyCode) ? keyCode : enumType.Name.ToString().Replace("EnumDes", "").Replace("Enum", "").Replace("Value", "");
  314. var enumNames = Enum.GetNames(enumType).ToList();
  315. if (excludeFields != null && excludeFields.Any())
  316. {
  317. enumNames = enumNames.Except(excludeFields).ToList();
  318. }
  319. if (includeFields != null && includeFields.Any())
  320. {
  321. enumNames = enumNames.Intersect(includeFields).ToList();
  322. }
  323. var children = new List<EnumFieldDTO>();
  324. foreach (var val in enumNames)
  325. {
  326. var id = (int)enumType.GetField(val).GetValue(val);
  327. var fieldValue = val.ToString();
  328. if (enumType == typeof(DiagnosisLiverLabelEnum) && fieldValue == "Hyperechoic")
  329. {
  330. fieldValue = "LiverHyperechoic";
  331. }
  332. children.Add(new EnumFieldDTO
  333. {
  334. Id = id,
  335. Value = fieldValue,
  336. });
  337. }
  338. return new EnumItemDTO
  339. {
  340. Code = keyCode,
  341. Children = children,
  342. };
  343. }
  344. /// <summary>
  345. /// 查询病灶轮廓的关键点
  346. /// </summary>
  347. /// <param name="request">查询病灶轮廓的关键点请求实体</param>
  348. /// <returns>关键点集合</returns>
  349. /// <value></value>
  350. /// <errorCodes></errorCodes>
  351. public async Task<List<DiagnosisKeyPointDTO>> GetKeyPointsOfContourAsync(GetKeyPointsOfContourRequest request)
  352. {
  353. var contourPoints = request.Contours.Select(c => new Point2D
  354. {
  355. X = c.X,
  356. Y = c.Y
  357. }).ToArray();
  358. var ls = request.LesionSize;
  359. var horizontalP1 = new Point2D(ls.HorizontalPoint1.X, ls.HorizontalPoint1.Y);
  360. var horizontalP2 = new Point2D(ls.HorizontalPoint2.X, ls.HorizontalPoint2.Y);
  361. var verticalP1 = new Point2D(ls.VerticalPoint1.X, ls.VerticalPoint1.Y);
  362. var verticalP2 = new Point2D(ls.VerticalPoint2.X, ls.VerticalPoint2.Y);
  363. var lesionSize = new LesionSize(horizontalP1, horizontalP2, verticalP1, verticalP2);
  364. // lesionSize.HorizontalLengthInPixel = ls.HorizontalLengthInPixel;
  365. // lesionSize.VerticalLengthInPixel = ls.VerticalLengthInPixel;
  366. var aiResult = ContourModifyHelper.KeyPointsOfContour(contourPoints, lesionSize);
  367. var resultData = KeyPointToDto(aiResult);
  368. return await Task.FromResult(resultData);
  369. }
  370. /// <summary>
  371. /// 移动光标,查询受影响关键点
  372. /// </summary>
  373. /// <param name="request">移动光标,查询受影响关键点请求实体</param>
  374. /// <returns>受影响关键点下标</returns>
  375. /// <value></value>
  376. /// <errorCodes></errorCodes>
  377. public async Task<List<int>> AffectedKeyPointsByDragActionAsync(AffectedKeyPointsByDragActionRequest request)
  378. {
  379. var origKeyPoints = request.KeyPoints.Select(c => new KeyPointInfo
  380. {
  381. Type = (EnumKeyPointType)c.Type,
  382. IndexInContour = c.IndexInContour,
  383. Point = new Point2D(c.Point.X, c.Point.Y)
  384. }).ToArray();
  385. var mousePoint = new Point2D(request.MousePoint.X, request.MousePoint.Y);
  386. var resultData = ContourModifyHelper.AffectedKeyPointsByDragAction(origKeyPoints, mousePoint).ToList();
  387. return await Task.FromResult(resultData);
  388. }
  389. /// <summary>
  390. /// 拖动光标,查询所有轮廓点和关键点
  391. /// </summary>
  392. /// <param name="request">拖动光标,查询所有轮廓点和关键点请求实体</param>
  393. /// <returns>所有轮廓点和关键点</returns>
  394. /// <value></value>
  395. /// <errorCodes></errorCodes>
  396. public async Task<ContourAndKeyPointsAfterDragResult> ContourAndKeyPointsAfterDragAsync(ContourAndKeyPointsAfterDragRequest request)
  397. {
  398. var origContourPoints = request.Contours.Select(c => new Point2D
  399. {
  400. X = c.X,
  401. Y = c.Y
  402. }).ToArray();
  403. var origKeyPoints = request.KeyPoints.Select(c => new KeyPointInfo
  404. {
  405. Type = (EnumKeyPointType)c.Type,
  406. IndexInContour = c.IndexInContour,
  407. Point = new Point2D(c.Point.X, c.Point.Y)
  408. }).ToArray();
  409. var dragStartPoint = new Point2D(request.StartPoint.X, request.StartPoint.Y);
  410. var dragEndPoint = new Point2D(request.EndPoint.X, request.EndPoint.Y);
  411. ContourModifyHelper.ContourAndKeyPointsAfterDrag(origContourPoints, origKeyPoints, dragStartPoint, dragEndPoint
  412. , out Point2D[] dstContourPoints, out KeyPointInfo[] dstKeyPoints, out int[] affectedKeyPointIndexes);
  413. var dstContours = PointToDto(dstContourPoints);
  414. var dstKeys = KeyPointToDto(dstKeyPoints);
  415. var resultData = new ContourAndKeyPointsAfterDragResult
  416. {
  417. DstContours = dstContours,
  418. DstKeyPoints = dstKeys,
  419. AffectedKeyPointIndexes = affectedKeyPointIndexes.ToList(),
  420. };
  421. return await Task.FromResult(resultData);
  422. }
  423. /// <summary>
  424. /// 画轮廓模式,查询鼠标位置到轮廓线的最短距离
  425. /// </summary>
  426. /// <param name="request">画轮廓模式,查询鼠标位置到轮廓线的最短距离请求实体</param>
  427. /// <returns>鼠标离轮廓线的距离,高亮点的下标</returns>
  428. /// <value></value>
  429. /// <errorCodes></errorCodes>
  430. public async Task<MinimumDistanceToContourPointsResult> MinimumDistanceToContourPointsAsync(MinimumDistanceToContourPointsRequest request)
  431. {
  432. var origContourPoints = request.ContourPoints.Select(c => new Point2D
  433. {
  434. X = c.X,
  435. Y = c.Y
  436. }).ToArray();
  437. var mousePoint = new Point2D(request.MousePoint.X, request.MousePoint.Y);
  438. var distance = ContourModifyHelper.MinimumDistanceToContourPoints(origContourPoints, mousePoint, out int closestPointIndex);
  439. var resultData = new MinimumDistanceToContourPointsResult
  440. {
  441. DistanceCaught = distance,
  442. ClosestPointIndex = closestPointIndex,
  443. };
  444. return await Task.FromResult(resultData);
  445. }
  446. /// <summary>
  447. /// 画轮廓模式,合并新旧轮廓
  448. /// </summary>
  449. /// <param name="request">画轮廓模式,查询鼠标位置到轮廓线的最短距离请求实体</param>
  450. /// <returns>合并后的完整轮廓线,合并后的横纵径尺寸结果</returns>
  451. /// <value></value>
  452. /// <errorCodes></errorCodes>
  453. public async Task<ContourMergeResult> ContourMergeAsync(ContourMergeRequest request)
  454. {
  455. var origContourPoints = request.ContourPoints.Select(c => new Point2D
  456. {
  457. X = c.X,
  458. Y = c.Y
  459. }).ToArray();
  460. var ls = request.LesionSize;
  461. var horizontalP1 = new Point2D(ls.HorizontalPoint1.X, ls.HorizontalPoint1.Y);
  462. var horizontalP2 = new Point2D(ls.HorizontalPoint2.X, ls.HorizontalPoint2.Y);
  463. var verticalP1 = new Point2D(ls.VerticalPoint1.X, ls.VerticalPoint1.Y);
  464. var verticalP2 = new Point2D(ls.VerticalPoint2.X, ls.VerticalPoint2.Y);
  465. var lesionSize = new LesionSize(horizontalP1, horizontalP2, verticalP1, verticalP2);
  466. // lesionSize.HorizontalLengthInPixel = ls.HorizontalLengthInPixel;
  467. // lesionSize.VerticalLengthInPixel = ls.VerticalLengthInPixel;
  468. var drawingNewContourPoints = request.DrawingNewContourPoints.Select(c => new Point2D
  469. {
  470. X = c.X,
  471. Y = c.Y
  472. }).ToArray();
  473. var aiResult = ContourModifyHelper.ContourMerge(origContourPoints, lesionSize, drawingNewContourPoints, out Point2D[] dstContourPoints, out LesionSize dstLesionSize);
  474. var dstContours = PointToDto(dstContourPoints);
  475. var resultData = new ContourMergeResult
  476. {
  477. DstContours = dstContours,
  478. DstLesionSize = new WingInterfaceLibrary.DTO.Comment.AIDiagnosisLesionSize
  479. {
  480. HorizontalPoint1 = new WingInterfaceLibrary.DTO.Comment.AIDiagnosisPoint2D { X = dstLesionSize.HorizontalPoint1.X, Y = dstLesionSize.HorizontalPoint1.Y },
  481. HorizontalPoint2 = new WingInterfaceLibrary.DTO.Comment.AIDiagnosisPoint2D { X = dstLesionSize.HorizontalPoint2.X, Y = dstLesionSize.HorizontalPoint2.Y },
  482. VerticalPoint1 = new WingInterfaceLibrary.DTO.Comment.AIDiagnosisPoint2D { X = dstLesionSize.VerticalPoint1.X, Y = dstLesionSize.VerticalPoint1.Y },
  483. VerticalPoint2 = new WingInterfaceLibrary.DTO.Comment.AIDiagnosisPoint2D { X = dstLesionSize.VerticalPoint2.X, Y = dstLesionSize.VerticalPoint2.Y },
  484. HorizontalLengthInPixel = dstLesionSize.HorizontalLengthInPixel,
  485. VerticalLengthInPixel = dstLesionSize.VerticalLengthInPixel,
  486. },
  487. };
  488. return await Task.FromResult(resultData);
  489. }
  490. /// <summary>
  491. /// 获取颈动脉ai报告数据
  492. /// </summary>
  493. /// <param name="request">生成AI报告请求实体</param>
  494. /// <returns></returns>
  495. private async Task<DiagnosisReportResult> GetCarotidAIMeasureResult(DiagnosisReportRequest request)
  496. {
  497. var result = new DiagnosisReportResult()
  498. {
  499. DiagnosisConclusion = DiagnosisConclusionEnum.NoObviousLesion
  500. };
  501. var orderedExamDatas = request.RemedicalList.OrderByDescending(m => m.CarotidResult?.CarotidScanType).Where(m => m.CarotidResult?.MeasureImageFiles != null && m.CarotidResult.MeasureImageFiles.Count > 0);
  502. if (orderedExamDatas?.Count() > 0)
  503. {
  504. DiagnosisRemicalDTO leftData = null;
  505. //left
  506. var leftExamDatas = orderedExamDatas.Where(m => m.CarotidResult.CarotidScanType == CarotidScanTypeEnum.CarotidLeft);
  507. if (leftExamDatas.Any())
  508. {
  509. foreach (var leftItem in leftExamDatas)
  510. {
  511. if (string.IsNullOrEmpty(leftItem.CarotidResult.MeasureResult))
  512. {
  513. result.DiagnosisConclusion = DiagnosisConclusionEnum.Unrecognized;
  514. continue;
  515. }
  516. //leftItem.CarotidResult.MeasureResult = "{\"IntimaResult\":{\"IsSuccess\":true,\"AntIntima\":{\"IntimaThick\":0.0,\"IsSuccess\":false},\"PostIntima\":{\"IntimaThick\":0.75,\"IsSuccess\":true}},\"IsYImageSuccess\":false,\"PlaqueResult\":{\"PlaquePostion\":0,\"PlaqueCountType\":2,\"PlaqueType\":0,\"PlaqueWidth\":1.75,\"PlaqueHeight\":3.76,\"Stenosis\":10.37,\"IsSuccess\":true}}";
  517. var measureResult = JsonConvert.DeserializeObject<CarotidAIMeasureResult>(leftItem.CarotidResult.MeasureResult) ?? new CarotidAIMeasureResult();
  518. if (measureResult.PlaqueResult?.PlaqueCountType != Carotid.Utilities.DetectPlaque.PlaqueCountType.NoPlaque)
  519. {
  520. leftData = leftItem;
  521. result.DiagnosisConclusion = DiagnosisConclusionEnum.Other;
  522. break;
  523. }
  524. }
  525. if (leftData == null)
  526. {
  527. leftData = leftExamDatas.First();
  528. }
  529. }
  530. if (leftData != null)
  531. {
  532. result.CarotidResult.Add(leftData);
  533. }
  534. //right
  535. float intimaThickStandard = 1.0f;
  536. DiagnosisRemicalDTO rightData = null;
  537. var rightExamDatas = orderedExamDatas.Where(m => m.CarotidResult.CarotidScanType == CarotidScanTypeEnum.CarotidRight);
  538. if (rightExamDatas.Any())
  539. {
  540. foreach (var rightItem in rightExamDatas)
  541. {
  542. if (string.IsNullOrEmpty(rightItem.CarotidResult.MeasureResult))
  543. {
  544. result.DiagnosisConclusion = DiagnosisConclusionEnum.Unrecognized;
  545. continue;
  546. }
  547. //rightItem.CarotidResult.MeasureResult = "{\"IntimaResult\":{\"IsSuccess\":true,\"AntIntima\":{\"IntimaThick\":0.0,\"IsSuccess\":false},\"PostIntima\":{\"IntimaThick\":0.75,\"IsSuccess\":true}},\"IsYImageSuccess\":false,\"PlaqueResult\":{\"PlaquePostion\":0,\"PlaqueCountType\":2,\"PlaqueType\":0,\"PlaqueWidth\":1.75,\"PlaqueHeight\":3.76,\"Stenosis\":10.37,\"IsSuccess\":true}}";
  548. var measureResult = JsonConvert.DeserializeObject<CarotidAIMeasureResult>(rightItem.CarotidResult.MeasureResult) ?? new CarotidAIMeasureResult();
  549. if (measureResult.IntimaResult?.PostIntima?.IntimaThick > intimaThickStandard)
  550. {
  551. rightData = rightItem;
  552. result.DiagnosisConclusion = DiagnosisConclusionEnum.Other;
  553. break;
  554. }
  555. }
  556. if (rightData == null)
  557. {
  558. rightData = rightExamDatas.First();
  559. }
  560. }
  561. if (rightData != null)
  562. {
  563. result.CarotidResult.Add(rightData);
  564. }
  565. Logger.WriteLineInfo($"AIDiagnosisService package carotidAIMeasureResult finished, CarotidLeftRemedicalCode:{leftData?.RemedicalCode}, CarotidLeft:{leftData?.CarotidResult?.MeasureResult}, CarotidRightRemedicalCode:{rightData?.RemedicalCode}, CarotidRight:" + rightData?.CarotidResult?.MeasureResult);
  566. }
  567. return result;
  568. }
  569. private async Task<BaseDiagnosis> CreateDiagnosisManagerAsync(DiagnosisReportRequest request)
  570. {
  571. var recordResults = new List<DiagnosisPerImageModel>();
  572. foreach (var remedical in request.RemedicalList)
  573. {
  574. foreach (var perImage in remedical.DiagnosisResult)
  575. {
  576. var localFile = await DownloadAsync(remedical.FileToken);
  577. if (File.Exists((localFile)))
  578. {
  579. var diagnosisPerImage = new DiagnosisPerImageModel();
  580. diagnosisPerImage.RemedicalCode = remedical.RemedicalCode;
  581. diagnosisPerImage.DataType = (WingAIDiagnosisService.Manage.DataType)remedical.DataType;
  582. diagnosisPerImage.LocalVidPath = localFile;
  583. diagnosisPerImage.Index = perImage.Index;
  584. diagnosisPerImage.PriorityScore = perImage.PriorityScore;
  585. var diagnosisResults = Newtonsoft.Json.JsonConvert.DeserializeObject<List<WingAIDiagnosisService.Manage.AIDiagnosisResultPerOrgan>>(Newtonsoft.Json.JsonConvert.SerializeObject(perImage.DiagResultsForEachOrgan));
  586. diagnosisPerImage.DiagResultsForEachOrgan = diagnosisResults;
  587. recordResults.Add(diagnosisPerImage);
  588. }
  589. }
  590. }
  591. switch (request.Organ)
  592. {
  593. case DiagnosisOrganEnum.Breast:
  594. return new BreastDiagnosis(recordResults, request.Organ);
  595. case DiagnosisOrganEnum.Liver:
  596. return new LiverDiagnosis(recordResults, request.Organ);
  597. case DiagnosisOrganEnum.Thyroid://甲状腺
  598. return new ThyroidDiagnosis(recordResults, request.Organ);
  599. default:
  600. throw new Exception($"not support organ type:{request.Organ.ToString()}");
  601. }
  602. }
  603. private async Task<CarotidResultDTO> CarotidDiagnosis(VinnoImageData imageData)
  604. {
  605. var resampleInputData = new ResampleInputData();
  606. resampleInputData.SurfaceFilePath = DiagnosisHelper.GetCacheFilePath(DiagnosisHelper.SurfaceFileSuffix);
  607. resampleInputData.MdlFilePath = DiagnosisHelper.GetCacheFilePath(DiagnosisHelper.ModelFileSuffix);
  608. resampleInputData.MdlZipFilePath = DiagnosisHelper.GetCacheFilePath(DiagnosisHelper.ZipFileSuffix);
  609. resampleInputData.BaseAIImagePath = DiagnosisHelper.GetCacheFilePath(DiagnosisHelper.JPGFileSuffix);
  610. resampleInputData.PlaqueAIImagePath = DiagnosisHelper.GetCacheFilePath(DiagnosisHelper.JPGFileSuffix);
  611. resampleInputData.YShapeAIImagePath = DiagnosisHelper.GetCacheFilePath(DiagnosisHelper.JPGFileSuffix);
  612. var resampleResult = TryResample(imageData, resampleInputData);
  613. if (resampleResult.ResampleErrorCode == ResampleErrorCode.Success)
  614. {
  615. var carotidAIImageTokens = new List<CarotidAIImage>();
  616. if (resampleResult.CarotidAIMeasureResult.IsYImageSuccess)
  617. {
  618. var fileUrl = await UploadFileAsync(resampleInputData.YShapeAIImagePath, Path.GetFileName(resampleInputData.YShapeAIImagePath));
  619. if (!string.IsNullOrWhiteSpace(fileUrl))
  620. {
  621. carotidAIImageTokens.Add(new CarotidAIImage() { AIImageToken = fileUrl, AIImageType = CarotidAIImageType.YShape });
  622. }
  623. File.Delete(resampleInputData.YShapeAIImagePath);
  624. }
  625. if (resampleResult.CarotidAIMeasureResult.IntimaResult.IsSuccess)
  626. {
  627. var fileUrl = await UploadFileAsync(resampleInputData.BaseAIImagePath, Path.GetFileName(resampleInputData.BaseAIImagePath));
  628. if (!string.IsNullOrWhiteSpace(fileUrl))
  629. {
  630. carotidAIImageTokens.Add(new CarotidAIImage() { AIImageToken = fileUrl, AIImageType = CarotidAIImageType.Base });
  631. }
  632. File.Delete(resampleInputData.BaseAIImagePath);
  633. }
  634. if (resampleResult.CarotidAIMeasureResult.PlaqueResult.IsSuccess && resampleResult.CarotidAIMeasureResult.PlaqueResult.PlaqueCountType != WingAIDiagnosisService.Carotid.Utilities.DetectPlaque.PlaqueCountType.NoPlaque)
  635. {
  636. var fileUrl = await UploadFileAsync(resampleInputData.PlaqueAIImagePath, Path.GetFileName(resampleInputData.PlaqueAIImagePath));
  637. if (!string.IsNullOrWhiteSpace(fileUrl))
  638. {
  639. carotidAIImageTokens.Add(new CarotidAIImage() { AIImageToken = fileUrl, AIImageType = CarotidAIImageType.Plaque });
  640. }
  641. File.Delete(resampleInputData.PlaqueAIImagePath);
  642. }
  643. var surfaceImageList = new List<string>();
  644. long surfaceFileSize = 0;
  645. var cdnSurfaceFile = "";
  646. if (!string.IsNullOrWhiteSpace(resampleInputData.SurfaceFilePath) && File.Exists(resampleInputData.SurfaceFilePath))
  647. {
  648. surfaceImageList = await SurfaceFile(resampleInputData.SurfaceFilePath);
  649. var surfaceFileTuple = await UploadFileTupleAsync(resampleInputData.SurfaceFilePath, Path.GetFileName(resampleInputData.SurfaceFilePath));
  650. surfaceFileSize = surfaceFileTuple.Item2;
  651. File.Delete(resampleInputData.SurfaceFilePath);
  652. resampleInputData.SurfaceFilePath = surfaceFileTuple.Item1;
  653. cdnSurfaceFile = surfaceFileTuple.Item1.ToUrlToken();//ToCDN
  654. }
  655. long mdlFileFileSize = 0;
  656. var cdnMdlFile = "";
  657. if (!string.IsNullOrWhiteSpace(resampleInputData.MdlFilePath) && File.Exists(resampleInputData.MdlFilePath))
  658. {
  659. var mdlFileTuple = await UploadFileTupleAsync(resampleInputData.MdlFilePath, Path.GetFileName(resampleInputData.MdlFilePath));
  660. mdlFileFileSize = mdlFileTuple.Item2;
  661. File.Delete(resampleInputData.MdlFilePath);
  662. resampleInputData.MdlFilePath = mdlFileTuple.Item1;
  663. cdnMdlFile = mdlFileTuple.Item1.ToUrlToken();//ToCDN
  664. }
  665. var result = new CarotidResultDTO
  666. {
  667. CarotidScanType = (CarotidScanTypeEnum)resampleInputData.ScanType,
  668. CarotidScanDirection = (CarotidScanDirectionEnum)resampleInputData.CarotidScanDirection,
  669. SurfaceFile = resampleInputData.SurfaceFilePath,
  670. SurfaceFileSize = surfaceFileSize,
  671. CDNSurfaceFile = cdnSurfaceFile,
  672. MdlFileSize = mdlFileFileSize,
  673. MdlFile = resampleInputData.MdlFilePath,
  674. CDNMdlFile = cdnMdlFile,
  675. MeasureImageFiles = carotidAIImageTokens.Select(x => new MeasureImageFileDTO { ImageType = (CarotidAIImageTypeEnum)x.AIImageType, ImageFile = x.AIImageToken }).ToList(),
  676. MeasureResult = JsonConvert.SerializeObject(resampleResult.CarotidAIMeasureResult),
  677. SurfaceImageList = surfaceImageList,
  678. };
  679. return result;
  680. }
  681. return null;
  682. }
  683. private ResampleResult TryResample(VinnoImageData vinnoImageData, ResampleInputData resampleInputData)
  684. {
  685. float scanDistance = 7;
  686. ResampleResult resampleResult = new ResampleResult(ResampleErrorCode.Fail);
  687. var vinnoExtendedData = VinnoCarotidExtendedData.FromBytes(vinnoImageData.ExtendedData);
  688. CarotidScanType scanType = CarotidScanType.CarotidLeft;
  689. CarotidScanDirection direction = CarotidScanDirection.TopToBottom;
  690. if (vinnoExtendedData != null)
  691. {
  692. // Should be 6~9cm normally.
  693. //scanDistance = vinnoExtendedData.ScanDistance;
  694. scanType = vinnoExtendedData.CarotidType == CarotidType.Left ? CarotidScanType.CarotidLeft : CarotidScanType.CarotidRight;
  695. direction = vinnoExtendedData.CarotidDirection == CarotidDirection.BottomToTop ? CarotidScanDirection.BottomToTop
  696. : CarotidScanDirection.TopToBottom;
  697. }
  698. else
  699. {
  700. //This is a walkaround : vid's first frame is black image or depth coordinate is not in the right place.
  701. var middleCount = vinnoImageData.ImageCount / 2;
  702. var vinnoImage = vinnoImageData.GetImage(middleCount);
  703. var imageBuffer = vinnoImage.ImageData;
  704. if (_recognizeCarotidTypeCanBeUse)
  705. {
  706. //转成彩色图像
  707. using var img = new Mat();
  708. CvInvoke.Imdecode(imageBuffer, ImreadModes.Color, img);
  709. using var image = img.ToImage<Bgr, byte>();
  710. //识别左右颈信息
  711. scanType = _recognizeCarotidType.RecognizeType(image);
  712. }
  713. }
  714. resampleInputData.ScanDistance = scanDistance;
  715. resampleInputData.ScanType = scanType;
  716. resampleInputData.CarotidScanDirection = direction;
  717. resampleResult = ResampleModel.Instance.Resample(vinnoImageData, resampleInputData, _workerLevel);
  718. return resampleResult;
  719. }
  720. /// <summary>
  721. ///
  722. /// </summary>
  723. /// <param name="filePath"></param>
  724. private async Task<List<string>> SurfaceFile(string filePath)
  725. {
  726. var fileUrls = new List<string>();
  727. using (var stream = new FileStream(filePath, FileMode.Open))
  728. {
  729. var reader = new Carotid3DStreamReader(stream);
  730. var width = reader.ReadInt();
  731. var height = reader.ReadInt();
  732. var depth = reader.ReadInt();
  733. var physicalData = VinnoCarotid3DPhysicalData.FromBytes(reader.ReadBytes());
  734. var imageCount = reader.ReadInt();
  735. var fileName = $"{Guid.NewGuid():N}";
  736. for (var i = 0; i < imageCount; i++)
  737. {
  738. var readBytes = reader.ReadBytes();
  739. if (readBytes.Any())
  740. {
  741. using var img = new Mat();
  742. CvInvoke.Imdecode(readBytes, ImreadModes.Color, img);
  743. var buf = new Emgu.CV.Util.VectorOfByte();
  744. using var image = img.ToImage<Gray, byte>();
  745. CvInvoke.Imencode(".jpg", image, buf, new KeyValuePair<ImwriteFlags, int>(ImwriteFlags.JpegQuality, 80));
  746. var jpgByte = buf.ToArray();
  747. var localPath = Path.Combine(_tempFolder, $"{fileName}_{i + 1}.jpg");
  748. File.WriteAllBytes(localPath, jpgByte);
  749. var fileUrl = await UploadFileAsync(localPath, Path.GetFileName(localPath));
  750. fileUrls.Add(fileUrl);
  751. File.Delete(localPath);
  752. }
  753. }
  754. }
  755. return fileUrls;
  756. }
  757. private SKBitmap CreateBitmap(VinnoImage vinnoImage)
  758. {
  759. try
  760. {
  761. return SKBitmap.Decode(vinnoImage.ImageData);
  762. }
  763. catch (Exception ex)
  764. {
  765. Logger.WriteLineError($"Create skbitmap by VinnoImage error:{ex}");
  766. }
  767. return null;
  768. }
  769. private EnumColorType MapTo(SKColorType sKColor)
  770. {
  771. switch (sKColor)
  772. {
  773. case SKColorType.Rgba8888:
  774. return EnumColorType.Rgba;
  775. case SKColorType.Rgb888x:
  776. return EnumColorType.Rgba;
  777. case SKColorType.Bgra8888:
  778. return EnumColorType.Bgra;
  779. case SKColorType.Gray8:
  780. return EnumColorType.Gray8;
  781. }
  782. throw new Exception($"AIService not support color type:{sKColor}");
  783. }
  784. private void AIdiagSystem_NotifyError(object sender, ErrorEventArgs e)
  785. {
  786. Logger.WriteLineError("AIdiagSystem_NotifyError:" + e.GetException());
  787. }
  788. private void AIdiagSystem_NotifyLog(object sender, LogEventArgs e)
  789. {
  790. if (e != null && !string.IsNullOrEmpty(e.Msg))
  791. {
  792. switch (e.LogType)
  793. {
  794. case EnumLogType.InfoLog:
  795. Logger.WriteLineInfo($"AIdiagSystem_NotifyLog:{e.Msg}");
  796. break;
  797. case EnumLogType.ErrorLog:
  798. Logger.WriteLineError($"AIdiagSystem_NotifyLog:{e.Msg}");
  799. break;
  800. case EnumLogType.WarnLog:
  801. Logger.WriteLineWarn($"AIdiagSystem_NotifyLog:{e.Msg}");
  802. break;
  803. case EnumLogType.FatalLog:
  804. Logger.WriteLineError($"AIdiagSystem_NotifyLog:{e.Msg}");
  805. break;
  806. default:
  807. Logger.WriteLineInfo(e.Msg);
  808. break;
  809. }
  810. }
  811. }
  812. private DiagnosisConclusion GetDiagnosisConclusion<T>(List<T> results) where T : AIDiagnosisPerImageModel
  813. {
  814. var diagnosisConclusions = new List<DiagnosisConclusion>();
  815. foreach (var imageResult in results)
  816. {
  817. foreach (var diagnosisResult in imageResult.DiagResultsForEachOrgan)
  818. {
  819. var benignLabels = new List<int>();
  820. var malignantLabels = new List<int>();
  821. if (diagnosisResult.Organ == DiagnosisOrganEnum.Breast)
  822. {
  823. benignLabels = new List<int> { 1, 2, 3 };
  824. malignantLabels = new List<int> { 4, 5, 6, 7 };
  825. }
  826. else if (diagnosisResult.Organ == DiagnosisOrganEnum.Liver)
  827. {
  828. benignLabels = new List<int> { 1, 2, 3, 5, 6, 7, 8 };
  829. malignantLabels = new List<int> { 4 };
  830. }
  831. else if (diagnosisResult.Organ == DiagnosisOrganEnum.Thyroid)
  832. {
  833. benignLabels = new List<int> { 1, 2, 7 };
  834. malignantLabels = new List<int> { 3, 4, 5, 6 };
  835. }
  836. var labels = diagnosisResult.DetectedObjects.Select(x => x.Label);
  837. if (labels.Contains(0))
  838. {
  839. diagnosisConclusions.Add(DiagnosisConclusion.NoObviousLesion);
  840. }
  841. if (labels.Intersect(benignLabels).Any())
  842. {
  843. diagnosisConclusions.Add(DiagnosisConclusion.Benign);
  844. }
  845. if (labels.Intersect(malignantLabels).Any())
  846. {
  847. diagnosisConclusions.Add(DiagnosisConclusion.Malignant);
  848. }
  849. }
  850. }
  851. var containsBenign = diagnosisConclusions.Contains(DiagnosisConclusion.Benign);
  852. var containsMalignant = diagnosisConclusions.Contains(DiagnosisConclusion.Malignant);
  853. var containsNoObviousLesion = diagnosisConclusions.Contains(DiagnosisConclusion.NoObviousLesion);
  854. if (containsBenign && containsMalignant)
  855. {
  856. return DiagnosisConclusion.BenignAndMalignant;
  857. }
  858. else if (containsBenign)
  859. {
  860. return DiagnosisConclusion.Benign;
  861. }
  862. else if (containsMalignant)
  863. {
  864. return DiagnosisConclusion.Malignant;
  865. }
  866. else if (containsNoObviousLesion)
  867. {
  868. return DiagnosisConclusion.NoObviousLesion;
  869. }
  870. else
  871. {
  872. return DiagnosisConclusion.Unrecognized;
  873. }
  874. }
  875. private List<DiagnosisOrganEnum> GetDiagnosisOrgans(List<AIDiagnosisPerImageModel> results)
  876. {
  877. var diagnosisOrgans = new List<DiagnosisOrganEnum>();
  878. foreach (var imageResult in results)
  879. {
  880. foreach (var diagnosisResult in imageResult.DiagResultsForEachOrgan)
  881. {
  882. var organName = Enum.GetName(typeof(DiagnosisOrganEnum), (int)diagnosisResult.Organ);
  883. if (!string.IsNullOrWhiteSpace(organName) && diagnosisResult.Organ != DiagnosisOrganEnum.Null && diagnosisResult.DetectedObjects?.Any() == true)
  884. {
  885. diagnosisOrgans.Add((DiagnosisOrganEnum)diagnosisResult.Organ);
  886. }
  887. }
  888. }
  889. return diagnosisOrgans.Distinct().ToList();
  890. }
  891. private List<DiagnosisOrganEnum> GetDiagnosisOrgans(List<DiagnosisPerImageModel> results)
  892. {
  893. var diagnosisOrgans = new List<DiagnosisOrganEnum>();
  894. foreach (var imageResult in results)
  895. {
  896. foreach (var diagnosisResult in imageResult.DiagResultsForEachOrgan)
  897. {
  898. var organName = Enum.GetName(typeof(DiagnosisOrganEnum), (int)diagnosisResult.Organ);
  899. if (!string.IsNullOrWhiteSpace(organName) && diagnosisResult.Organ != DiagnosisOrganEnum.Null && diagnosisResult.DetectedObjects?.Any() == true)
  900. {
  901. diagnosisOrgans.Add((DiagnosisOrganEnum)diagnosisResult.Organ);
  902. }
  903. }
  904. }
  905. return diagnosisOrgans.Distinct().ToList();
  906. }
  907. private Dictionary<int, AIDiagResultPerImg> NormalDiagnosis(VinnoImageData imageData)
  908. {
  909. var images = new List<RawImage>();
  910. var results = new Dictionary<int, AIDiagResultPerImg>();
  911. try
  912. {
  913. var totalCount = imageData.ImageCount;
  914. for (var i = 0; i < totalCount; i++)
  915. {
  916. var image = imageData.GetImage(i);
  917. var bitmap = CreateBitmap(image);
  918. if (bitmap != null)
  919. {
  920. images.Add(new RawImage(bitmap.Bytes, bitmap.Width, bitmap.Height, MapTo(bitmap.ColorType)));
  921. }
  922. }
  923. if (images.Count > 0)
  924. {
  925. var excuteCount = images.Count / _batchImageSize;
  926. var remainderCount = images.Count % _batchImageSize;
  927. var diagId = _diagSystem.StartEvalutationOfMultipleImageBatches(images.Count);
  928. for (var j = 0; j < excuteCount; j++)
  929. {
  930. var excuteImages = images.GetRange(j * _batchImageSize, _batchImageSize);
  931. _diagSystem.PushOneBatchOfImagesAsync(diagId, excuteImages);
  932. Thread.Sleep(_sleepTime);
  933. }
  934. if (remainderCount > 0)
  935. {
  936. var excuteImages = images.GetRange(excuteCount * _batchImageSize, remainderCount);
  937. _diagSystem.PushOneBatchOfImagesAsync(diagId, excuteImages);
  938. }
  939. results = _diagSystem.GetEvaluationsOfPushedMultipleImageBatches(diagId);
  940. }
  941. }
  942. catch (Exception ex)
  943. {
  944. Logger.WriteLineWarn($"AIService NormalDiagnosis err, {ex}");
  945. }
  946. finally
  947. {
  948. foreach (var image in images)
  949. {
  950. image?.Dispose();
  951. }
  952. }
  953. return results;
  954. }
  955. /// <summary>
  956. /// 保存AI诊断结果
  957. /// </summary>
  958. /// <param name="relationCode"></param>
  959. /// <param name="fileUrl"></param>
  960. /// <param name="diagnosisResultInfos"></param>
  961. /// <returns></returns>
  962. private async Task<bool> AddDiagnosisResultInfosAsync(string relationCode, string fileUrl, List<AIDiagnosisPerImageModel> diagnosisResultInfos)
  963. {
  964. try
  965. {
  966. var resultInfos = new List<DiagnosisResultDTO>();
  967. for (var i = 0; i < diagnosisResultInfos.Count; i++)
  968. {
  969. resultInfos.Add(new DiagnosisResultDTO
  970. {
  971. Index = i,
  972. DiagnosisResult = Newtonsoft.Json.JsonConvert.SerializeObject(diagnosisResultInfos[i]),
  973. });
  974. }
  975. var addRequest = new AddDiagnosisResultInfosDBRequest
  976. {
  977. RelationCode = relationCode,
  978. FileUrl = fileUrl,
  979. DiagnosisResultInfos = resultInfos,
  980. };
  981. _diagnosisResultDBService.AddDiagnosisResultInfosAsync(addRequest);
  982. return true;
  983. }
  984. catch (Exception ex)
  985. {
  986. Logger.WriteLineWarn($"AIDiagnosisService AddDiagnosisResultInfosAsync err, ex:{ex}");
  987. return false;
  988. }
  989. }
  990. /// <summary>下载文件</summary>
  991. /// <param name="fileUrl"></param>
  992. /// <returns></returns>
  993. private async Task<string> DownloadAsync(string fileUrl)
  994. {
  995. try
  996. {
  997. if (string.IsNullOrEmpty(fileUrl))
  998. {
  999. return string.Empty;
  1000. }
  1001. if (!Directory.Exists(_tempFolder))
  1002. {
  1003. Directory.CreateDirectory(_tempFolder);
  1004. }
  1005. var fileName = Path.GetFileName(fileUrl);
  1006. var tempFile = Path.Combine(_tempFolder, fileName);
  1007. if (File.Exists(tempFile))
  1008. {
  1009. return tempFile;
  1010. }
  1011. long fileSize = 0;
  1012. using (var request = new HttpRequestMessage())
  1013. {
  1014. request.RequestUri = new Uri(fileUrl);
  1015. request.Method = HttpMethod.Get;
  1016. var response = await _httpClient.SendAsync(request);
  1017. if (response != null && response.StatusCode == HttpStatusCode.OK)
  1018. {
  1019. var contentLength = response.Content.Headers.ContentLength;
  1020. fileSize = contentLength == null ? 0 : contentLength.Value;
  1021. }
  1022. }
  1023. if (fileSize <= 0)
  1024. {
  1025. throw new NotSupportedException($"fileSize is {fileSize}");
  1026. }
  1027. byte[] bytes = await _httpClient.GetByteArrayAsync(fileUrl);
  1028. File.WriteAllBytes(tempFile, bytes);
  1029. return tempFile;
  1030. }
  1031. catch (Exception ex)
  1032. {
  1033. Logger.WriteLineWarn($"DiagnosisService download file err, url: {fileUrl}, {ex}");
  1034. }
  1035. finally
  1036. {
  1037. //Logger.WriteLineInfo($"download file:{fileUrl}");
  1038. }
  1039. return string.Empty;
  1040. }
  1041. /// <summary>
  1042. /// 上传文件
  1043. /// </summary>
  1044. /// <param name="filePath"></param>
  1045. /// <param name="fileName"></param>
  1046. /// <returns></returns>
  1047. private async Task<string> UploadFileAsync(string filePath, string fileName)
  1048. {
  1049. var fileToken = "";
  1050. using (var fileStream = new FileStream(filePath, FileMode.Open))
  1051. {
  1052. var size = fileStream.Length;
  1053. byte[] buffer = new byte[fileStream.Length];
  1054. fileStream.Read(buffer, 0, buffer.Length);
  1055. fileToken = await DoUploadFile(fileName, buffer);
  1056. }
  1057. return fileToken;
  1058. }
  1059. /// <summary>
  1060. /// 上传文件
  1061. /// </summary>
  1062. /// <param name="filePath"></param>
  1063. /// <param name="fileName"></param>
  1064. /// <returns></returns>
  1065. private async Task<Tuple<string, long>> UploadFileTupleAsync(string filePath, string fileName)
  1066. {
  1067. var fileToken = "";
  1068. long size = 0;
  1069. using (var fileStream = new FileStream(filePath, FileMode.Open))
  1070. {
  1071. size = fileStream.Length;
  1072. byte[] buffer = new byte[fileStream.Length];
  1073. fileStream.Read(buffer, 0, buffer.Length);
  1074. fileToken = await DoUploadFile(fileName, buffer);
  1075. }
  1076. return new Tuple<string, long>(fileToken, size);
  1077. }
  1078. /// <summary>
  1079. /// 上传文件
  1080. /// </summary>
  1081. /// <param name="fileName"></param>
  1082. /// <param name="fileData"></param>
  1083. /// <returns></returns>
  1084. async Task<string> DoUploadFile(string fileName, byte[] fileData)
  1085. {
  1086. var requestHeads = new Dictionary<string, string>();
  1087. var defaultToken = await _authenticationService.GetServerDefaultTokenAsync();
  1088. var authorizationRet = await _storageService.GetAuthorizationAsync(
  1089. new FileServiceRequest
  1090. {
  1091. Token = defaultToken,
  1092. FileName = fileName,
  1093. });
  1094. requestHeads.Add("Authorization", authorizationRet.Authorization);
  1095. var fileUrl = authorizationRet.StorageUrl;
  1096. Logger.WriteLineInfo($"DoUploadFile fileUrl:{fileUrl}");
  1097. using (var request = new HttpRequestMessage())
  1098. {
  1099. var fileExtension = Path.GetExtension(fileName);
  1100. var mimeType = FileHelper.GetMimeType(fileExtension);
  1101. var contentType = MediaTypeHeaderValue.Parse(mimeType);
  1102. using (UploadContent content = new UploadContent(fileData, contentType))
  1103. {
  1104. request.RequestUri = new Uri(fileUrl);
  1105. request.Method = HttpMethod.Put;
  1106. request.Content = content;
  1107. foreach (var head in requestHeads)
  1108. {
  1109. request.Headers.TryAddWithoutValidation(head.Key, head.Value);
  1110. }
  1111. var result = await ExecuteRequest(request);
  1112. if (!result)
  1113. {
  1114. throw new Exception("Upload file failed!");
  1115. }
  1116. }
  1117. }
  1118. return fileUrl;
  1119. }
  1120. private UploadFileTypeEnum GetUploadFileType(string fileName)
  1121. {
  1122. if (fileName.EndsWith(".vid"))
  1123. {
  1124. return UploadFileTypeEnum.VID;
  1125. }
  1126. else if (fileName.EndsWith(".jpg"))
  1127. {
  1128. return UploadFileTypeEnum.JPG;
  1129. }
  1130. else
  1131. {
  1132. return UploadFileTypeEnum.MP4;
  1133. }
  1134. }
  1135. /// <summary>
  1136. /// 执行请求
  1137. /// </summary>
  1138. /// <param name="httpRequestMessage"></param>
  1139. /// <typeparam name="T"></typeparam>
  1140. /// <returns></returns>
  1141. public async Task<bool> ExecuteRequest(HttpRequestMessage httpRequestMessage)
  1142. {
  1143. try
  1144. {
  1145. var response = await _httpClient.SendAsync(httpRequestMessage);
  1146. if (response != null && response.StatusCode == HttpStatusCode.OK)
  1147. {
  1148. return true;
  1149. }
  1150. return false;
  1151. }
  1152. catch (Exception ex)
  1153. {
  1154. throw ex;
  1155. }
  1156. }
  1157. private List<WingInterfaceLibrary.DTO.Comment.AIDiagnosisPoint2D> PointToDto(Point2D[] points)
  1158. {
  1159. var dstContours = new List<WingInterfaceLibrary.DTO.Comment.AIDiagnosisPoint2D>();
  1160. foreach (var p in points)
  1161. {
  1162. dstContours.Add(new WingInterfaceLibrary.DTO.Comment.AIDiagnosisPoint2D
  1163. {
  1164. X = p.X,
  1165. Y = p.Y
  1166. });
  1167. }
  1168. return dstContours;
  1169. }
  1170. private List<DiagnosisKeyPointDTO> KeyPointToDto(KeyPointInfo[] points)
  1171. {
  1172. var dstKeys = new List<DiagnosisKeyPointDTO>();
  1173. foreach (var point in points)
  1174. {
  1175. dstKeys.Add(new DiagnosisKeyPointDTO
  1176. {
  1177. Type = (DiagnosisKeyPointType)point.Type,
  1178. IndexInContour = point.IndexInContour,
  1179. Point = new WingInterfaceLibrary.DTO.Comment.AIDiagnosisPoint2D
  1180. {
  1181. X = point.Point.X,
  1182. Y = point.Point.Y,
  1183. },
  1184. });
  1185. }
  1186. return dstKeys;
  1187. }
  1188. }
  1189. }