using SkiaSharp; using System; using System.IO; using System.Linq; using System.Threading; using Vinno.IUS.Common.Log; using Vinno.vCloud.Common.FIS.Helper; using Vinno.vCloud.Common.Vid2; using Vinno.vCloud.FIS.CrossPlatform.Common.Helper; using Vinno.vCloud.Protocol.Infrastructures; using WingInterfaceLibrary.DTO.Comment; using WingInterfaceLibrary.DTO.Measure; using WingInterfaceLibrary.DTO.Record; using WingInterfaceLibrary.Enum; using WingInterfaceLibrary.Interface; using WingInterfaceLibrary.Request.Examine; using WingInterfaceLibrary.Request.Storage; using WingInterfaceLibrary.Result.Examine; namespace Vinno.vCloud.Common.FIS.Remedicals { internal class vCloudScanDataV2 : IvCloudScanData, IDisposable { private UploadStatus _status; private readonly IRemedicalService _remedicalService; private readonly IStorageService _storageService; private readonly string _token; private bool _disposed; private const string BreastImageLocation = "BreastImageLocation"; private const string LiverImageLocation = "LiverImageLocation"; private const string ThyroidImageLocation = "ThyroidImageLocation"; /// /// /// Gets the id of this scan data. /// public string Id { get; } /// /// /// Gets the examId of this scan data. /// public string ExamId { get; } /// /// /// Gets or sets the ExamRecordId for this scan data. /// public string ExamRecordId { get; set; } /// /// /// Gets the workorder Id. /// public string WorkOrderId { get; } /// /// Raised when the status changed. /// public event EventHandler StatusChanged; /// /// /// Gets the status of this Scan data. /// public UploadStatus Status { get => _status; internal set { if (_status != value) { var old = _status; _status = value; if (_status == UploadStatus.Uploaded || _status == UploadStatus.Deleted) { Delete(old); } else { Move(old, _status); } OnStatusChanged(); } } } /// /// /// Gets the vid file path. /// public string VidFilePath { get; private set; } /// /// Gets the sd file path. /// public string ScanDataFilePath { get; private set; } /// /// /// Gets the description of this scan data. /// public string Description { get; } /// /// /// Gets the application of this scan data. /// public string Application { get; } /// /// Gets the Application Category of this scan data /// public string ApplicationCategory { get; } /// /// /// Gets the data type of this scan data. /// public VidType DataType { get; } /// /// /// Gets the patient info for this scan data. /// public PatientScanInfo PatientInfo { get; } /// /// Get the Measure Results /// public MeasuredResultsDTO MeasuredResults { get; } /// /// Get the comment Results /// public ScanImageDTO CommentResults { get; } public vCloudScanDataV2(IvCloudExamRecord record, string id, string vidFilePath, VidType dataType, string description, PatientScanInfo patientInfo, UploadStatus status, IRemedicalService remedicalService, IStorageService storageService, string token, MeasuredResultsDTO measuredResults = null, ScanImageDTO commentResults = null) { _status = status; _token = token; _remedicalService = remedicalService; _storageService = storageService; ExamRecordId = record.Id; ExamId = record.ExamId; WorkOrderId = record.WorkOrderId; Id = id; DataType = dataType; Description = description; PatientInfo = patientInfo; MeasuredResults = measuredResults; CommentResults = commentResults; //Generate files. VidFilePath = GetVidFilePath(_status); if (string.Compare(VidFilePath, vidFilePath, StringComparison.OrdinalIgnoreCase) != 0) { File.Move(vidFilePath, VidFilePath); } if (description == "FromSonopost") { Application = string.Empty; ApplicationCategory = description; } else { using (var vinnoImage = new VinnoImageData(VidFilePath, OperationMode.Open)) { ApplicationCategory = vinnoImage.Probe?.Application?.ApplicationCategoryName; Application = vinnoImage.Probe?.Application?.ApplicationName; } } //Save self. Save(); } ~vCloudScanDataV2() { DoDispose(); } private void OnStatusChanged() { StatusChanged?.Invoke(this, EventArgs.Empty); } /// /// Delete this scanData and its files. /// public void Delete() { Status = UploadStatus.Deleted; } /// /// Force update the scan data's status. /// /// The new status public void ForceUpdateStaus(UploadStatus status) { Status = status; } /// /// Force update and save the scan data. /// public void Update() { Save(); } /// /// Get saved scan data file path /// /// /// private string GetScanDataFilePath(UploadStatus status) { if (status == UploadStatus.Deleted) { throw new InvalidOperationException("File already deleted."); } var subFolder = string.Empty; if (status == UploadStatus.Idle || status == UploadStatus.Waiting || status == UploadStatus.Uploading) { subFolder = "Upload"; } if (status == UploadStatus.Fail || status == UploadStatus.FailBecauseExamIsFinished) { subFolder = "Failed"; } var folder = GetWorkingFolder(subFolder); var fileName = $"{Id}.sd"; return Path.Combine(folder, fileName); } private string GetWorkingFolder(string subFolder) { var folder = Path.Combine(vCloudTerminalV2.WorkingFolder, "RemedicalV2", subFolder); DirectoryHelper.CreateDirectory(folder); return folder; } /// /// Get the vid file path. /// /// /// private string GetVidFilePath(UploadStatus status) { if (status == UploadStatus.Deleted) { throw new InvalidOperationException("File already deleted."); } var subFolder = string.Empty; if (status == UploadStatus.Idle || status == UploadStatus.Waiting || status == UploadStatus.Uploading) { subFolder = "Upload"; } if (status == UploadStatus.Fail || status == UploadStatus.FailBecauseExamIsFinished) { subFolder = "Failed"; } var folder = GetWorkingFolder(subFolder); var fileName = $"{Id}.vid"; return Path.Combine(folder, fileName); } /// /// Delete the files /// /// private void Delete(UploadStatus status) { var scanDataFilePath = GetScanDataFilePath(status); var vidFilePath = GetVidFilePath(status); FileHelper.DeleteFile(scanDataFilePath); FileHelper.DeleteFile(vidFilePath); } /// /// Save scan data and vid file to the disk by current status. /// private void Save() { ScanDataFilePath = GetScanDataFilePath(Status); this.Serialize(ScanDataFilePath); } /// /// Move scan data from one to another /// /// /// private void Move(UploadStatus source, UploadStatus dest) { var sourceScanDataFilePath = GetScanDataFilePath(source); var sourceVidFilePath = GetVidFilePath(source); var destScanDataFilePath = GetScanDataFilePath(dest); var destVidFilePath = GetVidFilePath(dest); if (File.Exists(sourceScanDataFilePath) && !File.Exists(destScanDataFilePath)) { File.Move(sourceScanDataFilePath, destScanDataFilePath); ScanDataFilePath = destScanDataFilePath; } if (File.Exists(sourceVidFilePath) && !File.Exists(destVidFilePath)) { File.Move(sourceVidFilePath, destVidFilePath); VidFilePath = destVidFilePath; } } /// /// Upload the data to the vCloud server. /// public void Upload(CancellationTokenSource cancellationTokenSource) { string previewFileToken = string.Empty; string coverImageToken = string.Empty; ImageLocationDTO imageLocationDTO = null; using (var vinnoImage = new VinnoImageData(VidFilePath, OperationMode.Open)) { if (vinnoImage.ImageCount <= 0) { throw new FileNotFoundException("The VinnoImageData Image Count <=0"); } } if (vCloudServerConfig.Instance.IsUploadThumbnail) { var storageUrl = UploadFirstImage(cancellationTokenSource); if (string.IsNullOrWhiteSpace(storageUrl)) { throw new InvalidOperationException($"Get token failed when Uploading first image"); } coverImageToken = storageUrl; storageUrl = UploadPreviewImage(cancellationTokenSource); if (string.IsNullOrWhiteSpace(storageUrl)) { throw new InvalidOperationException($"Get token failed when Uploading preview image"); } previewFileToken = storageUrl; imageLocationDTO = GetImageLocation(cancellationTokenSource); } var url = UploadVidFile(cancellationTokenSource); if (string.IsNullOrWhiteSpace(url)) { throw new InvalidOperationException("Get token failed when Uploading VID file"); } var fileType = RemedicalFileDataTypeEnum.VinnoVidSingle; switch (DataType) { case VidType.VinnoVidMovie: fileType = RemedicalFileDataTypeEnum.VinnoVidMovie; break; case VidType.ThirdVidSingle: fileType = RemedicalFileDataTypeEnum.ThirdVidSingle; break; case VidType.ThirdVidMovie: fileType = RemedicalFileDataTypeEnum.ThirdVidMovie; break; } var fileInfo = new FileInfo(VidFilePath); var fileSize = fileInfo.Length; var uploadExamDataRequest = new UploadExamDataRequest { Token = _token, ExamCode = ExamRecordId, FileToken = url, FileSize = fileSize, Application = Application, ApplicationCategory = ApplicationCategory, FileDataType = fileType, PreviewFileToken = previewFileToken, CoverImageToken = coverImageToken, MeasuredResult = MeasuredResults, CommentResult = CommentResults, ImageLocation = imageLocationDTO }; if (cancellationTokenSource.IsCancellationRequested) { throw new OperationCanceledException("Cancelled"); } var result = JsonRpcHelper.UploadExamData(_remedicalService, uploadExamDataRequest); if (result == null) { throw new InvalidDataException($"JsonRPCHelper UploadExamData Result is null"); } else if (result.IsSuccess) { Status = UploadStatus.Uploaded; return; } else if (!result.IsSuccess) { if (result.ErrorCode == 4002) { Status = UploadStatus.FailBecauseExamIsFinished; Save(); return; } else if (result.ErrorCode == 4015) { if (CreateExamRecord()) { result = JsonRpcHelper.UploadExamData(_remedicalService, uploadExamDataRequest); if (result == null) { throw new InvalidDataException($"JsonRPCHelper UploadExamData Result is null"); } else if (result.IsSuccess) { Status = UploadStatus.Uploaded; return; } else { throw new InvalidDataException($"JsonRPCHelper UploadExamData Fail,Error Code is {result.ErrorCode}"); } } else { throw new InvalidDataException($"JsonRPCHelper UploadExamData Fail:ReCreateExamRecord Result is false"); } } else { throw new InvalidDataException($"JsonRPCHelper UploadExamData Fail,Error Code is {result.ErrorCode}"); } } } private ImageLocationDTO GetImageLocation(CancellationTokenSource cancellationTokenSource) { if (cancellationTokenSource.IsCancellationRequested) { throw new OperationCanceledException("Cancelled"); } using (var vinnoImage = new VinnoImageData(VidFilePath, OperationMode.Open)) { var imageLocationExtenedData = VidExtendedData.FromBytes(vinnoImage.ExtendedData); if (imageLocationExtenedData != null && imageLocationExtenedData.Data != null) { var positionTag = imageLocationExtenedData.Data.Keys.FirstOrDefault(t => t.Element == "Position" && t.Group == BreastImageLocation); var quadrantTag = imageLocationExtenedData.Data.Keys.FirstOrDefault(t => t.Element == "Quadrant" && t.Group == BreastImageLocation); if (positionTag != null && quadrantTag != null) { if (Enum.TryParse(imageLocationExtenedData.Data[positionTag].GetValue()?.ToString(), out var positionEnum) && Enum.TryParse(imageLocationExtenedData.Data[quadrantTag].GetValue()?.ToString(), out var quadrantEnum)) { return new ImageLocationDTO { Position = positionEnum.ToString(), Quadrant = quadrantEnum.ToString(), Group = positionTag.Group, }; } } positionTag = imageLocationExtenedData.Data.Keys.FirstOrDefault(t => t.Element == "Position" && t.Group == LiverImageLocation); if (positionTag != null) { if (Enum.TryParse(imageLocationExtenedData.Data[positionTag].GetValue()?.ToString(), out var positionEnum)) { return new ImageLocationDTO { Position = positionEnum.ToString(), Group = positionTag.Group, }; } } positionTag = imageLocationExtenedData.Data.Keys.FirstOrDefault(t => t.Element == FISTissueCategory.Position && !string.IsNullOrWhiteSpace(t.Group)); var groupName = positionTag?.Group; if (positionTag != null && !imageLocationExtenedData.Data.Keys.Any(x => x.Group == groupName && x.Element != FISTissueCategory.Position)) { return new ImageLocationDTO { Group = groupName, Position = imageLocationExtenedData.Data[positionTag].GetValue()?.ToString(), }; } } } return null; } /// /// /// Create an exam record from vCloud. /// /// The local excam id. /// The workerId /// The patient basic info for creating the exam record. /// public bool CreateExamRecord() { var createExaminfoRequest = new CreateExaminfoRequest { Token = _token, PatientType = vCloudServerConfig.Instance.PatientType.ToString(), ExamRecordCode = ExamRecordId, PatientInfo = DTOConverter.RenderPatientInfo(PatientInfo), PatientScanInfoList = DTOConverter.RenderPatientScanInfo(PatientInfo), }; CreateExaminfoResult result = JsonRpcHelper.CreateExamInfo(_remedicalService, createExaminfoRequest); if (result == null || result.ExamCode != ExamRecordId) { return false; } else { return true; } } private string UploadFirstImage(CancellationTokenSource cancellationTokenSource) { try { if (cancellationTokenSource.IsCancellationRequested) { throw new OperationCanceledException("Cancelled"); } var firstImageName = "firstImage_" + Path.GetFileNameWithoutExtension(VidFilePath) + ".jpg"; using (var vinnoImage = new VinnoImageData(VidFilePath, OperationMode.Open)) { var image = vinnoImage.GetImage(0); var fileServiceRequest = new FileServiceRequest { FileName = firstImageName, IsRechristen = true, Token = _token, }; var authorizationInfo = JsonRpcHelper.GetAuthorization(_storageService, fileServiceRequest); if (authorizationInfo == null) { throw new Exception("GetAuthorization Error,AuthorizationInfo is null "); } var uploadResult = UploadFileHelper.UploadFile(authorizationInfo.StorageUrl, image?.ImageData, authorizationInfo.ContentType, authorizationInfo.Authorization, null, cancellationTokenSource); if (cancellationTokenSource.IsCancellationRequested) { throw new OperationCanceledException("Cancelled"); } if (uploadResult) { return authorizationInfo.StorageUrl; } else { return string.Empty; } } } catch (Exception ex) { Logger.WriteLineError($"Upload First Image Error:{ex}"); return string.Empty; } } private string UploadPreviewImage(CancellationTokenSource cancellationTokenSource) { try { if (cancellationTokenSource.IsCancellationRequested) { throw new OperationCanceledException("Cancelled"); } var previewImageName = "preview_" + Path.GetFileNameWithoutExtension(VidFilePath) + ".jpg"; using (var vinnoImage = new VinnoImageData(VidFilePath, OperationMode.Open)) { var image = vinnoImage.GetImage(0); var fileServiceRequest = new FileServiceRequest { FileName = previewImageName, IsRechristen = true, Token = _token, }; var authorizationInfo = JsonRpcHelper.GetAuthorization(_storageService, fileServiceRequest); if (authorizationInfo == null) { throw new Exception("GetAuthorization Error,AuthorizationInfo is null "); } var compressedImage = Compress(image); var uploadResult = UploadFileHelper.UploadFile(authorizationInfo.StorageUrl, compressedImage?.ImageData, authorizationInfo.ContentType, authorizationInfo.Authorization, null, cancellationTokenSource); if (cancellationTokenSource.IsCancellationRequested) { throw new OperationCanceledException("Cancelled"); } if (uploadResult) { return authorizationInfo.StorageUrl; } else { return string.Empty; } } } catch (Exception ex) { Logger.WriteLineError($"Upload Preview Image Error:{ex}"); return string.Empty; } } private string UploadVidFile(CancellationTokenSource cancellationTokenSource) { try { if (cancellationTokenSource.IsCancellationRequested) { throw new OperationCanceledException("Cancelled"); } var fileName = Path.GetFileName(VidFilePath); var fileServiceRequest = new FileServiceRequest { FileName = fileName, IsRechristen = true, Token = _token, }; var authorizationInfo = JsonRpcHelper.GetAuthorization(_storageService, fileServiceRequest); if (authorizationInfo == null) { throw new Exception("GetAuthorization Error,AuthorizationInfo is null "); } var uploadResult = UploadFileHelper.UploadFile(authorizationInfo.StorageUrl, VidFilePath, authorizationInfo.ContentType, authorizationInfo.Authorization, null, cancellationTokenSource); if (cancellationTokenSource.IsCancellationRequested) { throw new OperationCanceledException("Cancelled"); } if (uploadResult) { return authorizationInfo.StorageUrl; } else { return string.Empty; } } catch (Exception ex) { Logger.WriteLineError($"Upload Vid File Error:{ex}"); return string.Empty; } } private void DoDispose() { try { if (!_disposed) { //if not uploaded just save it to disk. if (Status != UploadStatus.Uploaded && Status != UploadStatus.Deleted) { Save(); } } } catch (Exception ex) { Logger.WriteLineError($"vCloudScanData: dispose upload data error:{ex}"); } finally { _disposed = true; } } /// /// Compress a VinnoImage to a 100p image with {compressLevel} quality /// /// /// /// /// private VinnoImage Compress(VinnoImage vinnoImage, long compressLevel = 80) { byte[] compressedData = vinnoImage.ImageData; using (var ms = new MemoryStream(compressedData)) { using (var image = SKBitmap.Decode(ms)) { var ratio = (double)100 / image.Height; if (ratio < 1) { var width = (int)(image.Width * ratio); var height = 100; SKBitmap tempImage = image; if (image.Width != width || image.Height != height) { tempImage = image.Resize(new SKImageInfo(width, height), SKFilterQuality.High); } try { using (var map = new SKPixmap(new SKImageInfo(tempImage.Width, tempImage.Height, tempImage.ColorType), tempImage.GetPixels())) { using (var stream = new SKDynamicMemoryWStream()) { SKPixmap.Encode(stream, map, SKEncodedImageFormat.Jpeg, (int)compressLevel); compressedData = stream.CopyToData().ToArray(); } } } catch (Exception exception) { throw new Exception($"Compress error:{exception}"); } finally { tempImage.Dispose(); } var compressImage = new VinnoImage(vinnoImage.Index, width, height, compressedData); return compressImage; } } } return vinnoImage; } public void Dispose() { DoDispose(); GC.SuppressFinalize(this); } } }