Skip to content

Commit

Permalink
Merge pull request #159 from microsoft/dev
Browse files Browse the repository at this point in the history
Upgrade to 3.1.0
  • Loading branch information
sowu880 authored Dec 3, 2021
2 parents 978c679 + 3301ee8 commit 7f78d7c
Show file tree
Hide file tree
Showing 34 changed files with 664 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

using Dicom;
using Microsoft.Health.Dicom.Anonymizer.Core.Models;
using Microsoft.Health.Dicom.Anonymizer.Core.Processors;
using Xunit;

namespace Microsoft.Health.Dicom.Anonymizer.Core.UnitTests
Expand Down Expand Up @@ -60,6 +61,23 @@ public void GivenInvalidDicomDataSet_SetValidateInput_ExceptionWillBeThrown()
Assert.Throws<DicomValidationException>(() => engine.AnonymizeDataset(Dataset));
}

[Fact]
public void GivenDicomDataSet_UsingCustomProcessor_WhenAnonymize_ValidDicomDatasetWillBeReturned()
{
var processorFactory = new CustomProcessorFactory();
processorFactory.RegisterProcessors(typeof(MaskProcessor));

var engine = new AnonymizerEngine("./TestConfigurations/configuration-custom.json", processorFactory: processorFactory);

engine.AnonymizeDataset(Dataset);

var dicomFile = DicomFile.Open("DicomResults/custom.dcm");
foreach (var item in Dataset)
{
Assert.Equal(((DicomElement)item).Get<string>(), dicomFile.Dataset.GetString(item.Tag));
}
}

