Comments (7)
Some cool ideas in your story!
I do wonder though: do these systems change their acceptance often? Wouldn't it suffice to just query the preferences once per system (AE title), and then just assume that for future associations. You could even automatically trigger a background recalibration if the preferences do change at some point.
But in my experience, PACS systems are fairly consistent in their inconsistency. 🙂
Anyway, I hope Fellow Oak Dicom is useful to you!
from fo-dicom.
Can confirm from my experience that there are applications that just do the described method. I have seen entries on the SCP log, that there are echos with lots of abstractsyntaxes and various transfersyntaxes. So the client just wanted to check the features. Not only Storage SOP Classes, but this way you also can "ask" if C-Find, C-Get or C-Move, or Storage Commitment is accepted or rejected.
I also doubt that it woud be efficient to do this asking on every connection. Instead integrate this into your setup or configuration environment and then cache/store this setting.
And it was very interesting to see that you tried to find an answer for a very special usecase with chatGPT. Because it is not a common problem, chatGPT will not have found enough sample code to be trained with. I wonder if you asked some other fo-dicom related questions and chatGPT was able to generade working code?
from fo-dicom.
First, you should be aware of the fact that fo-dicom provides 2 ways to send DICOM requests:
- DicomClient
- AdvancedDicomClientConnectionFactory
DicomClient is pretty high level and handles the DICOM specific details for you, such as association request and release handling. This is done automatically based on the DICOM requests that you enqueue with the DicomClient.
That is why the API looks like this:
var request = new DicomCEchoRequest
{
OnResponseReceived = (req, res) => { ... }
OnTimeout = (sender, args) => { ... }
};
var client = DicomClientFactory.Create("127.0.0.1", port, false, "SCU", "ANY-SCP");
await client.AddRequestAsync(request);
await client.SendAsync();
Here, the DICOM client will automatically add the necessary SOP Class UIDs for the C-ECHO request in the association request.
So, in your case, you can keep using DicomClient and do the following:
List<DicomRequest> requests = ...; // prepare your DICOM requests in advance
var client = DicomClientFactory.Create("127.0.0.1", port, false, "SCU", "ANY-SCP");
foreach(var request in requests)
{
await client.AddRequestAsync(request);
}
await client.SendAsync();
If you don't want to create all of your requests in advance, but you do know somehow which abstract syntaxes + transfer syntaxes will be needed, you can also do this:
var presentationContexts = new DicomPresentationContextCollection();
presentationContexts.Add(DicomUID.EncapsulatedPDFStorage, DicomTransferSyntax.ImplicitVRLittleEndian, DicomTransferSyntax.ExplicitVRLittleEndian);
presentationContexts.Add(DicomUID.SecondaryCaptureImageStorage, DicomTransferSyntax.ExplicitVRLittleEndian);
foreach (var pc in presentationContexts)
{
client.AdditionalPresentationContexts.Add(pc);
}
However, the fact remains that DicomClient will only open a DICOM association when you actually send a request, and DicomClient does not offer control over when to open and close an association. It does this internally and automatically.
If you require full control over the inner DICOM handshake, you'll need to use AdvancedDicomClientConnectionFactory. (Note that DicomClient uses this internally, so you will have all of the same possibilities)
This is how you send a C-ECHO with the advanced API (copied from the README):
var cancellationToken = CancellationToken.None;
var connectionRequest = new AdvancedDicomClientConnectionRequest
{
NetworkStreamCreationOptions = new NetworkStreamCreationOptions
{
Host = "127.0.0.1",
Port = server.Port,
}
};
// Alternatively, inject IAdvancedDicomClientConnectionFactory via dependency injection instead of using this static method
using var connection = await AdvancedDicomClientConnectionFactory.OpenConnectionAsync(connectionRequest, cancellationToken);
var associationRequest = new AdvancedDicomClientAssociationRequest
{
CallingAE = "EchoSCU",
CalledAE = "EchoSCP",
// Optionally negotiate user identity
UserIdentityNegotiation = new DicomUserIdentityNegotiation
{
UserIdentityType = DicomUserIdentityType.UsernameAndPasscode,
PositiveResponseRequested = true,
PrimaryField = "USERNAME",
SecondaryField = "PASSCODE",
}
};
var cEchoRequest = new DicomCEchoRequest();
associationRequest.PresentationContexts.AddFromRequest(cEchoRequest);
associationRequest.ExtendedNegotiations.AddFromRequest(cEchoRequest);
using var association = await connection.OpenAssociationAsync(associationRequest, cancellationToken);
try
{
DicomCEchoResponse cEchoResponse = await association.SendCEchoRequestAsync(cEchoRequest, cancellationToken).ConfigureAwait(false);
Console.WriteLine(cEchoResponse.Status);
}
finally
{
await association.ReleaseAsync(cancellationToken);
}
Using AdvancedDicomClientAssociationRequest.PresentationContexts
you will have full control over the presentation contexts.
Does this answer your question?
from fo-dicom.
Thanks for the very informative answer.
Didn't know about the AdvancedDicomClientConnectionRequest, that would indeed be a solution for us.
But our scenario is a bit different.
We are able to send different formats of CSTORE (those 4) and we ask the server in which format they want it.
And then only construct 1 of those 4 to send. So that we don't waste resources creating 4 requests, when 3 would not be sent.
So I guess DicomClient is unusable for us in this particular scenario (association first, then send request based on the association result).
I'm just thinking, if DicomClient could be somehow enhanced to also support this scenario or if it would get only bloated with some advanced stuff that is not needed there.
from fo-dicom.
@jakubsuchybio If I understand correctly, you are saying that your application keeps the DICOM files in multiple formats already, and you want to send the DICOM file immediately in the format that the server requests it to avoid redundant transcoding?
You could probably get this working by sending a dummy ECHO request, capture the accepted association and then use that to add more request to the DicomClient while it's already sending.
Something like this:
var client = DicomClientFactory.Create("127.0.0.1", port, false, "SCU", "ANY-SCP");
// Use this setting to keep DICOM associations open for a little while after the last request has completed
// If you add more requests to the same client, it will reuse the existing association rather than opening a new one each time
// Just be aware that this will also prolong the SendAsync call after all requests have completed
client.ClientOptions.AssociationLingerTimeoutInMs = (int) TimeSpan.FromMinutes(1).TotalMilliseconds;
// Setup your presentation contexts
var presentationContexts = new DicomPresentationContextCollection();
presentationContexts.Add(DicomUID.EncapsulatedPDFStorage, DicomTransferSyntax.ImplicitVRLittleEndian, DicomTransferSyntax.ExplicitVRLittleEndian);
presentationContexts.Add(DicomUID.SecondaryCaptureImageStorage, DicomTransferSyntax.ExplicitVRLittleEndian);
foreach (var pc in presentationContexts)
{
client.AdditionalPresentationContexts.Add(pc);
}
// Send an initial echo request and capture the association response in a TaskCompletionSource
var associationTask = new TaskCompletionSource<DicomAssociation>(TaskCreationOptions.RunContinuationsAsynchronously);
client.AssociationAccepted += (_, associationAcceptedEventArgs) =>
{
associationTask.TrySetResult(associationAcceptedEventArgs.Association);
};
var echoRequest = new DicomCEchoRequest();
await client.AddRequestAsync(echoRequest);
// Start sending, but don't await
var sendTask = client.SendAsync();
// Wait until the association is established
var association = await associationTask.Task;
// Now we can add C-STORE requests based on the last association
var encapsulatedPdfStorageTransferSyntax = association.PresentationContexts
.Where(pc => pc.AbstractSyntax == DicomUID.EncapsulatedPDFStorage)
.Select(pc => pc.AcceptedTransferSyntax)
.First();
DicomCStoreRequest cStoreRequest;
if (encapsulatedPdfStorageTransferSyntax == DicomTransferSyntax.ExplicitVRLittleEndian)
{
cStoreRequest = new DicomCStoreRequest("pdf.explicit.dcm");
}
else if (encapsulatedPdfStorageTransferSyntax == DicomTransferSyntax.ImplicitVRLittleEndian)
{
cStoreRequest = new DicomCStoreRequest("pdf.implicit.dcm");
}
else
{
throw new NotImplementedException();
}
await client.AddRequestAsync(cStoreRequest);
if (client.IsSendRequired)
{
// we took a little long, client already stopped sending
// await initial send task to expose exceptions or cancellation
await sendTask;
sendTask = client.SendAsync();
}
await sendTask;
But, I'll be honest, I'm not sure I would recommend this. The advanced API seems better suited to your needs.
Or, like you said, we could add built in support for this use case in fo-dicom itself. I imagine it would be possible then to create a single DicomCStoreRequest with multiple DICOM files, one for each precomputed transfer syntax.
Then, after the association handshake, we could check if the accepted transfer syntax is already precomputed.
However, if it isn't, which file do you start from then to transcode?
I don't think this would be very simple to implement.
from fo-dicom.
Yeah, we figured that with dummy echo, we could get the accepted association and with second request we would send the CSTORE.
And no, we don't have multiple formats beforehand.
We have basically an examination in our prorietary format (ECG examination) and then pdf report for that examination.
Then we have a priority list of presentation contexts.
e.g. 1. pdf, 2. secondary, 3. multiframe 4. twelvelead
And only after we get some acceptance
e.g. multiframe
only then we take our PDF and rerender it into images and create CSTORE dicom request from those images.
But if we get the acceptance for the 1. pdf, then we don't rerender anything and send it as a pdf.
Thats what I'm talking about not to waste resources on rerendering into images or into twelve lead signal, if there is no need for it (if server accepts pdfs)
So we will try with the dummy echo on some of our Beta Testers and if it doesn't work, we will use that AdvancedClient.
The problem with Dicom Standard is, that everybody bends it to their will and a LOT of implementations doesn't conform to the standard at all. We see it day after day. Because of this we are rearchitecting our Dicom implementation from dicom-cs to fo-dicom, so that we have in the middle the xslt transformation from our models to dicom models and when someone is not conforming to the standard, we just bend our xslt transformation for them to work. Without the need for us to change the code and rebuild our application everytime someone doesn't conform to the standart :)
from fo-dicom.
I can attest from personal experience that any Fellow Oak Dicom related questions posed to ChatGPT have been nothing but total and utter disappointments. It's probably like you say @gofal the dataset that is available in the wild is too thin.
It hallucinates APIs all the time.
from fo-dicom.
Related Issues (20)
- The DicomImage method cannot be called correctly in version 5.0, and there is no pixelData in the returned instance HOT 2
- Wrong frame returned by DicomPixelData when highly parallel HOT 6
- Cannot render a DICOM file to System.Drawing.Bitmap under .netstandard2.0 HOT 1
- Being able to compare datasets HOT 8
- System.IO.FileNotFoundException: 'Could not load file or assembly 'Microsoft.Extensions.DependencyInjection, Version=6.0.0.0 HOT 3
- Handle ExplicitVR DICOM file contains invalid VR HOT 2
- Issue updating ContourImageSequence HOT 2
- Unsupported Transfer Syntax Error for Lossy JPEG 8 Bit Image Compression in fo-dicom on .NET 7.0 HOT 2
- MaxClientsAllowed still behaves incorrectly when the connections are full and open connections take longer than one minute to shutdown
- Strong memory use surge on HTTP request to DicomService HOT 8
- DLL "Dicom.Native" not found when rendering an image on .Net 4.8 (x64) HOT 3
- Association Abort Error HOT 1
- Dependency Injection error prevents me from passing an object to CStoreSCP HOT 1
- HTJ2K Support HOT 3
- TLS: tlsInitiator set pfx file,then client.SendAsync(), a sspi error has occurred. HOT 4
- How come a DICOM file has zero size? HOT 3
- How to read a dicom file from network stream HOT 3
- Equipment generates more than one print in the job queue
- Read DICOM file using UnseekableStream HOT 2
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from fo-dicom.