Skip to content

Commit

Permalink
gh-418 Update CLI to support --plugins and update API doc
Browse files Browse the repository at this point in the history
Signed-off-by: Victor Chang <[email protected]>
  • Loading branch information
mocsharp committed Aug 3, 2023
1 parent 2ca4c2e commit 039e2e9
Show file tree
Hide file tree
Showing 6 changed files with 102 additions and 6 deletions.
24 changes: 22 additions & 2 deletions docs/api/rest/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,11 @@ curl --location --request GET 'http://localhost:5000/config/ae'
"grouping": "0020,000D",
"timeout": 5,
"ignoredSopClasses": ["1.2.840.10008.5.1.4.1.1.1.1"],
"allowedSopClasses": ["1.2.840.10008.5.1.4.1.1.1.2"]
"allowedSopClasses": ["1.2.840.10008.5.1.4.1.1.1.2"],
"pluginAssemblies": [
"Monai.Deploy.InformaticsGateway.Test.Plugins.TestInputDataPluginAddWorkflow, Monai.Deploy.InformaticsGateway.Test.Plugins",
"Monai.Deploy.InformaticsGateway.Test.Plugins.TestInputDataPluginModifyDicomFile, Monai.Deploy.InformaticsGateway.Test.Plugins"
]
},
{
"name": "liver-seg",
Expand Down Expand Up @@ -151,6 +155,10 @@ curl --location --request POST 'http://localhost:5000/config/ae/' \
"timeout": 5,
"workflows": [
"3f6a08a1-0dea-44e9-ab82-1ff1adf43a8e"
],
"pluginAssemblies": [
"Monai.Deploy.InformaticsGateway.Test.Plugins.TestInputDataPluginAddWorkflow, Monai.Deploy.InformaticsGateway.Test.Plugins",
"Monai.Deploy.InformaticsGateway.Test.Plugins.TestInputDataPluginModifyDicomFile, Monai.Deploy.InformaticsGateway.Test.Plugins"
]
}
}'
Expand All @@ -162,7 +170,11 @@ curl --location --request POST 'http://localhost:5000/config/ae/' \
{
"name": "breast-tumor",
"aeTitle": "BREASTV1",
"workflows": ["3f6a08a1-0dea-44e9-ab82-1ff1adf43a8e"]
"workflows": ["3f6a08a1-0dea-44e9-ab82-1ff1adf43a8e"],
"pluginAssemblies": [
"Monai.Deploy.InformaticsGateway.Test.Plugins.TestInputDataPluginAddWorkflow, Monai.Deploy.InformaticsGateway.Test.Plugins",
"Monai.Deploy.InformaticsGateway.Test.Plugins.TestInputDataPluginModifyDicomFile, Monai.Deploy.InformaticsGateway.Test.Plugins"
]
}
```

Expand Down Expand Up @@ -210,6 +222,10 @@ curl --location --request PUT 'http://localhost:5000/config/ae/' \
"timeout": 3,
"workflows": [
"3f6a08a1-0dea-44e9-ab82-1ff1adf43a8e"
],
"pluginAssemblies": [
"Monai.Deploy.InformaticsGateway.Test.Plugins.TestInputDataPluginAddWorkflow, Monai.Deploy.InformaticsGateway.Test.Plugins",
"Monai.Deploy.InformaticsGateway.Test.Plugins.TestInputDataPluginModifyDicomFile, Monai.Deploy.InformaticsGateway.Test.Plugins"
]
}
}'
Expand All @@ -222,6 +238,10 @@ curl --location --request PUT 'http://localhost:5000/config/ae/' \
"name": "breast-tumor",
"aeTitle": "BREASTV1",
"workflows": ["3f6a08a1-0dea-44e9-ab82-1ff1adf43a8e"],
"pluginAssemblies": [
"Monai.Deploy.InformaticsGateway.Test.Plugins.TestInputDataPluginAddWorkflow, Monai.Deploy.InformaticsGateway.Test.Plugins",
"Monai.Deploy.InformaticsGateway.Test.Plugins.TestInputDataPluginModifyDicomFile, Monai.Deploy.InformaticsGateway.Test.Plugins"
],
"timeout": 3
}
```
Expand Down
13 changes: 13 additions & 0 deletions docs/setup/setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,19 @@ mig-cli aet add -a BrainAET -grouping 0020,000E, -t 30
The command creates a new listening AE Title with AE Title `BrainAET`. The listening AE Title
will group instances by the Series Instance UID (0020,000E) with a timeout value of 30 seconds.


### Optional: Input Data Plug-ins

