Repository navigation
Conversation
Phase 26: Migration Tooling - fo-dicom 5.x API surface analyzed from dcm2csv and nccid repos - SharpDicom API mapped to fo-dicom equivalents - Roslyn analyzer/CodeFixProvider patterns documented - Compat layer architecture and pitfalls catalogued
Phase 26: Migration Tooling - 7 plan(s) in 4 wave(s) - 3 parallel (wave 1-2), 4 sequential - Ready for execution
Address 4 blockers and 2 warnings: - 26-01: Add GetValues<T> test coverage and must_have truth - 26-02: Specify SendAsync query key mapping substeps - 26-03: Add dcm2csv source directory verification step - 26-04: Add end-to-end network test truth - 26-06: Add missing dependency on 26-05 (FoDicom4.Compat) - 26-01: Add scope justification note (warning acknowledged)
- DicomTag class with DictionaryEntry, well-known tags, Equals/GetHashCode - DicomVR class with static instances matching fo-dicom pattern - DicomUID class with well-known UIDs - DicomDictionaryEntry wrapper for tag metadata - DicomItem abstract base with Wrap factory for type dispatch - DicomElement abstract base with Get<T> typed value access - DicomStringElement with VM-indexed string values - DicomAttributeTag with Values property parsing raw AT bytes - DicomOtherElement fallback for binary elements - DicomSequence with Items property wrapping nested datasets - DicomDataset with GetSingleValue, GetValues, AddOrUpdate, enumeration - DicomFile with Open/Save wrapping SharpDicom file I/O - DicomDataException matching fo-dicom exception type - Compatibility extension methods with Unwrap() for gradual migration - All types in FellowOakDicom namespace per fo-dicom 5.x convention - Targets netstandard2.0, net8.0, net9.0, net10.0
- DicomDataset constructor accepting SharpDicom dataset made public for interop - 38 unit tests covering all compat API surface: - DicomFile.Open with non-null Dataset/FileMetaInfo verification - Dataset enumeration yields correct DicomItem subtypes - DicomStringElement.Get<string>(index) returns VM-indexed values - DicomTag.DictionaryEntry resolves Name and Keyword - DicomSequence.Items returns nested compat datasets - DicomFile.Save roundtrips correctly - GetSingleValue<string/int> type dispatch - GetValues<string> returns VM array (multi-valued elements) - AddOrUpdate creates/replaces elements with backslash-joined values - Contains, Remove, TryGetSingleValue, TryGetSequence, GetSequence - Empty dataset enumeration, Unwrap interop - DicomTag equality and ToString formatting - DicomVR static instances, DicomUID equality - Test project targets net10.0, references NUnit - All 38 compat tests pass, all 2263 existing tests still pass
Tasks completed: 2/2 - Create FoDicom5.Compat project scaffold and core types - Create DicomDataset and DicomFile wrappers with tests SUMMARY: .planning/phases/26-migration-tooling/26-01-SUMMARY.md
- DicomQueryRetrieveLevel enum with Patient/Study/Series/Image values - DicomStatus wrapper with Code, State, well-known statuses (Success/Pending/Cancel) - DicomRequest abstract base with Dataset and Type properties - DicomResponse base with Status and optional Dataset - DicomCFindRequest with Level, OnResponseReceived callback, writable Dataset - DicomCFindResponse extending DicomResponse for C-FIND results
…<T> API - Create SharpDicom.FoDicom4.Compat mirroring FoDicom5.Compat structure - Use Dicom namespace (not FellowOakDicom) matching fo-dicom 4.x - Add Get<T>(tag, index) as primary accessor (fo-dicom 4.x API) - Add Get<T>(tag, defaultValue) overload for missing tag handling - Keep GetSingleValue<T> as alias (late 4.x compatibility) - Multi-target: netstandard2.0, net8.0, net9.0, net10.0
…ests - IDicomClient interface matching fo-dicom: AddRequestAsync, SendAsync, NegotiateAsyncOps, IsBusy - DicomClient bridges fo-dicom request-queue to SharpDicom direct async pattern - SendAsync creates SharpDicom client, connects, executes buffered CFindRequests via CFindScu - DicomClientFactory.Create(host, port, useTls, callingAE, calledAE) returns IDicomClient - 16 unit tests verifying factory, buffering, request construction, callbacks, and enum values
…Get<T> API - 25 tests covering fo-dicom 4.x API surface - Verify using Dicom; namespace compiles (not FellowOakDicom) - Test Get<T>(tag) and Get<T>(tag, defaultValue) methods - Test GetSingleValue<T> backward compat (late 4.x) - Test DicomFile.Open, roundtrip save, enumeration, sequences - All 4584 solution tests pass with zero failures
Tasks completed: 2/2 - Task 1: Network compat types (DicomCFindRequest, DicomStatus, DicomQueryRetrieveLevel) - Task 2: DicomClient adapter with request-queue pattern and tests SUMMARY: .planning/phases/26-migration-tooling/26-02-SUMMARY.md
Tasks completed: 2/2 - Create FoDicom4.Compat project with Dicom namespace and 4.x API - Create FoDicom4 compat tests SUMMARY: .planning/phases/26-migration-tooling/26-05-SUMMARY.md
…mpatUsageAnalyzer - SharpDicom.Analyzers project targeting netstandard2.0 with EnforceExtendedAnalyzerRules - DiagnosticIds: SD0001-SD0003 (fo-dicom detection), SD0010-SD0011 (compat detection) - FoDicomUsageAnalyzer: detects FellowOakDicom and Dicom namespace usage via semantic analysis - CompatUsageAnalyzer: detects compat layer usage with Info severity for step 2 migration - NuGet packaging ready with analyzer DLL in analyzers/dotnet/cs path - Added Microsoft.CodeAnalysis.CSharp.Workspaces 5.0.0 to Directory.Packages.props
…ssues - Create integration test project referencing SharpDicom.FoDicom5.Compat - Extract dcm2csv Entry class into Dcm2CsvPatches.cs for compilation testing - Make DicomTag.DictionaryEntry non-nullable with fallback for unknown tags - Update all ToString() methods to use non-nullable DictionaryEntry - Add DicomDictionaryEntry.CreateUnknown factory for unknown tag fallback - dcm2csv pattern matching (DicomAttributeTag/DicomStringElement/DicomSequence) compiles unmodified
- FoDicomToCompatFix: rewrites fo-dicom using directives to compat layer namespaces - FellowOakDicom -> SharpDicom.FoDicom5.Compat.FellowOakDicom - Dicom -> SharpDicom.FoDicom4.Compat.Dicom - CompatToNativeFix: rewrites compat layer usings to native SharpDicom namespaces - Compat.FellowOakDicom -> SharpDicom.Data - Compat.FellowOakDicom.Network -> SharpDicom.Network - Both support Fix All via WellKnownFixAllProviders.BatchFixer
Tasks completed: 2/2 - Create analyzer project with FoDicomUsageAnalyzer and CompatUsageAnalyzer - Create code fix providers (FoDicomToCompatFix, CompatToNativeFix) SUMMARY: .planning/phases/26-migration-tooling/26-06-SUMMARY.md
…utput - 9 integration tests exercising dcm2csv Entry.ProcessTag against compat layer - String elements: correct CSV rows with PatientID, PatientName dictionary resolution - Sequence elements: recursive nested dataset enumeration produces entries - Multi-valued elements: ORIGINAL\PRIMARY produces 2 separate entries - AT elements: DicomAttributeTag.Values resolves to DicomTag.DictionaryEntry.Name - Numeric elements: fallback to DicomItem.ToString() for non-string types - Empty elements: handled gracefully without exceptions - DicomTag.DictionaryEntry: never null (matches fo-dicom behavior) - Real DICOM file: processes dcm2csv sample file correctly - Full solution: 4593 tests (4413 pass, 180 skipped, 0 failed)
Tasks completed: 3/3 - Build dcm2csv against compat layer and fix compilation issues - Create dcm2csv integration tests and verify correct output - Checkpoint: user approved dcm2csv migration SUMMARY: .planning/phases/26-migration-tooling/26-03-SUMMARY.md
…stic tests - Create SharpDicom.Analyzers.Tests project with Microsoft.CodeAnalysis.Testing - Add FoDicomUsageAnalyzerTests: 7 tests covering SD0001 detection and false-positive exclusion - Add CompatUsageAnalyzerTests: 5 tests covering SD0010 detection and false-positive exclusion - Pin Microsoft.CodeAnalysis.* to 5.0.0 to fix version alignment (Pitfall 7) - Suppress NU1701 for legacy Microsoft.Composition transitive dependency
- Extract DICOM query logic from nccid into NccidPatches.cs - 17 integration tests verifying all nccid fo-dicom API usage: - DicomClientFactory.Create, NegotiateAsyncOps - DicomCFindRequest construction at Study level - Dataset.AddOrUpdate with raw tag constructor (0x8, 0x5) - Dataset.AddOrUpdate with StudyDate, PatientID, StudyInstanceUID - OnResponseReceived callback with += operator - GetSingleValue<string> on response dataset - DicomWindow/DicomDate formatting for query ranges - End-to-end network test marked Explicit (known PDV issue) - All 26 migration integration tests pass (dcm2csv + nccid) - Full solution: 4623 tests, 0 failures
…ations - Add FoDicomToCompatFixTests: 4 tests verifying SD0001 using directive rewrites (FellowOakDicom -> compat, FellowOakDicom.Network -> compat, etc.) - Add CompatToNativeFixTests: 5 tests verifying SD0010 using directive rewrites (compat FellowOakDicom -> SharpDicom.Data, compat Network -> SharpDicom.Network, etc.) - Full solution: 4632 tests pass (0 failures)
Tasks completed: 2/2 - Task 1: Create analyzer test project and diagnostic tests - Task 2: Create code fix provider tests SUMMARY: .planning/phases/26-migration-tooling/26-07-SUMMARY.md
Tasks completed: 2/2 - Build nccid against compat layer and create integration tests - Checkpoint: user approved nccid migration SUMMARY: .planning/phases/26-migration-tooling/26-04-SUMMARY.md
Update ROADMAP.md to mark Phase 26 complete. Add verification report documenting 3/5 full verifications (compat layers + analyzers) and 2 partial (dcm2csv/nccid validated via extracted logic integration tests rather than full project compilation). Phase deliverables: FoDicom5.Compat (54 tests), FoDicom4.Compat (25 tests), SharpDicom.Analyzers (21 tests), dcm2csv validation (9 tests), nccid validation (17 tests). Total: 146 new tests.
There was a problem hiding this comment.
10 issues found across 84 files
Note: This PR contains a large number of files. cubic only reviews up to 75 files per PR, so some files may not have been reviewed.
Prompt for AI agents (all issues)
Check if these issues are valid — if so, understand the root cause of each and fix them.
<file name="src/SharpDicom.FoDicom4.Compat/DicomDataset.cs">
<violation number="1" location="src/SharpDicom.FoDicom4.Compat/DicomDataset.cs:290">
P2: Get<string>(tag) uses _inner.GetString for index 0, which returns the full multi-valued string (e.g., "A\B") instead of the first value. This breaks expected single-value semantics for multi-valued elements.</violation>
</file>
<file name="src/SharpDicom.FoDicom5.Compat/SharpDicom.FoDicom5.Compat.csproj">
<violation number="1" location="src/SharpDicom.FoDicom5.Compat/SharpDicom.FoDicom5.Compat.csproj:2">
P1: SDK-style projects must specify `TargetFramework`/`TargetFrameworks`; otherwise the project will not build. Add the intended target frameworks to this csproj.</violation>
</file>
<file name="src/SharpDicom.Analyzers/Analyzers/CompatUsageAnalyzer.cs">
<violation number="1" location="src/SharpDicom.Analyzers/Analyzers/CompatUsageAnalyzer.cs:52">
P2: The type-usage diagnostic never fires for compat types used from referenced assemblies because it only inspects named type declarations in source (`SymbolKind.NamedType` + `location.IsInSource`). This misses actual usage sites in consumer projects. Consider analyzing identifier/qualified name usages (or operations) and inspecting the symbol’s containing namespace instead.</violation>
</file>
<file name="src/SharpDicom.FoDicom5.Compat/DicomAttributeTag.cs">
<violation number="1" location="src/SharpDicom.FoDicom5.Compat/DicomAttributeTag.cs:57">
P2: Casting `DicomTag[]` to `object[]` will throw at runtime because `DicomTag` is a struct. Use the `string.Join` IEnumerable overload (or convert to string array) instead.</violation>
</file>
<file name="src/SharpDicom.FoDicom4.Compat/DicomAttributeTag.cs">
<violation number="1" location="src/SharpDicom.FoDicom4.Compat/DicomAttributeTag.cs:58">
P2: Casting `DicomTag[]` to `object[]` will throw `InvalidCastException` because `DicomTag` is a struct. This breaks `ToString()` for AT elements. Use the generic `string.Join` overload instead.</violation>
</file>
<file name="src/SharpDicom.FoDicom4.Compat/SharpDicom.FoDicom4.Compat.csproj">
<violation number="1" location="src/SharpDicom.FoDicom4.Compat/SharpDicom.FoDicom4.Compat.csproj:2">
P1: SDK-style projects must declare TargetFramework/TargetFrameworks. This new csproj omits it, which will cause MSBuild to error on build. Add the target frameworks (net8.0;net9.0;netstandard2.0 per PR description) to the PropertyGroup.</violation>
</file>
<file name="src/SharpDicom.Analyzers/Analyzers/FoDicomUsageAnalyzer.cs">
<violation number="1" location="src/SharpDicom.Analyzers/Analyzers/FoDicomUsageAnalyzer.cs:61">
P2: TypeInstantiationRule never fires for actual fo-dicom type usages because the analyzer only inspects declared NamedType symbols in source. This misses most instance type usage (e.g., variable types or object creation) and contradicts the rule’s stated purpose.</violation>
</file>
<file name="src/SharpDicom.FoDicom5.Compat/DicomDataset.cs">
<violation number="1" location="src/SharpDicom.FoDicom5.Compat/DicomDataset.cs:74">
P2: GetSingleValue<T> returns default values for missing/empty tags instead of throwing `DicomDataException`, which breaks fo-dicom 5.x compatibility (docs specify GetSingleValue throws when the tag is missing/empty).</violation>
<violation number="2" location="src/SharpDicom.FoDicom5.Compat/DicomDataset.cs:111">
P2: GetValue<T> should throw when the tag is missing or the index is out of range, but the string path returns "" instead, masking errors and diverging from fo-dicom behavior.</violation>
<violation number="3" location="src/SharpDicom.FoDicom5.Compat/DicomDataset.cs:237">
P2: AddOrUpdate always uses ASCII bytes, ignoring the dataset’s Specific Character Set/Encoding. This will corrupt non-ASCII values in compat mode; use the dataset encoding instead.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
| @@ -0,0 +1,17 @@ | |||
| <Project Sdk="Microsoft.NET.Sdk"> | |||
| <PropertyGroup> | |||
There was a problem hiding this comment.
P1: SDK-style projects must specify TargetFramework/TargetFrameworks; otherwise the project will not build. Add the intended target frameworks to this csproj.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/SharpDicom.FoDicom5.Compat/SharpDicom.FoDicom5.Compat.csproj, line 2:
<comment>SDK-style projects must specify `TargetFramework`/`TargetFrameworks`; otherwise the project will not build. Add the intended target frameworks to this csproj.</comment>
<file context>
@@ -0,0 +1,17 @@
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <RootNamespace>FellowOakDicom</RootNamespace>
+
</file context>
| @@ -0,0 +1,17 @@ | |||
| <Project Sdk="Microsoft.NET.Sdk"> | |||
| <PropertyGroup> | |||
There was a problem hiding this comment.
P1: SDK-style projects must declare TargetFramework/TargetFrameworks. This new csproj omits it, which will cause MSBuild to error on build. Add the target frameworks (net8.0;net9.0;netstandard2.0 per PR description) to the PropertyGroup.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/SharpDicom.FoDicom4.Compat/SharpDicom.FoDicom4.Compat.csproj, line 2:
<comment>SDK-style projects must declare TargetFramework/TargetFrameworks. This new csproj omits it, which will cause MSBuild to error on build. Add the target frameworks (net8.0;net9.0;netstandard2.0 per PR description) to the PropertyGroup.</comment>
<file context>
@@ -0,0 +1,17 @@
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <RootNamespace>Dicom</RootNamespace>
+
</file context>
|
|
||
| if (typeof(T) == typeof(string)) | ||
| { | ||
| if (index == 0) |
There was a problem hiding this comment.
P2: Get(tag) uses _inner.GetString for index 0, which returns the full multi-valued string (e.g., "A\B") instead of the first value. This breaks expected single-value semantics for multi-valued elements.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/SharpDicom.FoDicom4.Compat/DicomDataset.cs, line 290:
<comment>Get<string>(tag) uses _inner.GetString for index 0, which returns the full multi-valued string (e.g., "A\B") instead of the first value. This breaks expected single-value semantics for multi-valued elements.</comment>
<file context>
@@ -0,0 +1,345 @@
+
+ if (typeof(T) == typeof(string))
+ {
+ if (index == 0)
+ {
+ var str = _inner.GetString(sdTag) ?? "";
</file context>
| context.RegisterSyntaxNodeAction(AnalyzeUsingDirective, SyntaxKind.UsingDirective); | ||
|
|
||
| // Detect compat layer type usage via semantic analysis | ||
| context.RegisterSymbolAction(AnalyzeNamedType, SymbolKind.NamedType); |
There was a problem hiding this comment.
P2: The type-usage diagnostic never fires for compat types used from referenced assemblies because it only inspects named type declarations in source (SymbolKind.NamedType + location.IsInSource). This misses actual usage sites in consumer projects. Consider analyzing identifier/qualified name usages (or operations) and inspecting the symbol’s containing namespace instead.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/SharpDicom.Analyzers/Analyzers/CompatUsageAnalyzer.cs, line 52:
<comment>The type-usage diagnostic never fires for compat types used from referenced assemblies because it only inspects named type declarations in source (`SymbolKind.NamedType` + `location.IsInSource`). This misses actual usage sites in consumer projects. Consider analyzing identifier/qualified name usages (or operations) and inspecting the symbol’s containing namespace instead.</comment>
<file context>
@@ -0,0 +1,116 @@
+ context.RegisterSyntaxNodeAction(AnalyzeUsingDirective, SyntaxKind.UsingDirective);
+
+ // Detect compat layer type usage via semantic analysis
+ context.RegisterSymbolAction(AnalyzeNamedType, SymbolKind.NamedType);
+ }
+
</file context>
| public override string ToString() | ||
| { | ||
| var tags = GetTagValues(); | ||
| return $"{Tag} AT [{string.Join(", ", (object[])tags)}] {Tag.DictionaryEntry.Name}"; |
There was a problem hiding this comment.
P2: Casting DicomTag[] to object[] will throw at runtime because DicomTag is a struct. Use the string.Join IEnumerable overload (or convert to string array) instead.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/SharpDicom.FoDicom5.Compat/DicomAttributeTag.cs, line 57:
<comment>Casting `DicomTag[]` to `object[]` will throw at runtime because `DicomTag` is a struct. Use the `string.Join` IEnumerable overload (or convert to string array) instead.</comment>
<file context>
@@ -0,0 +1,85 @@
+ public override string ToString()
+ {
+ var tags = GetTagValues();
+ return $"{Tag} AT [{string.Join(", ", (object[])tags)}] {Tag.DictionaryEntry.Name}";
+ }
+
</file context>
| { | ||
| var tags = GetTagValues(); | ||
| var name = Tag.DictionaryEntry?.Name ?? "Unknown Tag"; | ||
| return $"{Tag} AT [{string.Join(", ", (object[])tags)}] {name}"; |
There was a problem hiding this comment.
P2: Casting DicomTag[] to object[] will throw InvalidCastException because DicomTag is a struct. This breaks ToString() for AT elements. Use the generic string.Join overload instead.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/SharpDicom.FoDicom4.Compat/DicomAttributeTag.cs, line 58:
<comment>Casting `DicomTag[]` to `object[]` will throw `InvalidCastException` because `DicomTag` is a struct. This breaks `ToString()` for AT elements. Use the generic `string.Join` overload instead.</comment>
<file context>
@@ -0,0 +1,86 @@
+ {
+ var tags = GetTagValues();
+ var name = Tag.DictionaryEntry?.Name ?? "Unknown Tag";
+ return $"{Tag} AT [{string.Join(", ", (object[])tags)}] {name}";
+ }
+
</file context>
| context.RegisterSyntaxNodeAction(AnalyzeUsingDirective, SyntaxKind.UsingDirective); | ||
|
|
||
| // Detect fo-dicom type usage via semantic analysis | ||
| context.RegisterSymbolAction(AnalyzeNamedType, SymbolKind.NamedType); |
There was a problem hiding this comment.
P2: TypeInstantiationRule never fires for actual fo-dicom type usages because the analyzer only inspects declared NamedType symbols in source. This misses most instance type usage (e.g., variable types or object creation) and contradicts the rule’s stated purpose.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/SharpDicom.Analyzers/Analyzers/FoDicomUsageAnalyzer.cs, line 61:
<comment>TypeInstantiationRule never fires for actual fo-dicom type usages because the analyzer only inspects declared NamedType symbols in source. This misses most instance type usage (e.g., variable types or object creation) and contradicts the rule’s stated purpose.</comment>
<file context>
@@ -0,0 +1,170 @@
+ context.RegisterSyntaxNodeAction(AnalyzeUsingDirective, SyntaxKind.UsingDirective);
+
+ // Detect fo-dicom type usage via semantic analysis
+ context.RegisterSymbolAction(AnalyzeNamedType, SymbolKind.NamedType);
+
+ // Detect fo-dicom static method calls
</file context>
| var values = _inner.GetStrings(sdTag); | ||
| if (values != null && index >= 0 && index < values.Length) | ||
| return (T)(object)values[index]; | ||
| return (T)(object)""; |
There was a problem hiding this comment.
P2: GetValue should throw when the tag is missing or the index is out of range, but the string path returns "" instead, masking errors and diverging from fo-dicom behavior.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/SharpDicom.FoDicom5.Compat/DicomDataset.cs, line 111:
<comment>GetValue<T> should throw when the tag is missing or the index is out of range, but the string path returns "" instead, masking errors and diverging from fo-dicom behavior.</comment>
<file context>
@@ -0,0 +1,266 @@
+ var values = _inner.GetStrings(sdTag);
+ if (values != null && index >= 0 && index < values.Length)
+ return (T)(object)values[index];
+ return (T)(object)"";
+ }
+
</file context>
| { | ||
| var sdTag = tag.ToSharpDicom(); | ||
| var joined = string.Join("\\", values); | ||
| var bytes = System.Text.Encoding.ASCII.GetBytes(joined); |
There was a problem hiding this comment.
P2: AddOrUpdate always uses ASCII bytes, ignoring the dataset’s Specific Character Set/Encoding. This will corrupt non-ASCII values in compat mode; use the dataset encoding instead.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/SharpDicom.FoDicom5.Compat/DicomDataset.cs, line 237:
<comment>AddOrUpdate always uses ASCII bytes, ignoring the dataset’s Specific Character Set/Encoding. This will corrupt non-ASCII values in compat mode; use the dataset encoding instead.</comment>
<file context>
@@ -0,0 +1,266 @@
+ {
+ var sdTag = tag.ToSharpDicom();
+ var joined = string.Join("\\", values);
+ var bytes = System.Text.Encoding.ASCII.GetBytes(joined);
+
+ // Pad to even length per DICOM spec
</file context>
|
|
||
| if (typeof(T) == typeof(string)) | ||
| { | ||
| var str = _inner.GetString(sdTag) ?? ""; |
There was a problem hiding this comment.
P2: GetSingleValue returns default values for missing/empty tags instead of throwing DicomDataException, which breaks fo-dicom 5.x compatibility (docs specify GetSingleValue throws when the tag is missing/empty).
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/SharpDicom.FoDicom5.Compat/DicomDataset.cs, line 74:
<comment>GetSingleValue<T> returns default values for missing/empty tags instead of throwing `DicomDataException`, which breaks fo-dicom 5.x compatibility (docs specify GetSingleValue throws when the tag is missing/empty).</comment>
<file context>
@@ -0,0 +1,266 @@
+
+ if (typeof(T) == typeof(string))
+ {
+ var str = _inner.GetString(sdTag) ?? "";
+ return (T)(object)str;
+ }
</file context>
There was a problem hiding this comment.
Review by RecurseML
🔍 Review performed on bfcb687..26a9df0
✨ No bugs found, your code is sparkling clean
✅ Files analyzed, no issues (50)
• .planning/ROADMAP.md
• .planning/STATE.md
• .planning/phases/26-migration-tooling/26-01-PLAN.md
• .planning/phases/26-migration-tooling/26-01-SUMMARY.md
• .planning/phases/26-migration-tooling/26-02-PLAN.md
• .planning/phases/26-migration-tooling/26-02-SUMMARY.md
• .planning/phases/26-migration-tooling/26-03-PLAN.md
• .planning/phases/26-migration-tooling/26-03-SUMMARY.md
• .planning/phases/26-migration-tooling/26-04-PLAN.md
• .planning/phases/26-migration-tooling/26-04-SUMMARY.md
• .planning/phases/26-migration-tooling/26-05-PLAN.md
• .planning/phases/26-migration-tooling/26-05-SUMMARY.md
• .planning/phases/26-migration-tooling/26-06-PLAN.md
• .planning/phases/26-migration-tooling/26-06-SUMMARY.md
• .planning/phases/26-migration-tooling/26-07-PLAN.md
• .planning/phases/26-migration-tooling/26-07-SUMMARY.md
• .planning/phases/26-migration-tooling/26-RESEARCH.md
• .planning/phases/26-migration-tooling/26-VERIFICATION.md
• Directory.Build.props
• Directory.Packages.props
• SharpDicom.sln
• src/SharpDicom.Analyzers/AnalyzerReleases.Shipped.md
• src/SharpDicom.Analyzers/AnalyzerReleases.Unshipped.md
• src/SharpDicom.Analyzers/Analyzers/CompatUsageAnalyzer.cs
• src/SharpDicom.Analyzers/Analyzers/FoDicomUsageAnalyzer.cs
• src/SharpDicom.Analyzers/CodeFixes/CompatToNativeFix.cs
• src/SharpDicom.Analyzers/CodeFixes/FoDicomToCompatFix.cs
• src/SharpDicom.Analyzers/DiagnosticIds.cs
• src/SharpDicom.Analyzers/SharpDicom.Analyzers.csproj
• src/SharpDicom.FoDicom4.Compat/Compatibility.cs
• src/SharpDicom.FoDicom4.Compat/DicomAttributeTag.cs
• src/SharpDicom.FoDicom4.Compat/DicomDataset.cs
• src/SharpDicom.FoDicom4.Compat/DicomDictionaryEntry.cs
• src/SharpDicom.FoDicom4.Compat/DicomElement.cs
• src/SharpDicom.FoDicom4.Compat/DicomFile.cs
• src/SharpDicom.FoDicom4.Compat/DicomItem.cs
• src/SharpDicom.FoDicom4.Compat/DicomOtherElement.cs
• src/SharpDicom.FoDicom4.Compat/DicomSequence.cs
• src/SharpDicom.FoDicom4.Compat/DicomStringElement.cs
• src/SharpDicom.FoDicom4.Compat/DicomTag.cs
• src/SharpDicom.FoDicom4.Compat/DicomUID.cs
• src/SharpDicom.FoDicom4.Compat/DicomVR.cs
• src/SharpDicom.FoDicom4.Compat/Exceptions/DicomDataException.cs
• src/SharpDicom.FoDicom4.Compat/SharpDicom.FoDicom4.Compat.csproj
• src/SharpDicom.FoDicom5.Compat/Compatibility.cs
• src/SharpDicom.FoDicom5.Compat/DicomAttributeTag.cs
• src/SharpDicom.FoDicom5.Compat/DicomDataset.cs
• src/SharpDicom.FoDicom5.Compat/DicomDictionaryEntry.cs
• src/SharpDicom.FoDicom5.Compat/DicomElement.cs
• src/SharpDicom.FoDicom5.Compat/DicomFile.cs
⏭️ Files skipped (34)
| Locations |
|---|
src/SharpDicom.FoDicom5.Compat/DicomItem.cs |
src/SharpDicom.FoDicom5.Compat/DicomOtherElement.cs |
src/SharpDicom.FoDicom5.Compat/DicomSequence.cs |
src/SharpDicom.FoDicom5.Compat/DicomStringElement.cs |
src/SharpDicom.FoDicom5.Compat/DicomTag.cs |
src/SharpDicom.FoDicom5.Compat/DicomUID.cs |
src/SharpDicom.FoDicom5.Compat/DicomVR.cs |
src/SharpDicom.FoDicom5.Compat/Exceptions/DicomDataException.cs |
src/SharpDicom.FoDicom5.Compat/Network/Client/DicomClient.cs |
src/SharpDicom.FoDicom5.Compat/Network/Client/DicomClientFactory.cs |
src/SharpDicom.FoDicom5.Compat/Network/Client/IDicomClient.cs |
src/SharpDicom.FoDicom5.Compat/Network/DicomCFindRequest.cs |
src/SharpDicom.FoDicom5.Compat/Network/DicomCFindResponse.cs |
src/SharpDicom.FoDicom5.Compat/Network/DicomQueryRetrieveLevel.cs |
src/SharpDicom.FoDicom5.Compat/Network/DicomRequest.cs |
src/SharpDicom.FoDicom5.Compat/Network/DicomResponse.cs |
src/SharpDicom.FoDicom5.Compat/Network/DicomStatus.cs |
src/SharpDicom.FoDicom5.Compat/SharpDicom.FoDicom5.Compat.csproj |
tests/SharpDicom.Analyzers.Tests/CompatToNativeFixTests.cs |
tests/SharpDicom.Analyzers.Tests/CompatUsageAnalyzerTests.cs |
tests/SharpDicom.Analyzers.Tests/FoDicomToCompatFixTests.cs |
tests/SharpDicom.Analyzers.Tests/FoDicomUsageAnalyzerTests.cs |
tests/SharpDicom.Analyzers.Tests/SharpDicom.Analyzers.Tests.csproj |
tests/SharpDicom.FoDicom4.Compat.Tests/FoDicom4CompatTests.cs |
tests/SharpDicom.FoDicom4.Compat.Tests/SharpDicom.FoDicom4.Compat.Tests.csproj |
tests/SharpDicom.FoDicom5.Compat.Tests/DicomDatasetCompatTests.cs |
tests/SharpDicom.FoDicom5.Compat.Tests/DicomFileCompatTests.cs |
tests/SharpDicom.FoDicom5.Compat.Tests/NetworkCompatTests.cs |
tests/SharpDicom.FoDicom5.Compat.Tests/SharpDicom.FoDicom5.Compat.Tests.csproj |
tests/SharpDicom.Migration.Integration/Dcm2CsvCompatTests.cs |
tests/SharpDicom.Migration.Integration/Dcm2CsvPatches.cs |
tests/SharpDicom.Migration.Integration/NccidCompatTests.cs |
tests/SharpDicom.Migration.Integration/NccidPatches.cs |
tests/SharpDicom.Migration.Integration/SharpDicom.Migration.Integration.csproj |
Summary
Dicomnamespace) and 5.x (FellowOakDicomnamespace), using composition wrappers over SharpDicom internalsAddRequestAsync/SendAsync) to SharpDicom's direct async patternNew Projects
Test plan
dotnet buildcompiles all 7 new projectsdotnet testpasses all 146 new tests (0 failures)using FellowOakDicom, code fix rewrites to compat namespaceSummary by cubic
Adds drop-in fo-dicom 4.x/5.x compatibility layers and Roslyn migration tooling to enable a two-step move to SharpDicom, including a client adapter for fo-dicom networking. Validated via unit and integration tests that mirror dcm2csv and nccid API usage.
New Features
Migration
Written for commit 26a9df0. Summary will update on new commits.
High-level PR Summary
This PR delivers complete migration tooling for transitioning projects from fo-dicom to SharpDicom. It introduces two compatibility packages (
SharpDicom.FoDicom5.Compatfor theFellowOakDicomnamespace andSharpDicom.FoDicom4.Compatfor theDicomnamespace) that provide drop-in replacements wrapping SharpDicom internals via composition. The network adapter bridges fo-dicom's request-queue pattern (AddRequestAsync/SendAsync) to SharpDicom's direct async pattern. Additionally, Roslyn diagnostic analyzers detect fo-dicom usage (SD0001-SD0003) and compat layer usage (SD0010-SD0011) with automated code fix providers for two-step migration. Integration tests validate the compat layers against real-world API patterns from dcm2csv (9 tests for file I/O) and nccid (17 tests for networking), with all 146 new tests passing across 7 new projects totaling 4632 tests overall.⏱️ Estimated Review Time: 30-90 minutes
💡 Review Order Suggestion
.planning/ROADMAP.md.planning/STATE.mdDirectory.Build.propsDirectory.Packages.propsSharpDicom.sln.planning/phases/26-migration-tooling/26-RESEARCH.mdsrc/SharpDicom.FoDicom5.Compat/SharpDicom.FoDicom5.Compat.csprojsrc/SharpDicom.FoDicom5.Compat/DicomTag.cssrc/SharpDicom.FoDicom5.Compat/DicomVR.cssrc/SharpDicom.FoDicom5.Compat/DicomUID.cssrc/SharpDicom.FoDicom5.Compat/DicomDictionaryEntry.cssrc/SharpDicom.FoDicom5.Compat/Exceptions/DicomDataException.cssrc/SharpDicom.FoDicom5.Compat/DicomItem.cssrc/SharpDicom.FoDicom5.Compat/DicomElement.cssrc/SharpDicom.FoDicom5.Compat/DicomStringElement.cssrc/SharpDicom.FoDicom5.Compat/DicomAttributeTag.cssrc/SharpDicom.FoDicom5.Compat/DicomOtherElement.cssrc/SharpDicom.FoDicom5.Compat/DicomSequence.cssrc/SharpDicom.FoDicom5.Compat/DicomDataset.cssrc/SharpDicom.FoDicom5.Compat/DicomFile.cssrc/SharpDicom.FoDicom5.Compat/Compatibility.cssrc/SharpDicom.FoDicom5.Compat/Network/DicomQueryRetrieveLevel.cssrc/SharpDicom.FoDicom5.Compat/Network/DicomStatus.cssrc/SharpDicom.FoDicom5.Compat/Network/DicomRequest.cssrc/SharpDicom.FoDicom5.Compat/Network/DicomResponse.cssrc/SharpDicom.FoDicom5.Compat/Network/DicomCFindRequest.cssrc/SharpDicom.FoDicom5.Compat/Network/DicomCFindResponse.cssrc/SharpDicom.FoDicom5.Compat/Network/Client/IDicomClient.cssrc/SharpDicom.FoDicom5.Compat/Network/Client/DicomClient.cssrc/SharpDicom.FoDicom5.Compat/Network/Client/DicomClientFactory.cstests/SharpDicom.FoDicom5.Compat.Tests/SharpDicom.FoDicom5.Compat.Tests.csprojtests/SharpDicom.FoDicom5.Compat.Tests/DicomFileCompatTests.cstests/SharpDicom.FoDicom5.Compat.Tests/DicomDatasetCompatTests.cstests/SharpDicom.FoDicom5.Compat.Tests/NetworkCompatTests.cssrc/SharpDicom.FoDicom4.Compat/SharpDicom.FoDicom4.Compat.csprojsrc/SharpDicom.FoDicom4.Compat/DicomTag.cssrc/SharpDicom.FoDicom4.Compat/DicomVR.cssrc/SharpDicom.FoDicom4.Compat/DicomUID.cssrc/SharpDicom.FoDicom4.Compat/DicomDictionaryEntry.cssrc/SharpDicom.FoDicom4.Compat/Exceptions/DicomDataException.cssrc/SharpDicom.FoDicom4.Compat/DicomItem.cssrc/SharpDicom.FoDicom4.Compat/DicomElement.cssrc/SharpDicom.FoDicom4.Compat/DicomStringElement.cssrc/SharpDicom.FoDicom4.Compat/DicomAttributeTag.cssrc/SharpDicom.FoDicom4.Compat/DicomOtherElement.cssrc/SharpDicom.FoDicom4.Compat/DicomSequence.cssrc/SharpDicom.FoDicom4.Compat/DicomDataset.cssrc/SharpDicom.FoDicom4.Compat/DicomFile.cssrc/SharpDicom.FoDicom4.Compat/Compatibility.cstests/SharpDicom.FoDicom4.Compat.Tests/SharpDicom.FoDicom4.Compat.Tests.csprojtests/SharpDicom.FoDicom4.Compat.Tests/FoDicom4CompatTests.cstests/SharpDicom.Migration.Integration/SharpDicom.Migration.Integration.csprojtests/SharpDicom.Migration.Integration/Dcm2CsvPatches.cstests/SharpDicom.Migration.Integration/NccidPatches.cstests/SharpDicom.Migration.Integration/Dcm2CsvCompatTests.cstests/SharpDicom.Migration.Integration/NccidCompatTests.cssrc/SharpDicom.Analyzers/SharpDicom.Analyzers.csprojsrc/SharpDicom.Analyzers/DiagnosticIds.cssrc/SharpDicom.Analyzers/Analyzers/FoDicomUsageAnalyzer.cssrc/SharpDicom.Analyzers/Analyzers/CompatUsageAnalyzer.cssrc/SharpDicom.Analyzers/CodeFixes/FoDicomToCompatFix.cssrc/SharpDicom.Analyzers/CodeFixes/CompatToNativeFix.cssrc/SharpDicom.Analyzers/AnalyzerReleases.Shipped.mdsrc/SharpDicom.Analyzers/AnalyzerReleases.Unshipped.mdtests/SharpDicom.Analyzers.Tests/SharpDicom.Analyzers.Tests.csprojtests/SharpDicom.Analyzers.Tests/FoDicomUsageAnalyzerTests.cstests/SharpDicom.Analyzers.Tests/CompatUsageAnalyzerTests.cstests/SharpDicom.Analyzers.Tests/FoDicomToCompatFixTests.cstests/SharpDicom.Analyzers.Tests/CompatToNativeFixTests.cs.planning/phases/26-migration-tooling/26-01-PLAN.md.planning/phases/26-migration-tooling/26-01-SUMMARY.md.planning/phases/26-migration-tooling/26-02-PLAN.md.planning/phases/26-migration-tooling/26-02-SUMMARY.md.planning/phases/26-migration-tooling/26-03-PLAN.md.planning/phases/26-migration-tooling/26-03-SUMMARY.md.planning/phases/26-migration-tooling/26-04-PLAN.md.planning/phases/26-migration-tooling/26-04-SUMMARY.md.planning/phases/26-migration-tooling/26-05-PLAN.md.planning/phases/26-migration-tooling/26-05-SUMMARY.md.planning/phases/26-migration-tooling/26-06-PLAN.md.planning/phases/26-migration-tooling/26-06-SUMMARY.md.planning/phases/26-migration-tooling/26-07-PLAN.md.planning/phases/26-migration-tooling/26-07-SUMMARY.md.planning/phases/26-migration-tooling/26-VERIFICATION.md