Skip to content

Commit ed72aca

Browse files
authored
Merge pull request #43 from Project-MONAI/vchang/message-structure
Extract and store DICOM JSON.
2 parents b0e1ef8 + ef94ff0 commit ed72aca

29 files changed

+1600
-204
lines changed

docs/setup/setup.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,34 @@ Locate the storage section of the configuration in `appsettings.json`:
136136
}
137137
}
138138
```
139+
### DICOM JSON Model
139140

141+
By default, Informatics Gateway stores all received and retrieved DICOM instances into JSON in additional to storing
142+
the original in DICOM part-10 format. The JSON stored is as specified by the [DICOM JSON model](https://dicom.nema.org/dicom/2013/output/chtml/part18/sect_F.2.html)
143+
without the following VR types:
144+
145+
- OB
146+
- OD
147+
- OF
148+
- OL
149+
- OV
150+
- OW
151+
- UN
152+
153+
This behavior may be changed in the configuration file:
154+
```json
155+
{
156+
157+
"InformaticsGateway": {
158+
"dicom": {
159+
"writeDicomJson": "None|IgnoreOthers|Complete"
160+
},
161+
...
162+
}
163+
}
164+
```
165+
166+
Refer to the [DicomJsonOptions](xref:Monai.Deploy.InformaticsGateway.Configuration.DicomJsonOptions) for complete description.
140167

141168
## Summary
142169

src/Api/MessageBroker/Message.cs

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -61,20 +61,6 @@ public sealed class Message : MessageBase
6161
/// </summary>
6262
public byte[] Body { get; init; }
6363

64-
public Message(byte[] body,
65-
string bodyDescription,
66-
string contentType,
67-
string correlationId)
68-
: this(body,
69-
bodyDescription,
70-
Guid.NewGuid().ToString(),
71-
Message.InformaticsGatewayApplicationId,
72-
contentType,
73-
correlationId,
74-
DateTime.UtcNow)
75-
{
76-
}
77-
7864
public Message(byte[] body,
7965
string bodyDescription,
8066
string messageId,
Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,57 @@
1-
using System;
1+
// Copyright 2022 MONAI Consortium
2+
// Licensed under the Apache License, Version 2.0 (the "License");
3+
// you may not use this file except in compliance with the License.
4+
// You may obtain a copy of the License at
5+
// http://www.apache.org/licenses/LICENSE-2.0
6+
// Unless required by applicable law or agreed to in writing, software
7+
// distributed under the License is distributed on an "AS IS" BASIS,
8+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9+
// See the License for the specific language governing permissions and
10+
// limitations under the License.
11+
12+
using Monai.Deploy.InformaticsGateway.Api.Storage;
13+
using Newtonsoft.Json;
14+
using System;
215
using System.Collections.Generic;
316

417
namespace Monai.Deploy.InformaticsGateway.Api.MessageBroker
518
{
619
public class WorkflowRequestMessage
720
{
8-
/// <summary>
9-
/// Gets or sets the name of bucket where the payload is stored.
10-
/// </summary>
11-
public string Bucket { get; set; }
12-
1321
/// <summary>
1422
/// Gets or sets the ID of the payload which is also used as the root path of the payload.
1523
/// </summary>
24+
[JsonProperty(PropertyName = "payload_id")]
1625
public Guid PayloadId { get; set; }
1726

1827
/// <summary>
1928
/// Gets or sets the associated workflows to be launched.
2029
/// </summary>
30+
[JsonProperty(PropertyName = "workflows")]
2131
public IEnumerable<string> Workflows { get; set; }
2232

2333
/// <summary>
2434
/// Gets or sets number of files in the payload.
2535
/// </summary>
36+
[JsonProperty(PropertyName = "file_count")]
2637
public int FileCount { get; set; }
2738

2839
/// <summary>
2940
/// For DIMSE, the correlation ID is the UUID associated with the first DICOM association received. For an ACR inference request, the correlation ID is the Transaction ID in the original request.
3041
/// </summary>
42+
[JsonProperty(PropertyName = "correlation_id")]
3143
public string CorrelationId { get; set; }
44+
45+
/// <summary>
46+
/// Gets or sets the time the data was received.
47+
/// </summary>
48+
[JsonProperty(PropertyName = "timestamp")]
49+
public DateTime Timestamp { get; set; }
50+
51+
/// <summary>
52+
/// Gets or sets a list of files and metadata files in this request.
53+
/// </summary>
54+
[JsonProperty(PropertyName = "payload")]
55+
public List<BlockStorageInfo> Payload { get; } = new List<BlockStorageInfo>();
3256
}
3357
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// Copyright 2022 MONAI Consortium
2+
// Licensed under the Apache License, Version 2.0 (the "License");
3+
// you may not use this file except in compliance with the License.
4+
// You may obtain a copy of the License at
5+
// http://www.apache.org/licenses/LICENSE-2.0
6+
// Unless required by applicable law or agreed to in writing, software
7+
// distributed under the License is distributed on an "AS IS" BASIS,
8+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9+
// See the License for the specific language governing permissions and
10+
// limitations under the License.
11+
12+
using Newtonsoft.Json;
13+
14+
namespace Monai.Deploy.InformaticsGateway.Api.Storage
15+
{
16+
public class BlockStorageInfo
17+
{
18+
/// <summary>
19+
/// Gets or sets the name of bucket where the file is stored.
20+
/// </summary>
21+
[JsonProperty(PropertyName = "bucket")]
22+
public string Bucket { get; set; }
23+
24+
/// <summary>
25+
/// Gets or sets the root path to the file.
26+
/// </summary>
27+
[JsonProperty(PropertyName = "path")]
28+
public string Path { get; set; }
29+
30+
/// <summary>
31+
/// Gets or sets the root path to the metadata file.
32+
/// </summary>
33+
[JsonProperty(PropertyName = "metadata")]
34+
public string Metadata { get; set; }
35+
}
36+
}

src/Api/Storage/DicomFileStorageInfo.cs

Lines changed: 50 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
// limitations under the License.
1111

1212
using Ardalis.GuardClauses;
13+
using System.Collections.Generic;
1314
using System.IO.Abstractions;
1415

1516
namespace Monai.Deploy.InformaticsGateway.Api.Storage
@@ -19,31 +20,66 @@ namespace Monai.Deploy.InformaticsGateway.Api.Storage
1920
/// </summary>
2021
public sealed record DicomFileStorageInfo : FileStorageInfo
2122
{
22-
public static readonly string FILE_EXTENSION = ".dcm";
23-
public static readonly string CONTENT_TYPE = "application/dicom";
23+
public static readonly string FilExtension = ".dcm";
24+
public static readonly string DicomJsonFileExtension = ".json";
25+
public static readonly string DicomContentType = "application/dicom";
26+
public static readonly string DicomJsonContentType = "application/json";
27+
2428
public string StudyInstanceUid { get; set; }
2529
public string SeriesInstanceUid { get; set; }
2630
public string SopInstanceUid { get; set; }
31+
public string DicomJsonFilePath { get; set; }
32+
33+
/// <summary>
34+
/// Gets the file path to be stored on the shared storage.
35+
/// </summary>
36+
public string DicomJsonUploadPath
37+
{
38+
get
39+
{
40+
if (string.IsNullOrWhiteSpace(FilePath))
41+
{
42+
_ = FilePath;
43+
};
44+
45+
var path = DicomJsonFilePath[StorageRootPath.Length..];
46+
if (FileSystem.Path.IsPathRooted(path))
47+
{
48+
return path[1..];
49+
}
50+
return path;
51+
}
52+
}
53+
54+
public override IEnumerable<string> FilePaths
55+
{
56+
get
57+
{
58+
yield return FilePath;
59+
yield return DicomJsonFilePath;
60+
}
61+
}
62+
2763

2864
public DicomFileStorageInfo() { }
2965

3066
public DicomFileStorageInfo(string correlationId,
3167
string storageRootPath,
3268
string messageId,
3369
string source)
34-
: base(correlationId, storageRootPath, messageId, FILE_EXTENSION, source, new FileSystem())
70+
: base(correlationId, storageRootPath, messageId, FilExtension, source, new FileSystem())
3571
{
36-
this.ContentType = CONTENT_TYPE;
72+
ContentType = DicomContentType;
3773
}
3874

3975
public DicomFileStorageInfo(string correlationId,
4076
string storageRootPath,
4177
string messageId,
4278
string source,
4379
IFileSystem fileSystem)
44-
: base(correlationId, storageRootPath, messageId, FILE_EXTENSION, source, fileSystem)
80+
: base(correlationId, storageRootPath, messageId, FilExtension, source, fileSystem)
4581
{
46-
this.ContentType = CONTENT_TYPE;
82+
ContentType = DicomContentType;
4783
}
4884

4985
protected override string GenerateStoragePath()
@@ -61,7 +97,15 @@ protected override string GenerateStoragePath()
6197
filePath = filePath.ToLowerInvariant();
6298
}
6399

100+
DicomJsonFilePath = $"{filePath}{DicomJsonFileExtension}";
64101
return filePath;
65102
}
103+
104+
public override BlockStorageInfo ToBlockStorageInfo(string bucket)
105+
{
106+
var blockStorage = base.ToBlockStorageInfo(bucket);
107+
blockStorage.Metadata = DicomJsonUploadPath;
108+
return blockStorage;
109+
}
66110
}
67111
}

src/Api/Storage/FhirFileStorageInfo.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@ namespace Monai.Deploy.InformaticsGateway.Api.Storage
2020
/// </summary>
2121
public sealed record FhirFileStorageInfo : FileStorageInfo
2222
{
23-
const string DirectoryPath = "ehr";
23+
public static readonly string JsonFilExtension = ".json";
24+
public static readonly string XmlFilExtension = ".xml";
25+
private static readonly string DirectoryPath = "ehr";
2426

2527
public string ResourceType { get; set; }
2628

@@ -31,15 +33,15 @@ public FhirFileStorageInfo(string correlationId,
3133
string messageId,
3234
FhirStorageFormat fhirFileFormat,
3335
string source)
34-
: base(correlationId, storageRootPath, messageId, fhirFileFormat == FhirStorageFormat.Json ? ".json" : ".xml", source, new FileSystem()) { }
36+
: base(correlationId, storageRootPath, messageId, fhirFileFormat == FhirStorageFormat.Json ? JsonFilExtension : XmlFilExtension, source, new FileSystem()) { }
3537

3638
public FhirFileStorageInfo(string correlationId,
3739
string storageRootPath,
3840
string messageId,
3941
FhirStorageFormat fhirFileFormat,
4042
string source,
4143
IFileSystem fileSystem)
42-
: base(correlationId, storageRootPath, messageId, fhirFileFormat == FhirStorageFormat.Json ? ".json" : ".xml", source, fileSystem)
44+
: base(correlationId, storageRootPath, messageId, fhirFileFormat == FhirStorageFormat.Json ? JsonFilExtension : XmlFilExtension, source, fileSystem)
4345
{
4446
}
4547

src/Api/Storage/FileStorageInfo.cs

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
using Ardalis.GuardClauses;
1313
using System;
14+
using System.Collections.Generic;
1415
using System.IO.Abstractions;
1516

1617
namespace Monai.Deploy.InformaticsGateway.Api.Storage
@@ -71,10 +72,10 @@ public string UploadPath
7172
{
7273
get
7374
{
74-
var path = FilePath.Substring(StorageRootPath.Length);
75+
var path = FilePath[StorageRootPath.Length..];
7576
if (FileSystem.Path.IsPathRooted(path))
7677
{
77-
return path.Substring(1);
78+
return path[1..];
7879
}
7980
return path;
8081
}
@@ -118,6 +119,17 @@ public string UploadFilename
118119
/// </summary>
119120
public string ContentType { get; set; }
120121

122+
/// <summary>
123+
/// Gets the file and any associated meta files.
124+
/// </summary>
125+
public virtual IEnumerable<string> FilePaths
126+
{
127+
get
128+
{
129+
yield return _filePath;
130+
}
131+
}
132+
121133
public FileStorageInfo() { }
122134

123135
public FileStorageInfo(string correlationId,
@@ -185,5 +197,14 @@ protected virtual string GenerateStoragePath()
185197

186198
return filePath;
187199
}
200+
201+
public virtual BlockStorageInfo ToBlockStorageInfo(string bucket)
202+
{
203+
return new BlockStorageInfo
204+
{
205+
Bucket = bucket,
206+
Path = UploadPath,
207+
};
208+
}
188209
}
189210
}

src/Api/Storage/Payload.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ public enum PayloadState
4848

4949
public string Key { get; init; }
5050

51+
public DateTime DateTimeCreated { get; private set; }
52+
5153
public int RetryCount { get; set; }
5254

5355
public bool HasTimedOut { get => ElapsedTime().TotalSeconds >= Timeout; }
@@ -67,6 +69,7 @@ public IEnumerable<string> Workflows
6769
}
6870

6971
public string CorrelationId { get; init; }
72+
public IList<BlockStorageInfo> UploadedFiles { get; set; }
7073

7174
public Payload(string key, string correlationId, uint timeout)
7275
{
@@ -80,6 +83,7 @@ public Payload(string key, string correlationId, uint timeout)
8083
RetryCount = 0;
8184
State = PayloadState.Created;
8285
Files = new List<FileStorageInfo>();
86+
UploadedFiles = new List<BlockStorageInfo>();
8387
}
8488

8589
public void Add(FileStorageInfo value)
@@ -89,6 +93,11 @@ public void Add(FileStorageInfo value)
8993
Files.Add(value);
9094
_lastReceived.Reset();
9195
_lastReceived.Start();
96+
97+
if(Files.Count == 1)
98+
{
99+
DateTimeCreated = value.Received;
100+
}
92101
}
93102

94103
public TimeSpan ElapsedTime()

src/Configuration/DicomConfiguration.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,5 +46,11 @@ public class DicomConfiguration
4646
/// </summary>
4747
[JsonProperty(PropertyName = "scu")]
4848
public ScuConfiguration Scu { get; set; } = new ScuConfiguration();
49+
50+
/// <summary>
51+
/// Gets or sets whether to write DICOM JSON file for each instance received.
52+
/// </summary>
53+
[JsonProperty(PropertyName = "writeDicomJson")]
54+
public DicomJsonOptions WriteDicomJson { get; set; } = DicomJsonOptions.IgnoreOthers;
4955
}
5056
}

0 commit comments

Comments
 (0)