Skip to content

Commit df7aac3

Browse files
authored
Added stow endpoint (#115)
* Added stow endpoint Signed-off-by: Joe Batt <joe.batt@answerdigital.com> * Added stow functionality to test harness Signed-off-by: Joe Batt <joe.batt@answerdigital.com> --------- Signed-off-by: Joe Batt <joe.batt@answerdigital.com>
1 parent 7446aa0 commit df7aac3

7 files changed

Lines changed: 175 additions & 23 deletions

File tree

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
using dotnet_performance_app.Support;
2+
using Microsoft.AspNetCore.Mvc;
3+
using System.Net;
4+
using System.Reflection;
5+
6+
namespace dotnet_performance_app.Controllers
7+
{
8+
[ApiController]
9+
[Route("stow")]
10+
public class StowController : ControllerBase
11+
{
12+
public StowController(IConfiguration configuration, IHttpClientFactory httpClientFactory)
13+
{
14+
Host = configuration.GetValue<string>("InformaticsGateway:Host");
15+
Port = configuration.GetValue<int>("InformaticsGateway:StowPort");
16+
Endpoint = $"/dicomweb/{configuration.GetValue<string>("InformaticsGateway:StowWorkflowId")}/studies";
17+
User = configuration.GetValue<string>("InformaticsGateway:StowUser");
18+
Password = configuration.GetValue<string>("InformaticsGateway:StowPassword");
19+
Stow = new Stow(httpClientFactory);
20+
}
21+
22+
private string? Host { get; set; }
23+
private string? Endpoint { get; set; }
24+
private int Port { get; set; }
25+
private string User { get; set; }
26+
private string Password { get; set; }
27+
private Stow Stow { get; set; }
28+
29+
[HttpGet]
30+
public async Task<IActionResult> DicomAssociation(
31+
[FromQuery(Name = "modality")] string modality)
32+
{
33+
var result = Stow.SendStowRequest(GetFolder(modality.ToUpper()).ToString(), $"http://{Host}:{Port}{Endpoint}", $"{User}:{Password}").Result;
34+
35+
if (result.StatusCode.Equals(HttpStatusCode.OK))
36+
{
37+
return Ok();
38+
}
39+
else
40+
{
41+
return BadRequest(result.Content);
42+
}
43+
}
44+
45+
private DirectoryInfo GetFolder(string subfolder)
46+
{
47+
var rand = new Random();
48+
var pathname = Path.Combine(GetDirectory(), "Data", "DICOM", subfolder);
49+
var directory = new DirectoryInfo(pathname);
50+
var directories = directory.GetDirectories();
51+
return directories[rand.Next(directories.Length)];
52+
}
53+
54+
private string GetDirectory()
55+
{
56+
return Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
57+
}
58+
}
59+
}

performance-testing/dotnet-performance-app/Program.cs

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,13 @@
88
builder.Services.AddEndpointsApiExplorer();
99
builder.Services.AddSwaggerGen();
1010

11+
builder.Services.AddHttpClient();
12+
1113
var app = builder.Build();
1214

1315
// Configure the HTTP request pipeline.
14-
if (app.Environment.IsDevelopment())
15-
{
16-
app.UseSwagger();
17-
app.UseSwaggerUI();
18-
}
19-
20-
app.UseHttpsRedirection();
16+
app.UseSwagger();
17+
app.UseSwaggerUI();
2118

2219
app.MapControllers();
2320

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Description
2+
The dotnet-performance-app can be used for sending c-store and stow-rs requests for CT, US, MR and RF data.
3+
4+
## Running dotnet-performance-app
5+
### Building the app locally
6+
7+
```bash
8+
cd performance-testing/dotnet-performance-app
9+
```
10+
11+
```bash
12+
dotnet build
13+
```
14+
15+
### Running as docker image
16+
```bash
17+
cd performance-testing/dotnet-performance-app
18+
```
19+
20+
```bash
21+
docker build -t dotnet-performance-app .
22+
```
23+
24+
```bash
25+
docker run -it --rm -p 5000:80 -p 5001:443 -e InformaticsGateway__Host={host} -e InformaticsGateway__Port={port} -e InformaticsGateway__StowPort={stowport} -e InformaticsGateway__StowUser={stowuser} -e InformaticsGateway__StowPassword={stowpassword} -e InformaticsGateway__StowWorkflowId={stowworkflowid} dotnet-performance-app
26+
```
27+
> **host** is to be replaced with the host that MIG is running on
28+
> **port** is to be replaced with the port that MIG is set up to receive C-STORE request on
29+
> **stowport** is to be replaced with the port that MIG is set up to receive STOW requests on
30+
> **stowuser** is to be replaced with the user that is required for MIG auth. **ONLY** required if MIG auth is enabled
31+
> **stowpassword** is to be replaced with the password that is required for MIG auth. **ONLY** required if MIG auth is enabled
32+
> **stowworkflowid** is to be replaced with the workflow id that you want the STOW request to trigger. This is the unique ID for a workflow within Workflow Manager
33+
34+
Navigate to http://localhost:5000/swagger to see endpoint documentation
35+
36+
37+

performance-testing/dotnet-performance-app/Support/DicomScu.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,13 @@ public async Task<DicomStatus> CStore(string host, int port, string callingAeTit
1717
var dicomClient = CreateClient(host, port, callingAeTitle, calledAeTitle);
1818
var countdownEvent = new CountdownEvent(dicomFiles.Count);
1919
var failureStatus = new List<DicomStatus>();
20+
var seriesUID = await Anonymize(dicomFiles[0].Dataset.GetString(DicomTag.SeriesInstanceUID).Trim());
21+
var studyUID = await Anonymize(dicomFiles[0].Dataset.GetString(DicomTag.StudyInstanceUID).Trim());
22+
2023
foreach (var file in dicomFiles)
2124
{
25+
file.Dataset.AddOrUpdate<string>(DicomTag.SeriesInstanceUID, seriesUID);
26+
file.Dataset.AddOrUpdate<string>(DicomTag.StudyInstanceUID, studyUID);
2227
var cStoreRequest = new DicomCStoreRequest(file);
2328
cStoreRequest.OnResponseReceived += (DicomCStoreRequest request, DicomCStoreResponse response) =>
2429
{
@@ -48,6 +53,15 @@ public async Task<DicomStatus> CStore(string host, int port, string callingAeTit
4853
return failureStatus.First();
4954
}
5055

56+
private async Task<string> Anonymize(string uid)
57+
{
58+
uid = uid.Substring(0, uid.Length - 10);
59+
var r = new Random();
60+
var x = r.Next(0, 99999);
61+
var s = x.ToString("000000");
62+
return uid + s;
63+
}
64+
5165
private IDicomClient CreateClient(string host, int port, string callingAeTitle, string calledAeTitle)
5266
{
5367
return DicomClientFactory.Create(host, port, false, callingAeTitle, calledAeTitle);
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
using System.Net.Http.Headers;
2+
3+
namespace dotnet_performance_app.Support
4+
{
5+
public class Stow
6+
{
7+
private readonly IHttpClientFactory _httpClientFactory;
8+
9+
public Stow(IHttpClientFactory httpClientFactory)
10+
{
11+
_httpClientFactory = httpClientFactory;
12+
}
13+
14+
public async Task<HttpResponseMessage> SendStowRequest(string directory, string url, string authenticationString)
15+
{
16+
var client = _httpClientFactory.CreateClient();
17+
var mimeType = "application/dicom";
18+
var multiContent = GetMultipartContent(mimeType);
19+
20+
foreach (var path in Directory.EnumerateFiles(directory, "*.*", SearchOption.AllDirectories))
21+
{
22+
var sContent = new StreamContent(File.OpenRead(path));
23+
24+
sContent.Headers.ContentType = new MediaTypeHeaderValue(mimeType);
25+
26+
multiContent.Add(sContent);
27+
}
28+
29+
if (multiContent.Count() > 0)
30+
{
31+
var request = new HttpRequestMessage(HttpMethod.Post, url);
32+
33+
var base64EncodedAuthenticationString = Convert.ToBase64String(System.Text.ASCIIEncoding.UTF8.GetBytes(authenticationString));
34+
35+
request.Headers.Add("Authorization", "Basic " + base64EncodedAuthenticationString);
36+
37+
request.Content = multiContent;
38+
39+
var response = await client.SendAsync(request);
40+
41+
return response;
42+
}
43+
44+
return null;
45+
}
46+
47+
private static MultipartContent GetMultipartContent(string mimeType)
48+
{
49+
var multiContent = new MultipartContent("related", "DICOM DATA BOUNDARY");
50+
51+
multiContent.Headers.ContentType.Parameters.Add(new System.Net.Http.Headers.NameValueHeaderValue("type", "\"" + mimeType + "\""));
52+
53+
return multiContent;
54+
}
55+
}
56+
}

performance-testing/dotnet-performance-app/appsettings.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,11 @@
77
},
88
"InformaticsGateway": {
99
"Host": "",
10-
"Port": 11511
10+
"Port": 104,
11+
"StowPort": 5000,
12+
"StowUser": "",
13+
"StowPassword": "",
14+
"StowWorkflowId": ""
1115
},
1216
"AllowedHosts": "*"
1317
}

performance-testing/dotnet-performance-app/dotnet-performance-app.csproj

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,21 +10,6 @@
1010
<DockerfileContext>.</DockerfileContext>
1111
</PropertyGroup>
1212

13-
<ItemGroup>
14-
<Compile Remove="Data\DICOM\MR\**" />
15-
<Compile Remove="Data\DICOM\ULTRASOUND\**" />
16-
<Compile Remove="Data\DICOM\XRAY\**" />
17-
<Content Remove="Data\DICOM\MRI\**" />
18-
<Content Remove="Data\DICOM\ULTRASOUND\**" />
19-
<Content Remove="Data\DICOM\XRAY\**" />
20-
<EmbeddedResource Remove="Data\DICOM\MRI\**" />
21-
<EmbeddedResource Remove="Data\DICOM\ULTRASOUND\**" />
22-
<EmbeddedResource Remove="Data\DICOM\XRAY\**" />
23-
<None Remove="Data\DICOM\MRI\**" />
24-
<None Remove="Data\DICOM\ULTRASOUND\**" />
25-
<None Remove="Data\DICOM\XRAY\**" />
26-
</ItemGroup>
27-
2813
<ItemGroup>
2914
<PackageReference Include="fo-dicom" Version="5.0.2" />
3015
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.14.0" />

0 commit comments

Comments
 (0)