Each listening AE Title may be configured with zero or more plug-ins to maniulate incoming DICOM files before saving to the storage
service and dispatching a workflow request. To include input data plug-ins, first create your plug-ins by implementing the
[IInputDataPlugin](xref:Monai.Deploy.InformaticsGateway.Api.IInputDataPlugin) interface and then use `-p` argument with the fully
qualified type name with the `mig-cli aet add` command. For example, the following command adds `MyNamespace.AnonymizePlugin`
and `MyNamespace.FixSeriesData` plug-ins from the `MyNamespace.Plugins` assembly file.

```bash
mig-cli aet add -a BrainAET -grouping 0020,000E, -t 30 -p "MyNamespace.AnonymizePlugin, MyNamespace.Plugins" "MyNamespace.FixSeriesData, MyNamespace.Plugins"
```

> [!Note]
> `-grouping` is optional, with a default value of 0020,000D.
> `-t` is optional, with a default value of 5 seconds.
Expand Down
3 changes: 3 additions & 0 deletions src/Api/MonaiApplicationEntity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@ public class MonaiApplicationEntity : MongoDBEntityBase
/// </summary>
public List<string> Workflows { get; set; } = default!;

/// <summary>
/// Optional list of data input plug-in type names to be executed by the <see cref="IInputDataPluginEngine"/>.
/// </summary>
public List<string> PluginAssemblies { get; set; } = default!;

/// <summary>
Expand Down
19 changes: 17 additions & 2 deletions src/CLI/Commands/AetCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,12 @@ private void SetupAddAetCommand()
IsRequired = false,
};
addCommand.AddOption(allowedSopsOption);
var plugins = new Option<List<string>>(new string[] { "-p", "--plugins" }, description: "A space separated list of fully qualified type names of the plug-ins (surround each plug-in with double quotes)")
{
AllowMultipleArgumentsPerToken = true,
IsRequired = false,
};
addCommand.AddOption(plugins);

addCommand.Handler = CommandHandler.Create<MonaiApplicationEntity, IHost, bool, CancellationToken>(AddAeTitlehandlerAsync);
}
Expand Down Expand Up @@ -130,6 +136,12 @@ private void SetupEditAetCommand()
IsRequired = false,
};
addCommand.AddOption(allowedSopsOption);
var plugins = new Option<List<string>>(new string[] { "-p", "--plugins" }, description: "A space separated list of fully qualified type names of the plug-ins (surround each plug-in with double quotes)")
{
AllowMultipleArgumentsPerToken = true,
IsRequired = false,
};
addCommand.AddOption(plugins);

addCommand.Handler = CommandHandler.Create<MonaiApplicationEntity, IHost, bool, CancellationToken>(EditAeTitleHandlerAsync);
}
Expand Down Expand Up @@ -274,8 +286,7 @@ private async Task<int> AddAeTitlehandlerAsync(MonaiApplicationEntity entity, IH
}
if (result.AllowedSopClasses.Any())
{
logger.MonaiAeAllowedSops(string.Join(',', result.AllowedSopClasses));
logger.AcceptedSopClassesWarning();
logger.MonaiAePlugins(string.Join(',', result.AllowedSopClasses));
}
}
catch (ConfigurationException ex)
Expand Down Expand Up @@ -330,6 +341,10 @@ private async Task<int> EditAeTitleHandlerAsync(MonaiApplicationEntity entity, I
logger.MonaiAeAllowedSops(string.Join(',', result.AllowedSopClasses));
logger.AcceptedSopClassesWarning();
}
if (result.AllowedSopClasses.Any())
{
logger.MonaiAePlugins(string.Join(',', result.AllowedSopClasses));
}
}
catch (ConfigurationException ex)
{
Expand Down
3 changes: 3 additions & 0 deletions src/CLI/Logging/Log.cs
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,9 @@ public static partial class Log
[LoggerMessage(EventId = 30061, Level = LogLevel.Critical, Message = "Error updating SCP Application Entity {aeTitle}: {message}")]
public static partial void ErrorUpdatingMonaiApplicationEntity(this ILogger logger, string aeTitle, string message);

[LoggerMessage(EventId = 30062, Level = LogLevel.Information, Message = "\tPlug-ins: {plugins}")]
public static partial void MonaiAePlugins(this ILogger logger, string plugins);

// Docker Runner
[LoggerMessage(EventId = 31000, Level = LogLevel.Debug, Message = "Checking for existing {applicationName} ({version}) containers...")]
public static partial void CheckingExistingAppContainer(this ILogger logger, string applicationName, string version);
Expand Down
46 changes: 44 additions & 2 deletions src/CLI/Test/AetCommandTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ public async Task AetAdd_Command()
{
Name = result.CommandResult.Children[0].Tokens[0].Value,
AeTitle = result.CommandResult.Children[1].Tokens[0].Value,
Workflows = result.CommandResult.Children[2].Tokens.Select(p => p.Value).ToList()
Workflows = result.CommandResult.Children[2].Tokens.Select(p => p.Value).ToList(),
};
Assert.Equal("MyName", entity.Name);
Assert.Equal("MyAET", entity.AeTitle);
Expand All @@ -123,6 +123,44 @@ public async Task AetAdd_Command()
It.IsAny<CancellationToken>()), Times.Once());
}

