AIDiagnosisService.cs 108 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102
  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. using WingInterfaceLibrary.Request.DBCopy;
  41. using Newtonsoft.Json.Linq;
  42. using System.Runtime.CompilerServices;
  43. using WingInterfaceLibrary.DTO.Common;
  44. using WingInterfaceLibrary.Request.User;
  45. namespace WingAIDiagnosisService.Service
  46. {
  47. /// <summary>
  48. /// AI诊断服务
  49. /// </summary>
  50. public partial class AIDiagnosisService : JsonRpcService, IAIDiagnosisService
  51. {
  52. private EnumPerformance _workerLevel = EnumPerformance.Medium;
  53. private int _batchImageSize = 16;
  54. private int _sleepTime = 400;
  55. private readonly string _tempFolder = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "DiagnosisTemp");
  56. private AIDiagSystem _diagSystem;
  57. private AILesionCompareUtils _lesionCompareUtil;
  58. private RecognizeCarotidType _recognizeCarotidType;
  59. //识别左右颈是否初始化成功
  60. private static bool _recognizeCarotidTypeCanBeUse = false;
  61. static HttpClient _httpClient = new HttpClient();
  62. private IAuthenticationService _authenticationService;
  63. private IStorageService _storageService;
  64. private readonly string _carotidName = "CarotidPlaqueDetect";
  65. private int _contourInterval = 10;
  66. private IDiagnosisResultDBService _diagnosisResultDBService;
  67. private bool _isCarotidOn = true;
  68. /// <summary>
  69. /// Init service
  70. /// </summary>
  71. public override void Load(JsonRpcClientPool jsonRpcClientPool)
  72. {
  73. base.Load(jsonRpcClientPool);
  74. _authenticationService = GetProxy<IAuthenticationService>();
  75. _storageService = GetProxy<IStorageService>();
  76. _workerLevel = (EnumPerformance)ConfigurationManager.GetParammeter<IntParameter>("AI", "WorkerLevel").Value;
  77. _batchImageSize = ConfigurationManager.GetParammeter<IntParameter>("AI", "BatchImageSize").Value;
  78. _contourInterval = ConfigurationManager.GetParammeter<IntParameter>("AI", "ContourInterval").Value;
  79. _httpClient.Timeout = TimeSpan.FromMinutes(15);
  80. _diagnosisResultDBService = GetProxy<IDiagnosisResultDBService>();
  81. InitAISystem();
  82. //初始化识别左右颈
  83. _isCarotidOn = ConfigurationManager.GetParammeter<BoolParameter>("AI", "IsCarotidOn").Value;
  84. if (_isCarotidOn)
  85. {
  86. _recognizeCarotidType = new RecognizeCarotidType();
  87. _recognizeCarotidTypeCanBeUse = _recognizeCarotidType.Initialization();
  88. }
  89. var keyPointDistanceThreshold = ConfigurationManager.GetParammeter<IntParameter>("AI", "KeyPointDistanceThreshold").Value;
  90. var dragStartPointCatchDistanceThreshold = ConfigurationManager.GetParammeter<IntParameter>("AI", "DragStartPointCatchDistanceThreshold").Value;
  91. ContourModifyHelper.KeyPointDistanceThreshold = keyPointDistanceThreshold;
  92. ContourModifyHelper.DragStartPointCatchDistanceThreshold = dragStartPointCatchDistanceThreshold;
  93. }
  94. private void InitAISystem()
  95. {
  96. var modeFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "AIDiagnosis", "Networks");
  97. _diagSystem = new AIDiagSystem(_workerLevel, modeFilePath, EnumInferWorkName.Default, false, true, true);
  98. if (_diagSystem != null)
  99. {
  100. _diagSystem.NotifyError += AIdiagSystem_NotifyError;
  101. _diagSystem.NotifyLog += AIdiagSystem_NotifyLog;
  102. }
  103. _lesionCompareUtil = new AILesionCompareUtils();
  104. }
  105. /// <summary>
  106. /// 查询杏聆荟支持的AI模块
  107. /// </summary>
  108. /// <param name="request">查询杏聆荟支持的AI模块请求实体</param>
  109. /// <returns></returns>
  110. /// <show>false</show>
  111. public async Task<IList<string>> FindDiagnosisModulesAsync(TokenRequest request)
  112. {
  113. try
  114. {
  115. var moduleNames = AIDiagSystem.GetValidModuleNamesForVclound() ?? new List<EnumAIModuleNames>();
  116. var resultData = moduleNames.Select(x => x.ToString()).ToList();
  117. if (!resultData.Contains(_carotidName))
  118. {
  119. resultData.Remove("CarotidArtery");
  120. resultData.Add(_carotidName);
  121. }
  122. return await Task.FromResult(resultData.Distinct().ToList());
  123. }
  124. catch (Exception)
  125. {
  126. return new List<string>();
  127. }
  128. }
  129. /// <summary>
  130. /// 图像诊断
  131. /// </summary>
  132. /// <param name="request">图像诊断请求实体</param>
  133. /// <returns>图像诊断结果</returns>
  134. /// <show>false</show>
  135. public async Task<DiagnosisImageResult> DiagnosisImageAsync(DiagnosisImageRequest request)
  136. {
  137. try
  138. {
  139. var relationCode = request.RelationCode;
  140. var fileUrl = request.FileToken;
  141. var localFile = "";
  142. if (!string.IsNullOrWhiteSpace(request.DiskPath))
  143. {
  144. localFile = request.DiskPath;
  145. }
  146. else
  147. {
  148. localFile = await DownloadAsync(fileUrl);
  149. }
  150. if (File.Exists(localFile))
  151. {
  152. if (_diagSystem == null)
  153. {
  154. InitAISystem();
  155. }
  156. var resultData = new DiagnosisImageResult();
  157. var isCaroid = false;
  158. var diagnosisPerImages = new List<AIDiagnosisPerImageModel>();
  159. var results = new Dictionary<int, AIDiagResultPerImg>();
  160. var organs = new List<DiagnosisOrganEnum>();
  161. using (var imageData = new VinnoImageData(localFile, OperationMode.Open))
  162. {
  163. isCaroid = DiagnosisHelper.IsCarotid(imageData);
  164. if (isCaroid)
  165. {
  166. if (_isCarotidOn)
  167. {
  168. //颈动脉
  169. var result = await CarotidDiagnosis(imageData);
  170. resultData = new DiagnosisImageResult()
  171. {
  172. DiagnosisOrgans = new List<DiagnosisOrganEnum>() { DiagnosisOrganEnum.CarotidArtery },
  173. CarotidResult = result
  174. };
  175. }
  176. }
  177. //乳腺、肝脏
  178. results = NormalDiagnosis(relationCode, fileUrl, imageData);
  179. //AI实体有很多参数仅支持get,为了方便后续反序列化,db入库前先做个转换
  180. diagnosisPerImages = results.Select(x => new AIDiagnosisPerImageModel(x.Key, x.Value, 1)).ToList();
  181. diagnosisPerImages = GetAIDiagnosisPerImages(diagnosisPerImages, imageData, isCaroid);
  182. if (diagnosisPerImages == null || !diagnosisPerImages.Any())
  183. {
  184. return new DiagnosisImageResult
  185. {
  186. DiagnosisConclusion = DiagnosisConclusionEnum.Unrecognized,
  187. DiagnosisResult = null,
  188. DiagnosisOrgans = new List<DiagnosisOrganEnum>(),
  189. };
  190. }
  191. organs = GetDiagnosisOrgans(diagnosisPerImages);
  192. await AddDiagnosisResultInfosAsync(relationCode, fileUrl, diagnosisPerImages);
  193. //轮廓线分隔处理
  194. diagnosisPerImages = results.Select(x => new AIDiagnosisPerImageModel(x.Key, x.Value, _contourInterval)).ToList();
  195. diagnosisPerImages = GetAIDiagnosisPerImages(diagnosisPerImages, imageData, isCaroid);
  196. }
  197. var diagnosisResult = JsonConvert.DeserializeObject<List<AIDiagnosisPerImageDTO>>(JsonConvert.SerializeObject(diagnosisPerImages));
  198. resultData.DiagnosisConclusion = (DiagnosisConclusionEnum)GetDiagnosisConclusion(diagnosisPerImages);
  199. resultData.DiagnosisResult = diagnosisResult;
  200. resultData.DiagnosisOrgans.AddRange(organs);
  201. return resultData;
  202. }
  203. }
  204. catch (Exception ex)
  205. {
  206. Logger.WriteLineWarn($"DiagnosisImageAsync err {ex}");
  207. }
  208. return new DiagnosisImageResult();
  209. }
  210. private List<AIDiagnosisPerImageModel> GetAIDiagnosisPerImages(List<AIDiagnosisPerImageModel> diagnosisPerImages, VinnoImageData imageData, bool isCaroid)
  211. {
  212. if (isCaroid && diagnosisPerImages.Count == 1)
  213. {
  214. foreach (var perImage in diagnosisPerImages)
  215. {
  216. if (perImage.DiagResultsForEachOrgan.All(x => x.Organ != DiagnosisOrganEnum.CarotidArtery))
  217. {
  218. var orgs = new List<WingAIDiagnosisService.Manage.AIDiagnosisResultPerOrgan>();
  219. foreach (var org in perImage.DiagResultsForEachOrgan)
  220. {
  221. org.Organ = DiagnosisOrganEnum.CarotidArtery;
  222. foreach (var detectedObject in org.DetectedObjects)
  223. {
  224. detectedObject.Label = 0;
  225. }
  226. orgs.Add(org);
  227. }
  228. perImage.DiagResultsForEachOrgan = orgs;
  229. }
  230. }
  231. }
  232. foreach (var perImage in diagnosisPerImages)
  233. {
  234. var orgs = new List<WingAIDiagnosisService.Manage.AIDiagnosisResultPerOrgan>();
  235. foreach (var org in perImage.DiagResultsForEachOrgan)
  236. {
  237. if (org.Organ == DiagnosisOrganEnum.CarotidArtery)
  238. {
  239. var physicalPerPixel = BaseDiagnosis.GetPhysicalPerPixel(imageData, perImage.Index, true);
  240. var info = GetReportDetectedObject(perImage.DiagResultsForEachOrgan, physicalPerPixel * 10);
  241. if (info.Item4 < 1.4)
  242. {
  243. foreach (var a in org.DetectedObjects)
  244. {
  245. a.Label = 0;
  246. }
  247. }
  248. orgs.Add(org);
  249. }
  250. else
  251. {
  252. orgs.Add(org);
  253. }
  254. }
  255. perImage.DiagResultsForEachOrgan = orgs;
  256. }
  257. var organs = GetDiagnosisOrgans(diagnosisPerImages);
  258. if (organs.Contains(DiagnosisOrganEnum.Breast))
  259. {
  260. var breastCount = diagnosisPerImages.Where(a => a.PriorityScore > 0 && a.DiagResultsForEachOrgan != null && a.DiagResultsForEachOrgan.Any(x => x.Organ == DiagnosisOrganEnum.Breast)).Count();
  261. var totalCount = diagnosisPerImages.Count;
  262. var breastRate = breastCount * 1.0 / totalCount * 1.0;
  263. if (organs.All(x => x == DiagnosisOrganEnum.Breast))
  264. {
  265. if (breastRate <= 1 / 3.0)
  266. {
  267. return new List<AIDiagnosisPerImageModel>();
  268. }
  269. }
  270. else
  271. {
  272. if (breastRate <= 1 / 3.0)
  273. {
  274. foreach (var item in diagnosisPerImages)
  275. {
  276. item.DiagResultsForEachOrgan = item.DiagResultsForEachOrgan?.Where(x => x.Organ != DiagnosisOrganEnum.Breast)?.ToList() ?? new List<Manage.AIDiagnosisResultPerOrgan>();
  277. }
  278. }
  279. }
  280. }
  281. if (isCaroid)
  282. {
  283. var carotidCount = diagnosisPerImages.Count(x => x.DiagResultsForEachOrgan.Any(x => x.Organ == DiagnosisOrganEnum.CarotidArtery));
  284. var throidCount = diagnosisPerImages.Count(x => x.DiagResultsForEachOrgan.Any(x => x.Organ == DiagnosisOrganEnum.Thyroid));
  285. if (carotidCount > throidCount)
  286. {
  287. foreach (var item in diagnosisPerImages)
  288. {
  289. item.DiagResultsForEachOrgan = item.DiagResultsForEachOrgan?.Where(x => x.Organ == DiagnosisOrganEnum.CarotidArtery)?.ToList() ?? new List<Manage.AIDiagnosisResultPerOrgan>();
  290. }
  291. }
  292. else
  293. {
  294. foreach (var item in diagnosisPerImages)
  295. {
  296. item.DiagResultsForEachOrgan = item.DiagResultsForEachOrgan?.Where(x => x.Organ == DiagnosisOrganEnum.Thyroid)?.ToList() ?? new List<Manage.AIDiagnosisResultPerOrgan>();
  297. }
  298. }
  299. }
  300. return diagnosisPerImages;
  301. }
  302. private class TempAIResult
  303. {
  304. public int Index { get; set; }
  305. public AIDiagnosisPerImageModel ImageDiagResult { get; set; }
  306. }
  307. private class Description : AI.Common.IDescription
  308. {
  309. public EnumDescriptionType Type { get; set; }
  310. // 其他必要的实现,根据您的接口定义
  311. }
  312. private class DescriptionConverter : JsonConverter
  313. {
  314. public override bool CanConvert(Type objectType)
  315. {
  316. return (objectType == typeof(AI.Common.IDescription));
  317. }
  318. public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
  319. {
  320. throw new NotImplementedException();
  321. }
  322. public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
  323. {
  324. var jsonObject = JObject.Load(reader);
  325. var description = new Description();
  326. serializer.Populate(jsonObject.CreateReader(), description);
  327. return description;
  328. }
  329. }
  330. public async Task<DiagnosisImageResult> GetDiagnosisImageResultByAIResultAsync(GetDiagnosisImageResultByAIResultRequest request)
  331. {
  332. try
  333. {
  334. var aiResult = request.AiResult;
  335. var relationCode = request.RelationCode;
  336. var fileUrl = request.FileUrl;
  337. var createTime = request.CreateTime;
  338. var upateTime = request.UpdateTime;
  339. //乳腺、肝脏
  340. // var settings = new JsonSerializerSettings
  341. // {
  342. // Converters = new List<JsonConverter> { new DescriptionConverter() }
  343. // };
  344. var aiDiagResultPerImgList = JsonConvert.DeserializeObject<List<TempAIResult>>(aiResult);
  345. foreach (var item in aiDiagResultPerImgList)
  346. {
  347. item.ImageDiagResult.Index = item.Index;
  348. }
  349. var diagnosisPerImages = aiDiagResultPerImgList.Select(x => x.ImageDiagResult)?.ToList() ?? new List<AIDiagnosisPerImageModel>();
  350. foreach (var perImage in diagnosisPerImages)
  351. {
  352. foreach (var perOrgan in perImage.DiagResultsForEachOrgan)
  353. {
  354. var detectedObjects = new List<WingAIDiagnosisService.Manage.AIDetectedObject>();
  355. foreach (var perDetectedObject in perOrgan.DetectedObjects)
  356. {
  357. if (perDetectedObject.Descriptions != null && perDetectedObject.Descriptions.Any())
  358. {
  359. foreach (var item in perDetectedObject.Descriptions)
  360. {
  361. item.Type = item.DescriptionType;
  362. item.Value = item.DescriptionValue;
  363. }
  364. }
  365. detectedObjects.Add(perDetectedObject);
  366. }
  367. perOrgan.DetectedObjects = detectedObjects;
  368. }
  369. }
  370. var organs = GetDiagnosisOrgans(diagnosisPerImages);
  371. await AddDiagnosisResultInfosAsync(relationCode, fileUrl, diagnosisPerImages, createTime, upateTime, false);
  372. //轮廓线分隔处理
  373. foreach (var perImage in diagnosisPerImages)
  374. {
  375. foreach (var perOrgan in perImage.DiagResultsForEachOrgan)
  376. {
  377. var organContours = new List<WingAIDiagnosisService.Manage.AIDiagnosisPoint2D>();
  378. if (perOrgan.OrganContour != null && perOrgan.OrganContour.Count > 0)
  379. {
  380. var coutours = perOrgan.OrganContour;
  381. if (coutours != null && coutours.Count > 0)
  382. {
  383. for (var i = 0; i < coutours.Count; i++)
  384. {
  385. if (i % _contourInterval == 0)
  386. {
  387. organContours.Add(coutours[i]);
  388. }
  389. }
  390. }
  391. }
  392. perOrgan.OrganContours = organContours;
  393. perOrgan.OrganContour = new List<Manage.AIDiagnosisPoint2D>();
  394. var detectedObjects = new List<WingAIDiagnosisService.Manage.AIDetectedObject>();
  395. foreach (var perDetectedObject in perOrgan.DetectedObjects)
  396. {
  397. var contours = new List<WingAIDiagnosisService.Manage.AIDiagnosisPoint2D>();
  398. if (perDetectedObject.Contour != null && perDetectedObject.Contour.Count > 0)
  399. {
  400. var coutours = perDetectedObject.Contour;
  401. if (coutours != null && coutours.Count > 0)
  402. {
  403. for (var i = 0; i < coutours.Count; i++)
  404. {
  405. if (i % _contourInterval == 0)
  406. {
  407. contours.Add(coutours[i]);
  408. }
  409. }
  410. }
  411. }
  412. perDetectedObject.Contours = contours;
  413. perDetectedObject.Contour = new List<Manage.AIDiagnosisPoint2D>();
  414. detectedObjects.Add(perDetectedObject);
  415. }
  416. perOrgan.DetectedObjects = detectedObjects;
  417. }
  418. }
  419. var diagnosisResult = Newtonsoft.Json.JsonConvert.DeserializeObject<List<AIDiagnosisPerImageDTO>>(Newtonsoft.Json.JsonConvert.SerializeObject(diagnosisPerImages));
  420. var resultData = new DiagnosisImageResult()
  421. {
  422. DiagnosisConclusion = (DiagnosisConclusionEnum)GetDiagnosisConclusion(diagnosisPerImages),
  423. DiagnosisResult = diagnosisResult,
  424. DiagnosisOrgans = organs,
  425. };
  426. return resultData;
  427. }
  428. catch (Exception ex)
  429. {
  430. Logger.WriteLineWarn("GetDiagnosisImageResultByAIResultAsync" + ex);
  431. return null;
  432. }
  433. }
  434. public async Task<CarotidResultDTO> GetCarotidResultAsync(GetCarotidResultRequest request)
  435. {
  436. try
  437. {
  438. var surfaceToken = request.SurfaceToken;
  439. var mdlToken = request.MdlToken;
  440. CarotidScanTypeEnum carotidScanType = request.CarotidScanType;
  441. CarotidScanDirectionEnum carotidScanDirection = request.CarotidScanDirection;
  442. var measureImageFiles = request.MeasureImageFiles;
  443. var measureResult = request.MeasureResult;
  444. var surfaceImageList = new List<string>();
  445. if (MatchCurrentStorage(surfaceToken))
  446. {
  447. surfaceImageList = await SurfaceFileByUrl(surfaceToken);
  448. }
  449. var result = new CarotidResultDTO
  450. {
  451. CarotidScanType = carotidScanType,
  452. CarotidScanDirection = carotidScanDirection,
  453. SurfaceFile = surfaceToken,
  454. SurfaceFileSize = 0,
  455. CDNSurfaceFile = surfaceToken,//源站地址
  456. MdlFileSize = 0,
  457. MdlFile = mdlToken,//源站地址
  458. CDNMdlFile = mdlToken,
  459. MeasureImageFiles = measureImageFiles,
  460. MeasureResult = measureResult,
  461. SurfaceImageList = surfaceImageList,
  462. };
  463. return result;
  464. }
  465. catch (Exception ex)
  466. {
  467. Logger.WriteLineWarn("GetCarotidResultAsync" + ex);
  468. return null;
  469. }
  470. }
  471. /// <summary>
  472. /// 根据颈动脉体数据生成3D 6面图像集合
  473. /// </summary>
  474. /// <param name="request"></param>
  475. /// <returns></returns>
  476. /// <show>false</show>
  477. public async Task<CreateCarotidSurfaceImagesResult> CreateCarotidSurfaceImagesAsync(CreateCarotidSurfaceImagesRequest request)
  478. {
  479. var surfaceImages = new List<string>();
  480. try
  481. {
  482. var surfaceFileUrl = request.SurfaceFileUrl;
  483. var result = await SurfaceFileByUrl(surfaceFileUrl);
  484. if (result != null && result.Any())
  485. {
  486. surfaceImages = result;
  487. }
  488. }
  489. catch (Exception ex)
  490. {
  491. Logger.WriteLineWarn($"AIDiagnosisService CreateCarotidSurfaceImagesAsync error, ex:{ex}");
  492. }
  493. return new CreateCarotidSurfaceImagesResult { SurfaceImages = surfaceImages };
  494. }
  495. /// <summary>
  496. ///
  497. /// </summary>
  498. /// <param name="baseUrl"></param>
  499. private async Task<List<string>> SurfaceFileByUrl(string baseUrl)
  500. {
  501. var fileUrls = new List<string>();
  502. var localPath = await DownloadAsync(baseUrl);
  503. if (!string.IsNullOrWhiteSpace(localPath))
  504. {
  505. fileUrls = await SurfaceFile(localPath);
  506. }
  507. return fileUrls;
  508. }
  509. /// <summary>
  510. /// 生成AI报告
  511. /// </summary>
  512. /// <param name="request">生成AI报告请求实体</param>
  513. /// <returns></returns>
  514. /// <show>false</show>
  515. public async Task<DiagnosisReportResult> DiagnosisReportAsync(DiagnosisReportRequest request)
  516. {
  517. var disgnosisReportResult = new DiagnosisReportResult();
  518. try
  519. {
  520. var is3DCarotid = false;
  521. if (request.Organ == DiagnosisOrganEnum.CarotidArtery && is3DCarotid)
  522. {
  523. var result = await GetCarotidAIMeasureResult(request);
  524. return result;
  525. }
  526. else
  527. {
  528. var reportPerImages = new List<DiagnosisPerImageModel>();
  529. if (request.Organ == DiagnosisOrganEnum.CarotidArtery)
  530. {
  531. var leftRemedicals = request.RemedicalList.Where(x => x.CarotidResult.CarotidScanType == CarotidScanTypeEnum.CarotidLeft)?.ToList() ?? new List<DiagnosisRemicalDTO>();
  532. var leftRemedicalCode = string.Empty;
  533. double leftPhysicalPerPixel = 0;
  534. if (leftRemedicals.Any())
  535. {
  536. var managerLeft = await CreateDiagnosisManagerAsync(new DiagnosisReportRequest { Organ = request.Organ, RemedicalList = leftRemedicals });
  537. var perImages = managerLeft.GetReportResults();
  538. leftRemedicalCode = perImages.FirstOrDefault()?.RemedicalCode;
  539. leftPhysicalPerPixel = perImages.FirstOrDefault()?.Pixel ?? 0;
  540. reportPerImages.AddRange(perImages);
  541. }
  542. disgnosisReportResult.CarotidItems.AddRange(GetCarotidLeftReportItems(leftRemedicals.FirstOrDefault(x => x.RemedicalCode == leftRemedicalCode), leftPhysicalPerPixel));
  543. var rightRemedicals = request.RemedicalList.Where(x => x.CarotidResult.CarotidScanType == CarotidScanTypeEnum.CarotidRight)?.ToList() ?? new List<DiagnosisRemicalDTO>();
  544. var rightRemedicalCode = string.Empty;
  545. double rightPhysicalPerPixel = 0;
  546. if (rightRemedicals.Any())
  547. {
  548. var managerRight = await CreateDiagnosisManagerAsync(new DiagnosisReportRequest { Organ = request.Organ, RemedicalList = rightRemedicals });
  549. var perImages = managerRight.GetReportResults();
  550. rightRemedicalCode = perImages.FirstOrDefault()?.RemedicalCode;
  551. rightPhysicalPerPixel = perImages.FirstOrDefault()?.Pixel ?? 0;
  552. reportPerImages.AddRange(perImages);
  553. }
  554. disgnosisReportResult.CarotidItems.AddRange(GetCarotidRightReportItems(rightRemedicals.FirstOrDefault(x => x.RemedicalCode == rightRemedicalCode), rightPhysicalPerPixel));
  555. }
  556. else
  557. {
  558. var manager = await CreateDiagnosisManagerAsync(request);
  559. reportPerImages = manager.GetReportResults();
  560. }
  561. var diagnosisConclusion = GetDiagnosisConclusion(reportPerImages);
  562. var diagnosisOrgans = GetDiagnosisOrgans(reportPerImages);
  563. var diagnosisResult = new List<DiagnosisPerImageDTO>();
  564. foreach (var item in reportPerImages)
  565. {
  566. var aiFileToken = await UploadFileAsync(item.AILocalImagePath, Path.GetFileName(item.AILocalImagePath));
  567. var previewPath = Path.Combine(_tempFolder, $"{Guid.NewGuid():N}.jpg");
  568. CreateThumbnailFile(item.AILocalImagePath, 100, 30, previewPath);
  569. var previewUrl = await UploadFileAsync(previewPath, Path.GetFileName(previewPath));
  570. var perImageJson = Newtonsoft.Json.JsonConvert.SerializeObject(item);
  571. var perImageResult = Newtonsoft.Json.JsonConvert.DeserializeObject<DiagnosisPerImageDTO>(perImageJson);
  572. perImageResult.RemedicalCode = item.RemedicalCode;
  573. perImageResult.DataType = (RemedicalFileDataTypeEnum)item.DataType;
  574. perImageResult.Pixel = item.Pixel;
  575. perImageResult.AIFileToken = aiFileToken;
  576. perImageResult.AIPreviewFileToken = previewUrl;
  577. perImageResult.PerImageJson = perImageJson;
  578. var conclusion = GetDiagnosisConclusion(new List<DiagnosisPerImageModel> { item });
  579. var organs = GetDiagnosisOrgans(new List<DiagnosisPerImageModel> { item });
  580. perImageResult.DiagnosisConclusion = (DiagnosisConclusionEnum)conclusion;
  581. perImageResult.DiagnosisOrgans = organs;
  582. diagnosisResult.Add(perImageResult);
  583. }
  584. disgnosisReportResult.DiagnosisConclusion = (DiagnosisConclusionEnum)diagnosisConclusion;
  585. disgnosisReportResult.DiagnosisResult = diagnosisResult;
  586. }
  587. }
  588. catch (Exception ex)
  589. {
  590. Logger.WriteLineWarn($"AIService DiagnosisReport err, {ex}");
  591. }
  592. return disgnosisReportResult;
  593. }
  594. private List<DataItemDTO> GetCarotidLeftReportItems(DiagnosisRemicalDTO leftData, double physicalPerPixel)
  595. {
  596. var reportItems = new List<DataItemDTO>();
  597. if (leftData == null)
  598. {
  599. reportItems.Add(new DataItemDTO { Key = "bd170b6a-e052-42f0-a778-4ec85138c1c6;33c0aaf8-e48e-4474-af68-b47d2378073f;LeftConclusion", Value = "" });
  600. reportItems.Add(new DataItemDTO { Key = "d2449fa2-70e7-45d1-80e3-f69c62652186;98d1e829-b0ee-440d-ae3b-02ff74521357;LeftPlaqueThick", Value = "" });
  601. reportItems.Add(new DataItemDTO { Key = "6f57bb90-6492-421e-ad3e-6c684b7f7452;6e36fa47-edb2-4ed8-8521-ddb9e1fa5536;LeftPlaquePosition", Value = "" });
  602. reportItems.Add(new DataItemDTO { Key = "ccef71cd-1c52-4614-a54f-d6d58cb8c586;d440eb6e-2630-4787-8700-486d097ba9f1;LeftPlaqueEcho", Value = "" });
  603. reportItems.Add(new DataItemDTO { Key = "9a6f96bb-b81e-407d-bc84-9d9815e96696;fe948295-b065-46b9-ab25-fe80144d7bff;LeftCarotidAD", Value = "" });
  604. reportItems.Add(new DataItemDTO { Key = "e3610e58-79c4-4e18-94c9-8928583b9dab;e6da9399-b849-4017-a3b8-e3212f55c4a4;LeftStenosisRate", Value = "" });
  605. return reportItems;
  606. }
  607. var carotidResultInfos = GetCarotidResultInfos(leftData);
  608. if (carotidResultInfos.Any(x => x.DetectedObjects.Any(x => x.Label == 1)))
  609. {
  610. //有斑块
  611. reportItems.Add(new DataItemDTO { Key = "bd170b6a-e052-42f0-a778-4ec85138c1c6;33c0aaf8-e48e-4474-af68-b47d2378073f;LeftConclusion", Value = FormatterPlaceholder2($"OnePlaque") });
  612. var info = GetReportDetectedObject(carotidResultInfos, physicalPerPixel * 10);
  613. var reportDetectedObject = info.Item1;
  614. reportItems.Add(new DataItemDTO { Key = "d2449fa2-70e7-45d1-80e3-f69c62652186;98d1e829-b0ee-440d-ae3b-02ff74521357;LeftPlaqueThick", Value = info.Item3.ToString() });
  615. var locationItem = GetDescription(reportDetectedObject, DiagnosisDescriptionEnum.QlaqueLocation);
  616. if (locationItem != null)
  617. {
  618. reportItems.Add(new DataItemDTO { Key = "6f57bb90-6492-421e-ad3e-6c684b7f7452;6e36fa47-edb2-4ed8-8521-ddb9e1fa5536;LeftPlaquePosition", Value = FormatterPlaceholder2($"{locationItem.Value}") });
  619. }
  620. var echoItem = GetDescription(reportDetectedObject, DiagnosisDescriptionEnum.QlaqueEchoPattern);
  621. if (echoItem != null)
  622. {
  623. reportItems.Add(new DataItemDTO { Key = "ccef71cd-1c52-4614-a54f-d6d58cb8c586;d440eb6e-2630-4787-8700-486d097ba9f1;LeftPlaqueEcho", Value = FormatterPlaceholder2($"{echoItem.Value}") });
  624. }
  625. var rateOfStenosis = GetDescription(reportDetectedObject, DiagnosisDescriptionEnum.CarotidRateOfStenosis);
  626. if (rateOfStenosis != null)
  627. {
  628. var rateOfStenosisValue = string.Empty;
  629. var rate = rateOfStenosis.Value.ToFloat() * 100;
  630. if (rate < 30)
  631. {
  632. rateOfStenosisValue = "Normal";
  633. }
  634. else if (rate >= 30 && rate < 50)
  635. {
  636. rateOfStenosisValue = "Mild";
  637. }
  638. else if (rate >= 50 && rate < 70)
  639. {
  640. rateOfStenosisValue = "Moderate";
  641. }
  642. else if (rate >= 70 && rate < 100)
  643. {
  644. rateOfStenosisValue = "Severe";
  645. }
  646. else
  647. {
  648. rateOfStenosisValue = "Blockage";
  649. }
  650. reportItems.Add(new DataItemDTO { Key = "e3610e58-79c4-4e18-94c9-8928583b9dab;e6da9399-b849-4017-a3b8-e3212f55c4a4;LeftStenosisRate", Value = FormatterPlaceholder2(rateOfStenosisValue) });
  651. }
  652. var innerDiameter = GetDescription(reportDetectedObject, DiagnosisDescriptionEnum.CarotidInnerDiameter);
  653. if (innerDiameter != null && !string.IsNullOrWhiteSpace(innerDiameter.Value))
  654. {
  655. var diagnosisMeasuringLine = JsonConvert.DeserializeObject<AIDiagnosisMeasuringLine>(innerDiameter.Value);
  656. var measuringLineLengthInPixel = Math.Round(diagnosisMeasuringLine.MeasuringLineLengthInPixel * physicalPerPixel * 10, 2);
  657. reportItems.Add(new DataItemDTO { Key = "9a6f96bb-b81e-407d-bc84-9d9815e96696;fe948295-b065-46b9-ab25-fe80144d7bff;LeftCarotidAD", Value = measuringLineLengthInPixel.ToString() });
  658. }
  659. }
  660. else
  661. {
  662. // reportItems.Add(new DataItemDTO { Key = "bd170b6a-e052-42f0-a778-4ec85138c1c6;33c0aaf8-e48e-4474-af68-b47d2378073f;LeftConclusion", Value = FormatterPlaceholder2($"NoPlaque2") });
  663. reportItems.Add(new DataItemDTO { Key = "bd170b6a-e052-42f0-a778-4ec85138c1c6;33c0aaf8-e48e-4474-af68-b47d2378073f;LeftConclusion", Value = "" });
  664. reportItems.Add(new DataItemDTO { Key = "d2449fa2-70e7-45d1-80e3-f69c62652186;98d1e829-b0ee-440d-ae3b-02ff74521357;LeftPlaqueThick", Value = "" });
  665. reportItems.Add(new DataItemDTO { Key = "6f57bb90-6492-421e-ad3e-6c684b7f7452;6e36fa47-edb2-4ed8-8521-ddb9e1fa5536;LeftPlaquePosition", Value = "" });
  666. reportItems.Add(new DataItemDTO { Key = "ccef71cd-1c52-4614-a54f-d6d58cb8c586;d440eb6e-2630-4787-8700-486d097ba9f1;LeftPlaqueEcho", Value = "" });
  667. reportItems.Add(new DataItemDTO { Key = "9a6f96bb-b81e-407d-bc84-9d9815e96696;fe948295-b065-46b9-ab25-fe80144d7bff;LeftCarotidAD", Value = "" });
  668. reportItems.Add(new DataItemDTO { Key = "e3610e58-79c4-4e18-94c9-8928583b9dab;e6da9399-b849-4017-a3b8-e3212f55c4a4;LeftStenosisRate", Value = "" });
  669. }
  670. return reportItems;
  671. }
  672. private List<DataItemDTO> GetCarotidRightReportItems(DiagnosisRemicalDTO rightData, double physicalPerPixel)
  673. {
  674. var reportItems = new List<DataItemDTO>();
  675. if (rightData == null)
  676. {
  677. reportItems.Add(new DataItemDTO { Key = "ec4c775e-601c-48cc-a1a9-f39c8629e3f7;9918912e-b702-4a2b-8b4f-61dcfb846be3;RightConclusion", Value = "" });
  678. reportItems.Add(new DataItemDTO { Key = "8e467ffb-da00-4d34-9aac-34e4e8f91844;46166cb6-ad3a-4ca2-821e-bd872137fa64;RightPlaqueThick", Value = "" });
  679. reportItems.Add(new DataItemDTO { Key = "1153870e-10c8-4674-99bf-108937db1060;053091d2-8ea0-4fcb-a222-00b46f60d12f;RightPlaquePosition", Value = "" });
  680. reportItems.Add(new DataItemDTO { Key = "c82e8655-169f-42be-ac99-5e7a56329924;e77f550d-37f3-492a-98dd-a981535383a5;RightPlaqueEcho", Value = "" });
  681. reportItems.Add(new DataItemDTO { Key = "b0092e8a-25ba-4690-b70f-e3525e6aaa39;2e742ddc-fa90-47bb-ac24-664238773b30;RightStenosisRate", Value = "" });
  682. reportItems.Add(new DataItemDTO { Key = "8c7543e9-95f2-4862-b2aa-4c4cb032948e;9f8e95ee-a262-41c2-9d4b-efbc7974b397;RightCarotidAD", Value = "" });
  683. return reportItems;
  684. }
  685. var carotidResultInfos = GetCarotidResultInfos(rightData);
  686. if (carotidResultInfos.Any(x => x.DetectedObjects.Any(x => x.Label == 1)))
  687. {
  688. //有斑块
  689. reportItems.Add(new DataItemDTO { Key = "ec4c775e-601c-48cc-a1a9-f39c8629e3f7;9918912e-b702-4a2b-8b4f-61dcfb846be3;RightConclusion", Value = FormatterPlaceholder2($"OnePlaque") });
  690. var info = GetReportDetectedObject(carotidResultInfos, physicalPerPixel * 10);
  691. var reportDetectedObject = info.Item1;
  692. reportItems.Add(new DataItemDTO { Key = "8e467ffb-da00-4d34-9aac-34e4e8f91844;46166cb6-ad3a-4ca2-821e-bd872137fa64;RightPlaqueThick", Value = info.Item3.ToString() });
  693. var locationItem = GetDescription(reportDetectedObject, DiagnosisDescriptionEnum.QlaqueLocation);
  694. if (locationItem != null)
  695. {
  696. reportItems.Add(new DataItemDTO { Key = "1153870e-10c8-4674-99bf-108937db1060;053091d2-8ea0-4fcb-a222-00b46f60d12f;RightPlaquePosition", Value = FormatterPlaceholder2($"{locationItem.Value}") });
  697. }
  698. var echoItem = GetDescription(reportDetectedObject, DiagnosisDescriptionEnum.QlaqueEchoPattern);
  699. if (echoItem != null)
  700. {
  701. reportItems.Add(new DataItemDTO { Key = "c82e8655-169f-42be-ac99-5e7a56329924;e77f550d-37f3-492a-98dd-a981535383a5;RightPlaqueEcho", Value = FormatterPlaceholder2($"{echoItem.Value}") });
  702. }
  703. var rateOfStenosis = GetDescription(reportDetectedObject, DiagnosisDescriptionEnum.CarotidRateOfStenosis);
  704. if (rateOfStenosis != null)
  705. {
  706. var rateOfStenosisValue = string.Empty;
  707. var rate = rateOfStenosis.Value.ToFloat() * 100;
  708. if (rate < 30)
  709. {
  710. rateOfStenosisValue = "Normal";
  711. }
  712. else if (rate >= 30 && rate < 50)
  713. {
  714. rateOfStenosisValue = "Mild";
  715. }
  716. else if (rate >= 50 && rate < 70)
  717. {
  718. rateOfStenosisValue = "Moderate";
  719. }
  720. else if (rate >= 70 && rate < 100)
  721. {
  722. rateOfStenosisValue = "Severe";
  723. }
  724. else
  725. {
  726. rateOfStenosisValue = "Blockage";
  727. }
  728. reportItems.Add(new DataItemDTO { Key = "b0092e8a-25ba-4690-b70f-e3525e6aaa39;2e742ddc-fa90-47bb-ac24-664238773b30;RightStenosisRate", Value = FormatterPlaceholder2(rateOfStenosisValue) });
  729. }
  730. var innerDiameter = GetDescription(reportDetectedObject, DiagnosisDescriptionEnum.CarotidInnerDiameter);
  731. if (innerDiameter != null && !string.IsNullOrWhiteSpace(innerDiameter.Value))
  732. {
  733. var diagnosisMeasuringLine = JsonConvert.DeserializeObject<AIDiagnosisMeasuringLine>(innerDiameter.Value);
  734. var measuringLineLengthInPixel = Math.Round(diagnosisMeasuringLine.MeasuringLineLengthInPixel * physicalPerPixel * 10, 2);
  735. reportItems.Add(new DataItemDTO { Key = "8c7543e9-95f2-4862-b2aa-4c4cb032948e;9f8e95ee-a262-41c2-9d4b-efbc7974b397;RightCarotidAD", Value = measuringLineLengthInPixel.ToString() });
  736. }
  737. }
  738. else
  739. {
  740. // reportItems.Add(new DataItemDTO { Key = "ec4c775e-601c-48cc-a1a9-f39c8629e3f7;9918912e-b702-4a2b-8b4f-61dcfb846be3;RightConclusion", Value = FormatterPlaceholder2($"NoPlaque2") });
  741. reportItems.Add(new DataItemDTO { Key = "ec4c775e-601c-48cc-a1a9-f39c8629e3f7;9918912e-b702-4a2b-8b4f-61dcfb846be3;RightConclusion", Value = "" });
  742. reportItems.Add(new DataItemDTO { Key = "8e467ffb-da00-4d34-9aac-34e4e8f91844;46166cb6-ad3a-4ca2-821e-bd872137fa64;RightPlaqueThick", Value = "" });
  743. reportItems.Add(new DataItemDTO { Key = "1153870e-10c8-4674-99bf-108937db1060;053091d2-8ea0-4fcb-a222-00b46f60d12f;RightPlaquePosition", Value = "" });
  744. reportItems.Add(new DataItemDTO { Key = "c82e8655-169f-42be-ac99-5e7a56329924;e77f550d-37f3-492a-98dd-a981535383a5;RightPlaqueEcho", Value = "" });
  745. reportItems.Add(new DataItemDTO { Key = "b0092e8a-25ba-4690-b70f-e3525e6aaa39;2e742ddc-fa90-47bb-ac24-664238773b30;RightStenosisRate", Value = "" });
  746. reportItems.Add(new DataItemDTO { Key = "8c7543e9-95f2-4862-b2aa-4c4cb032948e;9f8e95ee-a262-41c2-9d4b-efbc7974b397;RightCarotidAD", Value = "" });
  747. }
  748. return reportItems;
  749. }
  750. /// <summary>
  751. /// 查询AI诊断结论
  752. /// </summary>
  753. /// <param name="request">查询AI诊断结论请求实体</param>
  754. /// <returns></returns>
  755. /// <show>false</show>
  756. public async Task<GetDiagnosisConclusionResult> GetDiagnosisConclusionAsync(GetDiagnosisConclusionRequest request)
  757. {
  758. var preImage = Newtonsoft.Json.JsonConvert.DeserializeObject<DiagnosisPerImageModel>(request.PerImageJson);
  759. var conclusion = GetDiagnosisConclusion(new List<DiagnosisPerImageModel> { preImage });
  760. var organs = GetDiagnosisOrgans(new List<DiagnosisPerImageModel> { preImage });
  761. var resultData = new GetDiagnosisConclusionResult
  762. {
  763. DiagnosisConclusion = (DiagnosisConclusionEnum)conclusion,
  764. DiagnosisOrgans = organs,
  765. };
  766. return await Task.FromResult(resultData);
  767. }
  768. /// <summary>
  769. /// 查询AI相关枚举集合
  770. /// </summary>
  771. /// <param name="request">查询AI相关枚举集合请求实体</param>
  772. /// <returns>AI相关枚举集合</returns>
  773. /// <value></value>
  774. /// <errorCodes></errorCodes>
  775. public async Task<GetDiagnosisEnumItemsResult> GetDiagnosisEnumItemsAsync(GetDiagnosisEnumItemsRequest request)
  776. {
  777. var diagnosisItems = new List<EnumItemDTO>();
  778. //病灶
  779. diagnosisItems.Add(GetEnumItem(typeof(DiagnosisBreastLabelEnum), "Breast", new List<string> { "BIRads1" }));
  780. diagnosisItems.Add(GetEnumItem(typeof(DiagnosisLiverLabelEnum), "Liver", new List<string> { "BIRads1" }));
  781. diagnosisItems.Add(GetEnumItem(typeof(AIThyroidLabelEnum), "Thyroid", new List<string> { "TIRADS0" }));
  782. //病灶特性描述
  783. diagnosisItems.Add(GetEnumItem(typeof(EnumDesShapeValue)));
  784. diagnosisItems.Add(GetEnumItem(typeof(EnumDesOrientationValue)));
  785. diagnosisItems.Add(GetEnumItem(typeof(EnumDesEchoPatternValue)));
  786. diagnosisItems.Add(GetEnumItem(typeof(EnumDesLesionBoundaryValue)));
  787. diagnosisItems.Add(GetEnumItem(typeof(EnumDesMarginValue)));
  788. diagnosisItems.Add(GetEnumItem(typeof(EnumCalcificationsValue), "Calcification"));
  789. diagnosisItems.Add(GetEnumItem(typeof(EnumDesLiverShapeValue)));
  790. diagnosisItems.Add(GetEnumItem(typeof(EnumDesLiverBoundaryValue)));
  791. diagnosisItems.Add(GetEnumItem(typeof(EnumDesLiverEchoTextureValue)));
  792. diagnosisItems.Add(GetEnumItem(typeof(EnumDesThyroidEchoPatternValue)));
  793. diagnosisItems.Add(GetEnumItem(typeof(EnumDesThyroidShapeValue)));
  794. diagnosisItems.Add(GetEnumItem(typeof(EnumDesThyroidMarginValue)));
  795. diagnosisItems.Add(GetEnumItem(typeof(EnumDesThyroidEchogenicFociValue)));
  796. //局灶和弥漫区分
  797. diagnosisItems.Add(GetEnumItem(typeof(DiagnosisBreastLabelEnum), "BreastLocalLesion", includeFields: new List<string> { "Lipomyoma", "BIRads2", "BIRads3" }));
  798. diagnosisItems.Add(GetEnumItem(typeof(DiagnosisBreastLabelEnum), "BreastDiffuseLesion", includeFields: new List<string> { "BIRads4A", "BIRads4B", "BIRads4C", "BIRads5" }));
  799. diagnosisItems.Add(GetEnumItem(typeof(DiagnosisLiverLabelEnum), "LiverLocalLesion", includeFields: new List<string> { "Hyperechoic", "HHE", "CYST", "PossibleCancer" }));
  800. diagnosisItems.Add(GetEnumItem(typeof(DiagnosisLiverLabelEnum), "LiverDiffuseLesion", includeFields: new List<string> { "FattyLiver", "DiffuseLesions", "Cirrhosis", "PCLD" }));
  801. diagnosisItems.Add(GetEnumItem(typeof(AIThyroidLabelEnum), "ThyroidLocalLesion", includeFields: new List<string> { "TIRADS2", "TIRADS3", "TIRADS4a", "TIRADS4b", "TIRADS4c", "TIRADS5" }));
  802. diagnosisItems.Add(GetEnumItem(typeof(AIThyroidLabelEnum), "ThyroidDiffuseLesion", includeFields: new List<string> { "DiffuseDisease" }));
  803. var resultData = new GetDiagnosisEnumItemsResult { Source = diagnosisItems };
  804. return await Task.FromResult(resultData);
  805. }
  806. private EnumItemDTO GetEnumItem(Type enumType, string keyCode = "", List<string> excludeFields = null, List<string> includeFields = null)
  807. {
  808. keyCode = !string.IsNullOrWhiteSpace(keyCode) ? keyCode : enumType.Name.ToString().Replace("EnumDes", "").Replace("Enum", "").Replace("Value", "");
  809. var enumNames = Enum.GetNames(enumType).ToList();
  810. if (excludeFields != null && excludeFields.Any())
  811. {
  812. enumNames = enumNames.Except(excludeFields).ToList();
  813. }
  814. if (includeFields != null && includeFields.Any())
  815. {
  816. enumNames = enumNames.Intersect(includeFields).ToList();
  817. }
  818. var children = new List<EnumFieldDTO>();
  819. foreach (var val in enumNames)
  820. {
  821. var id = (int)enumType.GetField(val).GetValue(val);
  822. var fieldValue = val.ToString();
  823. if (enumType == typeof(DiagnosisLiverLabelEnum) && fieldValue == "Hyperechoic")
  824. {
  825. fieldValue = "LiverHyperechoic";
  826. }
  827. children.Add(new EnumFieldDTO
  828. {
  829. Id = id,
  830. Value = fieldValue,
  831. });
  832. }
  833. return new EnumItemDTO
  834. {
  835. Code = keyCode,
  836. Children = children,
  837. };
  838. }
  839. /// <summary>
  840. /// 查询病灶轮廓的关键点
  841. /// </summary>
  842. /// <param name="request">查询病灶轮廓的关键点请求实体</param>
  843. /// <returns>关键点集合</returns>
  844. /// <value></value>
  845. /// <errorCodes></errorCodes>
  846. public async Task<List<DiagnosisKeyPointDTO>> GetKeyPointsOfContourAsync(GetKeyPointsOfContourRequest request)
  847. {
  848. var contourPoints = request.Contours.Select(c => new Point2D
  849. {
  850. X = c.X,
  851. Y = c.Y
  852. }).ToArray();
  853. var ls = request.LesionSize;
  854. var horizontalP1 = new Point2D(ls.HorizontalPoint1.X, ls.HorizontalPoint1.Y);
  855. var horizontalP2 = new Point2D(ls.HorizontalPoint2.X, ls.HorizontalPoint2.Y);
  856. var verticalP1 = new Point2D(ls.VerticalPoint1.X, ls.VerticalPoint1.Y);
  857. var verticalP2 = new Point2D(ls.VerticalPoint2.X, ls.VerticalPoint2.Y);
  858. var lesionSize = new LesionSize(horizontalP1, horizontalP2, verticalP1, verticalP2);
  859. // lesionSize.HorizontalLengthInPixel = ls.HorizontalLengthInPixel;
  860. // lesionSize.VerticalLengthInPixel = ls.VerticalLengthInPixel;
  861. var aiResult = ContourModifyHelper.KeyPointsOfContour(contourPoints, lesionSize);
  862. var resultData = KeyPointToDto(aiResult);
  863. return await Task.FromResult(resultData);
  864. }
  865. /// <summary>
  866. /// 移动光标,查询受影响关键点
  867. /// </summary>
  868. /// <param name="request">移动光标,查询受影响关键点请求实体</param>
  869. /// <returns>受影响关键点下标</returns>
  870. /// <value></value>
  871. /// <errorCodes></errorCodes>
  872. public async Task<List<int>> AffectedKeyPointsByDragActionAsync(AffectedKeyPointsByDragActionRequest request)
  873. {
  874. var origKeyPoints = request.KeyPoints.Select(c => new KeyPointInfo
  875. {
  876. Type = (EnumKeyPointType)c.Type,
  877. IndexInContour = c.IndexInContour,
  878. Point = new Point2D(c.Point.X, c.Point.Y)
  879. }).ToArray();
  880. var mousePoint = new Point2D(request.MousePoint.X, request.MousePoint.Y);
  881. var resultData = ContourModifyHelper.AffectedKeyPointsByDragAction(origKeyPoints, mousePoint).ToList();
  882. return await Task.FromResult(resultData);
  883. }
  884. /// <summary>
  885. /// 拖动光标,查询所有轮廓点和关键点
  886. /// </summary>
  887. /// <param name="request">拖动光标,查询所有轮廓点和关键点请求实体</param>
  888. /// <returns>所有轮廓点和关键点</returns>
  889. /// <value></value>
  890. /// <errorCodes></errorCodes>
  891. public async Task<ContourAndKeyPointsAfterDragResult> ContourAndKeyPointsAfterDragAsync(ContourAndKeyPointsAfterDragRequest request)
  892. {
  893. var origContourPoints = request.Contours.Select(c => new Point2D
  894. {
  895. X = c.X,
  896. Y = c.Y
  897. }).ToArray();
  898. var origKeyPoints = request.KeyPoints.Select(c => new KeyPointInfo
  899. {
  900. Type = (EnumKeyPointType)c.Type,
  901. IndexInContour = c.IndexInContour,
  902. Point = new Point2D(c.Point.X, c.Point.Y)
  903. }).ToArray();
  904. var dragStartPoint = new Point2D(request.StartPoint.X, request.StartPoint.Y);
  905. var dragEndPoint = new Point2D(request.EndPoint.X, request.EndPoint.Y);
  906. ContourModifyHelper.ContourAndKeyPointsAfterDrag(origContourPoints, origKeyPoints, dragStartPoint, dragEndPoint
  907. , out Point2D[] dstContourPoints, out KeyPointInfo[] dstKeyPoints, out int[] affectedKeyPointIndexes);
  908. var dstContours = PointToDto(dstContourPoints);
  909. var dstKeys = KeyPointToDto(dstKeyPoints);
  910. var resultData = new ContourAndKeyPointsAfterDragResult
  911. {
  912. DstContours = dstContours,
  913. DstKeyPoints = dstKeys,
  914. AffectedKeyPointIndexes = affectedKeyPointIndexes.ToList(),
  915. };
  916. return await Task.FromResult(resultData);
  917. }
  918. /// <summary>
  919. /// 画轮廓模式,查询鼠标位置到轮廓线的最短距离
  920. /// </summary>
  921. /// <param name="request">画轮廓模式,查询鼠标位置到轮廓线的最短距离请求实体</param>
  922. /// <returns>鼠标离轮廓线的距离,高亮点的下标</returns>
  923. /// <value></value>
  924. /// <errorCodes></errorCodes>
  925. public async Task<MinimumDistanceToContourPointsResult> MinimumDistanceToContourPointsAsync(MinimumDistanceToContourPointsRequest request)
  926. {
  927. var origContourPoints = request.ContourPoints.Select(c => new Point2D
  928. {
  929. X = c.X,
  930. Y = c.Y
  931. }).ToArray();
  932. var mousePoint = new Point2D(request.MousePoint.X, request.MousePoint.Y);
  933. var distance = ContourModifyHelper.MinimumDistanceToContourPoints(origContourPoints, mousePoint, out int closestPointIndex);
  934. var resultData = new MinimumDistanceToContourPointsResult
  935. {
  936. DistanceCaught = distance,
  937. ClosestPointIndex = closestPointIndex,
  938. };
  939. return await Task.FromResult(resultData);
  940. }
  941. /// <summary>
  942. /// 画轮廓模式,合并新旧轮廓
  943. /// </summary>
  944. /// <param name="request">画轮廓模式,查询鼠标位置到轮廓线的最短距离请求实体</param>
  945. /// <returns>合并后的完整轮廓线,合并后的横纵径尺寸结果</returns>
  946. /// <value></value>
  947. /// <errorCodes></errorCodes>
  948. public async Task<ContourMergeResult> ContourMergeAsync(ContourMergeRequest request)
  949. {
  950. var origContourPoints = request.ContourPoints.Select(c => new Point2D
  951. {
  952. X = c.X,
  953. Y = c.Y
  954. }).ToArray();
  955. var ls = request.LesionSize;
  956. var horizontalP1 = new Point2D(ls.HorizontalPoint1.X, ls.HorizontalPoint1.Y);
  957. var horizontalP2 = new Point2D(ls.HorizontalPoint2.X, ls.HorizontalPoint2.Y);
  958. var verticalP1 = new Point2D(ls.VerticalPoint1.X, ls.VerticalPoint1.Y);
  959. var verticalP2 = new Point2D(ls.VerticalPoint2.X, ls.VerticalPoint2.Y);
  960. var lesionSize = new LesionSize(horizontalP1, horizontalP2, verticalP1, verticalP2);
  961. // lesionSize.HorizontalLengthInPixel = ls.HorizontalLengthInPixel;
  962. // lesionSize.VerticalLengthInPixel = ls.VerticalLengthInPixel;
  963. var drawingNewContourPoints = request.DrawingNewContourPoints.Select(c => new Point2D
  964. {
  965. X = c.X,
  966. Y = c.Y
  967. }).ToArray();
  968. var aiResult = ContourModifyHelper.ContourMerge(origContourPoints, lesionSize, drawingNewContourPoints, out Point2D[] dstContourPoints, out LesionSize dstLesionSize);
  969. var dstContours = PointToDto(dstContourPoints);
  970. var resultData = new ContourMergeResult
  971. {
  972. DstContours = dstContours,
  973. DstLesionSize = new WingInterfaceLibrary.DTO.Comment.AIDiagnosisLesionSize
  974. {
  975. HorizontalPoint1 = new WingInterfaceLibrary.DTO.Comment.AIDiagnosisPoint2D { X = dstLesionSize.HorizontalPoint1.X, Y = dstLesionSize.HorizontalPoint1.Y },
  976. HorizontalPoint2 = new WingInterfaceLibrary.DTO.Comment.AIDiagnosisPoint2D { X = dstLesionSize.HorizontalPoint2.X, Y = dstLesionSize.HorizontalPoint2.Y },
  977. VerticalPoint1 = new WingInterfaceLibrary.DTO.Comment.AIDiagnosisPoint2D { X = dstLesionSize.VerticalPoint1.X, Y = dstLesionSize.VerticalPoint1.Y },
  978. VerticalPoint2 = new WingInterfaceLibrary.DTO.Comment.AIDiagnosisPoint2D { X = dstLesionSize.VerticalPoint2.X, Y = dstLesionSize.VerticalPoint2.Y },
  979. HorizontalLengthInPixel = dstLesionSize.HorizontalLengthInPixel,
  980. VerticalLengthInPixel = dstLesionSize.VerticalLengthInPixel,
  981. },
  982. };
  983. return await Task.FromResult(resultData);
  984. }
  985. /// <summary>
  986. /// 获取颈动脉ai报告数据
  987. /// </summary>
  988. /// <param name="request">生成AI报告请求实体</param>
  989. /// <returns></returns>
  990. private async Task<DiagnosisReportResult> GetCarotidAIMeasureResult(DiagnosisReportRequest request)
  991. {
  992. var result = new DiagnosisReportResult()
  993. {
  994. DiagnosisConclusion = DiagnosisConclusionEnum.NoObviousLesion
  995. };
  996. var reportItems = new List<DataItemDTO>();
  997. var orderedExamDatas = request.RemedicalList.OrderByDescending(m => m.CarotidResult?.CarotidScanType).Where(m => m.CarotidResult?.MeasureImageFiles != null && m.CarotidResult.MeasureImageFiles.Count > 0);
  998. if (orderedExamDatas?.Count() > 0)
  999. {
  1000. DiagnosisRemicalDTO leftData = null;
  1001. //left
  1002. var leftExamDatas = orderedExamDatas.Where(m => m.CarotidResult.CarotidScanType == CarotidScanTypeEnum.CarotidLeft);
  1003. if (leftExamDatas.Any())
  1004. {
  1005. foreach (var leftItem in leftExamDatas)
  1006. {
  1007. if (string.IsNullOrEmpty(leftItem.CarotidResult.MeasureResult))
  1008. {
  1009. result.DiagnosisConclusion = DiagnosisConclusionEnum.Unrecognized;
  1010. continue;
  1011. }
  1012. //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}}";
  1013. var measureResult = JsonConvert.DeserializeObject<CarotidAIMeasureResult>(leftItem.CarotidResult.MeasureResult) ?? new CarotidAIMeasureResult();
  1014. if (measureResult.PlaqueResult?.PlaqueCountType != Carotid.Utilities.DetectPlaque.PlaqueCountType.NoPlaque)
  1015. {
  1016. leftData = leftItem;
  1017. result.DiagnosisConclusion = DiagnosisConclusionEnum.Other;
  1018. break;
  1019. }
  1020. }
  1021. if (leftData == null)
  1022. {
  1023. leftData = leftExamDatas.First();
  1024. }
  1025. }
  1026. if (leftData != null)
  1027. {
  1028. result.CarotidResult.Add(leftData);
  1029. var carotidResultInfos = GetCarotidResultInfos(leftData);
  1030. if (carotidResultInfos.Any(x => x.DetectedObjects.Any(x => x.Label == 1)))
  1031. {
  1032. //有斑块
  1033. reportItems.Add(new DataItemDTO { Key = "bd170b6a-e052-42f0-a778-4ec85138c1c6;33c0aaf8-e48e-4474-af68-b47d2378073f;LeftConclusion", Value = FormatterPlaceholder2($"OnePlaque") });
  1034. var info = GetReportDetectedObject(carotidResultInfos, 0.1);
  1035. var reportDetectedObject = info.Item1;
  1036. var maxArea = Math.Round(info.Item2 * 0.1, 2);
  1037. reportItems.Add(new DataItemDTO { Key = "d2449fa2-70e7-45d1-80e3-f69c62652186;98d1e829-b0ee-440d-ae3b-02ff74521357;LeftPlaqueThick", Value = maxArea.ToString() });
  1038. var locationItem = GetDescription(reportDetectedObject, DiagnosisDescriptionEnum.QlaqueLocation);
  1039. if (locationItem != null)
  1040. {
  1041. reportItems.Add(new DataItemDTO { Key = "6f57bb90-6492-421e-ad3e-6c684b7f7452;6e36fa47-edb2-4ed8-8521-ddb9e1fa5536;LeftPlaquePosition", Value = FormatterPlaceholder2($"{locationItem.Value}") });
  1042. }
  1043. var echoItem = GetDescription(reportDetectedObject, DiagnosisDescriptionEnum.QlaqueEchoPattern);
  1044. if (echoItem != null)
  1045. {
  1046. reportItems.Add(new DataItemDTO { Key = "ccef71cd-1c52-4614-a54f-d6d58cb8c586;d440eb6e-2630-4787-8700-486d097ba9f1;LeftPlaqueEcho", Value = FormatterPlaceholder2($"{echoItem.Value}") });
  1047. }
  1048. var rateOfStenosis = GetDescription(reportDetectedObject, DiagnosisDescriptionEnum.CarotidRateOfStenosis);
  1049. if (rateOfStenosis != null)
  1050. {
  1051. var rateOfStenosisValue = string.Empty;
  1052. var rate = rateOfStenosis.Value.ToFloat() * 100;
  1053. if (rate < 30)
  1054. {
  1055. rateOfStenosisValue = "Normal";
  1056. }
  1057. else if (rate >= 30 && rate < 50)
  1058. {
  1059. rateOfStenosisValue = "Mild";
  1060. }
  1061. else if (rate >= 50 && rate < 70)
  1062. {
  1063. rateOfStenosisValue = "Moderate";
  1064. }
  1065. else if (rate >= 70 && rate < 100)
  1066. {
  1067. rateOfStenosisValue = "Severe";
  1068. }
  1069. else
  1070. {
  1071. rateOfStenosisValue = "Blockage";
  1072. }
  1073. reportItems.Add(new DataItemDTO { Key = "e3610e58-79c4-4e18-94c9-8928583b9dab;e6da9399-b849-4017-a3b8-e3212f55c4a4;LeftStenosisRate", Value = FormatterPlaceholder2(rateOfStenosisValue) });
  1074. }
  1075. var innerDiameter = GetDescription(reportDetectedObject, DiagnosisDescriptionEnum.CarotidInnerDiameter);
  1076. if (innerDiameter != null && !string.IsNullOrWhiteSpace(innerDiameter.Value))
  1077. {
  1078. var diagnosisMeasuringLine = JsonConvert.DeserializeObject<AIDiagnosisMeasuringLine>(innerDiameter.Value);
  1079. var measuringLineLengthInPixel = Math.Round(diagnosisMeasuringLine.MeasuringLineLengthInPixel * 0.1, 2);
  1080. reportItems.Add(new DataItemDTO { Key = "9a6f96bb-b81e-407d-bc84-9d9815e96696;fe948295-b065-46b9-ab25-fe80144d7bff;LeftCarotidAD", Value = measuringLineLengthInPixel.ToString() });
  1081. }
  1082. }
  1083. else
  1084. {
  1085. reportItems.Add(new DataItemDTO { Key = "bd170b6a-e052-42f0-a778-4ec85138c1c6;33c0aaf8-e48e-4474-af68-b47d2378073f;LeftConclusion", Value = FormatterPlaceholder2($"NoPlaque2") });
  1086. }
  1087. }
  1088. else
  1089. {
  1090. reportItems.Add(new DataItemDTO { Key = "bd170b6a-e052-42f0-a778-4ec85138c1c6;33c0aaf8-e48e-4474-af68-b47d2378073f;LeftConclusion", Value = FormatterPlaceholder2($"NoPlaque2") });
  1091. }
  1092. //right
  1093. float intimaThickStandard = 1.0f;
  1094. DiagnosisRemicalDTO rightData = null;
  1095. var rightExamDatas = orderedExamDatas.Where(m => m.CarotidResult.CarotidScanType == CarotidScanTypeEnum.CarotidRight);
  1096. if (rightExamDatas.Any())
  1097. {
  1098. foreach (var rightItem in rightExamDatas)
  1099. {
  1100. if (string.IsNullOrEmpty(rightItem.CarotidResult.MeasureResult))
  1101. {
  1102. result.DiagnosisConclusion = DiagnosisConclusionEnum.Unrecognized;
  1103. continue;
  1104. }
  1105. //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}}";
  1106. var measureResult = JsonConvert.DeserializeObject<CarotidAIMeasureResult>(rightItem.CarotidResult.MeasureResult) ?? new CarotidAIMeasureResult();
  1107. if (measureResult.IntimaResult?.PostIntima?.IntimaThick > intimaThickStandard)
  1108. {
  1109. rightData = rightItem;
  1110. result.DiagnosisConclusion = DiagnosisConclusionEnum.Other;
  1111. break;
  1112. }
  1113. }
  1114. if (rightData == null)
  1115. {
  1116. rightData = rightExamDatas.First();
  1117. }
  1118. }
  1119. if (rightData != null)
  1120. {
  1121. result.CarotidResult.Add(rightData);
  1122. var carotidResultInfos = GetCarotidResultInfos(rightData);
  1123. if (carotidResultInfos.Any(x => x.DetectedObjects.Any(x => x.Label == 1)))
  1124. {
  1125. //有斑块
  1126. reportItems.Add(new DataItemDTO { Key = "ec4c775e-601c-48cc-a1a9-f39c8629e3f7;9918912e-b702-4a2b-8b4f-61dcfb846be3;RightConclusion", Value = FormatterPlaceholder2($"OnePlaque") });
  1127. var info = GetReportDetectedObject(carotidResultInfos, 0.1);
  1128. var reportDetectedObject = info.Item1;
  1129. var maxArea = Math.Round(info.Item2 * 0.1, 2);
  1130. reportItems.Add(new DataItemDTO { Key = "8e467ffb-da00-4d34-9aac-34e4e8f91844;46166cb6-ad3a-4ca2-821e-bd872137fa64;RightPlaqueThick", Value = maxArea.ToString() });
  1131. var locationItem = GetDescription(reportDetectedObject, DiagnosisDescriptionEnum.QlaqueLocation);
  1132. if (locationItem != null)
  1133. {
  1134. reportItems.Add(new DataItemDTO { Key = "1153870e-10c8-4674-99bf-108937db1060;053091d2-8ea0-4fcb-a222-00b46f60d12f;RightPlaquePosition", Value = FormatterPlaceholder2($"{locationItem.Value}") });
  1135. }
  1136. var echoItem = GetDescription(reportDetectedObject, DiagnosisDescriptionEnum.QlaqueEchoPattern);
  1137. if (echoItem != null)
  1138. {
  1139. reportItems.Add(new DataItemDTO { Key = "c82e8655-169f-42be-ac99-5e7a56329924;e77f550d-37f3-492a-98dd-a981535383a5;RightPlaqueEcho", Value = FormatterPlaceholder2($"{echoItem.Value}") });
  1140. }
  1141. var rateOfStenosis = GetDescription(reportDetectedObject, DiagnosisDescriptionEnum.CarotidRateOfStenosis);
  1142. if (rateOfStenosis != null)
  1143. {
  1144. var rateOfStenosisValue = string.Empty;
  1145. var rate = rateOfStenosis.Value.ToFloat() * 100;
  1146. if (rate < 30)
  1147. {
  1148. rateOfStenosisValue = "Normal";
  1149. }
  1150. else if (rate >= 30 && rate < 50)
  1151. {
  1152. rateOfStenosisValue = "Mild";
  1153. }
  1154. else if (rate >= 50 && rate < 70)
  1155. {
  1156. rateOfStenosisValue = "Moderate";
  1157. }
  1158. else if (rate >= 70 && rate < 100)
  1159. {
  1160. rateOfStenosisValue = "Severe";
  1161. }
  1162. else
  1163. {
  1164. rateOfStenosisValue = "Blockage";
  1165. }
  1166. reportItems.Add(new DataItemDTO { Key = "b0092e8a-25ba-4690-b70f-e3525e6aaa39;2e742ddc-fa90-47bb-ac24-664238773b30;RightStenosisRate", Value = FormatterPlaceholder2(rateOfStenosisValue) });
  1167. }
  1168. var innerDiameter = GetDescription(reportDetectedObject, DiagnosisDescriptionEnum.CarotidInnerDiameter);
  1169. if (innerDiameter != null && !string.IsNullOrWhiteSpace(innerDiameter.Value))
  1170. {
  1171. var diagnosisMeasuringLine = JsonConvert.DeserializeObject<AIDiagnosisMeasuringLine>(innerDiameter.Value);
  1172. var measuringLineLengthInPixel = Math.Round(diagnosisMeasuringLine.MeasuringLineLengthInPixel * 0.1, 2);
  1173. reportItems.Add(new DataItemDTO { Key = "8c7543e9-95f2-4862-b2aa-4c4cb032948e;9f8e95ee-a262-41c2-9d4b-efbc7974b397;RightCarotidAD", Value = measuringLineLengthInPixel.ToString() });
  1174. }
  1175. }
  1176. else
  1177. {
  1178. reportItems.Add(new DataItemDTO { Key = "ec4c775e-601c-48cc-a1a9-f39c8629e3f7;9918912e-b702-4a2b-8b4f-61dcfb846be3;RightConclusion", Value = FormatterPlaceholder2($"NoPlaque2") });
  1179. }
  1180. }
  1181. else
  1182. {
  1183. reportItems.Add(new DataItemDTO { Key = "ec4c775e-601c-48cc-a1a9-f39c8629e3f7;9918912e-b702-4a2b-8b4f-61dcfb846be3;RightConclusion", Value = FormatterPlaceholder2($"NoPlaque2") });
  1184. }
  1185. Logger.WriteLineInfo($"AIDiagnosisService package carotidAIMeasureResult finished, CarotidLeftRemedicalCode:{leftData?.RemedicalCode}, CarotidLeft:{leftData?.CarotidResult?.MeasureResult}, CarotidRightRemedicalCode:{rightData?.RemedicalCode}, CarotidRight:" + rightData?.CarotidResult?.MeasureResult);
  1186. }
  1187. result.CarotidItems = reportItems;
  1188. return result;
  1189. }
  1190. private string FormatterPlaceholder2(string val)
  1191. {
  1192. return string.Format("#@{0}@#", val);
  1193. }
  1194. private string FormatterPlaceholderArray(string format, string[] pars)
  1195. {
  1196. var parameters = string.Join(',', pars);
  1197. parameters = parameters.Replace("#@", "&@").Replace("@#", "@&");
  1198. return FormatterPlaceholder(format + $"[{parameters}]");
  1199. }
  1200. private string FormatterPlaceholder(string languageKey)
  1201. {
  1202. languageKey = languageKey?.Trim() ?? string.Empty;
  1203. if (!string.IsNullOrWhiteSpace(languageKey))
  1204. {
  1205. return string.Format("#@{0}@#", languageKey);
  1206. }
  1207. return string.Empty;
  1208. }
  1209. private List<WingInterfaceLibrary.DTO.Comment.AIDiagnosisResultPerOrgan> GetCarotidResultInfos(DiagnosisRemicalDTO remedical)
  1210. {
  1211. var diagnosisResult = remedical.DiagnosisResult;
  1212. var carotidResultInfos = new List<WingInterfaceLibrary.DTO.Comment.AIDiagnosisResultPerOrgan>();
  1213. if (diagnosisResult != null && diagnosisResult.Any())
  1214. {
  1215. foreach (var item in diagnosisResult)
  1216. {
  1217. var carotids = item.DiagResultsForEachOrgan?.Where(x => x.Organ == DiagnosisOrganEnum.CarotidArtery);
  1218. if (carotids != null && carotids.Any())
  1219. {
  1220. carotidResultInfos.AddRange(carotids);
  1221. }
  1222. }
  1223. }
  1224. return carotidResultInfos;
  1225. }
  1226. private Tuple<Manage.AIDetectedObject, int, string, double> GetReportDetectedObject(List<Manage.AIDiagnosisResultPerOrgan> carotidResultInfos, double physicalPerPixel)
  1227. {
  1228. Manage.AIDetectedObject detectedObject = null;
  1229. var maxArea = 0;
  1230. var maxAreaString = string.Empty;
  1231. double verticalLengthInPixel = 0;
  1232. double horizontalLengthInPixel = 0;
  1233. foreach (var organInfo in carotidResultInfos)
  1234. {
  1235. var plaqueDetectedObjects = organInfo.DetectedObjects.Where(x => x.Label == 1);
  1236. if (plaqueDetectedObjects == null || !plaqueDetectedObjects.Any())
  1237. {
  1238. continue;
  1239. }
  1240. foreach (var dob in plaqueDetectedObjects)
  1241. {
  1242. var lesionSizeDes = dob.Descriptions?.FirstOrDefault(x => x.Type == DiagnosisDescription.LesionSize);
  1243. if (lesionSizeDes != null && !string.IsNullOrWhiteSpace(lesionSizeDes.Value))
  1244. {
  1245. var diagnosisLesionSize = JsonConvert.DeserializeObject<Manage.AIDiagnosisLesionSize>(lesionSizeDes.Value);
  1246. var currArea = diagnosisLesionSize.VerticalLengthInPixel * diagnosisLesionSize.HorizontalLengthInPixel;
  1247. if (detectedObject == null || currArea > maxArea)
  1248. {
  1249. detectedObject = dob;
  1250. maxArea = currArea;
  1251. verticalLengthInPixel = Math.Round(diagnosisLesionSize.VerticalLengthInPixel * physicalPerPixel, 2);
  1252. horizontalLengthInPixel = Math.Round(diagnosisLesionSize.HorizontalLengthInPixel * physicalPerPixel, 2);
  1253. maxAreaString = $"{horizontalLengthInPixel} × {verticalLengthInPixel}";
  1254. }
  1255. }
  1256. }
  1257. }
  1258. return new Tuple<Manage.AIDetectedObject, int, string, double>(detectedObject, maxArea, maxAreaString, Math.Min(verticalLengthInPixel, horizontalLengthInPixel));
  1259. }
  1260. private Tuple<WingInterfaceLibrary.DTO.Comment.AIDetectedObject, int, string, double> GetReportDetectedObject(List<WingInterfaceLibrary.DTO.Comment.AIDiagnosisResultPerOrgan> carotidResultInfos, double physicalPerPixel)
  1261. {
  1262. WingInterfaceLibrary.DTO.Comment.AIDetectedObject detectedObject = null;
  1263. var maxArea = 0;
  1264. var maxAreaString = string.Empty;
  1265. double verticalLengthInPixel = 0;
  1266. double horizontalLengthInPixel = 0;
  1267. foreach (var organInfo in carotidResultInfos)
  1268. {
  1269. var plaqueDetectedObjects = organInfo.DetectedObjects.Where(x => x.Label == 1);
  1270. if (plaqueDetectedObjects == null || !plaqueDetectedObjects.Any())
  1271. {
  1272. continue;
  1273. }
  1274. foreach (var dob in plaqueDetectedObjects)
  1275. {
  1276. var lesionSizeDes = dob.Descriptions?.FirstOrDefault(x => x.Type == DiagnosisDescriptionEnum.LesionSize);
  1277. if (lesionSizeDes != null && !string.IsNullOrWhiteSpace(lesionSizeDes.Value))
  1278. {
  1279. var diagnosisLesionSize = JsonConvert.DeserializeObject<Manage.AIDiagnosisLesionSize>(lesionSizeDes.Value);
  1280. var currArea = diagnosisLesionSize.VerticalLengthInPixel * diagnosisLesionSize.HorizontalLengthInPixel;
  1281. if (detectedObject == null || currArea > maxArea)
  1282. {
  1283. detectedObject = dob;
  1284. maxArea = currArea;
  1285. verticalLengthInPixel = Math.Round(diagnosisLesionSize.VerticalLengthInPixel * physicalPerPixel, 2);
  1286. horizontalLengthInPixel = Math.Round(diagnosisLesionSize.HorizontalLengthInPixel * physicalPerPixel, 2);
  1287. maxAreaString = $"{horizontalLengthInPixel} × {verticalLengthInPixel}";
  1288. }
  1289. }
  1290. }
  1291. }
  1292. return new Tuple<WingInterfaceLibrary.DTO.Comment.AIDetectedObject, int, string, double>(detectedObject, maxArea, maxAreaString, Math.Min(verticalLengthInPixel, horizontalLengthInPixel));
  1293. }
  1294. private WingInterfaceLibrary.DTO.Comment.AIDiagnosisDescription GetDescription(WingInterfaceLibrary.DTO.Comment.AIDetectedObject detectedObject, DiagnosisDescriptionEnum descriptionType)
  1295. {
  1296. var description = detectedObject?.Descriptions.FirstOrDefault(x => x.Type == descriptionType);
  1297. return description;
  1298. }
  1299. private async Task<BaseDiagnosis> CreateDiagnosisManagerAsync(DiagnosisReportRequest request)
  1300. {
  1301. var recordResults = new List<DiagnosisPerImageModel>();
  1302. foreach (var remedical in request.RemedicalList)
  1303. {
  1304. foreach (var perImage in remedical.DiagnosisResult)
  1305. {
  1306. var localFile = await DownloadAsync(remedical.FileToken);
  1307. if (File.Exists((localFile)))
  1308. {
  1309. var diagnosisPerImage = new DiagnosisPerImageModel();
  1310. diagnosisPerImage.RemedicalCode = remedical.RemedicalCode;
  1311. diagnosisPerImage.DataType = (WingAIDiagnosisService.Manage.DataType)remedical.DataType;
  1312. diagnosisPerImage.LocalVidPath = localFile;
  1313. diagnosisPerImage.Index = perImage.Index;
  1314. diagnosisPerImage.PriorityScore = perImage.PriorityScore;
  1315. var diagnosisResults = Newtonsoft.Json.JsonConvert.DeserializeObject<List<WingAIDiagnosisService.Manage.AIDiagnosisResultPerOrgan>>(Newtonsoft.Json.JsonConvert.SerializeObject(perImage.DiagResultsForEachOrgan));
  1316. diagnosisPerImage.DiagResultsForEachOrgan = diagnosisResults;
  1317. recordResults.Add(diagnosisPerImage);
  1318. }
  1319. }
  1320. }
  1321. switch (request.Organ)
  1322. {
  1323. case DiagnosisOrganEnum.Breast:
  1324. return new BreastDiagnosis(recordResults, request.Organ);
  1325. case DiagnosisOrganEnum.Liver:
  1326. return new LiverDiagnosis(recordResults, request.Organ);
  1327. case DiagnosisOrganEnum.Thyroid://甲状腺
  1328. return new ThyroidDiagnosis(recordResults, request.Organ);
  1329. case DiagnosisOrganEnum.CarotidArtery://颈动脉
  1330. return new CarotidDiagnosis(recordResults, request.Organ);
  1331. default:
  1332. throw new Exception($"not support organ type:{request.Organ.ToString()}");
  1333. }
  1334. }
  1335. private async Task<CarotidResultDTO> CarotidDiagnosis(VinnoImageData imageData)
  1336. {
  1337. var resampleInputData = new ResampleInputData();
  1338. resampleInputData.SurfaceFilePath = DiagnosisHelper.GetCacheFilePath(DiagnosisHelper.SurfaceFileSuffix);
  1339. resampleInputData.MdlFilePath = DiagnosisHelper.GetCacheFilePath(DiagnosisHelper.ModelFileSuffix);
  1340. resampleInputData.MdlZipFilePath = DiagnosisHelper.GetCacheFilePath(DiagnosisHelper.ZipFileSuffix);
  1341. resampleInputData.BaseAIImagePath = DiagnosisHelper.GetCacheFilePath(DiagnosisHelper.JPGFileSuffix);
  1342. resampleInputData.PlaqueAIImagePath = DiagnosisHelper.GetCacheFilePath(DiagnosisHelper.JPGFileSuffix);
  1343. resampleInputData.YShapeAIImagePath = DiagnosisHelper.GetCacheFilePath(DiagnosisHelper.JPGFileSuffix);
  1344. var resampleResult = TryResample(imageData, resampleInputData);
  1345. if (imageData.ImageCount <= DiagnosisHelper.CarotidMinImageCounts)
  1346. {
  1347. return new CarotidResultDTO
  1348. {
  1349. CarotidScanType = (CarotidScanTypeEnum)resampleInputData.ScanType,
  1350. CarotidScanDirection = (CarotidScanDirectionEnum)resampleInputData.CarotidScanDirection,
  1351. };
  1352. }
  1353. return new CarotidResultDTO
  1354. {
  1355. CarotidScanType = (CarotidScanTypeEnum)resampleInputData.ScanType,
  1356. CarotidScanDirection = (CarotidScanDirectionEnum)resampleInputData.CarotidScanDirection,
  1357. };
  1358. if (resampleResult.ResampleErrorCode == ResampleErrorCode.Success)
  1359. {
  1360. var carotidAIImageTokens = new List<CarotidAIImage>();
  1361. if (resampleResult.CarotidAIMeasureResult.IsYImageSuccess)
  1362. {
  1363. var fileUrl = await UploadFileAsync(resampleInputData.YShapeAIImagePath, Path.GetFileName(resampleInputData.YShapeAIImagePath));
  1364. if (!string.IsNullOrWhiteSpace(fileUrl))
  1365. {
  1366. carotidAIImageTokens.Add(new CarotidAIImage() { AIImageToken = fileUrl, AIImageType = CarotidAIImageType.YShape });
  1367. }
  1368. File.Delete(resampleInputData.YShapeAIImagePath);
  1369. }
  1370. if (resampleResult.CarotidAIMeasureResult.IntimaResult.IsSuccess)
  1371. {
  1372. var fileUrl = await UploadFileAsync(resampleInputData.BaseAIImagePath, Path.GetFileName(resampleInputData.BaseAIImagePath));
  1373. if (!string.IsNullOrWhiteSpace(fileUrl))
  1374. {
  1375. carotidAIImageTokens.Add(new CarotidAIImage() { AIImageToken = fileUrl, AIImageType = CarotidAIImageType.Base });
  1376. }
  1377. File.Delete(resampleInputData.BaseAIImagePath);
  1378. }
  1379. if (resampleResult.CarotidAIMeasureResult.PlaqueResult.IsSuccess && resampleResult.CarotidAIMeasureResult.PlaqueResult.PlaqueCountType != WingAIDiagnosisService.Carotid.Utilities.DetectPlaque.PlaqueCountType.NoPlaque)
  1380. {
  1381. var fileUrl = await UploadFileAsync(resampleInputData.PlaqueAIImagePath, Path.GetFileName(resampleInputData.PlaqueAIImagePath));
  1382. if (!string.IsNullOrWhiteSpace(fileUrl))
  1383. {
  1384. carotidAIImageTokens.Add(new CarotidAIImage() { AIImageToken = fileUrl, AIImageType = CarotidAIImageType.Plaque });
  1385. }
  1386. File.Delete(resampleInputData.PlaqueAIImagePath);
  1387. }
  1388. var surfaceImageList = new List<string>();
  1389. long surfaceFileSize = 0;
  1390. var cdnSurfaceFile = "";
  1391. if (!string.IsNullOrWhiteSpace(resampleInputData.SurfaceFilePath) && File.Exists(resampleInputData.SurfaceFilePath))
  1392. {
  1393. surfaceImageList = await SurfaceFile(resampleInputData.SurfaceFilePath);
  1394. var surfaceFileTuple = await UploadFileTupleAsync(resampleInputData.SurfaceFilePath, Path.GetFileName(resampleInputData.SurfaceFilePath));
  1395. surfaceFileSize = surfaceFileTuple.Item2;
  1396. File.Delete(resampleInputData.SurfaceFilePath);
  1397. resampleInputData.SurfaceFilePath = surfaceFileTuple.Item1;
  1398. cdnSurfaceFile = surfaceFileTuple.Item1.ToUrlToken();//ToCDN
  1399. }
  1400. long mdlFileFileSize = 0;
  1401. var cdnMdlFile = "";
  1402. if (!string.IsNullOrWhiteSpace(resampleInputData.MdlZipFilePath) && File.Exists(resampleInputData.MdlZipFilePath))
  1403. {
  1404. var mdlFileTuple = await UploadFileTupleAsync(resampleInputData.MdlZipFilePath, Path.GetFileName(resampleInputData.MdlZipFilePath));
  1405. mdlFileFileSize = mdlFileTuple.Item2;
  1406. File.Delete(resampleInputData.MdlZipFilePath);
  1407. resampleInputData.MdlZipFilePath = mdlFileTuple.Item1;
  1408. cdnMdlFile = mdlFileTuple.Item1.ToUrlToken();//ToCDN
  1409. }
  1410. var result = new CarotidResultDTO
  1411. {
  1412. CarotidScanType = (CarotidScanTypeEnum)resampleInputData.ScanType,
  1413. CarotidScanDirection = (CarotidScanDirectionEnum)resampleInputData.CarotidScanDirection,
  1414. SurfaceFile = resampleInputData.SurfaceFilePath,
  1415. SurfaceFileSize = surfaceFileSize,
  1416. CDNSurfaceFile = cdnSurfaceFile,
  1417. MdlFileSize = mdlFileFileSize,
  1418. MdlFile = resampleInputData.MdlZipFilePath,
  1419. CDNMdlFile = cdnMdlFile,
  1420. MeasureImageFiles = carotidAIImageTokens.Select(x => new MeasureImageFileDTO { ImageType = (CarotidAIImageTypeEnum)x.AIImageType, ImageFile = x.AIImageToken }).ToList(),
  1421. MeasureResult = JsonConvert.SerializeObject(resampleResult.CarotidAIMeasureResult),
  1422. SurfaceImageList = surfaceImageList,
  1423. };
  1424. return result;
  1425. }
  1426. return null;
  1427. }
  1428. private ResampleResult TryResample(VinnoImageData vinnoImageData, ResampleInputData resampleInputData)
  1429. {
  1430. float scanDistance = 7;
  1431. ResampleResult resampleResult = new ResampleResult(ResampleErrorCode.Fail);
  1432. var vinnoExtendedData = VinnoCarotidExtendedData.FromBytes(vinnoImageData.ExtendedData);
  1433. CarotidScanType scanType = CarotidScanType.CarotidLeft;
  1434. CarotidScanDirection direction = CarotidScanDirection.TopToBottom;
  1435. if (vinnoExtendedData != null)
  1436. {
  1437. // Should be 6~9cm normally.
  1438. //scanDistance = vinnoExtendedData.ScanDistance;
  1439. scanType = vinnoExtendedData.CarotidType == CarotidType.Left ? CarotidScanType.CarotidLeft : CarotidScanType.CarotidRight;
  1440. direction = vinnoExtendedData.CarotidDirection == CarotidDirection.BottomToTop ? CarotidScanDirection.BottomToTop
  1441. : CarotidScanDirection.TopToBottom;
  1442. }
  1443. else
  1444. {
  1445. //This is a walkaround : vid's first frame is black image or depth coordinate is not in the right place.
  1446. var middleCount = vinnoImageData.ImageCount / 2;
  1447. var vinnoImage = vinnoImageData.GetImage(middleCount);
  1448. var imageBuffer = vinnoImage.ImageData;
  1449. if (_recognizeCarotidTypeCanBeUse)
  1450. {
  1451. //转成彩色图像
  1452. using var img = new Mat();
  1453. CvInvoke.Imdecode(imageBuffer, ImreadModes.Color, img);
  1454. using var image = img.ToImage<Bgr, byte>();
  1455. //识别左右颈信息
  1456. scanType = _recognizeCarotidType.RecognizeType(image);
  1457. }
  1458. }
  1459. resampleInputData.ScanDistance = scanDistance;
  1460. resampleInputData.ScanType = scanType;
  1461. resampleInputData.CarotidScanDirection = direction;
  1462. resampleResult = ResampleModel.Instance.Resample(vinnoImageData, resampleInputData, _workerLevel);
  1463. return resampleResult;
  1464. }
  1465. /// <summary>
  1466. ///
  1467. /// </summary>
  1468. /// <param name="filePath"></param>
  1469. private async Task<List<string>> SurfaceFile(string filePath)
  1470. {
  1471. var fileUrls = new List<string>();
  1472. try
  1473. {
  1474. using (var stream = new FileStream(filePath, FileMode.Open))
  1475. {
  1476. var reader = new Carotid3DStreamReader(stream);
  1477. var width = reader.ReadInt();
  1478. var height = reader.ReadInt();
  1479. var depth = reader.ReadInt();
  1480. var physicalData = VinnoCarotid3DPhysicalData.FromBytes(reader.ReadBytes());
  1481. var imageCount = reader.ReadInt();
  1482. var fileName = $"{Guid.NewGuid():N}";
  1483. for (var i = 0; i < imageCount; i++)
  1484. {
  1485. var readBytes = reader.ReadBytes();
  1486. if (readBytes.Any())
  1487. {
  1488. using var img = new Mat();
  1489. CvInvoke.Imdecode(readBytes, ImreadModes.Color, img);
  1490. var buf = new Emgu.CV.Util.VectorOfByte();
  1491. using var image = img.ToImage<Gray, byte>();
  1492. CvInvoke.Imencode(".jpg", image, buf, new KeyValuePair<ImwriteFlags, int>(ImwriteFlags.JpegQuality, 80));
  1493. var jpgByte = buf.ToArray();
  1494. var localPath = Path.Combine(_tempFolder, $"{fileName}_{i + 1}.jpg");
  1495. File.WriteAllBytes(localPath, jpgByte);
  1496. var fileUrl = await UploadFileAsync(localPath, Path.GetFileName(localPath));
  1497. fileUrls.Add(fileUrl);
  1498. File.Delete(localPath);
  1499. }
  1500. }
  1501. }
  1502. }
  1503. catch (Exception ex)
  1504. {
  1505. Logger.WriteLineWarn($"SurfaceFile err, ex:{ex}");
  1506. }
  1507. return fileUrls;
  1508. }
  1509. private SKBitmap CreateBitmap(VinnoImage vinnoImage)
  1510. {
  1511. try
  1512. {
  1513. return SKBitmap.Decode(vinnoImage.ImageData);
  1514. }
  1515. catch (Exception ex)
  1516. {
  1517. Logger.WriteLineError($"Create skbitmap by VinnoImage error:{ex}");
  1518. }
  1519. return null;
  1520. }
  1521. private EnumColorType MapTo(SKColorType sKColor)
  1522. {
  1523. switch (sKColor)
  1524. {
  1525. case SKColorType.Rgba8888:
  1526. return EnumColorType.Rgba;
  1527. case SKColorType.Rgb888x:
  1528. return EnumColorType.Rgba;
  1529. case SKColorType.Bgra8888:
  1530. return EnumColorType.Bgra;
  1531. case SKColorType.Gray8:
  1532. return EnumColorType.Gray8;
  1533. }
  1534. throw new Exception($"AIService not support color type:{sKColor}");
  1535. }
  1536. private void AIdiagSystem_NotifyError(object sender, ErrorEventArgs e)
  1537. {
  1538. Logger.WriteLineError("AIdiagSystem_NotifyError:" + e.GetException());
  1539. }
  1540. private void AIdiagSystem_NotifyLog(object sender, LogEventArgs e)
  1541. {
  1542. if (e != null && !string.IsNullOrEmpty(e.Msg))
  1543. {
  1544. switch (e.LogType)
  1545. {
  1546. case EnumLogType.InfoLog:
  1547. Logger.WriteLineInfo($"AIdiagSystem_NotifyLog:{e.Msg}");
  1548. break;
  1549. case EnumLogType.ErrorLog:
  1550. Logger.WriteLineError($"AIdiagSystem_NotifyLog:{e.Msg}");
  1551. break;
  1552. case EnumLogType.WarnLog:
  1553. Logger.WriteLineWarn($"AIdiagSystem_NotifyLog:{e.Msg}");
  1554. break;
  1555. case EnumLogType.FatalLog:
  1556. Logger.WriteLineError($"AIdiagSystem_NotifyLog:{e.Msg}");
  1557. break;
  1558. default:
  1559. Logger.WriteLineInfo(e.Msg);
  1560. break;
  1561. }
  1562. }
  1563. }
  1564. private DiagnosisConclusion GetDiagnosisConclusion<T>(List<T> results) where T : AIDiagnosisPerImageModel
  1565. {
  1566. var diagnosisConclusions = new List<DiagnosisConclusion>();
  1567. foreach (var imageResult in results)
  1568. {
  1569. foreach (var diagnosisResult in imageResult.DiagResultsForEachOrgan)
  1570. {
  1571. var benignLabels = new List<int>();
  1572. var malignantLabels = new List<int>();
  1573. if (diagnosisResult.Organ == DiagnosisOrganEnum.Breast)
  1574. {
  1575. benignLabels = new List<int> { 1, 2, 3 };
  1576. malignantLabels = new List<int> { 4, 5, 6, 7 };
  1577. }
  1578. else if (diagnosisResult.Organ == DiagnosisOrganEnum.Liver)
  1579. {
  1580. benignLabels = new List<int> { 1, 2, 3, 5, 6, 7, 8 };
  1581. malignantLabels = new List<int> { 4 };
  1582. }
  1583. else if (diagnosisResult.Organ == DiagnosisOrganEnum.Thyroid)
  1584. {
  1585. benignLabels = new List<int> { 1, 2, 7 };
  1586. malignantLabels = new List<int> { 3, 4, 5, 6 };
  1587. }
  1588. else if (diagnosisResult.Organ == DiagnosisOrganEnum.CarotidArtery)
  1589. {
  1590. benignLabels = new List<int> { 1 };
  1591. malignantLabels = new List<int> { };
  1592. }
  1593. var labels = diagnosisResult.DetectedObjects.Select(x => x.Label);
  1594. if (labels.Contains(0))
  1595. {
  1596. diagnosisConclusions.Add(DiagnosisConclusion.NoObviousLesion);
  1597. }
  1598. if (labels.Intersect(benignLabels).Any())
  1599. {
  1600. diagnosisConclusions.Add(DiagnosisConclusion.Benign);
  1601. }
  1602. if (labels.Intersect(malignantLabels).Any())
  1603. {
  1604. diagnosisConclusions.Add(DiagnosisConclusion.Malignant);
  1605. }
  1606. }
  1607. }
  1608. var containsBenign = diagnosisConclusions.Contains(DiagnosisConclusion.Benign);
  1609. var containsMalignant = diagnosisConclusions.Contains(DiagnosisConclusion.Malignant);
  1610. var containsNoObviousLesion = diagnosisConclusions.Contains(DiagnosisConclusion.NoObviousLesion);
  1611. if (containsBenign && containsMalignant)
  1612. {
  1613. return DiagnosisConclusion.BenignAndMalignant;
  1614. }
  1615. else if (containsBenign)
  1616. {
  1617. return DiagnosisConclusion.Benign;
  1618. }
  1619. else if (containsMalignant)
  1620. {
  1621. return DiagnosisConclusion.Malignant;
  1622. }
  1623. else if (containsNoObviousLesion)
  1624. {
  1625. return DiagnosisConclusion.NoObviousLesion;
  1626. }
  1627. else
  1628. {
  1629. return DiagnosisConclusion.Unrecognized;
  1630. }
  1631. }
  1632. private List<DiagnosisOrganEnum> GetDiagnosisOrgans(List<AIDiagnosisPerImageModel> results)
  1633. {
  1634. var diagnosisOrgans = new List<DiagnosisOrganEnum>();
  1635. foreach (var imageResult in results)
  1636. {
  1637. foreach (var diagnosisResult in imageResult.DiagResultsForEachOrgan)
  1638. {
  1639. var organName = Enum.GetName(typeof(DiagnosisOrganEnum), (int)diagnosisResult.Organ);
  1640. if (!string.IsNullOrWhiteSpace(organName) && diagnosisResult.Organ != DiagnosisOrganEnum.Null)
  1641. {
  1642. if (diagnosisResult.Organ == DiagnosisOrganEnum.CarotidArtery || diagnosisResult.DetectedObjects?.Any() == true)
  1643. {
  1644. diagnosisOrgans.Add((DiagnosisOrganEnum)diagnosisResult.Organ);
  1645. }
  1646. }
  1647. }
  1648. }
  1649. return diagnosisOrgans.Distinct().ToList();
  1650. }
  1651. private List<DiagnosisOrganEnum> GetDiagnosisOrgans(List<DiagnosisPerImageModel> results)
  1652. {
  1653. var diagnosisOrgans = new List<DiagnosisOrganEnum>();
  1654. foreach (var imageResult in results)
  1655. {
  1656. foreach (var diagnosisResult in imageResult.DiagResultsForEachOrgan)
  1657. {
  1658. var organName = Enum.GetName(typeof(DiagnosisOrganEnum), (int)diagnosisResult.Organ);
  1659. if (!string.IsNullOrWhiteSpace(organName) && diagnosisResult.Organ != DiagnosisOrganEnum.Null && diagnosisResult.DetectedObjects?.Any() == true)
  1660. {
  1661. diagnosisOrgans.Add((DiagnosisOrganEnum)diagnosisResult.Organ);
  1662. }
  1663. }
  1664. }
  1665. return diagnosisOrgans.Distinct().ToList();
  1666. }
  1667. private Dictionary<int, AIDiagResultPerImg> NormalDiagnosis(string relationCode, string fileUrl, VinnoImageData imageData)
  1668. {
  1669. var results = new Dictionary<int, AIDiagResultPerImg>();
  1670. try
  1671. {
  1672. var imageCount = imageData.ImageCount;
  1673. Logger.WriteLineInfo($"AIDiagnosisService diagnosis start, relationCode:{relationCode}, fileUrl:{fileUrl}, imageCount:{imageCount}");
  1674. if (imageCount > 0)
  1675. {
  1676. var diagId = _diagSystem.StartEvalutationOfMultipleImageBatches(imageCount);
  1677. for (var i = 0; i < imageCount; i++)
  1678. {
  1679. var image = imageData.GetImage(i);
  1680. using (var bitmap = CreateBitmap(image))
  1681. {
  1682. var rawImage = new RawImage(bitmap.Bytes, bitmap.Width, bitmap.Height, MapTo(bitmap.ColorType));
  1683. _diagSystem.PushOneBatchOfImagesAsync(diagId, new List<RawImage> { rawImage });
  1684. }
  1685. }
  1686. results = _diagSystem.GetEvaluationsOfPushedMultipleImageBatches(diagId);
  1687. Logger.WriteLineInfo($"AIDiagnosisService diagnosis successfully");
  1688. }
  1689. }
  1690. catch (Exception ex)
  1691. {
  1692. Logger.WriteLineWarn($"AIDiagnosisService NormalDiagnosis err, {ex}");
  1693. }
  1694. return results;
  1695. }
  1696. /// <summary>
  1697. /// 保存AI诊断结果
  1698. /// </summary>
  1699. /// <param name="relationCode"></param>
  1700. /// <param name="fileUrl"></param>
  1701. /// <param name="diagnosisResultInfos"></param>
  1702. /// <returns></returns>
  1703. private async Task<bool> AddDiagnosisResultInfosAsync(string relationCode, string fileUrl, List<AIDiagnosisPerImageModel> diagnosisResultInfos
  1704. , DateTime? createTime = null, DateTime? updateTime = null, bool syncOthers = true)
  1705. {
  1706. try
  1707. {
  1708. var resultInfos = new List<DiagnosisResultDTO>();
  1709. for (var i = 0; i < diagnosisResultInfos.Count; i++)
  1710. {
  1711. resultInfos.Add(new DiagnosisResultDTO
  1712. {
  1713. Index = i,
  1714. DiagnosisResult = Newtonsoft.Json.JsonConvert.SerializeObject(diagnosisResultInfos[i]),
  1715. });
  1716. }
  1717. var addRequest = new AddDiagnosisResultInfosDBRequest
  1718. {
  1719. RelationCode = relationCode,
  1720. FileUrl = fileUrl,
  1721. DiagnosisResultInfos = resultInfos,
  1722. CreateTime = createTime,
  1723. UpdateTime = updateTime,
  1724. SyncOthers = syncOthers,
  1725. };
  1726. if (syncOthers)
  1727. {
  1728. _diagnosisResultDBService.AddDiagnosisResultInfosAsync(addRequest);
  1729. }
  1730. else
  1731. {
  1732. await _diagnosisResultDBService.AddDiagnosisResultInfosAsync(addRequest);
  1733. }
  1734. return true;
  1735. }
  1736. catch (Exception ex)
  1737. {
  1738. Logger.WriteLineWarn($"AIDiagnosisService AddDiagnosisResultInfosAsync err, ex:{ex}");
  1739. return false;
  1740. }
  1741. }
  1742. /// <summary>下载文件</summary>
  1743. /// <param name="fileUrl"></param>
  1744. /// <returns></returns>
  1745. private async Task<string> DownloadAsync(string fileUrl)
  1746. {
  1747. try
  1748. {
  1749. if (string.IsNullOrEmpty(fileUrl))
  1750. {
  1751. return string.Empty;
  1752. }
  1753. if (!Directory.Exists(_tempFolder))
  1754. {
  1755. Directory.CreateDirectory(_tempFolder);
  1756. }
  1757. var fileName = Path.GetFileName(fileUrl);
  1758. var tempFile = Path.Combine(_tempFolder, fileName);
  1759. if (File.Exists(tempFile))
  1760. {
  1761. return tempFile;
  1762. }
  1763. long fileSize = 0;
  1764. using (var request = new HttpRequestMessage())
  1765. {
  1766. request.RequestUri = new Uri(fileUrl);
  1767. request.Method = HttpMethod.Get;
  1768. var response = await _httpClient.SendAsync(request);
  1769. if (response != null && response.StatusCode == HttpStatusCode.OK)
  1770. {
  1771. var contentLength = response.Content.Headers.ContentLength;
  1772. fileSize = contentLength == null ? 0 : contentLength.Value;
  1773. }
  1774. }
  1775. if (fileSize <= 0)
  1776. {
  1777. throw new NotSupportedException($"fileSize is {fileSize}");
  1778. }
  1779. byte[] bytes = await _httpClient.GetByteArrayAsync(fileUrl);
  1780. File.WriteAllBytes(tempFile, bytes);
  1781. return tempFile;
  1782. }
  1783. catch (Exception ex)
  1784. {
  1785. Logger.WriteLineWarn($"DiagnosisService download file err, url: {fileUrl}, {ex}");
  1786. }
  1787. finally
  1788. {
  1789. //Logger.WriteLineInfo($"download file:{fileUrl}");
  1790. }
  1791. return string.Empty;
  1792. }
  1793. /// <summary>
  1794. /// 上传文件
  1795. /// </summary>
  1796. /// <param name="filePath"></param>
  1797. /// <param name="fileName"></param>
  1798. /// <returns></returns>
  1799. private async Task<string> UploadFileAsync(string filePath, string fileName)
  1800. {
  1801. var fileToken = "";
  1802. using (var fileStream = new FileStream(filePath, FileMode.Open))
  1803. {
  1804. var size = fileStream.Length;
  1805. byte[] buffer = new byte[fileStream.Length];
  1806. fileStream.Read(buffer, 0, buffer.Length);
  1807. fileToken = await DoUploadFile(fileName, buffer);
  1808. }
  1809. return fileToken;
  1810. }
  1811. /// <summary>
  1812. /// 上传文件
  1813. /// </summary>
  1814. /// <param name="filePath"></param>
  1815. /// <param name="fileName"></param>
  1816. /// <returns></returns>
  1817. private async Task<Tuple<string, long>> UploadFileTupleAsync(string filePath, string fileName)
  1818. {
  1819. var fileToken = "";
  1820. long size = 0;
  1821. using (var fileStream = new FileStream(filePath, FileMode.Open))
  1822. {
  1823. size = fileStream.Length;
  1824. byte[] buffer = new byte[fileStream.Length];
  1825. fileStream.Read(buffer, 0, buffer.Length);
  1826. fileToken = await DoUploadFile(fileName, buffer);
  1827. }
  1828. return new Tuple<string, long>(fileToken, size);
  1829. }
  1830. /// <summary>
  1831. /// 上传文件
  1832. /// </summary>
  1833. /// <param name="fileName"></param>
  1834. /// <param name="fileData"></param>
  1835. /// <returns></returns>
  1836. async Task<string> DoUploadFile(string fileName, byte[] fileData)
  1837. {
  1838. var requestHeads = new Dictionary<string, string>();
  1839. var defaultToken = await _authenticationService.GetServerDefaultTokenAsync();
  1840. var authorizationRet = await _storageService.GetAuthorizationAsync(
  1841. new FileServiceRequest
  1842. {
  1843. Token = defaultToken,
  1844. FileName = fileName,
  1845. });
  1846. requestHeads.Add("Authorization", authorizationRet.Authorization);
  1847. var fileUrl = authorizationRet.StorageUrl;
  1848. Logger.WriteLineInfo($"DoUploadFile fileUrl:{fileUrl}");
  1849. using (var request = new HttpRequestMessage())
  1850. {
  1851. var fileExtension = Path.GetExtension(fileName);
  1852. var mimeType = FileHelper.GetMimeType(fileExtension);
  1853. var contentType = MediaTypeHeaderValue.Parse(mimeType);
  1854. using (UploadContent content = new UploadContent(fileData, contentType))
  1855. {
  1856. request.RequestUri = new Uri(fileUrl);
  1857. request.Method = HttpMethod.Put;
  1858. request.Content = content;
  1859. foreach (var head in requestHeads)
  1860. {
  1861. request.Headers.TryAddWithoutValidation(head.Key, head.Value);
  1862. }
  1863. var result = await ExecuteRequest(request);
  1864. if (!result)
  1865. {
  1866. throw new Exception("Upload file failed!");
  1867. }
  1868. }
  1869. }
  1870. return fileUrl;
  1871. }
  1872. private UploadFileTypeEnum GetUploadFileType(string fileName)
  1873. {
  1874. if (fileName.EndsWith(".vid"))
  1875. {
  1876. return UploadFileTypeEnum.VID;
  1877. }
  1878. else if (fileName.EndsWith(".jpg"))
  1879. {
  1880. return UploadFileTypeEnum.JPG;
  1881. }
  1882. else
  1883. {
  1884. return UploadFileTypeEnum.MP4;
  1885. }
  1886. }
  1887. /// <summary>
  1888. /// 执行请求
  1889. /// </summary>
  1890. /// <param name="httpRequestMessage"></param>
  1891. /// <typeparam name="T"></typeparam>
  1892. /// <returns></returns>
  1893. public async Task<bool> ExecuteRequest(HttpRequestMessage httpRequestMessage)
  1894. {
  1895. try
  1896. {
  1897. var response = await _httpClient.SendAsync(httpRequestMessage);
  1898. if (response != null && response.StatusCode == HttpStatusCode.OK)
  1899. {
  1900. return true;
  1901. }
  1902. return false;
  1903. }
  1904. catch (Exception ex)
  1905. {
  1906. throw ex;
  1907. }
  1908. }
  1909. private List<WingInterfaceLibrary.DTO.Comment.AIDiagnosisPoint2D> PointToDto(Point2D[] points)
  1910. {
  1911. var dstContours = new List<WingInterfaceLibrary.DTO.Comment.AIDiagnosisPoint2D>();
  1912. foreach (var p in points)
  1913. {
  1914. dstContours.Add(new WingInterfaceLibrary.DTO.Comment.AIDiagnosisPoint2D
  1915. {
  1916. X = p.X,
  1917. Y = p.Y
  1918. });
  1919. }
  1920. return dstContours;
  1921. }
  1922. private List<DiagnosisKeyPointDTO> KeyPointToDto(KeyPointInfo[] points)
  1923. {
  1924. var dstKeys = new List<DiagnosisKeyPointDTO>();
  1925. foreach (var point in points)
  1926. {
  1927. dstKeys.Add(new DiagnosisKeyPointDTO
  1928. {
  1929. Type = (DiagnosisKeyPointType)point.Type,
  1930. IndexInContour = point.IndexInContour,
  1931. Point = new WingInterfaceLibrary.DTO.Comment.AIDiagnosisPoint2D
  1932. {
  1933. X = point.Point.X,
  1934. Y = point.Point.Y,
  1935. },
  1936. });
  1937. }
  1938. return dstKeys;
  1939. }
  1940. /// <summary>
  1941. /// 创建缩略图文件
  1942. /// </summary>
  1943. /// <param name="vinnoImage"></param>
  1944. /// <param name="requestHeight"></param>
  1945. /// <param name="quality"></param>
  1946. /// <param name="fullPath"></param>
  1947. private void CreateThumbnailFile(string filePath, int requestHeight, int quality, string fullPath)
  1948. {
  1949. using (var fileStream = new FileStream(filePath, FileMode.Open))
  1950. {
  1951. var size = fileStream.Length;
  1952. byte[] buffer = new byte[fileStream.Length];
  1953. fileStream.Read(buffer, 0, buffer.Length);
  1954. byte[] compressedData = buffer;
  1955. using (var ms = new MemoryStream(compressedData))
  1956. {
  1957. using (var image = SKBitmap.Decode(ms))
  1958. {
  1959. var ratio = (double)requestHeight / image.Height;
  1960. if (ratio < 1)
  1961. {
  1962. var width = (int)(image.Width * ratio);
  1963. var height = (int)(image.Height * ratio);
  1964. SKBitmap tempImage = image;
  1965. if (image.Width != width || image.Height != height)
  1966. {
  1967. tempImage = image.Resize(new SKImageInfo(width, height), SKFilterQuality.High);
  1968. }
  1969. try
  1970. {
  1971. using (var map = new SKPixmap(new SKImageInfo(tempImage.Width, tempImage.Height, tempImage.ColorType), tempImage.GetPixels()))
  1972. {
  1973. using (var stream = new SKDynamicMemoryWStream())
  1974. {
  1975. SKPixmap.Encode(stream, map, SKEncodedImageFormat.Jpeg, quality);
  1976. compressedData = stream.CopyToData().ToArray();
  1977. }
  1978. }
  1979. }
  1980. catch (Exception ex)
  1981. {
  1982. throw ex;
  1983. }
  1984. finally
  1985. {
  1986. tempImage.Dispose();
  1987. }
  1988. }
  1989. }
  1990. }
  1991. using (var fs = File.Create(fullPath))
  1992. {
  1993. fs.Write(compressedData, 0, compressedData.Length);
  1994. }
  1995. }
  1996. }
  1997. /// <summary>
  1998. /// 是否与当前存储匹配
  1999. /// </summary>
  2000. /// <param name="value"></param>
  2001. /// <returns></returns>
  2002. private bool MatchCurrentStorage(string value)
  2003. {
  2004. if (!string.IsNullOrWhiteSpace(value))
  2005. {
  2006. var cfsServerUrl = EnvironmentConfigs.Storage.CFSServerUrl.Replace("http://", "").Replace("https://", "");
  2007. var cdnServerUrl = EnvironmentConfigs.Storage.CDNServerUrl.Replace("http://", "").Replace("https://", "");
  2008. return value.Contains(cfsServerUrl) || value.Contains(cdnServerUrl);
  2009. }
  2010. return false;
  2011. }
  2012. }
  2013. }