[Fact]
public void GivenDicomDataSet_SetValidationOutput_WhenAnonymize_ValidDicomDatasetWillBeReturned()
{
Expand Down
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// -------------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
// -------------------------------------------------------------------------------------------------

using System;
using System.Linq;
using Dicom;
using Microsoft.Health.Dicom.Anonymizer.Core.Models;
using Microsoft.Health.Dicom.Anonymizer.Core.Processors;
using Newtonsoft.Json.Linq;

namespace Microsoft.Health.Dicom.Anonymizer.Core.UnitTests
{
public class MaskProcessor : IAnonymizerProcessor
{
private int _maskedLength;

public MaskProcessor(JObject setting)
{
_maskedLength = int.Parse(setting.GetValue("maskedLength", StringComparison.OrdinalIgnoreCase).ToString());
}

public bool IsSupported(DicomItem item)
{
if (item is DicomStringElement)
{
return true;
}

return false;
}

public static MaskProcessor Create(JObject setting)
{
return new MaskProcessor(setting);
}

public void Process(DicomDataset dicomDataset, DicomItem item, ProcessContext context)
{
var mask = new string('*', this._maskedLength);
if (item is DicomStringElement)
{
var values = ((DicomStringElement)item).Get<string[]>().Where(x => !string.IsNullOrEmpty(x)).Select(x => x.Length > _maskedLength?mask + x[this._maskedLength..] : mask);
if (values.Count() != 0)
{
dicomDataset.AddOrUpdate(item.ValueRepresentation, item.Tag, values.ToArray());
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// -------------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
// -------------------------------------------------------------------------------------------------

using Microsoft.Health.Dicom.Anonymizer.Core.Exceptions;
using Microsoft.Health.Dicom.Anonymizer.Core.Processors;
using Newtonsoft.Json.Linq;
using Xunit;

namespace Microsoft.Health.Dicom.Anonymizer.Core.UnitTests.Processors
{
public class CustomProcessorFactoryUnitTests
{
[Fact]
public void GivenADicomProcessorFactory_GivenMethod_CorrectProcessorWillBeReturned()
{
var factory = new CustomProcessorFactory();
Assert.Equal(typeof(PerturbProcessor), factory.CreateProcessor("perturb", new JObject()).GetType());
Assert.Equal(typeof(EncryptProcessor), factory.CreateProcessor("encrypt", new JObject()).GetType());
Assert.Equal(typeof(RedactProcessor), factory.CreateProcessor("redact", new JObject()).GetType());
Assert.Equal(typeof(RefreshUIDProcessor), factory.CreateProcessor("refreshUID", new JObject()).GetType());
Assert.Equal(typeof(SubstituteProcessor), factory.CreateProcessor("substitute", new JObject()).GetType());
Assert.Equal(typeof(RemoveProcessor), factory.CreateProcessor("remove", new JObject()).GetType());
Assert.Equal(typeof(DateShiftProcessor), factory.CreateProcessor("dateshift", new JObject()).GetType());
Assert.Equal(typeof(CryptoHashProcessor), factory.CreateProcessor("cryptohash", new JObject()).GetType());
}

[Fact]
public void GivenADicomProcessorFactory_AddingCustomProcessor_GivenMethod_CorrectProcessorWillBeReturned()
{
var factory = new CustomProcessorFactory();
factory.RegisterProcessors(typeof(MaskProcessor), typeof(MockAnonymizerProcessor));
Assert.Equal(typeof(MaskProcessor), factory.CreateProcessor("mask", JObject.Parse("{\"maskedLength\":\"3\"}")).GetType());
Assert.Equal(typeof(MockAnonymizerProcessor), factory.CreateProcessor("mockanonymizer", new JObject()).GetType());
}

[Fact]
public void GivenADicomProcessorFactory_AddingBuildInProcessor_ExceptionWillBeThrown()
{
var factory = new CustomProcessorFactory();
Assert.Throws<AddCustomProcessorException>(() => factory.RegisterProcessors(typeof(RedactProcessor)));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// -------------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
// -------------------------------------------------------------------------------------------------

using Microsoft.Health.Dicom.Anonymizer.Core.Exceptions;
using Microsoft.Health.Dicom.Anonymizer.Core.Processors;
using Newtonsoft.Json.Linq;
using Xunit;

namespace Microsoft.Health.Dicom.Anonymizer.Core.UnitTests.Processors
{
public class DicomProcessorFactoryUnitTests
{
[Fact]
public void GivenADicomProcessorFactory_GivenMethod_CorrectProcessorWillBeReturned()
{
var factory = new DicomProcessorFactory();
Assert.Equal(typeof(PerturbProcessor), factory.CreateProcessor("perturb", new JObject()).GetType());
Assert.Equal(typeof(EncryptProcessor), factory.CreateProcessor("encrypt", new JObject()).GetType());
Assert.Equal(typeof(RedactProcessor), factory.CreateProcessor("redact", new JObject()).GetType());
Assert.Equal(typeof(RefreshUIDProcessor), factory.CreateProcessor("refreshUID", new JObject()).GetType());
Assert.Equal(typeof(SubstituteProcessor), factory.CreateProcessor("substitute", new JObject()).GetType());
Assert.Equal(typeof(RemoveProcessor), factory.CreateProcessor("remove", new JObject()).GetType());
Assert.Equal(typeof(DateShiftProcessor), factory.CreateProcessor("dateshift", new JObject()).GetType());
Assert.Equal(typeof(CryptoHashProcessor), factory.CreateProcessor("cryptohash", new JObject()).GetType());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// -------------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
// -------------------------------------------------------------------------------------------------

using System;
using Dicom;
using Microsoft.Health.Dicom.Anonymizer.Core.Models;
using Microsoft.Health.Dicom.Anonymizer.Core.Processors;
using Newtonsoft.Json.Linq;

namespace Microsoft.Health.Dicom.Anonymizer.Core.UnitTests.Processors
{
public class MockAnonymizerProcessor : IAnonymizerProcessor
{
public MockAnonymizerProcessor(JObject settintgs)
{
}

public bool IsSupported(DicomItem item)
{
throw new NotImplementedException();
}

public void Process(DicomDataset dicomDataset, DicomItem item, ProcessContext context)
{
throw new NotImplementedException();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"rules": [
{
"tag": "RetrieveAETitle",
"method": "mask",
"params": { "maskedLength": "3" }
}
],
"defaultSettings": {
"perturb": {
"span": 1,
"roundTo": 2,
"rangeType": "Proportional"
},
"dateShift": {
"dateShiftKey": "123",
"dateShiftScope": "SeriesInstance",
"dateShiftRange": 50
},
"cryptoHash": {
"cryptoHashKey": "123"
},
"redact": {
"enablePartialAgesForRedact": false,
"enablePartialDatesForRedact": false
},
"encrypt": {
"encryptKey": "123456781234567812345678"
},
"substitute": {
"replaceWith": "ANONYMOUS"
}
},
"customSettings": {
"perturbCustomerSetting": {
"span": 1,
"roundTo": 2,
"rangeType": "Proportional"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,18 @@ public class AnonymizerEngine
private readonly AnonymizerEngineOptions _anonymizerSettings;
private readonly AnonymizerRule[] _rules;

public AnonymizerEngine(string configFilePath = "configuration.json", AnonymizerEngineOptions anonymizerSettings = null, IAnonymizerRuleFactory ruleFactory = null)
: this(AnonymizerConfigurationManager.CreateFromJsonFile(configFilePath), anonymizerSettings, ruleFactory)
public AnonymizerEngine(string configFilePath = "configuration.json", AnonymizerEngineOptions anonymizerSettings = null, IAnonymizerRuleFactory ruleFactory = null, IAnonymizerProcessorFactory processorFactory = null)
: this(AnonymizerConfigurationManager.CreateFromJsonFile(configFilePath), anonymizerSettings, ruleFactory, processorFactory)
{
}

public AnonymizerEngine(AnonymizerConfigurationManager configurationManager, AnonymizerEngineOptions anonymizerSettings = null, IAnonymizerRuleFactory ruleFactory = null)
public AnonymizerEngine(AnonymizerConfigurationManager configurationManager, AnonymizerEngineOptions anonymizerSettings = null, IAnonymizerRuleFactory ruleFactory = null, IAnonymizerProcessorFactory processorFactory = null)
{
EnsureArg.IsNotNull(configurationManager, nameof(configurationManager));

_anonymizerSettings = anonymizerSettings ?? new AnonymizerEngineOptions();
ruleFactory ??= new AnonymizerRuleFactory(configurationManager.Configuration, new DicomProcessorFactory());

ruleFactory ??= new AnonymizerRuleFactory(configurationManager.Configuration, processorFactory ?? new DicomProcessorFactory());
_rules = ruleFactory.CreateDicomAnonymizationRules(configurationManager.Configuration.RuleContent);
_logger.LogDebug("Successfully initialized anonymizer engine.");
}
Expand Down
6 changes: 6 additions & 0 deletions DICOM/src/Microsoft.Health.Dicom.Anonymizer.Core/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
// -------------------------------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Health.Dicom.Anonymizer.Core.Models;

namespace Microsoft.Health.Dicom.Anonymizer.Core
{
internal static class Constants
Expand All @@ -11,5 +16,6 @@ internal static class Constants
internal const string MethodKey = "method";
internal const string Parameters = "params";
internal const string RuleSetting = "setting";
internal static readonly HashSet<string> BuiltInMethods = Enum.GetNames(typeof(AnonymizerMethod)).ToHashSet(StringComparer.InvariantCultureIgnoreCase);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// -------------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
// -------------------------------------------------------------------------------------------------

using System;

namespace Microsoft.Health.Dicom.Anonymizer.Core.Exceptions
{
public class AddCustomProcessorException : DicomAnonymizationException
{
public AddCustomProcessorException(DicomAnonymizationErrorCode errorCode, string message)
: base(errorCode, message)
{
}

public AddCustomProcessorException(DicomAnonymizationErrorCode errorCode, string message, Exception innerException)
: base(errorCode, message, innerException)
{
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,6 @@ public enum DicomAnonymizationErrorCode
InvalidRuleSettings = 1006,

UnsupportedAnonymizationMethod = 1101,
AddCustomProcessorFailed = 1102,
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// -------------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
// -------------------------------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Linq;
using EnsureThat;
using Microsoft.Health.Dicom.Anonymizer.Core.Exceptions;
using Newtonsoft.Json.Linq;

namespace Microsoft.Health.Dicom.Anonymizer.Core.Processors
{
public class CustomProcessorFactory : DicomProcessorFactory
{
private readonly Dictionary<string, Type> _customProcessors = new Dictionary<string, Type>(StringComparer.OrdinalIgnoreCase) { };

public override IAnonymizerProcessor CreateProcessor(string method, JObject settingObject = null)
{
EnsureArg.IsNotNullOrEmpty(method, nameof(method));

if (Constants.BuiltInMethods.Contains(method))
{
return base.CreateProcessor(method, settingObject);
}

return CreateCustomProcessor(method, settingObject);
}

public void RegisterProcessors(params Type[] processors)
{
if (processors != null)
{
RegisterProcessors(processors.AsEnumerable());
}
}

public void RegisterProcessors(IEnumerable<Type> processors)
{
foreach (Type processor in processors)
{
var method = GetMethodName(processor.ToString());
if (Constants.BuiltInMethods.Contains(method))
{
throw new AddCustomProcessorException(DicomAnonymizationErrorCode.AddCustomProcessorFailed, $"Anonymization method {method} is a built-in method. Please add custom processor with unique method name.");
}

_customProcessors.Add(method, processor);
}
}

private IAnonymizerProcessor CreateCustomProcessor(string method, JObject settingObject = null)
{
EnsureArg.IsNotNullOrEmpty(method, nameof(method));

if (_customProcessors.ContainsKey(method))
{
return (IAnonymizerProcessor)Activator.CreateInstance(
_customProcessors[method],
new object[] { settingObject });
}

return null;
}

private string GetMethodName(string processor)
{
return processor.Split(".").Last().Replace("Processor", string.Empty);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,17 @@
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
// -------------------------------------------------------------------------------------------------

using EnsureThat;
using Newtonsoft.Json.Linq;

namespace Microsoft.Health.Dicom.Anonymizer.Core.Processors
{
public class DicomProcessorFactory : IAnonymizerProcessorFactory
{
public IAnonymizerProcessor CreateProcessor(string method, JObject settingObject = null)
public virtual IAnonymizerProcessor CreateProcessor(string method, JObject settingObject = null)
{
EnsureArg.IsNotNullOrEmpty(method, nameof(method));

return method.ToLower() switch
{
"perturb" => new PerturbProcessor(settingObject),
Expand Down
Loading

0 comments on commit 7f78d7c

Please sign in to comment.