RawVolumeData.cs 55 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128
  1. using System.Collections.Generic;
  2. using System;
  3. using System.IO;
  4. using System.IO.Compression;
  5. using System.Text;
  6. using System.Runtime.InteropServices;
  7. using AI.Common;
  8. using AI.Common.Log;
  9. using AI.Common.Tools;
  10. namespace AI.Reconstruction
  11. {
  12. /// <summary>
  13. /// 原始的体数据
  14. /// </summary>
  15. public class RawVolumeData
  16. {
  17. #region private
  18. private byte[] _dataBuffer = new byte[0];
  19. private readonly object _dataLocker = new object();
  20. private EnumColorType _colorType = EnumColorType.Gray8;
  21. private int _bytesPerPixel = 1;
  22. private int _width = 0;
  23. private int _depth = 0;
  24. private int _imageCount = 0;
  25. private RectF _logicalCoordRegion = RectF.Empty;
  26. private float _physicalWidth = 0;
  27. private float _physicalDepth = 0;
  28. private float _physicalLength = 0;
  29. private float _spacingXY = 0;
  30. private TransducerImgPlaneToWorkCoordMatrix[] _transducerPoses = null;
  31. private EnumVolumeDataScanType _scanType = EnumVolumeDataScanType.NotSpecified;
  32. private bool _isCropped = false;
  33. private bool _isParallel = false;
  34. private bool _isInterpolated = false;
  35. private bool _isUniformCube = false;
  36. private byte[] _extendedData = new byte[0];
  37. private const string SurfaceFileSuffix = "sixface.surface";
  38. private const string ModelFileSuffix = "volume.model";
  39. private const string ZipFileSuffix = "volume.zip";
  40. private const string MdlZipFileVersion = "V1";
  41. #endregion
  42. #region events
  43. /// <summary>
  44. /// 通知订阅者,重建过程中发生了错误
  45. /// </summary>
  46. public event EventHandler<ErrorEventArgs> NotifyError;
  47. /// <summary>
  48. /// 通知订阅者,有log要记
  49. /// </summary>
  50. public event EventHandler<LogEventArgs> NotifyLog;
  51. #endregion
  52. #region property
  53. /// <summary>
  54. /// 是否为一个均一的立方体
  55. /// </summary>
  56. public bool IsUniformCube
  57. {
  58. get => _isUniformCube;
  59. }
  60. /// <summary>
  61. /// 数据区
  62. /// </summary>
  63. public byte[] DataBuffer
  64. {
  65. get => _dataBuffer;
  66. }
  67. /// <summary>
  68. /// 图像是哪种类型的灰度或彩色
  69. /// </summary>
  70. public EnumColorType ColorType
  71. {
  72. get => _colorType;
  73. }
  74. /// <summary>
  75. /// 宽度方向的像素个数
  76. /// </summary>
  77. public int Width
  78. {
  79. get => _width;
  80. }
  81. /// <summary>
  82. /// 深度方向的像素个数
  83. /// </summary>
  84. public int Depth
  85. {
  86. get => _depth;
  87. }
  88. /// <summary>
  89. /// 图像数量
  90. /// </summary>
  91. public int ImageCount
  92. {
  93. get => _imageCount;
  94. }
  95. /// <summary>
  96. /// 物理宽度
  97. /// </summary>
  98. public float PhysicalWidth
  99. {
  100. get => _physicalWidth;
  101. }
  102. /// <summary>
  103. /// 物理深度
  104. /// </summary>
  105. public float PhysicalDepth
  106. {
  107. get => _physicalDepth;
  108. }
  109. /// <summary>
  110. /// 物理长度
  111. /// </summary>
  112. public float PhysicalLength
  113. {
  114. get => _physicalLength;
  115. }
  116. /// <summary>
  117. /// xy方向的像素间隔在实际物理坐标系下的长度
  118. /// </summary>
  119. public float SpacingXY
  120. {
  121. get => _spacingXY;
  122. }
  123. /// <summary>
  124. /// 获取探头成像平面到世界坐标系下的变换矩阵
  125. /// </summary>
  126. public TransducerImgPlaneToWorkCoordMatrix[] TransducerPoses
  127. {
  128. get => _transducerPoses;
  129. }
  130. /// <summary>
  131. /// 获取扫查的类型
  132. /// </summary>
  133. public EnumVolumeDataScanType ScanType
  134. {
  135. get => _scanType;
  136. }
  137. /// <summary>
  138. /// DataBuffer总共所占的有效内存空间
  139. /// </summary>
  140. public int DataBufferByteCounts
  141. {
  142. get => _bytesPerPixel * _width * _depth * _imageCount;
  143. }
  144. #endregion
  145. #region constructor
  146. /// <summary>
  147. /// 创建空的RawVolumeData
  148. /// </summary>
  149. /// <param name="scanType"></param>
  150. /// <param name="colorType"></param>
  151. public RawVolumeData()
  152. {
  153. }
  154. #endregion
  155. #region public
  156. /// <summary>
  157. /// 直接通过一系列VinnoImage的DataBuffer创建(直线扫查,只给physicalLength即可)
  158. /// 注意:VinnoImage里的DataBuffer是未解码的
  159. /// </summary>
  160. /// <param name="vinnoImageDataBuffers"></param>
  161. /// <param name="width"></param>
  162. /// <param name="depth"></param>
  163. /// <param name="logicalCoordRegion"></param>
  164. /// <param name="physicalWidth"></param>
  165. /// <param name="physicalDepth"></param>
  166. /// <param name="physicalLength"></param>
  167. /// <param name="scanType"></param>
  168. /// <param name="extendedData"></param>
  169. public bool ReadFromVinnoImageDatas(List<byte[]> vinnoImageDataBuffers, int width, int depth, RectF logicalCoordRegion,
  170. float physicalWidth, float physicalDepth, float physicalLength,
  171. EnumVolumeDataScanType scanType = EnumVolumeDataScanType.NotSpecified,byte[] extendedData = null)
  172. {
  173. try
  174. {
  175. _scanType = scanType;
  176. _width = width;
  177. _depth = depth;
  178. if (_width == 0 || _depth == 0)
  179. {
  180. throw new Exception("unexpected image size( " + _width + " * " + _depth + " ).");
  181. }
  182. if (vinnoImageDataBuffers != null)
  183. {
  184. _imageCount = vinnoImageDataBuffers.Count;
  185. }
  186. if (_imageCount < 3)
  187. {
  188. throw new Exception("too few ( " + _imageCount + " ) images for a volumeData.");
  189. }
  190. _logicalCoordRegion = logicalCoordRegion;
  191. _physicalWidth = physicalWidth;
  192. _physicalDepth = physicalDepth;
  193. _physicalLength = physicalLength;
  194. if (extendedData == null)
  195. {
  196. _extendedData = new byte[0];
  197. }
  198. else
  199. {
  200. _extendedData = extendedData;
  201. }
  202. // 生成扫查位置
  203. _transducerPoses = GenTransducerPosesForStraightVolumeDatas(logicalCoordRegion, physicalLength, _imageCount);
  204. _spacingXY = _logicalCoordRegion.Width / _width;
  205. // 判断是否为均一立方体
  206. _isCropped = IsCropped(logicalCoordRegion, physicalWidth, physicalDepth);
  207. _isParallel = IsParallelAndEquallySpaced(_transducerPoses);
  208. _isInterpolated = IsInterpolated(_transducerPoses, _spacingXY);
  209. _isUniformCube = _isCropped && _isParallel && _isInterpolated;
  210. // 将Vinno图像从Jpg压缩后的byte[]解析成不压缩的纯byte数组
  211. lock (_dataLocker)
  212. {
  213. DecodeVinnoImageDataBuffers(vinnoImageDataBuffers, width, depth, _bytesPerPixel, ref _dataBuffer);
  214. }
  215. // 由于在DecodeVinnoImageDataBuffers中会固定将图像读成Gray8的,因此这里的_colorType总是Gray8
  216. _colorType = EnumColorType.Gray8;
  217. _bytesPerPixel = RawImage.GetBytesPerPixel(_colorType);
  218. return true;
  219. }
  220. catch (Exception excep)
  221. {
  222. NotifyLog?.Invoke(this, new LogEventArgs(EnumLogType.ErrorLog, "Failed at ReadFromVinnoImageDatas:" + excep.Message));
  223. NotifyError?.Invoke(this, new ErrorEventArgs(excep));
  224. return false;
  225. }
  226. }
  227. /// <summary>
  228. /// 直接通过一系列VinnoImage的DataBuffer创建(任意扫查,需要给出每张图片采集时的探头坐标)
  229. /// 注意:VinnoImage里的DataBuffer是未解码的
  230. /// </summary>
  231. /// <param name="vinnoImageDataBuffers"></param>
  232. /// <param name="width"></param>
  233. /// <param name="depth"></param>
  234. /// <param name="logicalCoordRegion"></param>
  235. /// <param name="physicalWidth"></param>
  236. /// <param name="physicalDepth"></param>
  237. /// <param name="scanPositions"></param>
  238. /// <param name="scanType"></param>
  239. /// <param name="extendedData"></param>
  240. /// <param name="colorType"></param>
  241. public bool ReadFromVinnoImageDatas(List<byte[]> vinnoImageDataBuffers, int width, int depth, RectF logicalCoordRegion,
  242. float physicalWidth, float physicalDepth, Point3DF[] scanPositions, EnumVolumeDataScanType scanType = EnumVolumeDataScanType.NotSpecified,
  243. byte[] extendedData = null)
  244. {
  245. try
  246. {
  247. return true;
  248. }
  249. catch (Exception excep)
  250. {
  251. NotifyLog?.Invoke(this, new LogEventArgs(EnumLogType.ErrorLog, "Failed at ReadFromVinnoImageDatas:" + excep.Message));
  252. NotifyError?.Invoke(this, new ErrorEventArgs(excep));
  253. return false;
  254. }
  255. }
  256. /// <summary>
  257. /// 直接通过一系列RawImage图像创建(直线扫查,只给physicalLength即可)
  258. /// 注意:RawImage里的DataBuffer是解码后的,每个byte由一个一个像素顺序排列而来
  259. /// </summary>
  260. /// <param name="rawImages"></param>
  261. /// <param name="logicalCoordRegion"></param>
  262. /// <param name="physicalWidth"></param>
  263. /// <param name="physicalDepth"></param>
  264. /// <param name="physicalLength"></param>
  265. /// <param name="scanType"></param>
  266. /// <param name="extendedData"></param>
  267. public bool ReadFromRawImages(List<RawImage> rawImages, RectF logicalCoordRegion,
  268. float physicalWidth, float physicalDepth, float physicalLength,EnumVolumeDataScanType scanType = EnumVolumeDataScanType.NotSpecified,
  269. byte[] extendedData = null)
  270. {
  271. try
  272. {
  273. _scanType = scanType;
  274. if (rawImages != null)
  275. {
  276. _imageCount = rawImages.Count;
  277. }
  278. if (_imageCount < 3)
  279. {
  280. throw new Exception("too few ( " + _imageCount + " ) images for a volumeData.");
  281. }
  282. _width = rawImages[0].Width;
  283. _depth = rawImages[0].Height;
  284. if (_width == 0 || _depth == 0)
  285. {
  286. throw new Exception("unexpected image size( " + _width + " * " + _depth + " ).");
  287. }
  288. _logicalCoordRegion = logicalCoordRegion;
  289. _physicalWidth = physicalWidth;
  290. _physicalDepth = physicalDepth;
  291. _physicalLength = physicalLength;
  292. if (extendedData == null)
  293. {
  294. _extendedData = new byte[0];
  295. }
  296. else
  297. {
  298. _extendedData = extendedData;
  299. }
  300. // 生成扫查位置
  301. _transducerPoses = GenTransducerPosesForStraightVolumeDatas(logicalCoordRegion, physicalLength, _imageCount);
  302. _spacingXY = _logicalCoordRegion.Width / _width;
  303. // 判断是否为均一立方体
  304. _isCropped = IsCropped(logicalCoordRegion, physicalWidth, physicalDepth);
  305. _isParallel = IsParallelAndEquallySpaced(_transducerPoses);
  306. _isInterpolated = IsInterpolated(_transducerPoses, _spacingXY);
  307. _isUniformCube = _isCropped && _isParallel && _isInterpolated;
  308. // 将Vinno图像从Jpg压缩后的byte[]解析成不压缩的纯byte数组
  309. lock (_dataLocker)
  310. {
  311. int imgDataByteCounts = _width * _depth;
  312. Array.Resize(ref _dataBuffer, _imageCount * imgDataByteCounts);
  313. for (int ni = 0; ni < _imageCount; ni++)
  314. {
  315. var image = rawImages[ni].Clone(EnumColorType.Gray8);
  316. Array.Copy(image.DataBuffer, 0, _dataBuffer, ni * imgDataByteCounts, imgDataByteCounts);
  317. }
  318. }
  319. // 由于在DecodeVinnoImageDataBuffers中会固定将图像读成Gray8的,因此这里的_colorType总是Gray8
  320. _colorType = EnumColorType.Gray8;
  321. _bytesPerPixel = RawImage.GetBytesPerPixel(_colorType);
  322. return true;
  323. }
  324. catch (Exception excep)
  325. {
  326. NotifyLog?.Invoke(this, new LogEventArgs(EnumLogType.ErrorLog, "Failed at ReadFromRawImages:" + excep.Message));
  327. NotifyError?.Invoke(this, new ErrorEventArgs(excep));
  328. return false;
  329. }
  330. }
  331. /// <summary>
  332. /// 直接通过一系列RawImage图像创建(任意扫查,需要给出每张图片采集时的探头坐标)
  333. /// 注意:RawImage里的DataBuffer是解码后的,每个byte由一个一个像素顺序排列而来
  334. /// </summary>
  335. /// <param name="rawImages"></param>
  336. /// <param name="logicalCoordRegion"></param>
  337. /// <param name="physicalWidth"></param>
  338. /// <param name="physicalDepth"></param>
  339. /// <param name="scanPositions"></param>
  340. /// <param name="scanType"></param>
  341. /// <param name="extendedData"></param>
  342. public bool ReadFromRawImages(List<RawImage> rawImages, RectF logicalCoordRegion,
  343. float physicalWidth, float physicalDepth, Point3DF[] scanPositions,byte[] extendedData = null)
  344. {
  345. try
  346. {
  347. return true;
  348. }
  349. catch (Exception excep)
  350. {
  351. NotifyLog?.Invoke(this, new LogEventArgs(EnumLogType.ErrorLog, "Failed at ReadFromRawImages:" + excep.Message));
  352. NotifyError?.Invoke(this, new ErrorEventArgs(excep));
  353. return false;
  354. }
  355. }
  356. /// <summary>
  357. /// 从.model和.surface文件里重建出体数据
  358. /// </summary>
  359. /// <param name="modelFolder"></param>
  360. public bool ReadFromFile(string modelPath, string surfacePath)
  361. {
  362. try
  363. {
  364. // 读出surface里的关键信息
  365. if (!File.Exists(surfacePath))
  366. {
  367. throw new ArgumentException("Failed to load surface file:" + surfacePath);
  368. }
  369. using (var stream = new FileStream(surfacePath, FileMode.Open))
  370. {
  371. var reader = new AI.Common.Tools.AIStreamReader(stream);
  372. // 读入模型的长宽高
  373. _width = reader.ReadInt();
  374. _depth = reader.ReadInt();
  375. _imageCount = reader.ReadInt();
  376. // 读入extendedData
  377. var length = reader.ReadInt();
  378. _extendedData = reader.ReadBytes(length);
  379. // 读入图像张数
  380. var surfaceCount = reader.ReadInt();
  381. // 分别读入图像
  382. for (int ni = 0; ni < surfaceCount; ni++)
  383. {
  384. int dataSize = reader.ReadInt();
  385. var imageDataEncoded = reader.ReadBytes(dataSize);
  386. // ToDo 读进来的surface数据拿来干嘛?是否有必要单独存储?
  387. }
  388. // 读入bool值以便确认是否为RawVolumeData写入到surface的
  389. bool isRawVolumeVersion = reader.ReadBool();
  390. if (isRawVolumeVersion)
  391. {
  392. // 读入_logicalCoordRegion信息
  393. float rectLeft = reader.ReadFloat();
  394. float rectTop = reader.ReadFloat();
  395. float rectWidth = reader.ReadFloat();
  396. float rectHeight = reader.ReadFloat();
  397. _logicalCoordRegion = new RectF(rectLeft, rectTop, rectWidth, rectHeight);
  398. // 读入_physicalWidth、_physicalDepth、_physicalLength
  399. _physicalWidth = reader.ReadFloat();
  400. _physicalDepth = reader.ReadFloat();
  401. _physicalLength = reader.ReadFloat();
  402. // 读入_scanType
  403. var scanType = reader.ReadInt();
  404. _scanType = (EnumVolumeDataScanType)scanType;
  405. }
  406. else
  407. {
  408. // 按照旧版本颈动脉3D里定义的格式读入相关信息
  409. using (var sm = new MemoryStream(_extendedData))
  410. {
  411. sm.Position = 0;
  412. var rd = new AIStreamReader(sm);
  413. var carotidType = rd.ReadByte();
  414. if (carotidType == 0)
  415. {
  416. _scanType = EnumVolumeDataScanType.CarotidLeftStraightScan;
  417. }
  418. if (carotidType == 1)
  419. {
  420. _scanType = EnumVolumeDataScanType.CarotidRightStraightScan;
  421. }
  422. var probe = rd.ReadBytes();
  423. // 注:从probe中可以解析出探头名,探头类型,应用名,帧率
  424. // 因为貌似不需要用,所以未做进一步解析
  425. var visualCount = rd.ReadByte();
  426. for (int ni = 0; ni < visualCount; ni++)
  427. {
  428. var visual = rd.ReadBytes();
  429. // 从visual中解析出扫查的物理坐标信息
  430. using (var smVisual = new MemoryStream(visual))
  431. {
  432. smVisual.Position = 0;
  433. var rdVisual = new AIStreamReader(smVisual);
  434. var visualType = rdVisual.ReadByte();
  435. if (visualType == 0)
  436. {
  437. // visualType = 0 表示VinnoVisualType.V2D,为1表示VinnoVisualType.V3D
  438. var displayMode = rdVisual.ReadByte();
  439. var indicator = rdVisual.ReadByte();
  440. var activeModeType = rdVisual.ReadByte();
  441. var modeCount = rdVisual.ReadByte();
  442. for (int nj = 0; nj < modeCount; nj++)
  443. {
  444. var mode = rdVisual.ReadBytes();
  445. // 注:从mode中可以解析出显示的是B模式还是多普勒等信息
  446. // 因为貌似不需要用,所以未做进一步的解析
  447. }
  448. var physicalCoordCount = rdVisual.ReadByte();
  449. for (int nj = 0; nj < physicalCoordCount; nj++)
  450. {
  451. var visualAreaType = rdVisual.ReadByte();
  452. var physicalCoord = rdVisual.ReadBytes();
  453. if (visualAreaType == 0)
  454. {
  455. // visualAreaType=0 表示为Tissue
  456. using (var smPhysical = new MemoryStream(physicalCoord))
  457. {
  458. smPhysical.Position = 0;
  459. var rdPhysical = new AIStreamReader(smPhysical);
  460. var type = rdPhysical.ReadByte();
  461. if (type == 3)
  462. {
  463. // type=3 表示为LinearTissue
  464. var depthStart = rdPhysical.ReadDouble();
  465. var depthEnd = rdPhysical.ReadDouble();
  466. var width = rdPhysical.ReadDouble();
  467. var beamPosition = rdPhysical.ReadDouble();
  468. var steer = rdPhysical.ReadDouble();
  469. _physicalDepth = (float)(depthEnd - depthStart);
  470. _physicalWidth = (float)(width);
  471. }
  472. }
  473. }
  474. }
  475. var logicalCoordCount = rdVisual.ReadByte();
  476. for (int nj = 0; nj < logicalCoordCount; nj++)
  477. {
  478. var visualAreaType = rdVisual.ReadByte();
  479. var logicalCoord = rdVisual.ReadBytes();
  480. if (visualAreaType == 0)
  481. {
  482. // visualAreaType=0 表示为Tissue
  483. using (var smLogical = new MemoryStream(logicalCoord))
  484. {
  485. smLogical.Position = 0;
  486. var rdLogical = new AIStreamReader(smLogical);
  487. var isFlipHorizontal = rdLogical.ReadBool();
  488. var isFlipVertical = rdLogical.ReadBool();
  489. var xUnit = rdLogical.ReadByte();
  490. var yUnit = rdLogical.ReadByte();
  491. var left = rdLogical.ReadDouble();
  492. var top = rdLogical.ReadDouble();
  493. var right = rdLogical.ReadDouble();
  494. var bottom = rdLogical.ReadDouble();
  495. _logicalCoordRegion = new RectF((float)left, (float)top, (float)(right - left), (float)(bottom - top));
  496. }
  497. }
  498. }
  499. }
  500. }
  501. }
  502. }
  503. // 从_extendedData中无法读出扫查的距离,默认为7cm
  504. _physicalLength = 7;
  505. }
  506. }
  507. // 生成扫查信息
  508. _transducerPoses = GenTransducerPosesForStraightVolumeDatas(_logicalCoordRegion, _physicalLength, _imageCount);
  509. _spacingXY = _logicalCoordRegion.Width / _width;
  510. // 判断是否为均一立方体
  511. _isCropped = IsCropped(_logicalCoordRegion, _physicalWidth, _physicalDepth);
  512. _isParallel = IsParallelAndEquallySpaced(_transducerPoses);
  513. _isInterpolated = IsInterpolated(_transducerPoses, _spacingXY);
  514. _isUniformCube = _isCropped && _isParallel && _isInterpolated;
  515. // 读出model里的关键信息
  516. if (!File.Exists(modelPath))
  517. {
  518. throw new ArgumentException("Failed to load model file:" + modelPath);
  519. }
  520. using (var stream = new FileStream(modelPath, FileMode.Open))
  521. {
  522. var reader = new AI.Common.Tools.AIStreamReader(stream);
  523. int imageCount = reader.ReadInt();
  524. int imgDataByteCounts = _width * _depth * _bytesPerPixel;
  525. byte[] decodedDataBuffer = new byte[imgDataByteCounts];
  526. Array.Resize(ref _dataBuffer, imgDataByteCounts * ImageCount);
  527. for (int ni = 0; ni < imageCount; ni++)
  528. {
  529. int encodedDataSize = reader.ReadInt();
  530. var encodedDataBuffer = reader.ReadBytes(encodedDataSize);
  531. if (!AIReconstructor.ImDataDecode(encodedDataBuffer, encodedDataBuffer.Length,
  532. decodedDataBuffer, decodedDataBuffer.Length))
  533. {
  534. int errorMaxLen = 256;
  535. StringBuilder errorMsg = new StringBuilder(errorMaxLen);
  536. AIReconstructor.EnumCppCoreErrorCode errorCode = AIReconstructor.EnumCppCoreErrorCode.None;
  537. AIReconstructor.GetErrorCodeAndMsg(ref errorCode, errorMsg, errorMaxLen);
  538. throw new Exception("Failed at decoding model image datas, error code: "
  539. + errorCode.ToString() + " , details: " + errorMsg.ToString() + " .");
  540. }
  541. Array.Copy(decodedDataBuffer, 0, _dataBuffer, ni * imgDataByteCounts, imgDataByteCounts);
  542. }
  543. }
  544. return true;
  545. }
  546. catch (Exception excep)
  547. {
  548. NotifyLog?.Invoke(this, new LogEventArgs(EnumLogType.ErrorLog, "Failed at ReadFromFile:" + excep.Message));
  549. NotifyError?.Invoke(this, new ErrorEventArgs(excep));
  550. return false;
  551. }
  552. }
  553. /// <summary>
  554. /// 将体数据保存成.model 和 .surface
  555. /// </summary>
  556. /// <param name="modelFolder"></param>
  557. public bool SaveToFile(string modelFolder)
  558. {
  559. if (!_isUniformCube)
  560. {
  561. if (!TurnToDesiredUniformCube(_spacingXY))
  562. {
  563. return false;
  564. }
  565. }
  566. // 保存模型(内部会再存一份压缩后的模型)
  567. if (!SaveModel(modelFolder))
  568. {
  569. return false;
  570. }
  571. // 保存模型表面数据
  572. if (!SaveSurface(modelFolder))
  573. {
  574. return false;
  575. }
  576. NotifyLog?.Invoke(this, new LogEventArgs(EnumLogType.InfoLog, "RawVolumeData saved into " +modelFolder+"."));
  577. return true;
  578. }
  579. /// <summary>
  580. /// 将原始数据裁切,插值,resize成一个均一立方体
  581. /// </summary>
  582. /// <returns></returns>
  583. public bool TurnToDesiredUniformCube()
  584. {
  585. return TurnToDesiredUniformCube(_spacingXY);
  586. }
  587. /// <summary>
  588. /// 将原始数据裁切,插值,resize成一个均一立方体
  589. /// </summary>
  590. /// <param name="expectedSpacing"></param>
  591. public bool TurnToDesiredUniformCube(float expectedSpacing)
  592. {
  593. try
  594. {
  595. if (_isUniformCube)
  596. {
  597. NotifyLog?.Invoke(this, new LogEventArgs(EnumLogType.InfoLog,
  598. "the volume data is uniform cube already."));
  599. return true;
  600. }
  601. // 希望的spacing不能太小,太小网格太密集,计算量大
  602. if (MathHelper.AlmostEqual(expectedSpacing, 0))
  603. {
  604. throw new ArgumentException("expected spacing is too small.");
  605. }
  606. // 计算每幅图上的裁切区域(即,超声成像区域)
  607. Rect cropRect = new Rect(0, 0, _width, _depth);
  608. if (!_isCropped)
  609. {
  610. // 目前碰到的超声图像都是_physicalDepth等于_logicalCoordRegion.Height的
  611. // (因为一般情况下,认为探头靠紧皮肤,深度方向起始位置即是0)
  612. // 即:认为深度方向上不用裁切,如果不等于,需要抛出异常,具体去看这种图像应该怎么裁
  613. if (!MathHelper.AlmostEqual(_physicalDepth, _logicalCoordRegion.Height))
  614. {
  615. throw new ArgumentException("_physicalDepth and _logicalCoordRegion.Height unequal.");
  616. }
  617. // 计算需要裁切多大范围的图像
  618. int cropWidth = (int)(_physicalWidth / _spacingXY);
  619. int cropHeight = (int)(_physicalDepth / _spacingXY);
  620. // 要裁切的范围不能超过原图尺寸
  621. if (cropWidth > _width)
  622. {
  623. cropWidth = _width;
  624. }
  625. if (cropHeight > _depth)
  626. {
  627. cropHeight = _depth;
  628. }
  629. // 一般认为图像是左右居中,上下靠上摆放的
  630. int cropLeft = (_width - cropWidth) / 2;
  631. int cropTop = 0;
  632. cropRect = new Rect(cropLeft, cropTop, cropWidth, cropHeight);
  633. }
  634. // 根据_spacingXY 和 expectedSpacing 计算xy需要的尺寸
  635. float ratioXY = _spacingXY / expectedSpacing;
  636. int desiredWidth = (int)(cropRect.Width * ratioXY);
  637. int desiredHeight = (int)(cropRect.Height * ratioXY);
  638. if (_isParallel)
  639. {
  640. int desiredImgCount = (int)(_physicalLength / expectedSpacing);
  641. IntPtr dataPointer = Marshal.UnsafeAddrOfPinnedArrayElement(_dataBuffer, 0);
  642. AIReconstructor.StructVolumeDataPreProcessorInfo dataInfo = new AIReconstructor.StructVolumeDataPreProcessorInfo(_width, _depth, _imageCount,
  643. _colorType, cropRect,desiredWidth, desiredHeight, desiredImgCount, dataPointer);
  644. byte[] uniformCubeBuffer = new byte[desiredWidth * desiredHeight * desiredImgCount * _bytesPerPixel];
  645. if (!AIReconstructor.StraightScanDataToUniformCube(dataInfo,uniformCubeBuffer))
  646. {
  647. int errorMaxLen = 256;
  648. StringBuilder errorMsg = new StringBuilder(errorMaxLen);
  649. AIReconstructor.EnumCppCoreErrorCode errorCode = AIReconstructor.EnumCppCoreErrorCode.None;
  650. AIReconstructor.GetErrorCodeAndMsg(ref errorCode, errorMsg, errorMaxLen);
  651. throw new Exception("Failed at StraightScanDataToUniformCube, error code: "
  652. + errorCode.ToString() + " , details: " + errorMsg.ToString() + " .");
  653. }
  654. // 将得到的均一立方体替换当前裸数据的信息
  655. lock (_dataLocker)
  656. {
  657. _dataBuffer = uniformCubeBuffer;
  658. }
  659. _width = desiredWidth;
  660. _depth = desiredHeight;
  661. _imageCount = desiredImgCount;
  662. _logicalCoordRegion = new RectF(0, 0, _physicalWidth, _physicalDepth);
  663. _spacingXY = expectedSpacing;
  664. _transducerPoses = GenTransducerPosesForStraightVolumeDatas(_logicalCoordRegion, _physicalLength, _imageCount);
  665. _isCropped = true;
  666. _isParallel = true;
  667. _isInterpolated = true;
  668. _isUniformCube = true;
  669. }
  670. else
  671. {
  672. // ToDO 不平行的怎么计算,还没有实现
  673. }
  674. return true;
  675. }
  676. catch (Exception excep)
  677. {
  678. NotifyLog?.Invoke(this, new LogEventArgs(EnumLogType.ErrorLog, "Failed at TurnToDesiredUniformCube:"+excep.Message));
  679. NotifyError?.Invoke(this, new ErrorEventArgs(excep));
  680. return false;
  681. }
  682. }
  683. /// <summary>
  684. /// 根据融合结果更新相关信息
  685. /// </summary>
  686. /// <param name="fusedResult"></param>
  687. /// <returns></returns>
  688. internal bool ReadFromFusedResult(AIReconstructor.StructUniformVolumeDataInfo fusedResult)
  689. {
  690. _colorType = fusedResult.ColorType;
  691. _bytesPerPixel = RawImage.GetBytesPerPixel(_colorType);
  692. _width = fusedResult.X;
  693. _depth = fusedResult.Y;
  694. _imageCount = fusedResult.Z;
  695. _spacingXY = fusedResult.Spacing;
  696. _physicalWidth = _spacingXY * _width;
  697. _physicalDepth = _spacingXY * _depth;
  698. _physicalLength = _spacingXY * _imageCount;
  699. _logicalCoordRegion = new RectF(0, 0, _physicalWidth, _physicalDepth);
  700. _scanType = EnumVolumeDataScanType.ComputerReconstructed;
  701. _transducerPoses = GenTransducerPosesForStraightVolumeDatas(_logicalCoordRegion, _physicalLength, _imageCount);
  702. _isCropped = true;
  703. _isParallel = true;
  704. _isInterpolated = true;
  705. _isUniformCube = true;
  706. // ToDo 融合后得到的数据,暂未给extendedData赋值
  707. _extendedData = new byte[0];
  708. // 复制所需长度的数据
  709. int byteCounts =_width * _depth * _imageCount * _bytesPerPixel;
  710. Array.Resize(ref _dataBuffer, byteCounts);
  711. Marshal.Copy(fusedResult.DataPointer, _dataBuffer, 0, byteCounts);
  712. return true;
  713. }
  714. #endregion
  715. #region private
  716. /// <summary>
  717. /// 判断当前体数据是否已经裁切
  718. /// </summary>
  719. /// <param name="logicalCoordRegion"></param>
  720. /// <param name="physicalWidth"></param>
  721. /// <param name="physicalDepth"></param>
  722. /// <returns></returns>
  723. private bool IsCropped(RectF logicalCoordRegion, float physicalWidth, float physicalDepth)
  724. {
  725. // 判断是否已裁切
  726. return MathHelper.AlmostEqual(logicalCoordRegion.Width, physicalWidth) &&
  727. MathHelper.AlmostEqual(logicalCoordRegion.Height, physicalDepth);
  728. }
  729. /// <summary>
  730. /// 判断当前体数据的每一帧在扫查时是否平行且等间距
  731. /// </summary>
  732. /// <param name="transducerPoses"></param>
  733. /// <param name="spacing"></param>
  734. /// <returns></returns>
  735. private bool IsParallelAndEquallySpaced(TransducerImgPlaneToWorkCoordMatrix[] transducerPoses)
  736. {
  737. // 图像太少不能计算是否等间距
  738. int imgCount = transducerPoses.Length;
  739. if (imgCount < 2)
  740. {
  741. NotifyLog?.Invoke(this, new LogEventArgs(EnumLogType.WarnLog,
  742. "Need at least 2 images for the calculation of the spacing of transducer image planes."));
  743. return false;
  744. }
  745. var startPose = transducerPoses[0];
  746. var endPose = transducerPoses[imgCount - 1];
  747. var origin = new Point3DF(0, 0, 0);
  748. var startOrigin = startPose.Transform(origin);
  749. var endOrigin = endPose.Transform(origin);
  750. var spacingZ = Point3DF.Distance(startOrigin, endOrigin) / (imgCount - 1);
  751. // 一个个pose判断Z方向是否平行且等间距
  752. for (int ni = 1; ni < imgCount - 1; ni++)
  753. {
  754. var curPose = transducerPoses[ni];
  755. if (!curPose.ImagePlaneParallels(startPose, endPose))
  756. {
  757. return false;
  758. }
  759. var prePose = transducerPoses[ni - 1];
  760. var preOrigin = prePose.Transform(origin);
  761. var curOrigin = curPose.Transform(origin);
  762. var distance = Point3DF.Distance(curOrigin, preOrigin);
  763. if (!MathHelper.AlmostEqual(spacingZ, distance))
  764. {
  765. return false;
  766. }
  767. }
  768. return true;
  769. }
  770. /// <summary>
  771. /// 判断当前体数据是否已插值(xy方向的间距是否和z方向一致)
  772. /// </summary>
  773. /// <param name="transducerPoses"></param>
  774. /// <param name="spacingXY"></param>
  775. /// <returns></returns>
  776. private bool IsInterpolated(TransducerImgPlaneToWorkCoordMatrix[] transducerPoses,float spacingXY)
  777. {
  778. // 图像太少不能计算是否等间距
  779. int imgCount = transducerPoses.Length;
  780. if (imgCount < 2)
  781. {
  782. NotifyLog?.Invoke(this, new LogEventArgs(EnumLogType.WarnLog,
  783. "Need at least 2 images for the calculation of the spacing of transducer image planes."));
  784. return false;
  785. }
  786. // 判断Z方向的间距和XY方向是否相同
  787. var startPose = transducerPoses[0];
  788. var endPose = transducerPoses[imgCount - 1];
  789. var origin = new Point3DF(0, 0, 0);
  790. var startOrigin = startPose.Transform(origin);
  791. var endOrigin = endPose.Transform(origin);
  792. var spacingZ = Point3DF.Distance(startOrigin, endOrigin) / (imgCount -1);
  793. if (!MathHelper.AlmostEqual(spacingXY, spacingZ))
  794. {
  795. return false;
  796. }
  797. return true;
  798. }
  799. /// <summary>
  800. /// 体数据在一条直线上,认为没有旋转,只有平移,xy方向不动,z方向等间距生成平移量即可
  801. /// </summary>
  802. /// <param name="scanType"></param>
  803. /// <param name="logicalCoordRegion"></param>
  804. /// <param name="physicalLength"></param>
  805. /// <param name="imgCount"></param>
  806. /// <returns></returns>
  807. private TransducerImgPlaneToWorkCoordMatrix[] GenTransducerPosesForStraightVolumeDatas(RectF logicalCoordRegion, float physicalLength, int imgCount)
  808. {
  809. // 假定每张图扫查的时候没有旋转只有平移,且xy方向未发生移动,只有z方向平移
  810. // z可以根据physicalLength和imgcount等间距生成
  811. // x 取 logicalCoordinateRegion里的(Left+Right)/2 (如果是右甲状腺,则加上一个偏移量)
  812. // y 取 logicalCoordinateRegion里的Top
  813. float x = (logicalCoordRegion.Left + logicalCoordRegion.Right) / 2;
  814. float y = logicalCoordRegion.Top;
  815. var poses = new TransducerImgPlaneToWorkCoordMatrix[imgCount];
  816. if (imgCount > 0)
  817. {
  818. for (int ni = 0; ni < imgCount; ni++)
  819. {
  820. poses[ni] = new TransducerImgPlaneToWorkCoordMatrix(x, y, ni * physicalLength / (imgCount - 1));
  821. }
  822. }
  823. return poses;
  824. }
  825. /// <summary>
  826. /// 将vinnoImageDataBuffers里的JPG图像转存的byte数据解码成未压缩的byte数组
  827. /// </summary>
  828. /// <param name="vinnoImageDataBuffers"></param>
  829. /// <param name="width"></param>
  830. /// <param name="depth"></param>
  831. /// <param name="colorType"></param>
  832. /// <returns></returns>
  833. private void DecodeVinnoImageDataBuffers(List<byte[]> vinnoImageDataBuffers, int width, int depth, int bytesPerPixel, ref byte[] decodedDataBuffer)
  834. {
  835. var imgCount = vinnoImageDataBuffers.Count;
  836. var imgDataByteCounts = width * depth * bytesPerPixel;
  837. var decodeDataByteCounts = imgDataByteCounts * imgCount;
  838. if (decodedDataBuffer.Length < decodeDataByteCounts)
  839. {
  840. Array.Resize(ref decodedDataBuffer, decodeDataByteCounts);
  841. }
  842. byte[] oneDecodedDataBuffer = new byte[imgDataByteCounts];
  843. for (int ni = 0; ni < imgCount; ni++)
  844. {
  845. var encodedData = vinnoImageDataBuffers[ni];
  846. if (!AIReconstructor.ImDataDecode(encodedData, encodedData.Length, oneDecodedDataBuffer,
  847. imgDataByteCounts, AIReconstructor.EnumImreadMode.Grayscale))
  848. {
  849. int errorMaxLen = 256;
  850. StringBuilder errorMsg = new StringBuilder(errorMaxLen);
  851. AIReconstructor.EnumCppCoreErrorCode errorCode = AIReconstructor.EnumCppCoreErrorCode.None;
  852. AIReconstructor.GetErrorCodeAndMsg(ref errorCode, errorMsg, errorMaxLen);
  853. throw new Exception("Failed at decoding vinno image data buffers, error code: "
  854. + errorCode.ToString() + " , details: " + errorMsg.ToString() + " .");
  855. }
  856. Array.Copy(oneDecodedDataBuffer, 0, decodedDataBuffer, ni * imgDataByteCounts, imgDataByteCounts);
  857. }
  858. }
  859. /// <summary>
  860. /// 将模型存到指定路径下
  861. /// </summary>
  862. /// <param name="modelFolder"></param>
  863. /// <returns></returns>
  864. private bool SaveModel(string modelFolder)
  865. {
  866. try
  867. {
  868. if (!_isUniformCube)
  869. {
  870. throw new Exception("the volume data should be turned to uniform cube before save");
  871. }
  872. if (!Directory.Exists(modelFolder))
  873. {
  874. Directory.CreateDirectory(modelFolder);
  875. }
  876. // 保存模型(逐帧将图像数据压缩成jpg)
  877. string modelFilePath = modelFolder + "\\" + ModelFileSuffix;
  878. int imgDataByteCounts = _width * _depth * _bytesPerPixel;
  879. // 如果不需要保存压缩模型,不需要保存stackedEncodedDataBuffer
  880. byte[] stackedEncodedDataBuffer = new byte[imgDataByteCounts * _imageCount];
  881. int stackedDataSize = 0;
  882. using (var stream = File.OpenWrite(modelFilePath))
  883. {
  884. stream.Write(BitConverter.GetBytes(_imageCount), 0, sizeof(int));
  885. byte[] oneDecodedDataBuffer = new byte[imgDataByteCounts];
  886. byte[] oneEncodedDataBuffer = new byte[imgDataByteCounts];
  887. AIReconstructor.StructImwriteParam[] imwriteParams = new AIReconstructor.StructImwriteParam[1];
  888. imwriteParams[0] = new AIReconstructor.StructImwriteParam(AIReconstructor.EnumImwriteFlags.JpegQuality, 80);
  889. var decodedPointer = Marshal.UnsafeAddrOfPinnedArrayElement(oneDecodedDataBuffer, 0);
  890. AIReconstructor.StructImageInfo imageInfo = new AIReconstructor.StructImageInfo(_width, _depth, _colorType, decodedPointer);
  891. for (int ni = 0; ni < _imageCount; ni++)
  892. {
  893. int encodedDataSize = imgDataByteCounts;
  894. Array.Copy(_dataBuffer, ni * imgDataByteCounts, oneDecodedDataBuffer, 0, imgDataByteCounts);
  895. if (!AIReconstructor.ImDataEncode(imageInfo, AIReconstructor.EnumImwriteExtension.Jpg,
  896. imwriteParams, 1, oneEncodedDataBuffer, ref encodedDataSize))
  897. {
  898. int errorMaxLen = 256;
  899. StringBuilder errorMsg = new StringBuilder(errorMaxLen);
  900. AIReconstructor.EnumCppCoreErrorCode errorCode = AIReconstructor.EnumCppCoreErrorCode.None;
  901. AIReconstructor.GetErrorCodeAndMsg(ref errorCode, errorMsg, errorMaxLen);
  902. throw new Exception("Failed at encoding model image datas, error code: "
  903. + errorCode.ToString() + " , details: " + errorMsg.ToString() + " .");
  904. }
  905. stream.Write(BitConverter.GetBytes(encodedDataSize), 0, sizeof(int));
  906. stream.Write(oneEncodedDataBuffer, 0, encodedDataSize);
  907. Array.Copy(oneEncodedDataBuffer, 0, stackedEncodedDataBuffer, stackedDataSize, encodedDataSize);
  908. stackedDataSize += encodedDataSize;
  909. }
  910. }
  911. // 保存压缩后的数据
  912. string zipFilePath = modelFolder + "\\" + ZipFileSuffix;
  913. using (var stream = File.OpenWrite(zipFilePath))
  914. {
  915. // 写入压缩版本号
  916. var zipVersion = Encoding.Unicode.GetBytes(MdlZipFileVersion);
  917. stream.Write(BitConverter.GetBytes(zipVersion.Length), 0, sizeof(int));
  918. stream.Write(zipVersion, 0, zipVersion.Length);
  919. // 借用GZipStream完成压缩
  920. using (var memoryStream = new MemoryStream())
  921. {
  922. using (var compressionStream = new GZipStream(memoryStream, CompressionMode.Compress))
  923. {
  924. compressionStream.Write(stackedEncodedDataBuffer, 0, stackedDataSize);
  925. compressionStream.Close();
  926. }
  927. var compressedBytes = memoryStream.ToArray();
  928. // 写入压缩后的数据
  929. stream.Write(BitConverter.GetBytes(compressedBytes.Length), 0, sizeof(int));
  930. stream.Write(compressedBytes, 0, compressedBytes.Length);
  931. }
  932. }
  933. return true;
  934. }
  935. catch (Exception excep)
  936. {
  937. NotifyLog?.Invoke(this, new LogEventArgs(EnumLogType.ErrorLog, "Failed at SaveModel:" + excep.Message));
  938. NotifyError?.Invoke(this, new ErrorEventArgs(excep));
  939. return false;
  940. }
  941. }
  942. /// <summary>
  943. /// 将模型表面数据保存到指定路径下
  944. /// </summary>
  945. /// <param name="modelFolder"></param>
  946. /// <returns></returns>
  947. private bool SaveSurface(string modelFolder)
  948. {
  949. try
  950. {
  951. if (!_isUniformCube)
  952. {
  953. throw new Exception("the volume data should be turned to uniform cube before save");
  954. }
  955. if (!Directory.Exists(modelFolder))
  956. {
  957. Directory.CreateDirectory(modelFolder);
  958. }
  959. string surfaceFilePath = modelFolder + "\\" + SurfaceFileSuffix;
  960. using (var stream = File.OpenWrite(surfaceFilePath))
  961. {
  962. // 模型的宽高长
  963. stream.Write(BitConverter.GetBytes(_width), 0, sizeof(int));
  964. stream.Write(BitConverter.GetBytes(_depth), 0, sizeof(int));
  965. stream.Write(BitConverter.GetBytes(_imageCount), 0, sizeof(int));
  966. // 写入extendedData
  967. stream.Write(BitConverter.GetBytes(_extendedData.Length),0 ,sizeof(int));
  968. stream.Write(_extendedData, 0, _extendedData.Length);
  969. // 写入图像张数
  970. int surfaceNum = 6;
  971. stream.Write(BitConverter.GetBytes(surfaceNum), 0, sizeof(int));
  972. // 得到六个表面
  973. var volumeDataPointer = Marshal.UnsafeAddrOfPinnedArrayElement(_dataBuffer, 0);
  974. AIReconstructor.StructUniformVolumeDataInfo volumeDataInfo = new AIReconstructor.StructUniformVolumeDataInfo(_width, _depth,
  975. _imageCount, _spacingXY, _colorType, volumeDataPointer);
  976. AIReconstructor.StructSurfacePicInfo[] surfaceInfos = new AIReconstructor.StructSurfacePicInfo[6];
  977. // 写入时图像顺序参照之前颈动脉三维重建的代码中的写入顺序
  978. // 右
  979. var rightSurfaceBuffer = new byte[_depth * _imageCount * _bytesPerPixel];
  980. var rightSurfacePointer = Marshal.UnsafeAddrOfPinnedArrayElement(rightSurfaceBuffer, 0);
  981. surfaceInfos[0] = new AIReconstructor.StructSurfacePicInfo(AIReconstructor.EnumSurfacePicType.Right,
  982. new AIReconstructor.StructImageInfo(_depth, _imageCount, _colorType, rightSurfacePointer));
  983. // 左
  984. var leftSurfaceBuffer = new byte[_depth * _imageCount * _bytesPerPixel];
  985. var leftSurfacePointer = Marshal.UnsafeAddrOfPinnedArrayElement(leftSurfaceBuffer, 0);
  986. surfaceInfos[1] = new AIReconstructor.StructSurfacePicInfo(AIReconstructor.EnumSurfacePicType.Left,
  987. new AIReconstructor.StructImageInfo(_depth, _imageCount, _colorType,leftSurfacePointer));
  988. // 后
  989. var behindSurfaceBuffer = new byte[_width * _depth * _bytesPerPixel];
  990. var behindSurfacePointer = Marshal.UnsafeAddrOfPinnedArrayElement(behindSurfaceBuffer, 0);
  991. surfaceInfos[2] = new AIReconstructor.StructSurfacePicInfo(AIReconstructor.EnumSurfacePicType.Behind,
  992. new AIReconstructor.StructImageInfo(_width, _depth, _colorType, behindSurfacePointer));
  993. // 前
  994. var frontSurfaceBuffer = new byte[_width * _depth * _bytesPerPixel];
  995. var frontSurfacePointer = Marshal.UnsafeAddrOfPinnedArrayElement(frontSurfaceBuffer, 0);
  996. surfaceInfos[3] = new AIReconstructor.StructSurfacePicInfo(AIReconstructor.EnumSurfacePicType.Front,
  997. new AIReconstructor.StructImageInfo(_width, _depth, _colorType, frontSurfacePointer));
  998. // 上
  999. var topSurfaceBuffer = new byte[_width * _imageCount * _bytesPerPixel];
  1000. var topSurfacePointer = Marshal.UnsafeAddrOfPinnedArrayElement(topSurfaceBuffer, 0);
  1001. surfaceInfos[4] = new AIReconstructor.StructSurfacePicInfo(AIReconstructor.EnumSurfacePicType.Top,
  1002. new AIReconstructor.StructImageInfo(_width, _imageCount, _colorType, topSurfacePointer));
  1003. // 下
  1004. var bottomSurfaceBuffer = new byte[_width * _imageCount * _bytesPerPixel];
  1005. var bottomSurfacePointer = Marshal.UnsafeAddrOfPinnedArrayElement(bottomSurfaceBuffer, 0);
  1006. surfaceInfos[5] = new AIReconstructor.StructSurfacePicInfo(AIReconstructor.EnumSurfacePicType.Bottom,
  1007. new AIReconstructor.StructImageInfo(_width, _imageCount, _colorType, bottomSurfacePointer));
  1008. if (!AIReconstructor.GetSurfacePicsFromUniformCube(volumeDataInfo, surfaceNum, surfaceInfos))
  1009. {
  1010. int errorMaxLen = 256;
  1011. StringBuilder errorMsg = new StringBuilder(errorMaxLen);
  1012. AIReconstructor.EnumCppCoreErrorCode errorCode = AIReconstructor.EnumCppCoreErrorCode.None;
  1013. AIReconstructor.GetErrorCodeAndMsg(ref errorCode, errorMsg, errorMaxLen);
  1014. throw new Exception("Failed at GetSurfacePicsFromUniformCube, error code: "
  1015. + errorCode.ToString() + " , details: " + errorMsg.ToString() + " .");
  1016. }
  1017. // 分别写入这六张图(数据)
  1018. int index = 0;
  1019. AIReconstructor.StructImwriteParam[] imwriteParams = new AIReconstructor.StructImwriteParam[1];
  1020. imwriteParams[0] = new AIReconstructor.StructImwriteParam(AIReconstructor.EnumImwriteFlags.JpegQuality, 80);
  1021. for (int ni = 0; ni < surfaceNum; ni++)
  1022. {
  1023. var imageInfo = surfaceInfos[ni].ImageInfo;
  1024. int imageByteCount = imageInfo.Width * imageInfo.Height * _bytesPerPixel;
  1025. index += imageByteCount;
  1026. byte[] imageDataEncoded = new byte[imageByteCount];
  1027. int encodedDataSize = imageByteCount;
  1028. if (!AIReconstructor.ImDataEncode(imageInfo, AIReconstructor.EnumImwriteExtension.Jpg,
  1029. imwriteParams, 1, imageDataEncoded, ref encodedDataSize))
  1030. {
  1031. int errorMaxLen = 256;
  1032. StringBuilder errorMsg = new StringBuilder(errorMaxLen);
  1033. AIReconstructor.EnumCppCoreErrorCode errorCode = AIReconstructor.EnumCppCoreErrorCode.None;
  1034. AIReconstructor.GetErrorCodeAndMsg(ref errorCode, errorMsg, errorMaxLen);
  1035. throw new Exception("Failed at encoding surface image datas, error code: "
  1036. + errorCode.ToString() + " , details: " + errorMsg.ToString() + " .");
  1037. }
  1038. stream.Write(BitConverter.GetBytes(encodedDataSize), 0, sizeof(int));
  1039. stream.Write(imageDataEncoded, 0, encodedDataSize);
  1040. }
  1041. // 写入bool值以便确认是通过RawVolumeData写入到surface里的信息
  1042. stream.Write(BitConverter.GetBytes(true), 0, sizeof(bool));
  1043. // 写入_logicalCoordRegion的信息
  1044. stream.Write(BitConverter.GetBytes(_logicalCoordRegion.Left), 0, sizeof(float));
  1045. stream.Write(BitConverter.GetBytes(_logicalCoordRegion.Top), 0, sizeof(float));
  1046. stream.Write(BitConverter.GetBytes(_logicalCoordRegion.Width), 0, sizeof(float));
  1047. stream.Write(BitConverter.GetBytes(_logicalCoordRegion.Height), 0, sizeof(float));
  1048. // 写入_physicalWidth、_physicalDepth、_physicalLength
  1049. stream.Write(BitConverter.GetBytes(_physicalWidth), 0, sizeof(float));
  1050. stream.Write(BitConverter.GetBytes(_physicalDepth), 0, sizeof(float));
  1051. stream.Write(BitConverter.GetBytes(_physicalLength), 0, sizeof(float));
  1052. // 写入_scanType
  1053. stream.Write(BitConverter.GetBytes((int)_scanType), 0, sizeof(int));
  1054. }
  1055. return true;
  1056. }
  1057. catch (Exception excep)
  1058. {
  1059. NotifyLog?.Invoke(this, new LogEventArgs(EnumLogType.ErrorLog, "Failed at SaveSurface:" + excep.Message));
  1060. NotifyError?.Invoke(this, new ErrorEventArgs(excep));
  1061. return false;
  1062. }
  1063. }
  1064. #endregion
  1065. }
  1066. }