# fo-dicom is a DICOM toolkit for .NET that is async all the way down

> Fellow Oak DICOM parses, stores, renders and serves DICOM objects in C#, with a fully asynchronous API, three interchangeable rendering backends, codec packages, anonymization and OpenTelemetry instrumentation. Its rough edges are the setup dance with a static service provider, the fact that file opening and rendering cannot use dependency injection, and a runtime support list that is narrower than the platforms its description mentions.

**fo-dicom/fo-dicom** — Fellow Oak DICOM for .NET, .NET Core, Universal Windows, Android, iOS, Mono and Unity

- Repository: https://github.com/fo-dicom/fo-dicom
- Website: https://fo-dicom.github.io/stable/v5/index.html
- Stars: 1,217 · Forks: 670
- Language: C#
- License: NOASSERTION
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/fo-dicom-fo-dicom

## A DICOM library for a domain with very large files and slow networks

Fellow Oak DICOM is a C# library for the DICOM standard, and the feature list explains what the domain actually demands. The library supports very large datasets with content loading on demand, which is the first thing a DICOM implementation has to get right, because a single study can hold thousands of slices and loading it eagerly will exhaust your memory.

The API is described as high performance and fully asynchronous, with async and await throughout. That matters in this domain more than in most, because DICOM networking is a protocol of store, move, find and query operations over connections that are often slow, and a synchronous API in that environment produces thread pool exhaustion long before it produces a throughput problem.

Beyond parsing and storing, the library covers the rest of what an application needs. It renders images to System.Drawing.Bitmap or to SixLabors.ImageSharp, it exports and imports JSON and XML, it has anonymization, and it implements the DICOM services, which is the network side of the standard, the part that talks to a PACS or to a modality. Components can be customised through a DI container.

The dictionary is the other half of the story, and the library states its version: DICOM dictionary version 2026b. A dictionary is the mapping of tags to names and value representations, and it changes as the standard is revised, so a version number in the readme tells you exactly which revision of the standard your code is being compiled against.

The audience is application developers in hospitals, medical device companies and imaging software, and also the researchers and students who need to read DICOM files without a hospital around them.

## Compression is not in the core package

The one line in the feature list that needs unpacking is the parenthesis around the image compression formats. The library supports JPEG, including lossless, JPEG-LS, JPEG2000, HTJPEG2000 and RLE image compression, and it says that this is via an additional package.

That is the design of the package matrix rather than an accident. The core package, fo-dicom, contains the parser, the services and the tools, and it ships without the codecs. The compression codecs live in fo-dicom.Codecs, which the documentation attributes to Efferent Health and describes as cross-platform DICOM codecs for fo-dicom.

The reason to split them is the same reason imaging is split. A DICOM codec is a piece of numerical or native code with a licence and a platform story, and many of the fastest implementations are not managed. By keeping the codecs in a separate package, an application that only reads metadata, or that only needs an uncompressed transfer syntax, does not pull them in, and a team that has to satisfy a licence review has one package to look at.

The other packages follow the same logic. fo-dicom.Instrumentation adds OpenTelemetry instrumentation of fo-dicom, which is the piece a platform team will want and an application team may not. And the three imaging packages, Desktop with System.Drawing, ImageSharp and SkiaSharp, exist because rendering is the one part of the library that has to touch a platform specific graphics stack, and System.Drawing is not cross platform while ImageSharp and SkiaSharp are.

So the dependency question for a new project is: do you need to render pixels, do you need compressed transfer syntaxes, and do you need telemetry. Each answer adds one package.

## Every modern .NET host needs the same extra line

The setup section of the documentation is the part to read carefully, because it describes a two step configuration in every hosting style, and the reason is the same each time.

With the web application builder:

```csharp
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddFellowOakDicom();
var app = builder.Build();
// This is still necessary for now until fo-dicom has first-class AspNetCore integration
DicomSetupBuilder.UseServiceProvider(app.Services);
```