[Fact(DisplayName = "aet add comand with plug-ins")]
public async Task AetAdd_Command_WithPlugins()
{
var command = "aet add -n MyName -a MyAET --workflows App MyCoolApp TheApp --plugins \"PluginTypeA\" \"PluginTypeB\"";
var result = _paser.Parse(command);
Assert.Equal(ExitCodes.Success, result.Errors.Count);

var entity = new MonaiApplicationEntity()
{
Name = result.CommandResult.Children[0].Tokens[0].Value,
AeTitle = result.CommandResult.Children[1].Tokens[0].Value,
Workflows = result.CommandResult.Children[2].Tokens.Select(p => p.Value).ToList(),
PluginAssemblies = result.CommandResult.Children[3].Tokens.Select(p => p.Value).ToList(),
};
Assert.Equal("MyName", entity.Name);
Assert.Equal("MyAET", entity.AeTitle);
Assert.Collection(entity.Workflows,
item => item.Equals("App"),
item => item.Equals("MyCoolApp"),
item => item.Equals("TheApp"));
Assert.Collection(entity.PluginAssemblies,
item => item.Equals("PluginTypeA"),
item => item.Equals("PluginTypeB"));

_informaticsGatewayClient.Setup(p => p.MonaiScpAeTitle.Create(It.IsAny<MonaiApplicationEntity>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(entity);

int exitCode = await _paser.InvokeAsync(command);

Assert.Equal(ExitCodes.Success, exitCode);

_informaticsGatewayClient.Verify(p => p.ConfigureServiceUris(It.IsAny<Uri>()), Times.Once());
_informaticsGatewayClient.Verify(
p => p.MonaiScpAeTitle.Create(
It.Is<MonaiApplicationEntity>(o => o.AeTitle == entity.AeTitle && o.Name == entity.Name && Enumerable.SequenceEqual(o.Workflows, entity.Workflows)),
It.IsAny<CancellationToken>()), Times.Once());
}

[Fact(DisplayName = "aet add comand with allowed & ignored SOP classes")]
public async Task AetAdd_Command_AllowedIgnoredSopClasses()
{
Expand Down Expand Up @@ -335,7 +373,7 @@ public async Task AetList_Command_Empty()
[Fact(DisplayName = "aet update command")]
public async Task AetUpdate_Command()
{
var command = "aet update -n MyName --workflows App MyCoolApp TheApp -i A B C -s D E F";
var command = "aet update -n MyName --workflows App MyCoolApp TheApp -i A B C -s D E F -p PlugInAssemblyA PlugInAssemblyB";
var result = _paser.Parse(command);
Assert.Equal(ExitCodes.Success, result.Errors.Count);

Expand All @@ -346,6 +384,7 @@ public async Task AetUpdate_Command()
Workflows = result.CommandResult.Children[1].Tokens.Select(p => p.Value).ToList(),
IgnoredSopClasses = result.CommandResult.Children[2].Tokens.Select(p => p.Value).ToList(),
AllowedSopClasses = result.CommandResult.Children[3].Tokens.Select(p => p.Value).ToList(),
PluginAssemblies = result.CommandResult.Children[4].Tokens.Select(p => p.Value).ToList(),
};

Assert.Equal("MyName", entity.Name);
Expand All @@ -362,6 +401,9 @@ public async Task AetUpdate_Command()
item => item.Equals("A"),
item => item.Equals("B"),
item => item.Equals("C"));
Assert.Collection(entity.PluginAssemblies,
item => item.Equals("PlugInAssemblyA"),
item => item.Equals("PlugInAssemblyB"));

_informaticsGatewayClient.Setup(p => p.MonaiScpAeTitle.Update(It.IsAny<MonaiApplicationEntity>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(entity);
Expand Down

0 comments on commit 039e2e9

Please sign in to comment.