AIDiagnosisService.cs 56 KB

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