With the host builder the shape is the same, with ConfigureServices calling AddFellowOakDicom, and the same UseServiceProvider call afterwards. And if you are not using the host builder at all, you build your own ServiceCollection, call AddFellowOakDicom on it, build the service provider yourself and pass it to the same method.

The comment attached to the call in all three snippets is the important part. It is still necessary for now until fo-dicom has first-class AspNetCore integration. In other words, the library resolves some dependencies through a globally registered static service provider, and the DI container you configure is not picked up automatically.

For .NET Framework the equivalent is a single builder:

```csharp
new DicomSetupBuilder()
    .RegisterServices(s => s.AddFellowOakDicom())
.Build();
```

This is a real friction point in an otherwise modern library. The workaround is one line, but it is a line whose omission fails at runtime rather than at compile time, which is the worst time to find out. If you adopt the library, put that call in your startup path immediately after the container is built, and treat it as part of your application wiring rather than something each feature team remembers.

## Dependency injection works for the network layer, not for files

The documentation includes a table that answers a question most libraries leave vague, which is which APIs can be resolved through DI and which fall back to the static provider.

Creating a DICOM server is done with DicomServerFactory.Create, and the table says you can use dependency injection instead, by asking for an IDicomServerFactory. Creating a DICOM client with DicomClientFactory.Create works the same way, with IDicomClientFactory. Creating an advanced client connection goes through AdvancedDicomClientConnectionFactory.OpenConnectionAsync, with IAdvancedDicomClientConnectionFactory.

And then two rows say no. Opening a DICOM file with DicomFile.OpenAsync cannot use dependency injection. Rendering a DICOM file with new DicomImage(..).RenderImage(..) cannot either.

The pattern is not accidental. The network factories are the parts that have pluggable implementations, the server, the client, the transport, and they are where the library expects an application to substitute its own implementations. File opening and rendering are concrete operations with a fixed pipeline, and there is nothing to swap, so they are static and they are simple.

The documentation also states the general rule: whenever you use APIs such as DicomFile.Open or DicomServerFactory.Create, the global statically registered service provider will be used to resolve dependencies, and dependency injection is generally preferred over the static APIs if it is available.

So the practical guidance for a reviewer is: prefer the interfaces where they exist, and treat the static calls as the deliberate exceptions rather than as an accident of the design.

## Custom dependencies have an exact three parameter constructor contract

If you want your own objects inside the DICOM services, the documentation is unusually specific about the requirement, and the specificity is the point.

You must have these exact three constructor parameters: INetworkStream stream, Encoding fallbackEncoding and ILogger logger. The names and their order do not matter, but the types do.

There is a second rule that is easy to miss. ILogger here is the non-generic typed logger, so if you want a Logger<T> you can add an extra constructor parameter and use it however you see fit. In other words, the three required parameters are a floor and not a ceiling, and the typed logger you probably want is something you add on top.

That is a strict contract for a plugin author, and it exists because the library constructs network components itself and needs to know how to hand them a stream, an encoding and a logger. It is a cost, and the cost is paid by anyone who wants to plug in a custom transport or a custom logger.

The example in the documentation is a full standalone host. It configures logging with the console provider and a minimum level of Information, registers the fo-dicom services, registers a hosted service, registers a CustomDependency class as a singleton that is then injected into the DICOM service, calls DicomSetupBuilder.UseServiceProvider with the host's services, and then runs the host.

The pattern to copy is the ordering: configure logging, configure services, build, hand the provider to fo-dicom, then run.

## Version 5, an MS-PL licence, and a runtime list that is narrower than the description

The licence is the Microsoft Public License, as the readme states, with the details in License.txt. That is why the repository metadata reports the licence as a custom one that GitHub cannot classify. The MS-PL is a permissive licence with a file level copyleft on the modified files, which is a reasonable middle ground for a library of this size, though a commercial deployment should read License.txt rather than assume MIT or Apache terms.

