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);
}
}
}