-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1 from HicServices/release/1.0.1
Release/1.0.1
- Loading branch information
Showing
42 changed files
with
2,811 additions
and
91 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
dist: bionic | ||
jobs: | ||
include: | ||
- language: csharp | ||
mono: none | ||
dotnet: 2.2.100 | ||
|
||
script: | ||
- dotnet test ./Tests/Tests.csproj |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
# Changelog | ||
All notable changes to this project will be documented in this file. | ||
|
||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), | ||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). | ||
|
||
## [Unreleased] | ||
|
||
## [1.0.1] - 2019-12-09 | ||
|
||
### Added | ||
|
||
- Added initial iteration of dicom tag repopulator (anonymises dicom images based on CSV data) | ||
- Updated [DicomTypeTranslation] to 2.1.2 | ||
|
||
## [1.0.0] - 2019-10-01 | ||
|
||
Initial version | ||
|
||
|
||
[Unreleased]: https://github.com/HicServices/DicomTemplateBuilder/compare/v1.0.0...develop | ||
[1.0.1]: https://github.com/HicServices/DicomTemplateBuilder/compare/v1.0.0...v1.0.1 | ||
[1.0.0]: https://github.com/HicServices/DicomTemplateBuilder/tree/v1.0.0 | ||
[DicomTypeTranslation]: https://github.com/HicServices/DicomTypeTranslation |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using Dicom; | ||
|
||
namespace Repopulator | ||
{ | ||
public class CsvToDicomColumn | ||
{ | ||
public string Name { get; } | ||
public int Index { get; } | ||
public HashSet<DicomTag> TagsToPopulate { get; } | ||
public bool IsFilePath { get; set; } | ||
|
||
|
||
public CsvToDicomColumn(string colName, int index, bool isFileColumn,params DicomTag[] mappedTags) | ||
{ | ||
if (mappedTags != null && mappedTags.Any() && isFileColumn) | ||
throw new ArgumentException("Column has ambiguous role, it should either provide dicom tag substitutions or be the file path column not both"); | ||
|
||
if ((mappedTags == null || !mappedTags.Any())&& !isFileColumn) | ||
throw new ArgumentException("Column has no clear role, it should either provide dicom tag substitutions or be the file path column"); | ||
|
||
if (index < 0) | ||
throw new ArgumentException("index cannot be negative"); | ||
|
||
if (mappedTags != null) | ||
{ | ||
var sq = mappedTags.FirstOrDefault(t => t.DictionaryEntry.ValueRepresentations.Contains(DicomVR.SQ)); | ||
if(sq != null) | ||
throw new ArgumentException($"Sequence tags are not supported ({sq.DictionaryEntry.Keyword})"); | ||
} | ||
|
||
|
||
Name = colName; | ||
Index = index; | ||
TagsToPopulate = new HashSet<DicomTag>(mappedTags?? new DicomTag[0]); | ||
IsFilePath = isFileColumn; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,196 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.IO; | ||
using System.Linq; | ||
using System.Text; | ||
using CsvHelper; | ||
using CsvHelper.Configuration; | ||
using Dicom; | ||
using Repopulator.Matchers; | ||
|
||
namespace Repopulator | ||
{ | ||
public class CsvToDicomTagMapping | ||
{ | ||
/// <summary> | ||
/// The column of the CSV which records the file path to the image(s) being processed. These should be expressed | ||
/// relatively (i.e. not absolute path names) | ||
/// </summary> | ||
public CsvToDicomColumn FilenameColumn; | ||
|
||
/// <summary> | ||
/// Columns which contain dicom tag values. This is not all the columns in the CSV. It does not include <see cref="FilenameColumn"/> | ||
/// or any columns which could not be mapped; | ||
/// </summary> | ||
public List<CsvToDicomColumn> TagColumns = new List<CsvToDicomColumn>(); | ||
|
||
/// <summary> | ||
/// The file that was read during <see cref="BuildMap"/> | ||
/// </summary> | ||
public FileInfo CsvFile { get; private set; } | ||
|
||
/// <summary> | ||
/// Clears current column mappings | ||
/// </summary> | ||
public void Clear() | ||
{ | ||
FilenameColumn = null; | ||
CsvFile = null; | ||
TagColumns.Clear(); | ||
IsBuilt = false; | ||
} | ||
|
||
/// <summary> | ||
/// True if the class has been built yet | ||
/// </summary> | ||
public bool IsBuilt { get; private set; } | ||
|
||
/// <summary> | ||
/// Reads the headers from the CSV specified in <paramref name="options"/> and builds <see cref="TagColumns"/> and <see cref="FilenameColumn"/>. | ||
/// Returns true if the headers constitute a valid set (at least 1 and <see cref="FilenameColumn"/> found). | ||
/// </summary> | ||
/// <param name="options"></param> | ||
/// <param name="log"></param> | ||
/// <returns></returns> | ||
public bool BuildMap(DicomRepopulatorOptions options, out string log) | ||
{ | ||
Clear(); | ||
|
||
StringBuilder sb = new StringBuilder(); | ||
|
||
//how we will tie CSV rows to files | ||
IRepopulatorMatcher matcher = null; | ||
|
||
try | ||
{ | ||
var extraMappings = GetExtraMappings(options); | ||
|
||
CsvFile = options.CsvFileInfo; | ||
|
||
using (var reader = new CsvReader(CsvFile.OpenText())) | ||
{ | ||
reader.Configuration.TrimOptions = TrimOptions.Trim; | ||
reader.Read(); | ||
var couldReadHeader = reader.ReadHeader(); | ||
|
||
sb.AppendLine("Could Read Header:" + couldReadHeader); | ||
|
||
if (couldReadHeader) | ||
{ | ||
for (var index = 0; index < reader.Context.HeaderRecord.Length; index++) | ||
{ | ||
var header = reader.Context.HeaderRecord[index]; | ||
var match = GetKeyDicomTagAndColumnName(options, header, index,extraMappings); | ||
|
||
if (match != null) | ||
{ | ||
if(match.IsFilePath) | ||
if (FilenameColumn != null) | ||
throw new Exception("There are 2+ FilenameColumn in the CSV"); | ||
else | ||
FilenameColumn = match; | ||
else | ||
{ | ||
if(TagColumns.Any(c=>c.TagsToPopulate.Intersect(match.TagsToPopulate).Any())) | ||
throw new Exception($"There are 2+ columns that both populate for one of the DicomTag(s) '{string.Join(",",match.TagsToPopulate)}'"); | ||
|
||
TagColumns.Add(match); | ||
} | ||
|
||
sb.AppendLine($"Validated header '{header}'"); | ||
} | ||
else | ||
sb.AppendLine($"Could not determine tag for '{header}'"); | ||
} | ||
} | ||
|
||
sb.AppendLine($"Found {TagColumns.Count} valid mappings"); | ||
sb.AppendLine($"FilenameColumn is: {FilenameColumn?.Name ?? "Not Set"}"); | ||
} | ||
|
||
IsBuilt = true; | ||
|
||
var matcherFactory = new MatcherFactory(); | ||
using(matcher = matcherFactory.Create(this,options)) | ||
sb.AppendLine($"Matching Strategy is: { matcher?.ToString() ?? "No Strategy Found"}"); | ||
} | ||
catch (Exception e) | ||
{ | ||
sb.AppendLine(e.ToString()); | ||
log = sb.ToString(); | ||
return false; | ||
} | ||
|
||
log = sb.ToString(); | ||
|
||
|
||
return TagColumns.Count > 0 && matcher != null; | ||
} | ||
|
||
|
||
|
||
private Dictionary<string, HashSet<DicomTag>> GetExtraMappings(DicomRepopulatorOptions state) | ||
{ | ||
Dictionary<string, HashSet<DicomTag>> toReturn = new Dictionary<string, HashSet<DicomTag>>(StringComparer.CurrentCultureIgnoreCase); | ||
|
||
if(string.IsNullOrWhiteSpace(state.InputExtraMappings)) | ||
return null; | ||
|
||
var extraMappingsFile = state.ExtraMappings; | ||
|
||
int lineNumber = 0; | ||
foreach (string[] pair in File.ReadAllLines(extraMappingsFile.FullName).Select(l => l.Split(new []{':'},StringSplitOptions.RemoveEmptyEntries))) | ||
{ | ||
lineNumber++; | ||
|
||
//ignore blank lines | ||
if(pair.Length == 0) | ||
continue; | ||
|
||
if(pair.Length != 2) | ||
throw new Exception($"Bad line in extra mappings file (line number {lineNumber}). Line did not match expected format 'ColumnName:TagName'"); | ||
|
||
|
||
var found = DicomDictionary.Default.SingleOrDefault(entry => string.Equals(entry.Keyword ,pair[1],StringComparison.CurrentCultureIgnoreCase)); | ||
|
||
if (found == null) | ||
throw new Exception( | ||
$"Bad tag '{pair[1]}' on line number {lineNumber} of ExtraMappings file '{extraMappingsFile.FullName}'. It is not a valid DicomTag name"); | ||
|
||
if(!toReturn.ContainsKey(pair[0])) | ||
toReturn.Add(pair[0],new HashSet<DicomTag>()); | ||
|
||
toReturn[pair[0]].Add(found.Tag); | ||
} | ||
|
||
return toReturn; | ||
} | ||
|
||
/// <summary> | ||
/// Creates a mapping between a single CSV file column and one or more <see cref="DicomTag"/> | ||
/// </summary> | ||
public CsvToDicomColumn GetKeyDicomTagAndColumnName(DicomRepopulatorOptions state, string columnName,int index,Dictionary<string,HashSet<DicomTag>> extraMappings) | ||
{ | ||
CsvToDicomColumn toReturn = null; | ||
if(columnName.Equals(state.FileNameColumn,StringComparison.CurrentCultureIgnoreCase)) | ||
toReturn = new CsvToDicomColumn(columnName,index,true); | ||
|
||
var found = DicomDictionary.Default.SingleOrDefault(entry => string.Equals(entry.Keyword ,columnName,StringComparison.CurrentCultureIgnoreCase)); | ||
|
||
if(found != null) | ||
if (toReturn == null) | ||
toReturn = new CsvToDicomColumn(columnName,index,false,found.Tag); | ||
else | ||
toReturn.TagsToPopulate.Add(found.Tag); //it's a file path AND a tag! ok... | ||
|
||
|
||
if (extraMappings != null && extraMappings.ContainsKey(columnName)) | ||
if(toReturn == null) | ||
toReturn = new CsvToDicomColumn(columnName,index,false,extraMappings[columnName].ToArray()); | ||
else | ||
toReturn.TagsToPopulate.UnionWith(extraMappings[columnName]); | ||
|
||
return toReturn; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
<?xml version="1.0" encoding="utf-8"?> | ||
<ClassDiagram MajorVersion="1" MinorVersion="1"> | ||
<Class Name="Repopulator.Matchers.FilePathMatcher" Collapsed="true"> | ||
<Position X="4.5" Y="4" Width="1.5" /> | ||
<TypeIdentifier> | ||
<HashCode>AgAAAAAAACAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAA=</HashCode> | ||
<FileName>Matchers\FilePathMatcher.cs</FileName> | ||
</TypeIdentifier> | ||
</Class> | ||
<Class Name="Repopulator.Matchers.MatcherFactory"> | ||
<Position X="3.5" Y="2.5" Width="1.5" /> | ||
<TypeIdentifier> | ||
<HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAA=</HashCode> | ||
<FileName>Matchers\MatcherFactory.cs</FileName> | ||
</TypeIdentifier> | ||
</Class> | ||
<Class Name="Repopulator.Matchers.RepopulatorMatcher" Collapsed="true"> | ||
<Position X="5.5" Y="2.5" Width="2.25" /> | ||
<TypeIdentifier> | ||
<HashCode>AgAAAAAAADIAAAAAAAAAAAAAAAAAAAAAIAAAAABAAAA=</HashCode> | ||
<FileName>Matchers\RepopulatorMatcher.cs</FileName> | ||
</TypeIdentifier> | ||
<ShowAsAssociation> | ||
<Property Name="Map" /> | ||
</ShowAsAssociation> | ||
<Lollipop Position="0.2" /> | ||
</Class> | ||
<Class Name="Repopulator.Matchers.TagMatcher" Collapsed="true"> | ||
<Position X="7" Y="4" Width="1.5" /> | ||
<TypeIdentifier> | ||
<HashCode>AgAAEAAAACAAAAABAAAAAgAAAAAAAAAAJAQgBAAAAAA=</HashCode> | ||
<FileName>Matchers\TagMatcher.cs</FileName> | ||
</TypeIdentifier> | ||
<ShowAsAssociation> | ||
<Field Name="_indexer" /> | ||
</ShowAsAssociation> | ||
</Class> | ||
<Class Name="Repopulator.CsvToDicomColumn"> | ||
<Position X="9.25" Y="6.5" Width="2.25" /> | ||
<TypeIdentifier> | ||
<HashCode>AAAAAAAAAAAAAAAAAgAAAAQAAAAAAAAAAAEAABAAAAA=</HashCode> | ||
<FileName>CsvToDicomColumn.cs</FileName> | ||
</TypeIdentifier> | ||
</Class> | ||
<Class Name="Repopulator.CsvToDicomTagMapping"> | ||
<Position X="5" Y="5.75" Width="2" /> | ||
<AssociationLine Name="FilenameColumn" Type="Repopulator.CsvToDicomColumn" FixedToPoint="true"> | ||
<Path> | ||
<Point X="7" Y="7.923" /> | ||
<Point X="9.25" Y="7.923" /> | ||
</Path> | ||
</AssociationLine> | ||
<TypeIdentifier> | ||
<HashCode>AAACAAGAAAAAAAAAAAAAAABAAAAAAAAABAAAAARBAAA=</HashCode> | ||
<FileName>CsvToDicomTagMapping.cs</FileName> | ||
</TypeIdentifier> | ||
<ShowAsAssociation> | ||
<Field Name="FilenameColumn" /> | ||
</ShowAsAssociation> | ||
<ShowAsCollectionAssociation> | ||
<Field Name="TagColumns" /> | ||
</ShowAsCollectionAssociation> | ||
</Class> | ||
<Class Name="Repopulator.DicomRepopulatorOptions"> | ||
<Position X="12" Y="0.5" Width="2.75" /> | ||
<Compartments> | ||
<Compartment Name="Properties" Collapsed="true" /> | ||
</Compartments> | ||
<TypeIdentifier> | ||
<HashCode>AAAAgQgABAAAAAAAQAECAAQQCAAAEAgEASAAAAACAAA=</HashCode> | ||
<FileName>DicomRepopulatorOptions.cs</FileName> | ||
</TypeIdentifier> | ||
</Class> | ||
<Class Name="Repopulator.DicomRepopulatorProcessor" BaseTypeListCollapsed="true"> | ||
<Position X="9.25" Y="0.5" Width="2.25" /> | ||
<Compartments> | ||
<Compartment Name="Fields" Collapsed="true" /> | ||
</Compartments> | ||
<TypeIdentifier> | ||
<HashCode>AAhAAAAIACAAQQAAAgAAAIBAMAAABACAAAAQAAAAgAA=</HashCode> | ||
<FileName>DicomRepopulatorProcessor.cs</FileName> | ||
</TypeIdentifier> | ||
<ShowAsAssociation> | ||
<Property Name="Matcher" /> | ||
</ShowAsAssociation> | ||
<Lollipop Position="0.2" Collapsed="true" /> | ||
</Class> | ||
<Class Name="Repopulator.RepopulatorJob"> | ||
<Position X="9.25" Y="4.5" Width="2" /> | ||
<TypeIdentifier> | ||
<HashCode>AAAAAAAAABAAAAAAAAIAAAAAAAAEAAAAAAAAAAAAAAA=</HashCode> | ||
<FileName>RepopulatorJob.cs</FileName> | ||
</TypeIdentifier> | ||
<ShowAsAssociation> | ||
<Property Name="Map" /> | ||
</ShowAsAssociation> | ||
</Class> | ||
<Interface Name="Repopulator.Matchers.IRepopulatorMatcher"> | ||
<Position X="5.25" Y="0.5" Width="2.75" /> | ||
<TypeIdentifier> | ||
<HashCode>AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAA=</HashCode> | ||
<FileName>Matchers\IRepopulatorMatcher.cs</FileName> | ||
</TypeIdentifier> | ||
</Interface> | ||
<Font Name="Segoe UI" Size="9" /> | ||
</ClassDiagram> |
Oops, something went wrong.