Class: DicomS::MetaCodec

Inherits:
Object
  • Object
show all
Defined in:
lib/dicoms/meta_codec.rb

Overview

Inserting/extracting DICOM metadata in video files with FFMpeg

Example of use:

# Add metadata to a file
dicom = DICOM::DObject.read(dicom_file)
meta_codec = MetaCodec.new(mode: :chunked)
meta_file = 'ffmetadata'
meta_codec.(dicom, meta_file, dx: 111, dy: 222, dz: 333)
input_file = 'video.mkv'
output_file = 'video_with_metadata.mkv'
`ffmpeg -i #{input_file} -i #{meta_file} -map_metadata 1 -codec copy #{output_file}`

# Extract metadata from a file
`ffmpeg -i #{output_file}  -f ffmetadata #{meta_file}`
dicom_elements, additional_values = meta_codec.(meta_file)

Instance Method Summary collapse

Constructor Details

#initialize(options = {}) ⇒ MetaCodec

Two encoding modes:

  • :chunked : use few metadata entries (dicom_0) that encode all the DICOM elements (several are used because there’s a limit in the length of a single metadata entry)

  • :individual : use individual metadata entries for each DICOM tag



25
26
27
# File 'lib/dicoms/meta_codec.rb', line 25

def initialize(options = {})
  @mode = options[:mode] || :individual
end

Instance Method Details

#decode_metadata(txt) ⇒ Object



62
63
64
65
66
67
68
69
# File 'lib/dicoms/meta_codec.rb', line 62

def (txt)
  txt = unescape(txt)
  data = txt.split(PAIR_SEPARATOR).map{|pair| pair.split(VALUE_SEPARATOR)}
  data = data.map{|tag, value| [inner_unescape(tag), inner_unescape(value)]}
  data.map{|tag, value|
    DICOM::Element.new(tag, value)
  }
end

#encode_metadata(dicom, additional_metadata = {}, &blk) ⇒ Object



29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# File 'lib/dicoms/meta_codec.rb', line 29

def (dicom,  = {}, &blk)
  elements = dicom.elements.select{|e| !e.value.nil?}
  elements = elements.select(&blk) if blk
  elements = elements.map{|e| [e.tag, e.value]}
  case @mode
  when :chunked
    txt = elements.map{|tag, value| "#{inner_escape(tag)}#{VALUE_SEPARATOR}#{inner_escape(value)}"}.join(PAIR_SEPARATOR)
    chunks = in_chunks(txt, CHUNK_SIZE).map{|txt| escape(txt)}
     = Hash[chunks.each_with_index.to_a.map(&:reverse)]
  else
    pairs = elements.map { |tag, value|
      group, tag = tag.split(',')
      ["#{group}_#{tag}", escape(value)]
    }
     = Hash[pairs]
  end

  .merge()
end

#read_metadata(metadatafile) ⇒ Object

Can extract the metadatafile from a video input_file with:

`ffmpeg -i #{input_file}  -f ffmetadata #{metadatafile}`


73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
# File 'lib/dicoms/meta_codec.rb', line 73

def (metadatafile)
  lines = File.read(metadatafile).lines[1..-1]
  lines = lines.reject { |line|
    line = line.strip
    line.empty? || line[0, 1] == '#' || line[0, 1] == ';'
  }
  chunks = []
  elements = []
   = {}
  lines.each do |line|
    key, value = line.strip.split('=')
    key = key.downcase
    if match = key.match(/\Adicom_(\d+)\Z/)
      i = match[1].to_i
      chunks << [i, value]
    elsif match = key.match(/\Adicom_(\h+)_(\h+)\Z/)
      group = match[1]
      tag = match[2]
      tag = "#{group},#{tag}"
      elements << DICOM::Element.new(tag, unescape(value))
    elsif match = key.match(/\Adicom_(.+)\Z/)
      [match[1].downcase.to_sym] = value # TODO: type conversion?
    end
  end
  if chunks.size > 0
    elements += (chunks.sort_by(&:first).map(&:last).join)
  end
  [elements, ]
end

#write_metadata(dicom, metadatafile, additional_metadata = {}, &blk) ⇒ Object

Write DICOM metatada encoded for FFMpeg into a metadatafile The file can be attached to a video input_file with:

`ffmpeg -i #{input_file} -i #{metadatafile} -map_metadata 1 -codec copy #{output_file}`


52
53
54
55
56
57
58
59
60
# File 'lib/dicoms/meta_codec.rb', line 52

def (dicom, metadatafile,  = {}, &blk)
   = (dicom, , &blk)
  File.open(metadatafile, 'w') do |file|
    file.puts ";FFMETADATA1"
    .each do |name, value|
      file.puts "dicom_#{name}=#{value}"
    end
  end
end