PACS (نظم أرشيف الصور والاتصالات) وخدمات التليراديولوجيا تحول الصورة الطبية من خلال تمكين الوصول عن بعد إلى الصور التشخيصية. ومع ذلك، نقل بيانات المرضى إلى البيئات السحابية يتطلب اهتماما كبيرا للخصوصية والأمن.
لماذا الهوية في السحابة والتلفزيون؟
عندما تترك صور DICOM شبكة المستشفى لتخزين السحابة أو القراءة عن بعد، تنطبق ملاحظات الخصوصية الإضافية:
- إقامة البيانات: قد تتجاوز بيانات المرضى الحدود الجغرافية حيث تنطبق أحكام مختلفة
- الوصول من طرف ثالث: مزودي السحابة وخدمات التليراديولوجيا هم شركاء أعمال بموجب HIPAA
- الإرسال عبر الشبكة: تتطلب البيانات عبر الإنترنت حماية إضافية
- البيئات المتعددة المهارات: يمكن أن تخزن أنظمة السحابة البيانات من العديد من المنظمات الصحية
- أطباء الإشعاع عن بعد: قد لا يحتاج القارئ الخارجي إلى الوصول إلى علامات تحديد المرضى
يخلق المجهول طبقة أمنية تحمي خصوصية المرضى حتى إذا فشلت إجراءات أمان أخرى.
خدمة Cloud Upload Anonymization
إنشاء خدمة تسمح بتسمية ملفات DICOM قبل تحميل السحابة:
using Aspose.Medical.Dicom;
using Aspose.Medical.Dicom.Anonymization;
public class CloudUploadAnonymizer
{
private readonly ConfidentialityProfile _profile;
private readonly Dictionary<string, string> _studyIdMapping;
private readonly string _organizationPrefix;
public CloudUploadAnonymizer(string organizationPrefix)
{
_organizationPrefix = organizationPrefix;
_studyIdMapping = new Dictionary<string, string>();
// Create profile optimized for cloud storage
var options = ConfidentialityProfileOptions.BasicProfile |
ConfidentialityProfileOptions.RetainDeviceIdentity |
ConfidentialityProfileOptions.CleanDescriptions;
_profile = ConfidentialityProfile.CreateDefault(options);
}
public CloudUploadResult AnonymizeForCloud(string inputPath, string outputPath)
{
var result = new CloudUploadResult
{
OriginalPath = inputPath,
ProcessedAt = DateTime.UtcNow
};
try
{
DicomFile dicomFile = DicomFile.Open(inputPath);
var dataset = dicomFile.Dataset;
// Capture original identifiers for mapping
string originalStudyUid = dataset.GetString(DicomTag.StudyInstanceUID);
string originalPatientId = dataset.GetString(DicomTag.PatientID);
string originalAccession = dataset.GetString(DicomTag.AccessionNumber);
// Generate cloud-safe identifiers
string cloudStudyId = GetOrCreateCloudStudyId(originalStudyUid);
result.OriginalStudyUID = originalStudyUid;
result.CloudStudyId = cloudStudyId;
result.OriginalPatientId = originalPatientId;
// Apply anonymization
var anonymizer = new Anonymizer(_profile);
anonymizer.Anonymize(dataset);
// Apply cloud-specific identifiers
dataset.AddOrUpdate(DicomTag.PatientID, $"{_organizationPrefix}-{cloudStudyId}");
dataset.AddOrUpdate(DicomTag.PatientName, $"CloudStudy^{cloudStudyId}");
dataset.AddOrUpdate(DicomTag.AccessionNumber, cloudStudyId);
// Add cloud tracking metadata
dataset.AddOrUpdate(DicomTag.InstitutionName, _organizationPrefix);
dicomFile.Save(outputPath);
result.CloudPath = outputPath;
result.Success = true;
}
catch (Exception ex)
{
result.Success = false;
result.ErrorMessage = ex.Message;
}
return result;
}
private string GetOrCreateCloudStudyId(string originalStudyUid)
{
if (!_studyIdMapping.ContainsKey(originalStudyUid))
{
string timestamp = DateTime.UtcNow.ToString("yyyyMMddHHmmss");
string random = Guid.NewGuid().ToString("N").Substring(0, 8);
_studyIdMapping[originalStudyUid] = $"{timestamp}-{random}";
}
return _studyIdMapping[originalStudyUid];
}
public Dictionary<string, string> GetStudyMapping()
{
return new Dictionary<string, string>(_studyIdMapping);
}
}
public class CloudUploadResult
{
public string OriginalPath { get; set; }
public string CloudPath { get; set; }
public string OriginalStudyUID { get; set; }
public string CloudStudyId { get; set; }
public string OriginalPatientId { get; set; }
public DateTime ProcessedAt { get; set; }
public bool Success { get; set; }
public string ErrorMessage { get; set; }
}
التليراديولوجيا تدفق العمل التكامل
بناء خط أنابيب التليراديولوجيا المجهول الكامل:
public class TeleradiologyAnonymizationPipeline
{
private readonly CloudUploadAnonymizer _anonymizer;
private readonly string _stagingDirectory;
private readonly string _mappingDirectory;
public TeleradiologyAnonymizationPipeline(
string organizationId,
string stagingDirectory,
string mappingDirectory)
{
_anonymizer = new CloudUploadAnonymizer(organizationId);
_stagingDirectory = stagingDirectory;
_mappingDirectory = mappingDirectory;
Directory.CreateDirectory(_stagingDirectory);
Directory.CreateDirectory(_mappingDirectory);
}
public async Task<TeleradiologyBatch> ProcessStudyForRemoteReading(
string studyDirectory,
string priority = "ROUTINE")
{
var batch = new TeleradiologyBatch
{
BatchId = Guid.NewGuid().ToString(),
Priority = priority,
SubmittedAt = DateTime.UtcNow,
Results = new List<CloudUploadResult>()
};
var dicomFiles = Directory.GetFiles(studyDirectory, "*.dcm", SearchOption.AllDirectories);
// Create batch output directory
string batchOutputDir = Path.Combine(_stagingDirectory, batch.BatchId);
Directory.CreateDirectory(batchOutputDir);
foreach (var inputFile in dicomFiles)
{
string relativePath = Path.GetRelativePath(studyDirectory, inputFile);
string outputPath = Path.Combine(batchOutputDir, relativePath);
Directory.CreateDirectory(Path.GetDirectoryName(outputPath));
var result = _anonymizer.AnonymizeForCloud(inputFile, outputPath);
batch.Results.Add(result);
}
// Save mapping file for this batch
await SaveBatchMapping(batch);
// Generate manifest for teleradiology service
batch.ManifestPath = await GenerateTeleradiologyManifest(batch, batchOutputDir);
batch.Success = batch.Results.All(r => r.Success);
batch.TotalFiles = batch.Results.Count;
batch.SuccessfulFiles = batch.Results.Count(r => r.Success);
return batch;
}
private async Task SaveBatchMapping(TeleradiologyBatch batch)
{
var mapping = new
{
BatchId = batch.BatchId,
SubmittedAt = batch.SubmittedAt,
StudyMappings = _anonymizer.GetStudyMapping(),
FileMappings = batch.Results.Select(r => new
{
r.OriginalPath,
r.CloudPath,
r.OriginalStudyUID,
r.CloudStudyId,
r.OriginalPatientId
})
};
string mappingPath = Path.Combine(_mappingDirectory, $"{batch.BatchId}_mapping.json");
string json = JsonSerializer.Serialize(mapping, new JsonSerializerOptions { WriteIndented = true });
await File.WriteAllTextAsync(mappingPath, json);
}
private async Task<string> GenerateTeleradiologyManifest(
TeleradiologyBatch batch,
string outputDirectory)
{
var manifest = new
{
Version = "1.0",
BatchId = batch.BatchId,
Priority = batch.Priority,
SubmittedAt = batch.SubmittedAt.ToString("O"),
TotalStudies = batch.Results
.Where(r => r.Success)
.Select(r => r.CloudStudyId)
.Distinct()
.Count(),
TotalImages = batch.Results.Count(r => r.Success),
Studies = batch.Results
.Where(r => r.Success)
.GroupBy(r => r.CloudStudyId)
.Select(g => new
{
CloudStudyId = g.Key,
ImageCount = g.Count(),
Files = g.Select(r => Path.GetFileName(r.CloudPath)).ToList()
})
.ToList()
};
string manifestPath = Path.Combine(outputDirectory, "manifest.json");
string json = JsonSerializer.Serialize(manifest, new JsonSerializerOptions { WriteIndented = true });
await File.WriteAllTextAsync(manifestPath, json);
return manifestPath;
}
}
public class TeleradiologyBatch
{
public string BatchId { get; set; }
public string Priority { get; set; }
public DateTime SubmittedAt { get; set; }
public List<CloudUploadResult> Results { get; set; }
public string ManifestPath { get; set; }
public bool Success { get; set; }
public int TotalFiles { get; set; }
public int SuccessfulFiles { get; set; }
}
تحميل برنامج Azure Blob Storage
تحميل ملفات DICOM المجهولة إلى Azure Blob Storage:
using Azure.Storage.Blobs;
using Azure.Storage.Blobs.Models;
public class AzureDicomUploader
{
private readonly BlobContainerClient _containerClient;
private readonly TeleradiologyAnonymizationPipeline _pipeline;
public AzureDicomUploader(
string connectionString,
string containerName,
string organizationId)
{
_containerClient = new BlobContainerClient(connectionString, containerName);
_containerClient.CreateIfNotExists();
_pipeline = new TeleradiologyAnonymizationPipeline(
organizationId,
Path.Combine(Path.GetTempPath(), "dicom_staging"),
Path.Combine(Path.GetTempPath(), "dicom_mappings"));
}
public async Task<AzureUploadResult> UploadStudyAsync(
string studyDirectory,
string priority = "ROUTINE")
{
var result = new AzureUploadResult
{
StartedAt = DateTime.UtcNow
};
try
{
// Anonymize the study
var batch = await _pipeline.ProcessStudyForRemoteReading(studyDirectory, priority);
result.BatchId = batch.BatchId;
if (!batch.Success)
{
result.Success = false;
result.ErrorMessage = "Anonymization failed for some files";
return result;
}
// Upload anonymized files to Azure
string batchDirectory = Path.GetDirectoryName(batch.ManifestPath);
var filesToUpload = Directory.GetFiles(batchDirectory, "*.*", SearchOption.AllDirectories);
foreach (var filePath in filesToUpload)
{
string blobName = $"{batch.BatchId}/{Path.GetRelativePath(batchDirectory, filePath)}";
BlobClient blobClient = _containerClient.GetBlobClient(blobName);
using (var stream = File.OpenRead(filePath))
{
await blobClient.UploadAsync(stream, new BlobUploadOptions
{
HttpHeaders = new BlobHttpHeaders
{
ContentType = GetContentType(filePath)
},
Metadata = new Dictionary<string, string>
{
{ "batch_id", batch.BatchId },
{ "priority", priority },
{ "uploaded_at", DateTime.UtcNow.ToString("O") }
}
});
}
result.UploadedFiles.Add(blobName);
}
// Clean up staging directory
Directory.Delete(batchDirectory, true);
result.Success = true;
result.CompletedAt = DateTime.UtcNow;
result.BlobContainerUri = _containerClient.Uri.ToString();
}
catch (Exception ex)
{
result.Success = false;
result.ErrorMessage = ex.Message;
}
return result;
}
private string GetContentType(string filePath)
{
return Path.GetExtension(filePath).ToLower() switch
{
".dcm" => "application/dicom",
".json" => "application/json",
_ => "application/octet-stream"
};
}
}
public class AzureUploadResult
{
public string BatchId { get; set; }
public DateTime StartedAt { get; set; }
public DateTime CompletedAt { get; set; }
public bool Success { get; set; }
public string ErrorMessage { get; set; }
public string BlobContainerUri { get; set; }
public List<string> UploadedFiles { get; set; } = new List<string>();
}
AWS S3 التكامل
تحميل DICOM المجهول إلى Amazon S3:
using Amazon.S3;
using Amazon.S3.Transfer;
public class AwsDicomUploader
{
private readonly IAmazonS3 _s3Client;
private readonly string _bucketName;
private readonly TeleradiologyAnonymizationPipeline _pipeline;
public AwsDicomUploader(
IAmazonS3 s3Client,
string bucketName,
string organizationId)
{
_s3Client = s3Client;
_bucketName = bucketName;
_pipeline = new TeleradiologyAnonymizationPipeline(
organizationId,
Path.Combine(Path.GetTempPath(), "dicom_staging"),
Path.Combine(Path.GetTempPath(), "dicom_mappings"));
}
public async Task<S3UploadResult> UploadStudyAsync(
string studyDirectory,
string priority = "ROUTINE")
{
var result = new S3UploadResult
{
StartedAt = DateTime.UtcNow
};
try
{
// Anonymize
var batch = await _pipeline.ProcessStudyForRemoteReading(studyDirectory, priority);
result.BatchId = batch.BatchId;
if (!batch.Success)
{
result.Success = false;
result.ErrorMessage = "Anonymization failed";
return result;
}
// Upload to S3
var transferUtility = new TransferUtility(_s3Client);
string batchDirectory = Path.GetDirectoryName(batch.ManifestPath);
await transferUtility.UploadDirectoryAsync(new TransferUtilityUploadDirectoryRequest
{
BucketName = _bucketName,
Directory = batchDirectory,
KeyPrefix = batch.BatchId,
SearchOption = SearchOption.AllDirectories
});
// Clean up
Directory.Delete(batchDirectory, true);
result.Success = true;
result.S3Uri = $"s3://{_bucketName}/{batch.BatchId}/";
result.CompletedAt = DateTime.UtcNow;
}
catch (Exception ex)
{
result.Success = false;
result.ErrorMessage = ex.Message;
}
return result;
}
}
public class S3UploadResult
{
public string BatchId { get; set; }
public DateTime StartedAt { get; set; }
public DateTime CompletedAt { get; set; }
public bool Success { get; set; }
public string ErrorMessage { get; set; }
public string S3Uri { get; set; }
}
بوابة المجهول في الوقت الحقيقي
إنشاء بوابة API التي تسمى DICOM في الوقت الحقيقي للمشاهدين في السحابة:
[ApiController]
[Route("api/[controller]")]
public class CloudDicomGatewayController : ControllerBase
{
private readonly CloudUploadAnonymizer _anonymizer;
private readonly ILogger<CloudDicomGatewayController> _logger;
public CloudDicomGatewayController(ILogger<CloudDicomGatewayController> logger)
{
_anonymizer = new CloudUploadAnonymizer("GATEWAY");
_logger = logger;
}
[HttpPost("anonymize-stream")]
public async Task<IActionResult> AnonymizeStream(IFormFile file)
{
if (file == null || file.Length == 0)
return BadRequest("No DICOM file provided");
var tempInput = Path.GetTempFileName();
var tempOutput = Path.GetTempFileName();
try
{
// Save uploaded file
using (var stream = new FileStream(tempInput, FileMode.Create))
{
await file.CopyToAsync(stream);
}
// Anonymize
var result = _anonymizer.AnonymizeForCloud(tempInput, tempOutput);
if (!result.Success)
{
return StatusCode(500, $"Anonymization failed: {result.ErrorMessage}");
}
// Return anonymized file
var fileBytes = await System.IO.File.ReadAllBytesAsync(tempOutput);
// Add tracking headers
Response.Headers.Add("X-Cloud-Study-Id", result.CloudStudyId);
Response.Headers.Add("X-Processed-At", result.ProcessedAt.ToString("O"));
return File(fileBytes, "application/dicom", $"{result.CloudStudyId}.dcm");
}
finally
{
if (System.IO.File.Exists(tempInput))
System.IO.File.Delete(tempInput);
if (System.IO.File.Exists(tempOutput))
System.IO.File.Delete(tempOutput);
}
}
[HttpPost("batch-prepare")]
public async Task<IActionResult> PrepareBatchForCloud([FromBody] BatchPrepareRequest request)
{
var pipeline = new TeleradiologyAnonymizationPipeline(
request.OrganizationId,
Path.Combine(Path.GetTempPath(), "staging"),
Path.Combine(Path.GetTempPath(), "mappings"));
var batch = await pipeline.ProcessStudyForRemoteReading(
request.StudyDirectory,
request.Priority);
return Ok(new
{
batch.BatchId,
batch.Success,
batch.TotalFiles,
batch.SuccessfulFiles,
batch.ManifestPath,
StagingDirectory = Path.GetDirectoryName(batch.ManifestPath)
});
}
}
public class BatchPrepareRequest
{
public string OrganizationId { get; set; }
public string StudyDirectory { get; set; }
public string Priority { get; set; } = "ROUTINE";
}
خدمة إعادة التعرف
تنفيذ يضمن إعادة التعرف على تقارير القراءة:
public class ReidentificationService
{
private readonly string _mappingDirectory;
public ReidentificationService(string mappingDirectory)
{
_mappingDirectory = mappingDirectory;
}
public ReidentificationResult Reidentify(string batchId, string cloudStudyId)
{
var result = new ReidentificationResult();
try
{
string mappingFile = Path.Combine(_mappingDirectory, $"{batchId}_mapping.json");
if (!File.Exists(mappingFile))
{
result.Success = false;
result.ErrorMessage = "Mapping file not found";
return result;
}
string json = File.ReadAllText(mappingFile);
var mapping = JsonSerializer.Deserialize<BatchMapping>(json);
// Find original identifiers
var fileMapping = mapping.FileMappings
.FirstOrDefault(f => f.CloudStudyId == cloudStudyId);
if (fileMapping == null)
{
result.Success = false;
result.ErrorMessage = "Study not found in mapping";
return result;
}
result.OriginalPatientId = fileMapping.OriginalPatientId;
result.OriginalStudyUID = fileMapping.OriginalStudyUID;
result.Success = true;
}
catch (Exception ex)
{
result.Success = false;
result.ErrorMessage = ex.Message;
}
return result;
}
}
public class BatchMapping
{
public string BatchId { get; set; }
public DateTime SubmittedAt { get; set; }
public Dictionary<string, string> StudyMappings { get; set; }
public List<FileMapping> FileMappings { get; set; }
}
public class FileMapping
{
public string OriginalPath { get; set; }
public string CloudPath { get; set; }
public string OriginalStudyUID { get; set; }
public string CloudStudyId { get; set; }
public string OriginalPatientId { get; set; }
}
public class ReidentificationResult
{
public bool Success { get; set; }
public string OriginalPatientId { get; set; }
public string OriginalStudyUID { get; set; }
public string ErrorMessage { get; set; }
}
أفضل الممارسات لـ Cloud PACS Anonymization
- تخزين خرائط آمنة: الحفاظ على ملفات خريط الهوية مشفرة ومفصلة عن البيانات المجهولة
- استخدام الهوية المتسقة: تأكد من أن نفس المريض/الدراسة يحصل على نفس معرف مجهول عبر التحميلات
- التسجيل في مراجعة التنفيذ: تتبع جميع عمليات الهوية وإعادة التعرف عليها
- اختبار بعناية: تأكد من عدم تسرب PHI من خلال العلامات الخاصة أو البيانات المدمجة
- أمن الشبكة في الاعتبار: استخدم TLS لجميع التحميلات السحابية والتخزين المشفر عند الاسترخاء
استنتاجات
يتطلب تجسيد ملفات DICOM لـ Cloud PACS و teleradiology المعالجة الدقيقة لتحديدات المرضى مع الحفاظ على سلامة الدراسة للقراءة عن بعد. Aspose.Medical for .NET يوفر الأساس لبناء أنابيب الهوية الآمنة التي تتكامل مع Azure و AWS وغيرها من منصات السحابة.
للحصول على مزيد من المعلومات حول DICOM المجهول، يرجى زيارة الوسوم - الوثائق الطبية.
More in this category
- إعداد مجموعات DICOM للذكاء الاصطناعي والتعلم الآلي مع Aspose.Medical
- بناء خدمة DICOM Anonymization Microservice في ASP.NET Core
- لماذا يهم DICOM المجهول لـ HIPAA و GDPR في تدفقات العمل .NET
- ملفات تعريف الخصوصية المخصصة التي تتكيف مع سياسات المستشفى الخاصة بك
- تخزين بيانات DICOM في قواعد البيانات SQL و NoSQL باستخدام C#