Inserting a Waveform Group Into a Data Set Example for C++ 6.0 and later

/*****************************************************************/
/*  This is a comprehensive sample, which shows how to insert a  */
/*  waveform group with one ECG channel into a Data Set.         */
/*  The main function is InsertECGWaveform; the rest of the      */
/*  functions are helping functions.                             */
/*****************************************************************/

#pragma warning(disable: 4786)

// Functions Prototypes
BOOL InsertECGChannel(ILEADDicomDSPtr& spDataSet,
                      IDicomWaveformGroupPtr& spWaveformGroup,
                      SAFEARRAY* psaSamples);
BOOL SetChannelSourceAndSensitivity(ILEADDicomDSPtr& spDataSet,
                                    IDicomWaveformChannelPtr& spWaveformChannel);
BOOL SetChannelAnnotations(IDicomWaveformChannelPtr& spWaveformChannel);

// The main function that creates the waveform group and adds to the Data Set
// Note: The function assumes that psaSamples is a safearray of short (VT_I2) values.
BOOL InsertECGWaveform(ILEADDicomDSPtr& spDataSet, SAFEARRAY* psaSamples)
{
   if (!psaSamples)
   {
      return FALSE;
   }
   
   // Our new waveform group
   IDicomWaveformGroupPtr spECGWaveformGroup(__uuidof(DicomWaveformGroup));
   
   spECGWaveformGroup->EnableMethodErrors = VARIANT_FALSE;
   
   // Reset the waveform group, we don't really need to call this!
   spECGWaveformGroup->Reset ();
   
   // Set the Waveform Sample Interpretation
   spECGWaveformGroup->SampleInterpretation = WAVEFORM_SAMPLE_INTERPRETATION_SS;

   ULONG uNumberOfSamples = psaSamples->rgsabound->cElements;
   
   // Set the Number of Waveform Samples. You can obtain this number by accessing
   // the NumberOfSamplesPerChannel property.
   if (spECGWaveformGroup->SetNumberOfSamplesPerChannel (uNumberOfSamples) != DICOM_SUCCESS)
   {
      return FALSE;
   }

   // Set the Sampling Frequency
   spECGWaveformGroup->SamplingFrequency = 240.0;

   // No Multiplex Group Time Offset. When the value is defined, it can be set using the
   // MultiplexGroupTimeOffset property.
   spECGWaveformGroup->ValueDefined [MULTIPLEX_GROUP_TIME_OFFSET] = VARIANT_FALSE;
   
   // No Trigger Time Offset. When the value is defined, it can be set using the
   // TriggerTimeOffset property.
   spECGWaveformGroup->ValueDefined [TRIGGER_TIME_OFFSET] = VARIANT_FALSE;
   
   // No Trigger Sample Position. When the value is defined, it can be set using the
   // TriggerSamplePosition property.
   spECGWaveformGroup->ValueDefined [TRIGGER_SAMPLE_POSITION] = VARIANT_FALSE;

   // Waveform Originality is ORIGINAL
   spECGWaveformGroup->WaveformOriginality = WAVEFORM_ORIGINALITY_ORIGINAL;
   
   // Set the Multiplex Group Label
   spECGWaveformGroup->ValueDefined [MULTIPLEX_GROUP_LABEL] = VARIANT_TRUE;
   spECGWaveformGroup->MultiplexGroupLabel = "SCPECG Waveform";

   // Set the Waveform Padding Value
   spECGWaveformGroup->ValueDefined [WAVEFORM_PADDING_VALUE] = VARIANT_TRUE;
   spECGWaveformGroup->WaveformPaddingValue = 32768;
   
   if (!InsertECGChannel(spDataSet, spECGWaveformGroup, psaSamples))
   {
      return FALSE;
   }
   
   // Delete any waveform groups that already exist in the Data Set
   while (spDataSet->GetWaveformGroupCount() > 0)
   {
      spDataSet->DeleteWaveformGroup (0, 0);
   }

   // Insert the new waveform group into the Data Set
   if (spDataSet->AddWaveformGroup (spECGWaveformGroup, 0, ELEMENT_INDEX_MAX) != DICOM_SUCCESS)
   {
      return FALSE;
   }
   
   return TRUE;
}