The release line is 5.x, with 5.2.4 on 2025-10-03, 5.2.5 on 2025-11-17 and 5.2.6 on 2026-03-30. The repository is not archived and the last push was on 2026-10-15, so the branch is moving ahead of the last published package. The default branch is development, and the documentation site publishes three versions, the latest release of fo-dicom 4, the latest release of fo-dicom 5, and the development version of fo-dicom 5, which is a sensible arrangement for a library that supports a major version transition.

The one thing to check against your own plans is the runtime support. The readme says the library officially supports .NET 8.0, .NET 9.0 and .NET 10.0, and adds that other runtimes may work but that the CI pipeline only tests these platforms. Meanwhile the project description lists .NET, .NET Core, Universal Windows, Android, iOS, Mono and Unity.

That gap is the practical boundary. The description describes the history and the ambition of the library, the supported runtimes list is what is tested, and if your product targets Xamarin, Mono or Unity you are in the untested category, where the documentation offers no guarantee. For a medical application that is a decision to make explicitly rather than to assume.

## Conclusion

fo-dicom is worth adopting for a .NET team that has to read, write, anonymize or serve DICOM and wants everything in one managed library with a modern async surface, since the rendering backends, the codec packages and the instrumentation package cover the parts that usually force you to a native dependency. It is the wrong choice if you need a platform outside .NET 8, 9 and 10, if you need the file opening and rendering paths to be dependency injected, or if you need the standard to be implemented beyond what the dictionary version covers. Before you adopt it, read the setup section rather than the samples, because the DicomSetupBuilder.UseServiceProvider call is still required in every modern .NET host, and note that the newest release is 5.2.6 from 2026-03-30 while the repository was pushed to on 2026-10-15.

## FAQ

### What is Fellow Oak DICOM and what can it do?

It is a C# library for the DICOM standard that parses, stores, renders and serves DICOM objects. The feature list covers a fully asynchronous API, very large datasets with content loading on demand, rendering to System.Drawing.Bitmap or SixLabors.ImageSharp, JSON and XML export and import, anonymization, DICOM services, and customisation through a DI container, against DICOM dictionary version 2026b.

### Which .NET versions does fo-dicom officially support?

The readme lists .NET 8.0, .NET 9.0 and .NET 10.0 as officially supported, and says that other runtimes may work but that the CI pipeline only tests these platforms.

### How do I register fo-dicom in a modern .NET application?

Call services.AddFellowOakDicom() on the container, build it, and then call DicomSetupBuilder.UseServiceProvider with the built services. The readme comments that this last call is still necessary for now until fo-dicom has first-class AspNetCore integration, and the same pattern is shown for the web application builder, the host builder and a hand built ServiceCollection.

### Which fo-dicom packages do I need for image compression and rendering?

The compression formats, including JPEG, JPEG-LS, JPEG2000, HTJPEG2000 and RLE, come from the separate fo-dicom.Codecs package. For rendering you add one of fo-dicom.Imaging.Desktop for System.Drawing, fo-dicom.Imaging.ImageSharp or fo-dicom.Imaging.SkiaSharp, and fo-dicom.Instrumentation adds OpenTelemetry instrumentation.

### Can I use dependency injection everywhere in fo-dicom?

Not everywhere. The documentation's table shows that creating a DICOM server, a DICOM client and an advanced client connection can all use DI through IDicomServerFactory, IDicomClientFactory and IAdvancedDicomClientConnectionFactory, while opening a DICOM file with DicomFile.OpenAsync and rendering with new DicomImage(..).RenderImage(..) fall back to the static API.

## Sources

- [fo-dicom/fo-dicom on GitHub](https://github.com/fo-dicom/fo-dicom)
- [Issues](https://github.com/fo-dicom/fo-dicom/issues)
- [Project website](https://fo-dicom.github.io/stable/v5/index.html)
- [README](https://github.com/fo-dicom/fo-dicom/blob/development/README.md)
- [Releases](https://github.com/fo-dicom/fo-dicom/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/fo-dicom-fo-dicom
