Skip to content

Commit 4aff9cf

Browse files
neildsouthmocsharp
authored andcommitted
adding tag list to configuration
Signed-off-by: Neil South <[email protected]>
1 parent d701f62 commit 4aff9cf

File tree

10 files changed

+202
-38
lines changed

10 files changed

+202
-38
lines changed

src/Api/Storage/RemoteAppExecution.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public class RemoteAppExecution
2828
public string WorkflowInstanceId { get; set; } = string.Empty;
2929
public string CorrelationId { get; set; } = string.Empty;
3030
public string? StudyUid { get; set; }
31-
public string? OutgoingStudyUid { get; set; }
31+
public string? OutgoingUid { get; set; }
3232
public List<DestinationApplicationEntity> ExportDetails { get; set; } = new();
3333
public List<string> Files { get; set; } = new();
3434
public FileExportStatus Status { get; set; }
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* Copyright 2021-2023 MONAI Consortium
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
using System.Collections.Generic;
18+
19+
namespace Monai.Deploy.InformaticsGateway.Configuration
20+
{
21+
public class PluginConfiguration
22+
{
23+
public Dictionary<string, string> Configuration { get; set; } = new();
24+
}
25+
}

src/Database/EntityFramework/Repositories/RemoteAppExecutionRepository.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ public class RemoteAppExecutionRepository : IRemoteAppExecutionRepository, IDisp
3838
private readonly DbSet<RemoteAppExecution> _dataset;
3939
private bool _disposedValue;
4040