// Adds an ECG channel to the group
BOOL InsertECGChannel(ILEADDicomDSPtr& spDataSet,
                      IDicomWaveformGroupPtr& spWaveformGroup,
                      SAFEARRAY* psaSamples)
{
   IDicomWaveformChannelPtr spECGWaveformChannel;

   // Add a channel to the group
   spECGWaveformChannel = spWaveformGroup->Channels->Add();

   VARIANT varSamples;
   varSamples.vt = VT_ARRAY | VT_I2;
   varSamples.parray = psaSamples;
   
   // Set the samples of the channel
   if (spECGWaveformChannel->SetChannelSamples (varSamples) < 0)
   {
      return FALSE;
   }
   
   // Set the Channel Source and Sensitivity
   if (!SetChannelSourceAndSensitivity(spDataSet, spECGWaveformChannel))
   {
      return FALSE;
   }
   
   // Set the Channel Status
   spECGWaveformChannel->ValueDefined [CHANNEL_STATUS] = VARIANT_TRUE;
   spECGWaveformChannel->ChannelStatus = CHANNEL_STATUS_OK;
   
   // Set the Channel Time Skew
   spECGWaveformChannel->ValueDefined [CHANNEL_TIME_SKEW] = VARIANT_TRUE;
   spECGWaveformChannel->ChannelTimeSkew = 0.0;

   // Set the Waveform Channel Number
   spECGWaveformChannel->ValueDefined [WAVEFORM_CHANNEL_NUMBER] = VARIANT_TRUE;
   spECGWaveformChannel->WaveformChannelNumber = 0;
   
   // Set the Channel Label
   spECGWaveformChannel->ValueDefined [CHANNEL_LABEL] = VARIANT_TRUE;
   spECGWaveformChannel->ChannelLabel = "First Channel";
   
   // No Channel Offset. When the value is defined, it can be set using the ChannelOffset
   // property
   spECGWaveformChannel->ValueDefined [CHANNEL_OFFSET] = VARIANT_FALSE;
   
   // Set the Filter Low Frequency
   spECGWaveformChannel->ValueDefined [FILTER_LOW_FREQUENCY] = VARIANT_TRUE;
   spECGWaveformChannel->FilterLowFrequency = 0.05;
   
   // Set Filter High Frequency
   spECGWaveformChannel->ValueDefined [FILTER_HIGH_FREQUENCY] = VARIANT_TRUE;
   spECGWaveformChannel->FilterHighFrequency = 100.0;
   
   // Set the Channel Minimum Value
   spECGWaveformChannel->ValueDefined [CHANNEL_MINIMUM_VALUE] = VARIANT_TRUE;
   spECGWaveformChannel->ChannelMinimumValue = -386;
   
   // Set the Channel Maximum Value
   spECGWaveformChannel->ValueDefined [CHANNEL_MAXIMUM_VALUE] = VARIANT_TRUE;
   spECGWaveformChannel->ChannelMaximumValue = 1264;
   
   // When the Notch Filter Frequency and Bandwidth are defined, they can be set using the
   // NotchFilterFrequency and NotchFilterBandwidth properties.
   
   // Last, but not least, set the channel annotations!
   return SetChannelAnnotations(spECGWaveformChannel);
}

#define CID_3001   "CID 3001"
#define CID_3082   "CID 3082"

