Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@
// -------------------------------------------------------------------------------------------------

using System;
using System.CommandLine;
using System.CommandLine.Invocation;
using System.Threading.Tasks;
using CommandLine;

namespace Microsoft.Health.Dicom.Anonymizer.CommandLineTool
{
Expand All @@ -27,8 +28,107 @@ public static async Task<int> Main(string[] args)

public static async Task ExecuteCommandsAsync(string[] args)
{
await Parser.Default.ParseArguments<AnonymizerOptions>(args)
.MapResult(async options => await AnonymizerLogic.AnonymizeAsync(options).ConfigureAwait(false), _ => Task.FromResult(1)).ConfigureAwait(false);
var inputFileOption = new Option<string>(
new[] { "-i", "--inputFile" },
"Input DICOM file");

var outputFileOption = new Option<string>(
new[] { "-o", "--outputFile" },
"Output DICOM file");

var configFileOption = new Option<string>(
new[] { "-c", "--configFile" },
() => "configuration.json",
"Anonymization configuration file path.");

var inputFolderOption = new Option<string>(
new[] { "-I", "--inputFolder" },
"Input folder");

var outputFolderOption = new Option<string>(
new[] { "-O", "--outputFolder" },
"Output folder");

var validateInputOption = new Option<bool>(
"--validateInput",
"Validate input DICOM data items.");

var validateOutputOption = new Option<bool>(
"--validateOutput",
"Validate output DICOM data items.");

var rootCommand = new RootCommand("DICOM Data Anonymization Tool");
rootCommand.AddOption(inputFileOption);
rootCommand.AddOption(outputFileOption);
rootCommand.AddOption(configFileOption);
rootCommand.AddOption(inputFolderOption);
rootCommand.AddOption(outputFolderOption);
rootCommand.AddOption(validateInputOption);
rootCommand.AddOption(validateOutputOption);

Exception thrownException = null;

rootCommand.SetHandler(async (context) =>
{
try
{
var inputFile = context.ParseResult.GetValueForOption(inputFileOption);
var outputFile = context.ParseResult.GetValueForOption(outputFileOption);
var configFile = context.ParseResult.GetValueForOption(configFileOption);
var inputFolder = context.ParseResult.GetValueForOption(inputFolderOption);
var outputFolder = context.ParseResult.GetValueForOption(outputFolderOption);
var validateInput = context.ParseResult.GetValueForOption(validateInputOption);
var validateOutput = context.ParseResult.GetValueForOption(validateOutputOption);

// Validate command-line argument combinations
bool hasInputFile = !string.IsNullOrEmpty(inputFile);
bool hasOutputFile = !string.IsNullOrEmpty(outputFile);
bool hasInputFolder = !string.IsNullOrEmpty(inputFolder);
bool hasOutputFolder = !string.IsNullOrEmpty(outputFolder);

// Check for invalid combinations
if ((hasInputFile && !hasOutputFile) ||
(!hasInputFile && hasOutputFile) ||
(hasInputFolder && !hasOutputFolder) ||
(!hasInputFolder && hasOutputFolder) ||
(hasInputFile && hasInputFolder) ||
(hasOutputFile && hasOutputFolder))
{
throw new ArgumentException("Invalid parameters. Please specify inputFile (or inputFolder) and outputFile (or outputFolder) at the same time.\r\nSamples:\r\n [-i inputFile -o outputFile]\r\nor\r\n [-I inputFolder -O outputFolder]");
}

if (!hasInputFile && !hasInputFolder)
{
throw new ArgumentException("Invalid parameters. Please specify inputFile (or inputFolder) and outputFile (or outputFolder) at the same time.\r\nSamples:\r\n [-i inputFile -o outputFile]\r\nor\r\n [-I inputFolder -O outputFolder]");
}

var options = new AnonymizerOptions
{
InputFile = inputFile,
OutputFile = outputFile,
ConfigurationFilePath = configFile,
InputFolder = inputFolder,
OutputFolder = outputFolder,
ValidateInput = validateInput,
ValidateOutput = validateOutput,
};

await AnonymizerLogic.AnonymizeAsync(options).ConfigureAwait(false);
}
catch (Exception ex)
{
thrownException = ex;
throw;
}
});

var result = await rootCommand.InvokeAsync(args);

// For test compatibility, re-throw the exception
if (thrownException != null)
{
throw thrownException;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,31 +3,22 @@
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
// -------------------------------------------------------------------------------------------------

using CommandLine;

namespace Microsoft.Health.Dicom.Anonymizer.CommandLineTool
{
public class AnonymizerOptions
{
[Option('i', "inputFile", Required = false, HelpText = "Input DICOM file")]
public string InputFile { get; set; }

[Option('o', "outputFile", Required = false, HelpText = "Output DICOM file")]
public string OutputFile { get; set; }

[Option('c', "configFile", Required = false, Default = "configuration.json", HelpText = "Anonymization configuration file path.")]
public string ConfigurationFilePath { get; set; }

[Option('I', "inputFolder", Required = false, HelpText = "Input folder")]
public string InputFolder { get; set; }

[Option('O', "outputFolder", Required = false, HelpText = "Output folder")]
public string OutputFolder { get; set; }

[Option("validateInput", Required = false, Default = false, HelpText = "Validate input DICOM data items.")]
public bool ValidateInput { get; set; }

[Option("validateOutput", Required = false, Default = false, HelpText = "Validate output DICOM data items.")]
public bool ValidateOutput { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="CommandLineParser" Version="2.8.0" />
<PackageReference Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
<PackageReference Include="fo-dicom" Version="5.1.1" Exclude="runtime" />
</ItemGroup>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="CommandLineParser" Version="2.9.1" />
<PackageReference Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="9.0.6" />
</ItemGroup>

Expand Down
Original file line number Diff line number Diff line change
@@ -1,40 +1,122 @@
using System;
using System.CommandLine;
using System.CommandLine.Invocation;
using System.IO;
using System.Threading.Tasks;
using CommandLine;
using Microsoft.Extensions.Logging;
using Microsoft.Health.Fhir.Anonymizer.Core;

namespace Microsoft.Health.Fhir.Anonymizer.Tool
{
class Options
public class Program
{
public async static Task<int> Main(string[] args)
{
var inputOption = new Option<string>(
new[] { "-i", "--inputFolder" },
"Folder to locate input resource files.")
{
IsRequired = true
};

var outputOption = new Option<string>(
new[] { "-o", "--outputFolder" },
"Folder to save anonymized resource files.")
{
IsRequired = true
};

var configOption = new Option<string>(
new[] { "-c", "--configFile" },
() => "configuration-sample.json",
"Anonymizer configuration file path.");

var bulkDataOption = new Option<bool>(
new[] { "-b", "--bulkData" },
"Resource file is in bulk data format (.ndjson).");

var skipOption = new Option<bool>(
new[] { "-s", "--skip" },
"Skip existed files in target folder.");

var recursiveOption = new Option<bool>(
new[] { "-r", "--recursive" },
"Process resource files in input folder recursively.");

var verboseOption = new Option<bool>(
new[] { "-v", "--verbose" },
"Provide additional details in processing.");

var validateInputOption = new Option<bool>(
"--validateInput",
"Validate input resources. Details can be found in verbose log.");

var validateOutputOption = new Option<bool>(
"--validateOutput",
"Validate anonymized resources. Details can be found in verbose log.");

var rootCommand = new RootCommand("FHIR Data Anonymization Tool");
rootCommand.AddOption(inputOption);
rootCommand.AddOption(outputOption);
rootCommand.AddOption(configOption);
rootCommand.AddOption(bulkDataOption);
rootCommand.AddOption(skipOption);
rootCommand.AddOption(recursiveOption);
rootCommand.AddOption(verboseOption);
rootCommand.AddOption(validateInputOption);
rootCommand.AddOption(validateOutputOption);

rootCommand.SetHandler(async (context) =>
{
var inputFolder = context.ParseResult.GetValueForOption(inputOption);
var outputFolder = context.ParseResult.GetValueForOption(outputOption);
var configFile = context.ParseResult.GetValueForOption(configOption);
var bulkData = context.ParseResult.GetValueForOption(bulkDataOption);
var skip = context.ParseResult.GetValueForOption(skipOption);
var recursive = context.ParseResult.GetValueForOption(recursiveOption);
var verbose = context.ParseResult.GetValueForOption(verboseOption);
var validateInput = context.ParseResult.GetValueForOption(validateInputOption);
var validateOutput = context.ParseResult.GetValueForOption(validateOutputOption);

var options = new Options
{
InputFolder = inputFolder,
OutputFolder = outputFolder,
ConfigurationFilePath = configFile,
IsBulkData = bulkData,
SkipExistedFile = skip,
IsRecursive = recursive,
IsVerbose = verbose,
ValidateInput = validateInput,
ValidateOutput = validateOutput
};

await AnonymizationLogic.AnonymizeAsync(options).ConfigureAwait(false);
});

try
{
return await rootCommand.InvokeAsync(args);
}
catch (Exception ex)
{
Console.Error.WriteLine($"Error: {ex.Message}");
return 1;
}
}
}

// Keep the existing Options class for internal use
internal class Options
{
[Option('i', "inputFolder", Required = true, HelpText = "Folder to locate input resource files.")]
public string InputFolder { get; set; }
[Option('o', "outputFolder", Required = true, HelpText = "Folder to save anonymized resource files.")]
public string OutputFolder { get; set; }
[Option('c', "configFile", Required = false, Default = "configuration-sample.json", HelpText = "Anonymizer configuration file path.")]
public string ConfigurationFilePath { get; set; }
[Option('b', "bulkData", Required = false, Default = false, HelpText = "Resource file is in bulk data format (.ndjson).")]
public bool IsBulkData { get; set; }
[Option('s', "skip", Required = false, Default = false, HelpText = "Skip existed files in target folder.")]
public bool SkipExistedFile { get; set; }
[Option('r', "recursive", Required = false, Default = false, HelpText = "Process resource files in input folder recursively.")]
public bool IsRecursive { get; set; }
[Option('v', "verbose", Required = false, Default = false, HelpText = "Provide additional details in processing.")]
public bool IsVerbose { get; set; }
[Option("validateInput", Required = false, Default = false, HelpText = "Validate input resources. Details can be found in verbose log.")]
public bool ValidateInput { get; set; }
[Option("validateOutput", Required = false, Default = false, HelpText = "Validate anonymized resources. Details can be found in verbose log.")]
public bool ValidateOutput { get; set; }
}

public class Program
{
public async static Task Main(string[] args)
{
await CommandLine.Parser.Default.ParseArguments<Options>(args)
.MapResult(async options => await AnonymizationLogic.AnonymizeAsync(options).ConfigureAwait(false), _ => Task.FromResult(1)).ConfigureAwait(false);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="CommandLineParser" Version="2.9.1" />
<PackageReference Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="9.0.6" />
</ItemGroup>

Expand Down