41+
42+
// Note. this implementaion (unlike the Mongo one) Does not delete the entries
43+
// so a cleanup routine will have to be implemented to peridoically remove old entries !
44+
4145
public RemoteAppExecutionRepository(
4246
IServiceScopeFactory serviceScopeFactory,
4347
ILogger<PayloadRepository> logger,
@@ -74,7 +78,7 @@ public async Task<int> RemoveAsync(string OriginalStudyUid, CancellationToken ca
7478

7579
return await _retryPolicy.ExecuteAsync(async () =>
7680
{
77-
var result = await _dataset.SingleOrDefaultAsync(p => p.OutgoingStudyUid == OriginalStudyUid).ConfigureAwait(false);
81+
var result = await _dataset.SingleOrDefaultAsync(p => p.OutgoingUid == OriginalStudyUid).ConfigureAwait(false);
7882
if (result is not null)
7983
{
8084
_dataset.Remove(result);
@@ -91,7 +95,7 @@ public async Task<int> RemoveAsync(string OriginalStudyUid, CancellationToken ca
9195

9296
return await _retryPolicy.ExecuteAsync(async () =>
9397
{
94-
var result = await _dataset.SingleOrDefaultAsync(p => p.OutgoingStudyUid == OutgoingStudyUid).ConfigureAwait(false);
98+
var result = await _dataset.SingleOrDefaultAsync(p => p.OutgoingUid == OutgoingStudyUid).ConfigureAwait(false);
9599
if (result is not null)
96100
{
97101
return result;

src/Database/MongoDB/Repositories/RemoteAppExecutionRepository.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ public RemoteAppExecutionRepository(IServiceScopeFactory serviceScopeFactory,
6262
private void CreateIndexes()
6363
{
6464
var options = new CreateIndexOptions { Unique = true, ExpireAfter = TimeSpan.FromDays(7), Name = "RequestTime" };
65-
var indexDefinitionState = Builders<RemoteAppExecution>.IndexKeys.Ascending(_ => _.OutgoingStudyUid);
65+
var indexDefinitionState = Builders<RemoteAppExecution>.IndexKeys.Ascending(_ => _.OutgoingUid);
6666
var indexModel = new CreateIndexModel<RemoteAppExecution>(indexDefinitionState, options);
6767

6868
_collection.Indexes.CreateOne(indexModel);
@@ -83,7 +83,7 @@ public async Task<int> RemoveAsync(string OutgoingStudyUid, CancellationToken ca
8383
{
8484
return await _retryPolicy.ExecuteAsync(async () =>
8585
{
86-
var results = await _collection.DeleteManyAsync(Builders<RemoteAppExecution>.Filter.Where(p => p.OutgoingStudyUid == OutgoingStudyUid), cancellationToken).ConfigureAwait(false);
86+
var results = await _collection.DeleteManyAsync(Builders<RemoteAppExecution>.Filter.Where(p => p.OutgoingUid == OutgoingStudyUid), cancellationToken).ConfigureAwait(false);
8787
return Convert.ToInt32(results.DeletedCount);
8888
}).ConfigureAwait(false);
8989
}
@@ -92,7 +92,7 @@ public async Task<int> RemoveAsync(string OutgoingStudyUid, CancellationToken ca
9292
{
9393
return await _retryPolicy.ExecuteAsync(async () =>
9494
{
95-
return await _collection.Find(p => p.OutgoingStudyUid == OutgoingStudyUid).FirstOrDefaultAsync().ConfigureAwait(false);
95+
return await _collection.Find(p => p.OutgoingUid == OutgoingStudyUid).FirstOrDefaultAsync().ConfigureAwait(false);
9696
}).ConfigureAwait(false);
9797
}
9898

src/InformaticsGateway/Common/IDicomToolkit.cs

100644100755
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@
1515
* limitations under the License.
1616
*/
1717

18+
using System;
1819
using System.IO;
20+
using System.Text.RegularExpressions;
1921
using System.Threading.Tasks;
2022
using FellowOakDicom;
2123

@@ -28,5 +30,7 @@ public interface IDicomToolkit
2830
DicomFile Load(byte[] fileContent);
2931

3032
StudySerieSopUids GetStudySeriesSopInstanceUids(DicomFile dicomFile);
33+
34+
static DicomTag GetDicomTagByName(string tag) => DicomDictionary.Default[tag] ?? DicomDictionary.Default[Regex.Replace(tag, @"\s+", "", RegexOptions.None, TimeSpan.FromSeconds(1))];
3135
}
3236
}

src/InformaticsGateway/ExecutionPlugins/ExternalAppIncoming.cs

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,16 @@
1515
*/
1616

1717
using System;
18+
using System.Linq;
1819
using System.Threading.Tasks;
1920
using FellowOakDicom;
2021
using Microsoft.Extensions.DependencyInjection;
2122
using Microsoft.Extensions.Logging;
23+
using Microsoft.Extensions.Options;
2224
using Monai.Deploy.InformaticsGateway.Api;
2325
using Monai.Deploy.InformaticsGateway.Api.Storage;
26+
using Monai.Deploy.InformaticsGateway.Common;
27+
using Monai.Deploy.InformaticsGateway.Configuration;
2428
using Monai.Deploy.InformaticsGateway.Database.Api.Repositories;
2529

2630
namespace Monai.Deploy.InformaticsGateway.ExecutionPlugins
@@ -29,21 +33,27 @@ public class ExternalAppIncoming : IInputDataPlugin
2933
{
3034
private readonly ILogger<ExternalAppIncoming> _logger;
3135
private readonly IServiceScopeFactory _serviceScopeFactory;
36+
private readonly PluginConfiguration _options;
3237

3338
public ExternalAppIncoming(
3439
ILogger<ExternalAppIncoming> logger,
35-
IServiceScopeFactory serviceScopeFactory)
40+
IServiceScopeFactory serviceScopeFactory,
41+
IOptions<PluginConfiguration> configuration)
3642
{
3743
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
3844
_serviceScopeFactory = serviceScopeFactory ?? throw new ArgumentNullException(nameof(serviceScopeFactory));
45+
_options = configuration.Value ?? throw new ArgumentNullException(nameof(configuration));
46+
if (_options.Configuration.ContainsKey("ReplaceTags") is false) { throw new ArgumentNullException(nameof(configuration)); }
3947
}
4048

4149
public async Task<(DicomFile dicomFile, FileStorageMetadata fileMetadata)> Execute(DicomFile dicomFile, FileStorageMetadata fileMetadata)
4250
{
4351
var scope = _serviceScopeFactory.CreateScope();
4452
var repository = scope.ServiceProvider.GetRequiredService<IRemoteAppExecutionRepository>();
4553

46-
var incommingStudyUid = dicomFile.Dataset.GetString(DicomTag.StudyInstanceUID);
54+
var tagUsedAsKey = GetTags(_options.Configuration["ReplaceTags"]).First();
55+
56+
var incommingStudyUid = dicomFile.Dataset.GetString(tagUsedAsKey);
4757
var remoteAppExecution = await repository.GetAsync(incommingStudyUid);
4858
if (remoteAppExecution is null)
4959
{
@@ -54,11 +64,17 @@ public ExternalAppIncoming(
5464
{
5565
dicomFile.Dataset.AddOrUpdate(key, remoteAppExecution.OriginalValues[key]);
5666
}
57-
dicomFile.Dataset.AddOrUpdate(DicomTag.StudyInstanceUID, remoteAppExecution.StudyUid);
67+
//dicomFile.Dataset.AddOrUpdate(DicomTag.StudyInstanceUID, remoteAppExecution.StudyUid);
5868
fileMetadata.WorkflowInstanceId = remoteAppExecution.WorkflowInstanceId;
5969
fileMetadata.TaskId = remoteAppExecution.ExportTaskId;
6070

6171
return (dicomFile, fileMetadata);
6272
}
73+
74+
private static DicomTag[] GetTags(string values)
75+
{
76+
var names = values.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
77+
return names.Select(n => IDicomToolkit.GetDicomTagByName(n)).ToArray();
78+
}
6379
}
6480
}

src/InformaticsGateway/ExecutionPlugins/ExternalAppOutgoing.cs

Lines changed: 52 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,16 @@
1515
*/
1616

1717
using System;
18+
using System.Linq;
1819
using System.Threading;
1920
using System.Threading.Tasks;
2021
using FellowOakDicom;
2122
using Microsoft.Extensions.DependencyInjection;
2223
using Microsoft.Extensions.Logging;
24+
using Microsoft.Extensions.Options;
2325
using Monai.Deploy.InformaticsGateway.Api;
2426
using Monai.Deploy.InformaticsGateway.Api.Storage;
27+
using Monai.Deploy.InformaticsGateway.Common;
2528
using Monai.Deploy.InformaticsGateway.Configuration;
2629
using Monai.Deploy.InformaticsGateway.Database.Api.Repositories;
2730

@@ -31,44 +34,74 @@ public class ExternalAppOutgoing : IOutputDataPlugin
3134
{
3235
private readonly ILogger<ExternalAppOutgoing> _logger;
3336
private readonly IServiceScopeFactory _serviceScopeFactory;
37+
private readonly PluginConfiguration _options;
3438

3539
public ExternalAppOutgoing(
3640
ILogger<ExternalAppOutgoing> logger,
37-
IServiceScopeFactory serviceScopeFactory)
41+
IServiceScopeFactory serviceScopeFactory,
42+
IOptions<PluginConfiguration> configuration)
3843
{
3944
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
4045
_serviceScopeFactory = serviceScopeFactory ?? throw new ArgumentNullException(nameof(serviceScopeFactory));
46+
_options = configuration.Value ?? throw new ArgumentNullException(nameof(configuration));
47+
if (_options.Configuration.ContainsKey("ReplaceTags") is false) { throw new ArgumentNullException(nameof(configuration)); }
4148
}
4249

4350
public async Task<(DicomFile dicomFile, ExportRequestDataMessage exportRequestDataMessage)> Execute(DicomFile dicomFile, ExportRequestDataMessage exportRequestDataMessage)
4451
{
45-
//these are the standard tags, but this needs moving into config.
46-
DicomTag[] tags = { DicomTag.StudyInstanceUID, DicomTag.AccessionNumber, DicomTag.SeriesInstanceUID, DicomTag.SOPInstanceUID };
52+
var tags = GetTags(_options.Configuration["ReplaceTags"]);
4753

4854
var scope = _serviceScopeFactory.CreateScope();
4955
var repository = scope.ServiceProvider.GetRequiredService<IRemoteAppExecutionRepository>();
5056

5157
var remoteAppExecution = await GetRemoteAppExecution(exportRequestDataMessage, tags).ConfigureAwait(false);
5258
remoteAppExecution.StudyUid = dicomFile.Dataset.GetString(DicomTag.StudyInstanceUID);
5359

54-
await repository.AddAsync(remoteAppExecution).ConfigureAwait(false);
55-
_logger.LogStudyUidChanged(remoteAppExecution.StudyUid, remoteAppExecution.OutgoingStudyUid);
56-
5760
foreach (var tag in tags)
5861
{
59-
if (tag.Equals(DicomTag.StudyInstanceUID) is false &&
60-
dicomFile.Dataset.TryGetString(tag, out var value))
62+
if (dicomFile.Dataset.TryGetString(tag, out var value))
6163
{
6264
remoteAppExecution.OriginalValues.Add(tag, value);
63-
dicomFile.Dataset.AddOrUpdate(tag, DicomUIDGenerator.GenerateDerivedFromUUID());
65+
SetTag(dicomFile, tag);
6466
}
6567
}
6668

67-
dicomFile.Dataset.AddOrUpdate(DicomTag.StudyInstanceUID, remoteAppExecution.OutgoingStudyUid);
69+
remoteAppExecution.OutgoingUid = dicomFile.Dataset.GetString(tags.First());
70+
71+
await repository.AddAsync(remoteAppExecution).ConfigureAwait(false);
72+
_logger.LogStudyUidChanged(remoteAppExecution.StudyUid, remoteAppExecution.OutgoingUid);
6873

6974
return (dicomFile, exportRequestDataMessage);
7075
}
7176

77+
private static void SetTag(DicomFile dicomFile, DicomTag tag)
78+
{
79+
// partial implementation for now see
80+
// https://dicom.nema.org/dicom/2013/output/chtml/part05/sect_6.2.html
81+
// for full list
82+
83+
switch (tag.DictionaryEntry.ValueRepresentations.First().Code)
84+
{
85+
case "UI":
86+
case "LO":
87+
case "LT":
88+
{
89+
dicomFile.Dataset.AddOrUpdate(tag, DicomUIDGenerator.GenerateDerivedFromUUID());
90+
break;
91+
}
92+
case "SH":
93+
case "AE":
94+
case "CS":
95+
case "PN":
96+
case "ST":
97+
case "UT":
98+
{
99+
dicomFile.Dataset.AddOrUpdate(tag, "no Value");
100+
break;
101+
}
102+
}
103+
}
104+
72105
private async Task<RemoteAppExecution> GetRemoteAppExecution(ExportRequestDataMessage request, DicomTag[] tags)
73106
{
74107
var remoteAppExecution = new RemoteAppExecution
@@ -82,12 +115,12 @@ private async Task<RemoteAppExecution> GetRemoteAppExecution(ExportRequestDataMe
82115

83116

84117
var outgoingStudyUid = DicomUIDGenerator.GenerateDerivedFromUUID().UID;
85-
remoteAppExecution.OutgoingStudyUid = outgoingStudyUid;
118+
remoteAppExecution.OutgoingUid = outgoingStudyUid;
86119

87120

88121
foreach (var destination in request.Destinations)
89122
{
90-
remoteAppExecution.ExportDetails.Add(await LookupDestinationAsync(destination, new CancellationToken()));
123+
remoteAppExecution.ExportDetails.Add(await LookupDestinationAsync(destination, new CancellationToken()).ConfigureAwait(false));
91124
}
92125

93126
return remoteAppExecution;
@@ -111,5 +144,12 @@ private async Task<DestinationApplicationEntity> LookupDestinationAsync(string d
111144

112145
return destination;
113146
}
147+
148+
private static DicomTag[] GetTags(string values)
149+
{
150+
var names = values.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
151+
return names.Select(n => IDicomToolkit.GetDicomTagByName(n)).ToArray();
152+
}
153+
114154
}
115155
}

src/InformaticsGateway/Program.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ internal static IHostBuilder CreateHostBuilder(string[] args) =>
9898
services.AddOptions<MessageBrokerServiceConfiguration>().Bind(hostContext.Configuration.GetSection("InformaticsGateway:messaging"));
9999
services.AddOptions<StorageServiceConfiguration>().Bind(hostContext.Configuration.GetSection("InformaticsGateway:storage"));
100100
services.AddOptions<AuthenticationOptions>().Bind(hostContext.Configuration.GetSection("MonaiDeployAuthentication"));
101+
services.AddOptions<PluginConfiguration>().Bind(hostContext.Configuration.GetSection("InformaticsGateway:PluginConfiguration"));
101102
services.TryAddEnumerable(ServiceDescriptor.Singleton<IValidateOptions<InformaticsGatewayConfiguration>, ConfigurationValidator>());
102103

103104
services.ConfigureDatabase(hostContext.Configuration?.GetSection("ConnectionStrings"));

0 commit comments

Comments
 (0)