// Sets the Channel Source and Sensitivity
BOOL SetChannelSourceAndSensitivity(ILEADDicomDSPtr& spDataSet,
                                    IDicomWaveformChannelPtr& spWaveformChannel)
{
   // We will use the Context Group Table

   IDicomCodedConceptPtr spCodedConcept;
   spCodedConcept = spDataSet->CurrentCodedConcept;

   // -------------------- Channel Source --------------------
   
   // Load the ECG Leads Context Group
   spDataSet->LoadContextGroup(CID_3001);
   if (!spDataSet->FindContextGroup(CID_3001))
   {
      return FALSE;
   }
   
   // 5.6.3-9-1 is Lead I (Einthoven)
   if (!spDataSet->FindCodedConcept("SCPECG", "5.6.3-9-1"))
   {
      return FALSE;
   }

   IDicomCodeSequenceItemPtr spChannelSource;
   spChannelSource = spWaveformChannel->ChannelSource;

   // Set the Channel Source
   spChannelSource->CodingSchemeDesignator = spCodedConcept->CodingSchemeDesignator;
   spChannelSource->CodingSchemeVersion    = spCodedConcept->CodingSchemeVersion;
   spChannelSource->CodeValue              = spCodedConcept->CodeValue;
   spChannelSource->CodeMeaning            = spCodedConcept->CodeMeaning;

   // -------------------- Channel Sensitivity --------------------
   
   spWaveformChannel->ValueDefined [CHANNEL_SENSITIVITY] = VARIANT_TRUE;

   // The Channel Sensitivity
   spWaveformChannel->ChannelSensitivity= 0.00122;
      
   // Load the Cardiology Units of Measurement Context Group
   spDataSet->LoadContextGroup(CID_3082);
   if (!spDataSet->FindContextGroup(CID_3082))
   {
      return FALSE;
   }
   
   if (!spDataSet->FindCodedConcept("UCUM", "mV"))
   {
      return FALSE;
   }

   IDicomCodeSequenceItemPtr spChannelSensitivityUnits;
   spChannelSensitivityUnits = spWaveformChannel->ChannelSensitivityUnits;
   
   // The Channel Sensitivity Units
   spChannelSensitivityUnits->CodingSchemeDesignator = spCodedConcept->CodingSchemeDesignator;
   spChannelSensitivityUnits->CodingSchemeVersion    = spCodedConcept->CodingSchemeVersion;
   spChannelSensitivityUnits->CodeValue              = spCodedConcept->CodeValue;
   spChannelSensitivityUnits->CodeMeaning            = spCodedConcept->CodeMeaning;
   
   // The Channel Sensitivity Correction Factor
   spWaveformChannel->ChannelSensitivityCF = 1.0;
   
   // The Channel Baseline
   spWaveformChannel->ChannelBaseline = 0.0;
   
   return TRUE;
}

// Adds annotations for the channel
BOOL SetChannelAnnotations(IDicomWaveformChannelPtr& spWaveformChannel)
{
   // Delete any existing channel annotations
   while (spWaveformChannel->Annotations->Count> 0)
   {
      spWaveformChannel->Annotations->Remove (0);
   }

   IDicomWaveformAnnotationPtr spECGWaveformAnnotation;
   
   // Add an annotation object
   spECGWaveformAnnotation = spWaveformChannel->Annotations->Add();
   
   // Our annotation is defined by a Coded Name/Numeric Measurement pair
   spECGWaveformAnnotation->ValueType = TYPE_CODED_NAME_AND_NUMERIC_VALUE;

   IDicomCodeSequenceItemPtr spCodeSequenceItem;
   
   // The Coded Name. Note: Instead of setting these values directly, you can also use
   // the Context Group Table; the Context Groups defined by the DICOM Content Mapping
   // Resource (DCMR) can be loaded into this table. Take a look at the function
   // SetChannelSourceAndSensitivity above for an example.
   spCodeSequenceItem = spECGWaveformAnnotation->CodedName;
   spCodeSequenceItem->CodingSchemeDesignator = "LN";
   spCodeSequenceItem->CodingSchemeVersion    = "19971101";
   spCodeSequenceItem->CodeValue              = "8867-4";
   spCodeSequenceItem->CodeMeaning            = "Heart rate";
   
   // The Numeric Value
   spECGWaveformAnnotation->NumericValueCount = 1;
   spECGWaveformAnnotation->NumericValue[0] = 69.0;
   
   // The Measurement Units. Refer to the note given when the Coded Name was set.
   spECGWaveformAnnotation->ValueDefined[MEASUREMENT_UNITS] = VARIANT_TRUE;
   spCodeSequenceItem = spECGWaveformAnnotation->MeasurementUnits;
   spCodeSequenceItem->CodingSchemeDesignator = "UCUM";
   spCodeSequenceItem->CodingSchemeVersion    = "1.4";
   spCodeSequenceItem->CodeValue              = "{H.B.}/min";
   spCodeSequenceItem->CodeMeaning            = "Heart beat per minute";
   
   return TRUE;
}