--[[
    This file provides a number of common property extraction functions 
    that will be called called for every attribute collection, regardless its 
    modality.
    Thus, the functions defined below work with DICOM elements that are
    present in most SOP classes.   
    
	Cristian Bonciu,    July-August 2004          
	-	CREATED and implemented the functionality from pseudo-code prototypes provided by Allan 
	Cristian Bonciu			27 SEP 2004        
	-	added the tag definitions used by CR scripts 
     Cristian Bonciu			27 SEP 2004        
     -	added CONTRAST_PROP as per CRDB00024804: Sequence Contrast property should be
		defined as Yes or No
     Allan Noordvyk			01 OCT 2004             
     -	Added ACQ_DEVICE_PROP and ExtractAcqDevice function
     -	Converted MULTI_FRAME_PROP to FRAME_TYPE_PROP and fixed function to determine 
		if cine by also looking for time-based relationship between frames.
     Allan Noordvyk			02 OCT 2004             
     -	Added remaining missing properties as defined in HMI Sequence Properites Supplementary
          Specification document.
     Allan Noordvyk			12 OCT 2004            
     -	Added LL and LR as recognized projections
     Allan Noordvyk			27 OCT 2004            
     -	Added new common-for-DP flag to Register calls when appropriate
     Allan Noordvyk			13 NOV 2004            
     -	Added checks for "LT" and "RT" as abbreviations of Left and Right laterality
     -	Had non-MR/CT images skip logic for View, Contrast, and Phase properties.
     -	Had non-CR/DX images skip logic for Projection property
     Cristian Bonciu			21 FEB 2005            
     -	Modified the laterality recommended property values from Left, Right, Bilateral to L,R,B
          to match the way ExtractLaterality works
     Allan Noordvyk			11 MAR 2005           
     -	Changed laterality search to use new BestWordMatchInAttr function so that "VIRTUAL" 
          would not match "RT" and "ADULT BRAIN" would not match "LT" but "LT KNEE" would 
		still match "LT"
     -	Same as above for other properties' matching of abbreviations.
     Allan Noordvyk			17 MAR 2005           
     -	Fixed definition of FRAME_INC_POINTER tag.  Now Acuson Sequoia cine clips will be
          recognized as such (discovered during Mercy Iowa beta test).
     -	Added "LOCALIZE" as another alias for scout based on Marconi MR at Mercy Iowa.
     Allan Noordvyk			17 MAR 2005           
     -	Changed body region to be NOT common for DP.   It was causing more issues than 
		benefits (really only useful for CR extremity studies and then only if the device is 
		correctly marking each body region's image).
     Cristian Bonciu			09 JUN 2005              
     -	Added IMAGE_LOCATION_PATIENT property to be used for sorting images within
          sequences by their position in patient space  
     -	allowed view property extraction for PET images
     Allan Noordvyk			22 JUL 2005		 
     -	Added Burned in annotation tag for use in detecting R2 montage images in ExtractMG.tls  
     Allan Noordvyk			23 AUG 2005               
     -	CRDB00034935: Sequencing MR studies from Phillips T5 - seek View property in Image Comments 	  
     Allan Noordvyk			14 SEP 2005               
     -	Removed ExtractPhase routine since the majority of CT and MR devices do not seem to use
		relative acquisiton number as a reliable indication of phase. Instead, device-specific logic 
		is used in ExtractCT and ExtractMR for the few devices that do.  N.B.  This may cause some 
		problems for existing display protocols which used to have "N/A for Toshiba" as phase 
		information and will now simply not have the property set to any value.  Such display 
		protocols will simply need to have the phase property check manually removed using DPE,
		or have the user drag and drop desired series into blank viewport and choose "Update".
     -	ExtractContrast now ignores "N" and "NO" as bolus names.
     Mahesh Nagarajan		27 SEP 2005		 
     -	CRDB00034332: Modified function "ExtractLaterality" to accomodate studies that 
		used RL and LL to calculate laterality
     Allan Noordvyk			01 OCT 2005
     -	Added new COMMON_SCRIPT_VER_PROP and MODALITY_SCRIPT_VER_PROP properties to
          better detect when sequence result was caused with an out of date script.
     Vladan Petrovic			03 OCT 2005
     -	CONTRAST_ROUTE_TAG added and check for it's value in ExtractContrast()
     Allan Noordvyk			04 OCT 2005
     -	Removed phase as a Common for DP attribute (HRS11-STRQ1526)
     -	Added "Delayed" sequence property (HRS11-STRQ1550)
     Allan Noordvyk			06 OCT 2005
     -	The changes below are related to one or more of the following STRQs:
          -  STRQ1572 Sequencing: Improper sequencing for MIP images
          -  STRQ1563 Sequencing concerns for SIEMENS MR MAGNETOM VISION VB33G 
          -  STRQ1558 Sequencing Concerns for SIEMENS CT SOMATOM PLUS 4 VC10C 
          -  STRQ1559 Sequencing concerns for Siemens CT Sensation 16 VA60B
          -  STRQ1560 Sequencing for Siemens MR MAGNETOM EXPERT VB33G 
          -  STRQ1561 Sequencing concerns for Siemens MR MAGNETOM IMPACT VB33G 
          -  STRQ1562 Sequencing concerns for SIEMENS MR MAGNETOM OPEN viva VB33G
          -  STRQ1536 Sequencing: Sort images in head CT studies from inferior to superior
          and other issues observed in situ at Gwinnett Medical
     -	Added "TRAN" and "TRA" (short for transverse) as a keyword match for Axial.
     -	Dropped determination of "Image Type" for CR images (since distinction between 
          Original and Derived is not important.
     -	Added check of special private element used by some Siemens devices for View
          information.
     -	Moved "COR" check before "SAG" to handle case of "Cor > Sag" designation in some
		coronal images.
     -	Added detection of "W/GAD" and "C+" in series description as indicator that series has
          contrast.
     -	Added detection of "POST" word in series description as indicator that series has
          contrast, as long as not part of "POST FOSSA" or POST OP"
     -	Moved Image Type tag to later in check order for View due to Siemens Somaris/5 3D 
          but that labels all MPR's as Axial like their source data.
     -	Added check for C0R (zero instead of O) as coronal.
     -	Remove image comments from check for image type so we don't get false scouts.
     Allan Noordvyk			09 OCT 2005
     -	Added pick missing MPR and MIP standard values to Image Type property
     -	Added detection of "MIPSeries" as an alias for MIP based on example sighted by Max.
     Mahesh Nagarajan		13 OCT 2005
     -	Added "PROJECTION IMAGE" as an alias for MIP 
     -	Added detection of Laterality information in DICOM tag 0020, 0060
     Allan Noordvyk			18 OCT 2005
     -	Added new Position property for noting whether patient is prone or supine (for cross-
		sectional imaging), or upright, flexion (for projection imaging). Prone/supine needed as 
		support for Cristian B's fix in IP library for HRS-STRQ1586: Defection correction 
		on CT Prone series display Upright/standing/weight-bearing/flexion/extension 
		additionally needed for: CRDB00031302
     Mahesh Nagarajan    		18 OCT 2005
     -	Observed in a Gwinnett Angio study that an MIP series can also be indicated by the 
		value "3D" in the DICOM tag (0018, 0023) or ACQ MR Acquisition Type. This value 
		is also extracted.	    
     Allan Noordvyk			24 OCT 2005
     -	If we detect the word "REFORMATTED" in series description, we should change image
		type to MPR.
     -	Added "Scanogram" to list of synonyms for "SCOUT"
     -	"TRS" as another possible last resort alias for "Axial" or "Transverse"
     -	Ignored the value "NONE" in function ExtractContrast() of ExtractCommon.tls
     -	Added detection of "Screen Save" in function ExtractImageType() of ExtractCommon.tls
     -	Incorporated Cristian's mathematical view determination logic.
     Mahesh Nagarajan		24 OCT 2005
     -	Strange errors were noticed when generating metada for US studies using AliDSSeqUpdate.
          These were found to be the result of using a 'Get" statement to retrieve the value
          "Projection Image" from the 3rd array index of the Image Type Tag while these US studies
          had only two array values for this tag. This was corrected.
     Mahesh Nagarajan		25 OCT 2005
     -	Removed "Survey" as an Alias for Scout; owing to oversplit problems observed in MR 
          Study at Elkhart, Indiana
     Allan Noordvyk			26 OCT 2005
     -	Added expanded CR projection related position values described in HRS11-STRQ1604
          (see new ExtractPositionProjection function)
     Mahesh Nagarajan		01 NOV 2005
     -	Added "SCANO" as abbreviation for "Scanogram" and alias for "Scout"
     Allan Noordvyk			12 Nov 2005
     -	Added new property "Slice Type" for CT and MR
     -	Added new property "Color" for all modalities
     -	Fixed search by substring for long values of position property
     -	Add WKL Sheduled Procedure Step for position and body region
     Allan Noordvyk			14 Nov 2005
     -	Add look in REL Image Comments for word DELAY so we would handle Toshiba Aquillion 
		studies where delayed slices are sent in same series and only difference in tech notes.
     Allan Noordvyk			18 Nov 2005
     -	Added new property Projection Angle for describing type of angle/spin used for a 3D 
		projection (e.g. Basilar, Tumbled or Rotated)
     Mahesh Nagarajan		22 Nov 2005
     -	Added new Image Type "MRCP"
     Mahesh Nagarajan		25 Nov 2005
     -	Adding a new property "Thumbnail Group" for all modalities
     Mahesh Nagarajan		30 NOV 2005
     -	Added "SURVEY/MST" and "SURVEY" as aliases for scouts from Philips devices only
     Mahesh Nagarajan		05 DEC 2005
     -	Added a check for Contrast values in the REL Image Comments tags. The search in this 
          tag is very specific - if the value "Unenhanced" is found, Contrast is set to "No";
          but if "* Omni *" is found where * is a wildcard, then Contrast is set to "Yes"
     Mahesh Nagarajan		08 DEC 2005
     -	On request by radiologists at St. Lukes - Chesterfield, MO; added code to 
          ExtactThumbnailGroup() to check if the images belong to a calibration scan. If this is
          the case, the sequence is sent to the end of the thumbnail sequence, behind the scouts.
     Mahesh Nagarajan		21 DEC 2005
     -	Added "LOCS" and "Topogram" as aliases for Scout
     -	Added code to ensure "Control Scan" sequences went to the end of the line
     Mahesh Nagarajan		28 DEC 2005
     -	Modified the process of extracting laterality of images.
		1. The three primary laterality-related DICOM tags are searched - (0020, 0060) Laterality,
		(0020, 0062) Image Laterality and (0020,9072) Frame Laterality. 
		2. The values "L" and "R" are not searched for in the alternate tags.
		3. The values "Left" and "Right" are accepted from the alternate tags only if they are not 
		a part of projection strings like "left lateral" or "right lateral oblique"
     Cristian Bonciu			30 DEC 2005
     -	defined the laterality tags used by Max's update described above
     Mahesh Nagarajan		11 JAN 2006
     -	Added "LOCATOR" as new alias for "Scout"
     -	Treat "DISPLAY" (Image Type) similar to Screen Save
     -	Added "Projection Imag" as alternate for "Projection Image"
     -	Added code to ensure "Smart Prep" sequences appear at the end of the thumbnail bar
     -	Mapped "Lateral" and "Late" found in (0018, 5101) ACQ View Position to "Lat; otherwise more 
          work is required for setting up DPs
     -	Added "MRACP" as alias for "MRCP"
     -	Added check for value "3D" in (0020, 4000) REL Image Comments; means that image type is MIP
     -	Extended laterality extraction to not confuse with projection types LAO, RAO, LPO & RPO
     -	Added "3 PLANE" as an alias for Scout; this is considered true only if the series description
          is populated with "3 PLANE" alone
     Mahesh Nagarajan		16 JAN 2006
     -	Added "PosDisp" as new alias for "Screen Save"
     -	Added "3-pl" as alias for Scout
     -	Added check for laterality in (0040, 0254) WKL Performed Procedure Step Description as a 
          secondary search
     -	Added "Late as an alternate for "Delay"; the delay property is set to "Yes" if this keyword 
          is detected
     Cristian Bonciu			20 JAN 2006
     -	extended the Position property to take into account the decubitus left & right patient positions
          on the scanner table
     Mahesh Nagarajan		23 JAN 2006
     -	Mapped ACQ View Position values RLDECUB & LLDECUB to RLD & LLD projection types
     Mahesh Nagarajan		26 JAN 2006
     -	Reduced image priority for Screen Saves in ExtractThumbnailGroupCT( ) so that they come after 
		the Scouts; on request by St. Lukes
     -	Added "0" to Thumbnail Group property so that it doesnt appear on DP editor. 
     -	Enhanced detection of image type "Screen Save" in (0008, 0008) ID Image Type
     Mahesh Nagarajan		14 FEB 2006
     -	A fundamental mistake was found in the implementation of determining if an image was a part of a
	     Cine clip; this is corrected and now, determination of frame type as Cine requires number of frames
	     to be greater than 1 AND the image object has Element (0028,0009) IMG Frame Increment Pointer, 
	     and the value of this is either (0018,1063) Frame Time or (0018,1065) Frame Time Vector
     Mahesh Nagarajan		22 FEB 2006
     -	Added function ExtractSeriesUID() to offset a breakdown in functionality of the sequencing 
	     engine  when the same DICOM tag was found more than once. When a certain study has one regular 
	     instance of (0020, 000e) and one instance as a sequence element of a private tag, the engine decided 
	     that these are just one instance; and that (0020, 000e) is itself a sequence attribute with two 
	     elements. To prevent this, we extract the first instance of (0020, 000e) and use this for grouping.
     Mahesh Nagarajan		03 MAR 2006  	
     -	Added new property "Phase Name" as a second level phase identification property; phase number is not 
	     always reliable and some vendors like to use terms like "Venous" or "Capillary" to indicate phase
     Mahesh Nagarajan		04 MAR 2006
     -	On input from Allan Noordvyk, added code to re-map non-standard values like to "PRESENTATION" & 
		"PROCESSING" to standard values i.e. "FOR PRESENTATION" & "FOR PROCESSING"
     Mahesh Nagarajan		18 APR 2006
     -	Had to correct sloppy code change from above in ExtractIntent(); ensured that the Intent tag actually
          existed before looking for specific values
     -	Added "Timing Bolus" to list of useless sequences that need to be sent to the end of the thumbnail bar
     Mahesh Nagarajan		04 MAY 2006   
     -	Added "SmartScout" to list of useless sequences that need to be sent to the end of the thumbnail bar
     Mahesh Nagarajan		27 MAY 2006
     -	Added "Asset Cal" to list of useless sequences that need to be sent to the end of the thumbnail bar
     -	Removed property "Slice Type" from "starred" property evaluation procedures
     Mahesh Nagarajan		06 JUNE 2006
     -	Added code to ensure that all images with presentation intent "FOR PROCESSING" appear at the end 
		of the thumbnail bar
     Mahesh Nagarajan		06 JUNE 2006
     -	Changed the image priority so that Recons, Reformats, MIPs and Derived sequence thumbnails appear together 
	-	Added "Dose Report" to list of sequences that should be pushed back to the end of the thumbnail bar
     Mahesh Nagarajan		10 JULY 2006
	-	Added new position value "ABER"; included MR in the list of modalities that are now checked for special 
	     position property values
     Mahesh Nagarajan		18 SEP 2006
	-	modified code so that ABER projections for MR are handled seperately than other Projective modalities 
     Mahesh Nagarajan		04 OCT 2006       
     -	Added a quick search routine for view property; first look for simple string searches in the series 
          description before calculating this mathematically
	-	Changed the ranges used to determine slice thickness - 3.4 or greater is thick, 1.5 to 3.4 is thin and anything
	     less than 1.5 is very thin
	Mahesh Nagarajan		09 NOV 2006
	-	Added "Anonym" to list of series description that must be ignored if encountered
	Mahesh Nagarajan		13 FEB 2007
	-	As part of v11.5, re-organised the "DeclareTags section owing to its messy structure
	Mahesh Nagarajan		14 FEB 2007
	-	Added tag declarations/definitions for enhanced CT/MR properties
	-	Added extraction routines for sort details, sort hints and frame type of enhanced CT/MR objects
	-	Removed "EMF Navigation" property as the new functionality makes this property useless	
     Cartik Sharma			15 FEB 2007
	-	Fixed bug where image location property was not calculated and set when the quick search routine
		was used to determine the view property
	Mahesh Nagarajan		05 MAR 2007
	-	Upto now, one method of determining the image type as "MIP" has been to check if the tag (0018, 0023) 
		ACQ MR Acquisition Type has the value "3D". This is actually inaccurate as not all 3D acquisitions are 
		MIPs but such a check was necessary to handle MIP related sorting issues. These sorting issues have since 
		been handled differently, and the current misclassification of 3D acquisitions as MIPs causes problems with
		orientation. 3D acquisitions will no longer be considered as MIPs unless one other DICOM tag has the 
		string "MIP". Ref: CRDB00048745
	Mahesh Nagarajan		26 MAR 2007
	-	Removing (0008, 0008) ID Image type from list of DICOM tags used to search for laterality info after
		noticing false matches at Univ. of Colorado.
	Mahesh Nagarajan		16 MAY 2007
	-	Changed the value type of Phase property from string to "int" - treating this property of strings causes weird sorting results
	-	Found an interesting problem where when a property is set to value type "int", the IP sequencing engine does not
		recognise the value list if provided. The next string following the value type is usually the default value list; but 
		when the type is "int", the next string is assumed to be the property name.
	-	As a consequence of above, the empty value list provided for certain other properties results in these properties not
		showing their property names in the metadata. These empty value lists are now removed until further notice.
	Mahesh Nagarajan		22 MAY 2007	
	-	Lack of contrast can be indicated in (0018, 0010) as "off"; this is added as another exception in ExtractContrast()
	Mahesh Nagarajan		11 JUN 2007	
	-	Added code to function "ExtractLaterality" to ignore values "U" and "B" as they do not indicate specific laterality information
		that is useful for hanging protocols.
	Mahesh Nagarajan		13 JUN 2007
	-	Snapshots from Vital Images software (Vital Connect) should be treated as screen saves by IP; changes are made here to 
		assign the appropriate image type to such images.	
	-	Compacting the function "ExtractImageType" for better readibility and reduction of redundant code		
	Mahesh Nagarajan		18 JUN 2007
	-	Adding "+C" in series description as another indicator of contrast based on input from Univ. of Colorado	
	Mahesh Nagarajan		06 JUL 2007
	-	Removing code that ignores laterality "B"; this is used for cleavage views in Mammo studies.	
	Mahesh Nagarajan		25 SEP 2007
	-	Adding support for new Screen Save aliases - "VOLR" & "CSA 3DPROJECTION" observed on Philips & Siemens modalities
		in France; this designation is not accurate but is harmless and will enable IP team to disable orientation correction for such images
	-	Noticed that the string "C-" in series description seems to trigger a positive contrast indication even though the expected
		indicator is "C+"; added code to correct this
	-	Also modified the search routines for contrast indicators in the series description to make them more readable
	Mahesh Nagarajan		22 OCT 2007
	-	Adding support for new Screen Save alias - "CSA VRT" observed on Philips & Siemens modalities; this designation is not 
		accurate but is harmless and will enable IP team to disable orientation correction for such images
	Mahesh Nagarajan		30 NOV 2007
	-	Adding "MAX_IP" and "MIN_IP" as aliases for MIP image type; these are DICOM defined terms for MIP
	Mahesh Nagarajan		14 JAN 2008
	-	Adding a new property "Study Description" to assist with display protocol building at Suburban Radiology
	Mahesh Nagarajan		24 JAN 2008
	-	Adding the term "ReportCARD" to list of series description values used to in assignment of the thumbnail group no. 31
		i.e. useless series designation
	Mahesh Nagarajan		07 FEB 2008
	-	Adding back site-specific code snippets; after a long discussion with Cristian B., it is understood that this code has to stay
		in the code until proper site files have been installed. Such code is added back but within "dollar sign" demarcations to
		ensure easy removal at a later date.
	Mahesh Nagarajan		11 APR 2008
	-	Changing the property type of PHASE_PROP from int to string; this is specifically done for Dr. Mendel of Caritas to help him 
		use this property in his hanging protocols
	Mahesh Nagarajan		12 JUN 2008
	-	Adding new Screen Save aliases - "VOLREN" (extension of VOLR), "CAD Snapshots" and "CAD~ Snapshots" to help IPConfig
		recognise these images and disable orientation correction
	Mahesh Nagarajan		31 JUL 2008	
	-	Adding check for the string "MIP" in the REL Image Comments tag as requested by North Country
	Shudong Liu			11 SEP 2009
	- 	Implemented "NOT FOR DIAGNOSIS" warning message  (CRDB39341). 
]]--	

-- Enable tracing in this module for debugging (comment out for production version)
-- AliDSTraceLevel = 1

-- ////////////////////////////////////////////////////////////////////////////
-- commonly used strings definitions
-- ////////////////////////////////////////////////////////////////////////////    

-- scout property strings 

    SCOUT_VAL = "Scout"
    LOCALIZER_VAL = "Localizer"   -- synonymn for scout in many images
    LOC_VAL = "LOC"               -- abbreviation for localizer
    LOCALIZE_VAL = "Localize"     -- synonymn for scout in many images
    SCANOGRAM_VAL = "Scanogram"   -- synonym for scout used in Kosciusko @ Warsaw,IN
    SCANO_VAL = "SCANO"           -- synonym for scout used at Mercy Chicago 
    SURVEY_VAL = "Survey"
    SURVEY_MST_VAL = "SURVEY/MST" -- Used at CHS-Omaha
    TOPOGRAM_VAL = "Topogram"
    LOCATOR_VAL = "Locator"
    THREE_PL_VAL = "3-pl"             -- Short for 3 Plane; used @ Shields (Boston, MA)
	THREE_PLANE_VAL = "3 PLANE"
    
-- view (section description) property strings 

    CORONAL_VAL = "Coronal"
    SAGITTAL_VAL = "Sagittal"
    SAGITAL_VAL = "Sagital"  -- common mispelling
    AXIAL_VAL = "Axial"
    OBLIQUE_VAL = "Oblique"
    
    COR_ABBR_VAL = "COR"
    SAG_ABBR_VAL = "SAG"
    AX_ABBR_VAL	= "AX"
    TRA_ABBR_VAL = "TRA"  -- short for transverse, a synonym for axial.
    TRAN_ABBR_VAL = "TRAN"  -- short for transverse, a synonym for axial.
    C_ZERO_R_VAL = "C0R" -- coronal spelled with a zero instead of an O
    TRS_ABBR_VAL = "TRS" -- short for transverse, used in Kosciusko @ Warsaw,IN
    
-- Siemens notation needing special handling due to misleading keyword after greater than sign
-- (e.g. TRA>COR is really axial, SAG>COR is really sagittal, etc.)   
    TRA_GT_VAL = "TRA>"
    COR_GT_VAL = "COR>"
    SAG_GT_VAL = "SAG>"
    
-- Patterns for view truncated at end of series description
    T_PATTERN_VAL = "_T$" -- Single letter for transverse (axial)
    S_PATTERN_VAL = "_S$" -- Single letter for sagittal
    C_PATTERN_VAL = "_C$" -- Single letter for coronal
    TA_PATTERN_VAL = "_TR$" -- Two letter for transverse (axial)
    SA_PATTERN_VAL = "_SA$" -- Two letter for sagittal
    CO_PATTERN_VAL = "_Co$" -- Two letter for coronal
    
-- frame type property strings

    CINE_VAL = "Cine"
    STILL_VAL = "Still"    
    
-- projection property strings

    PA_VAL = "PA"       -- posterior-anterior
    AP_VAL = "AP"       -- anterior-posterior
    LAT_VAL = "Lat"     -- lateral with left/right unknown
    LL_VAL = "LL"       -- left lateral
    RL_VAL = "RL"       -- right lateral
    LLD_VAL = "LLD"     -- left lateral decubitus
    RLD_VAL = "RLD"     -- right lateral decubitus
    LLO_VAL = "LLO"     -- left lateral oblique
    RLO_VAL = "RLO"     -- right lateral oblique
    RLD_ABB_VAL = "RLDECUB"
    LLD_ABB_VAL = "LLDECUB"
    LL_LONG_VAL = "left lateral"
    RL_LONG_VAL = "right lateral"
    LLD_LONG_VAL = "left lateral decubitus"
    RLD_LONG_VAL = "right lateral decubitus"
    LLO_LONG_VAL = "left lateral oblique"
    RLO_LONG_VAL = "right lateral oblique"
    LAO_LONG_VAL = "left anterior"
    RAO_LONG_VAL = "right anterior"
    LPO_LONG_VAL = "left posterior"
    RPO_LONG_VAL = "right posterior"
    LATERAL_VAL = "Lateral"
    LATE_VAL = "Late"

-- laterality strings

    RIGHT_VAL = "Right"
    LEFT_VAL = "Left"
    L_VAL = "L"
    R_VAL = "R"
    LT_VAL = "LT"
    RT_VAL = "RT"
    
-- image type strings

    ORIGINAL_VAL = "Original"
    RECON_VAL = "Recon"
    REFORMAT_VAL = "Reformat"
    DERIVED_VAL = "Derived"
    
-- common description strings
    SCREEN_SAVE_VAL = "Screen Save"
    DISPLAY_VAL = "DISPLAY"
    POS_DISP_VAL = "POSDISP"
	VI_SNAPSHOT_VAL = "VI SnapShot"
	VC_SNAPSHOT_VAL = "VC SnapShot"
	CAD_SNAPSHOT1_VAL = "CAD Snapshots"
	CAD_SNAPSHOT2_VAL = "CAD~ Snapshots"
	VOLR_VAL = "VOLR"
	VOLREN_VAL = "VOLREN"
	CSA_3DPROJ_VAL = "CSA 3DPROJECTION"
	CSA_VRT_VAL = "CSA VRT"
    THREE_D_VAL = "3D"
    REFORMATTED_VAL = "Reformatted"
    MIP_VAL = "MIP"
	MAX_IP_VAL = "MAX_IP"
	MIN_IP_VAL = "MIN_IP"
    MPR_VAL = "MPR"
    MIPSERIES_VAL = "MIPSeries"  -- MIP series description used by at least one vendor
    PROJIMAGE_VAL = "PROJECTION IMAGE"
    PROJIMAG_VAL = "PROJECTION IMAG"
    MRCP_VAL = "MRCP"
    MRACP_VAL = "MRACP"
    
-- Echo Values
    T1_VAL	= "T1"
    T2_VAL	= "T2"
    PD_VAL	= "PD"
    
-- Contrast key phrases
    WGAD_VAL = "W/GAD"              -- short for "With Gadolinium"
    C_PLUS_VAL = "C+"               -- notation of "contrast positive"
	PLUS_C_VAL = "+C"
    POST_VAL = "POST"               -- short for "post contrast"
    POST_FOS_VAL = "POST FOS"       -- used to prevent false matches to "POST FOSSA"
    POST_DASH_FOS_VAL = "POST-FOS"  -- used to prevent false matches to "POST-FOSSA"
    POST_OP_VAL = "POST OP"         -- used to prevent false matches to "POST OPERATIVE"
    POST_DASH_OP_VAL = "POST-OP"    -- used to prevent false matches to "POST-OPERATIVE"
    OMNI_VAL = "Omni"
    UNENHANCED_VAL = "Unenhanced"

-- Position values
    -- prone / supine
    HFS_VAL = "HFS"  -- head-first supine
    HFP_VAL = "HFP"  -- head-first prone
    FFS_VAL = "FFS"  -- feet-first supine
    FFP_VAL = "FFP"  -- feet-first prone
    -- decubitus
    HFDR_VAL = "HFDR" -- Head First-Decubitus Right 
    HFDL_VAL = "HFDL" -- Head First-Decubitus Left
    FFDR_VAL = "FFDR" -- Feet First-Decubitus Right 
    FFDL_VAL = "FFDL" -- Feet First-Decubitus Left
   
    -- AXIAL_VAL .. don't need to define it again because it is included above for CT/MR views
       -- Projection in which the central ray goes from the base to the vertex or from the vertex to the base of a structure.
    
    AXILLARY_VAL = "Axillary"
      -- Projection in which the patient is supine, the arm is abducted, and the central ray 
      -- enters the axilla at an angle; performed to visualize structures in the shoulder region.
      
    BROW_DOWN_VAL = "Brow-Down"
      -- Posteroanterior projection of the head with the patient prone.
      
    BROW_UP_VAL = "Brow-Up"
      -- Anteroposterior projection of the head with the patient supine. 

    CALDWELL_VAL = "Caldwell"
      -- Posteroanterior projection of the head, used for viewing the frontal and anterior ethmoidal sinuses; 
      -- the central ray enters the back of the head from a slightly superior angle.
      
    CARPAL_TUNNEL_VAL = "Carpal Tunnel"
      -- A radiographic projection with the wrist hyperextended and the 
      -- central ray entering the proximal palm at an angle, for visualization of bones 
      -- and other structures of the proximal palm and wrist.
      
    CROSS_TABLE_VAL = "Cross-Table"
      -- Projection of the spine, pelvis, or leg with the patient either prone or supine on a table and 
      -- the central ray entering laterally.
      
    DIDIEE_VAL = "Didiee"
      -- Rare type of radiographic projection for evaluation of an unstable or repeatedly dislocating
      -- shoulder that may have a subtle condition such as the Hill-Sachs lesion; the patient lies prone 
      -- and the central ray enters the shoulder from a lateral oblique direction.
      -- Note that the first e in Didiee is supposed to have an accent, but we don't do that
      -- in case some subsystem we use doesn't handle those extended characters well.
    
    DECUBITUS_LEFT_VAL = "Decubitus left"
    DECUBITUS_RIGHT_VAL = "Decubitus right"
 
    EXTENSION_VAL = "Extension"
      
    DORSOPLANTAR_VAL = "Dorsoplantar"
      -- Projection of the foot with the central ray passing from the dorsal surface to the plantar surface.
      
    FLEXION_VAL = "Flexion"

    FROG_LEG_VAL = "Frog-Leg"
      -- Anteroposterior projection of the abducted hips.
      
    HALF_AXIAL_VAL = "Half-Axial"
      -- Projection of the head with the central ray at an angle to the frontal and medial planes; 
      -- it may be either anteroposterior or posteroanterior. Called also semiaxial.
      
    HARRIS_AND_BEATH_VAL = "Harris & Beath"
      -- Projection postion for posterior and middle facets of STJ.  Central ray aimed from 
      -- 30 to 50 degrees at posterior calcaneus
    
    HEINIG_VAL = "Heinig"
      -- Projection for visualization of the sternum and sternoclavicular joint; the arm closer 
      -- to the tube is abducted over the head and the central ray enters the body from an 
      -- inferior lateral direction, angling towards the opposite shoulder.
      
    HERMODSSON_VAL = "Hermodsson"
      -- Rare type of radiographic projection for evaluation of an unstable or repeatedly dislocating
      -- shoulder that may have a subtle condition such as the Hill-Sachs lesion; the patient stands
      -- with the affected arm behind the back and the hand over the lumbar vertebrae, and the central
      -- ray enters laterally to the scapula from an inferior posterior direction.
      
    HUGHSTON_VAL = "Hughston"
      -- Projection of the patellofemoral region, with the patient prone, the legs at 50 to 60 degrees
      -- of flexion, and the central ray at 45 degrees from vertical passing tangentially across the knee.
      
    INFEROSUPERIOR_VAL = "Inferosuperior"
      -- Projection in which the central ray enters the body or a body part from below.
    
    LAURIN_VAL = "Laurin"
      -- An axial radiograph of the knees made with the patient seated with the knees flexed at 30 degrees 
      -- and the legs together; the x-ray tube is placed between the patient's feet and the film is placed 
      -- against the anterior thighs perpendicular to the beam.
      
    MERCHANT_VAL = "Merchant"
      -- An axial radiograph of the knees made with the patient supine and the knees flexed at 45 degrees 
      -- over the table end with the legs together; the tube is angled 30 degrees below the horizontal 
      -- and the film is held on the tibia perpendicular to the beam.
      
    MORTISE_VAL = "Mortise"
      -- An anteroposterior projection of the ankle with the foot rotated internally 15 to 20 degrees 
      -- so that the bases of the tibia and fibula are no longer in front of the talus.
      
    NON_WEIGHT_BEARING_VAL = "Non-Weight-Bearing"
      -- The limb being imaged is held such that there is no weight being placed on it by the patient.
      
    NOTCH_VAL = "Notch"
      -- 1. Projection for visualization of the intercondylar notch of the femur.
      --         or
      -- 2. A rare type of radiographic projection for evaluation of an unstable or repeatedly dislocating 
      -- shoulder that may have a subtle condition such as the Hill-Sachs lesion. The patient lies 
      -- supine with the elbow on the affected side vertical and the hand next to or above the head; 
      -- the central ray enters anteriorly and angles slightly upwards towards the coracoid process.
      -- Also called Stryker's Notch.
      
    ODONTOID_VAL = "Odontoid"
      -- Anteroposterior projection of the cervical spine, particularly the axis and atlas, done through
      -- the open mouth with the patient supine and the central ray pointing vertically downward.
      --
      -- Also called Open-Mouth.
      
    PILLAR_VAL = "Pillar"
      -- Projection of the articular pillars of the cervical spine, usually with the patient supine
      -- and the central ray angled slightly towards the feet from a position above the face; for 
      -- an unobstructed view, the head may be rotated so that the mandible is not above the spine.
      
    PRONE_VAL = "Prone"
    
    SCHULLER_VAL = "Schuller"
      -- A lateral projection of the head, with the beam originating 30° above horizontal; it 
      -- allows a view of the antrum, part of the attic, and the head of the malleus.
      -- Note that the u in Schuller is supposed to have an umlaut but we don't do that 
      -- in case there is some subsystem in the software the doesn't handle those characters
      -- well.

    SETTEGAST_VAL = "Settegast"
      -- Tangential projection of the patellofemoral region, usually with the patient prone, the 
      -- knee flexed more than 90 degrees, and the central ray directed vertically downward at the knee. 
      -- Called also sunrise.
      
    STANDING_VAL = "Standing"
      
    STENVERS_VAL = "Stenvers"
       -- Oblique projection of the head, done with the head rotated 45° so that the petrous part 
       -- of the temporal bone is perpendicular to the beam; it allows a view of the petrous apex, 
       -- the labyrinth, and the internal acoustic meatus.
       
    STRESS_VAL = "Stress"
       -- Projection of a movable joint, such as the wrist or knee, while it is under significant stress.
    
    SUBMENTOVERTEX_VAL = "Submentovertex"
       -- Projection of the head with the central ray entering under the chin and directed toward the vertex.
       
    SUPINE_VAL = "Supine"

    SWIMMER_VAL = "Swimmer"
       -- Projection of the cervical or thoracic spine with the patient lying supine on a table, 
       -- the neck hyperextended, the arm nearer the camera parallel to the body, and the arm 
       -- nearer the film placed above the head; the central ray enters laterally.

    TANGENTIAL_VAL = "Tangential"
       -- A radiographic projection with the central ray on a tangent to an organ or part.

    THALAMOCORTICAL_VAL = "Thalamocortical"
       -- see under fiber.

    TOWNE_VAL = "Towne"
       -- Projection of the head in which the central ray enters obliquely through the frontal bone, 
       -- yielding a view of facial structures and the occipital bone; it is an anteroposterior 
       -- half-axial projection.

    TUNNEL_VAL = "Tunnel"
       -- A posteroanterior projection in which the knee is flexed 40 to 50 degrees with the patient 
       -- prone and the foot supported; the central ray is perpendicular to the lower leg for a view 
       -- of the intercondylar fossa of the femur.
       
    UPRIGHT_VAL = "Upright"

    VERTICOSUBMENTAL_VAL = "Verticosubmental"
       -- Projection in which the central ray passes from the vertex of the head to the base of the chin, 
       -- for visualization of the mandible, the base of the skull, and the nasal region.

    WATERS_VAL = "Waters"
       -- Projection of the anterior head, used for viewing the maxillary sinuses and sphenoid bone; the 
       -- central ray enters at an angle through the chin.
       
    WEIGHT_BEARING_VAL = "Weight-Bearing"
      -- The limb being imaged is held such that there is weight being placed on it by the patient.

    WEST_POINT_VAL = "West Point"
       -- Rare type of  projection for evaluation of an unstable or repeatedly dislocating shoulder that may have a subtle condition 
       -- such as the Hill-Sachs lesion. The patient lies prone with the affected arm abducted and the forearm hanging off the table; 
       -- the central ray enters in the scapular region at an angle from the rear to visualize scapular and shoulder structures.
	   
	ABER_VAL = "ABER"
    
    -- Slice Type values
    THICK_VAL = "Thick"
    THIN_VAL = "Thin"
    VERY_THIN_VAL = "Very Thin"
    
    -- Projection Angle values
    BASILAR_VAL = "Basilar"
    ROTATED_VAL = "Rotated"
    TUMBLED_VAL = "Tumbled"
    R_L_VAL = "R-L"  -- abbreviation for Right to Left rotated view
    L_R_VAL = "L-R"  -- abbreviation for Left to Right rotated view
    T_D_VAL = "T-D"  -- abbreviation for Top to Down tumble view
    D_T_VAL = "D-T"  -- abbreviation for Down to Top tumble view

    -- Phase Name Values
    BEFORE_VAL = "Before"
    ARTERIAL_VAL = "Arterial"
    CAPILLARY_VAL = "Capillary"
    VENOUS_VAL = "Venous"

function RegisterProperties() 
-- ////////////////////////////////////////////////////////////////////////////    
-- This is the most important function in this file.  It defines he properties that
-- will be available for association with an image.  Each desired property
-- must be made known to the system using the Register function.
--
-- Common property IDs should be in the range (3711,1101)- (3711,11FF).  The 3711
-- part is handled automatically by the Register function.  Properties specific
-- to other modalities will each use speicific higher ranges than 11FF.
-- ////////////////////////////////////////////////////////////////////////////    

    local valList  -- used to build concatenated standard value lists

    -- IMAGE_TYPE_PROP indicates the method of creation for the images in the sequence.  The primary use of this 
    -- property will be to separate scout images from other images that may happen to be in the same DICOM 
    -- series.   However, it will also be used in display protocols to position different types of images in 
    -- different locations (e.g. Scouts and Derived images placed to the side of standard reconstructions).  This 
    -- property may also be used to construct a series description (see SERIES_DESC_PROP) when there is no 
    -- corresponding value in the DICOM header.
    --
    -- Standard Values:	        Scout, Original, Recon, Reformat, Derived
    -- Usage in DP:	        Common
    -- Related DICOM Tags:	Image Type <0008, 0008> or Series Description <0008, 103e> 
    valList = SCOUT_VAL .. "," .. ORIGINAL_VAL .. "," .. RECON_VAL .. "," .. REFORMAT_VAL .. "," .. 
              DERIVED_VAL .. "," .. MIP_VAL .. "," .. MPR_VAL .. "," .. SCREEN_SAVE_VAL
    Register("IMAGE_TYPE_PROP", "1101", "string", valList, "Image Type",{1})
    
    -- VIEW_PROP indicates the viewing angle of the patient's anatomy that is displayed in the image  
    -- The primary use of this property will be to allow placement of the sequence in display protocols at 
    -- predictable locations regardless of actual order of the sequence in the study (or whether or not it 
    -- is present at all).  This property may also be used to construct a series description (see SERIES_DESC_PROP) 
    -- when there is no corresponding value in the DICOM header.
    --
    -- Standard Values:         Axial, Sagittal, Coronal, Oblique
    -- Usage in DP: 	        Common
    -- Applicable Modalities: 	CT, MR
    -- Related DICOM Tags: 	Image Type <0008, 0008> and/or Series Description <0008, 103e> 
    valList = CORONAL_VAL .. "," .. SAGITTAL_VAL .. "," .. AXIAL_VAL .. "," .. OBLIQUE_VAL 
    Register("VIEW_PROP", "1102", "string", valList, "View",{1})

    -- SERIES_DESC_PROP is a description of the image's sequence intended to be shown to the user (e.g. beneath
    -- the series thumbnails in Emerald).  The primary purpose of this property is informational.  
    -- It is not expected that users will have to resort to using series description for display protocols 
    -- except in rare instances where the DICOM header contains no other useful information that can be 
    -- used to derive one of the other properties listed in this document.
    --
    -- The series description property will always be assigned a value.  The convention of using 
    -- a question mark "?" will be employed in those rare situation where no series description 
    -- can be found directly or constructed artificially from information in the DICOM header.
    --
    -- When constructing a series description value, the scripts will attempt to keep the 
    -- descriptions reasonably concise (e.g. using the "+C" suffix to indicate contrast agent use) 
    -- as this space in the user interface is often limited.  Likewise, elements most likely 
    -- to be the relevant pieces of information for a human to distinguish between two sequences 
    -- in the same study will be placed at the beginning of any constructed description.
    --
    -- This property is named "Series Description" rather than "Sequence Description" because 
    -- sequence is a term only used internally and thus would be confusing to end users.
    -- 
    -- Usage in DP: 	        Rare
    -- Related DICOM Tags: 	Series Description <008,103e> or, if absent, a combination of 
    --                          any values found for View, Projection, Body Region, and/or 
    --                          Contrast, depending on modality 
    Register("SERIES_DESC_PROP", "1103", "string","", "Series Description")
    
    -- BODY_REGION_PROP represents the patient's body region that is shown in the image's of the sequence.  This is 
    -- most valuable for handing CR/DX studies of extremities as it will allow display protocols to place feet, 
    -- hands, etc. in the same study consistent locations, regardless of order in the study.  It will frequently 
    -- be absent.    This property may also be used to construct a series description (see 2.3.13 below) when 
    -- there is no corresponding value in the DICOM header.
    --
    -- NOTE: This property has no standard values defined in line.  Instead it uses a union
    -- of the set of values in HMI Body Part Table and the set of body parts defined 
    -- for Body Part Examined in the DICOM standard.
    --
    -- Usage in DP:             Rare
    -- Related DICOM Tags:      Body Part Examined <0018,0015>, or, if absent, extracted 
    --                          from Series Description <0008, 103e>, Study Description <0008, 1030>, or ACQ
    --                          Device Processing <0018,1400>,  
    Register("BODY_REGION_PROP", "1104", "string", "", "Body Region")
    
    -- This property whether the sequence is inherently a collection of still frames or is a cine clip (i.e. 
    -- intended to be presented as a movie).  This property will be used primarily by clients to determine best 
    -- initial presentation of the sequence.  It will also often be used in display protocols to place stills in 
    -- viewports suitable for tiled navigation and cine clips in viewports suitable for cine playback.
    -- 
    -- Standard Values:	        Still, Cine
    -- Usage in DP:	        Common
    -- Related DICOM Tags:	Number of Frames <0028,0008>, Frame Time <0018,1063>, Frame Increment Pointer <0008,0009>
    valList = STILL_VAL .. "," .. CINE_VAL
    Register("FRAME_TYPE_PROP", "1105", "string", valList, "Frame Type",{1})
    
    -- This property indicates whether the images in the sequence were acquired with or without a contrast agent 
    -- injected into the patient.  This is most valuable for handing CT and MR studies as it will allow display 
    -- protocols to place pre- and post-contrast images in the same study consistent locations, regardless of 
    -- order (or presence) in the study.  Note that for the purposes of display protocols it is not important what 
    -- kind of contrast agent was used (although this can be determined from the DICOM header), just whether any 
    -- was present or not.  This property will usually be present with a value in CT and MR studies, but often 
    -- absent in others.    This property may also be used to construct a series description (see SERIES_DESC_PROP) 
    -- when there is no corresponding value in the DICOM header (e.g. by appending "C+" for series where 
    -- Contrast = "Yes").
    -- 
    -- Standard Values:	        Yes, No
    -- Usage in DP:	        Common
    -- Related DICOM Tags:	ACQ Contrast/Bolus Agent < 0018, 0010>.  "Yes" if non-empty value found.   
    Register("CONTRAST_PROP", "1106", "string","Yes,No","Contrast",{1})
    
    -- Used to record acquisition device manufacturer/model - this allows 
    -- display protocols to have special logic in necessary for weird devices.
    Register("ACQ_DEVICE_PROP", "1107", "string","","Acquisition Device")
    
    -- LATERALITY_PROP indicates whether the images of the sequence are from the left or right of the patient's 
    -- body.  The primary use of this property will be to allow placement of the sequence in display protocols at 
    -- predictable locations regardless of actual order of the sequence in the study.  This property may also be 
    -- used to construct a series description (see 2.3.13 below) when there is no corresponding value in the DICOM 
    -- header.
    --
    -- Standard Values: Right, Left, Bilateral. 
    -- Usage in DP: Common
    -- Related DICOM Tags: Laterality <0020, 0062> or, if absent, extracted from Image Type <0008, 0008> or 
    -- Series Description <0008, 103e> 
    Register("LATERALITY_PROP", "1108", "string", "L,R,B", "Laterality",{1})
    
    -- PHASE_PROP indicates the acquisition phase of the images in the sequence.  The primary use of this 
    -- property would be to separate a single DICOM series into distinct sequences intended for parallel display 
    -- in different viewports of an Emerald display protocol.  This property would often be absent.
    --
    -- Standard Values: 1, 2, 3, 4, 5, 6
    -- Usage in DP: Rare
    -- Applicable Modalities: CR, MR, XA
    -- Related DICOM Tags: Relative Acquisition Number <0020, 0012>.  However, this is not set properly on 
    --    many devices and thus may be ignored for certain specific models of acquisition device. 
    Register("PHASE_PROP", "1109", "string","1,2,3,4,5", "Phase", {0})

    -- MODALITY_PROP indicates the modality of the images in the sequence.  This property will be rarely used as 
    -- typically all images in a study are the same.  The only exception may be to deal with positioning sequences 
    -- in a display protocol for those small set of studies which combine images from two different modalities 
    -- (e.g. an RF/CR exam).
    --
    --~ Standard Values:        CT, MR, MG, CR, RF, etc.
    --~ Usage in DP:            Common
    --~ Related DICOM Tags:     Modality <0008, 0060>  
    Register("MODALITY_PROP", "110A", "string", "DR,DX,CR,CT,MG,MR,NM,PT,RG,RF,RT,US,XA", "Modality",{1})
    
    -- PROJECTION_PROP indicates the projection used to acquire the images in the sequence. 
    -- The standard values  (PA, AP, Lat, Oblique) for this property are those used in general projection x-ray 
    -- modalities of CR, DX, or DR..  The primary use of this property will be to allow placement of the 
    -- sequence in display protocols at predictable locations regardless of actual order of the 
    -- sequence in the study (or whether or not it is present at all).  This property may also be
    -- used to construct a series description (see SERIES_DESC_PROP) when there is no corresponding value
    -- in the DICOM header.
    --
    -- This property will likely be present for the majority of all CR and DR image sequences. It will 
    -- always be present for DX image sequences.  See also MG_PROJECTION_PROP in ExtractMG.tls.
    --
    -- Standard Values: 	PA, AP, Lat, Oblique, LL, RL, LLO, RLO, ..
    -- Usage in DP: 	        Common
    -- Applicable Modalities: 	CR/DX/DR, MG
    -- Related DICOM Tags: 	View/Position <0018,5101> or, if absent, extracted from Image Type <0008, 0008>, 
    --                          Series Description <0008, 103e>, or Study Description <0008, 1030> 
    valList = PA_VAL .. "," .. AP_VAL .. "," .. LL_VAL .. "," .. RL_VAL .. "," ..
              LLD_VAL .. "," .. RLD_VAL .. "," .. LLO_VAL .. "," .. RLO_VAL .. "," ..
              LAT_VAL .. "," .. OBLIQUE_VAL

    Register("PROJECTION_PROP", "110B", "string", valList, "Projection",{1})
    
    
    -- ECHO_TYPE property indicates the echo classification of images in the sequence.  This is most 
    -- valuable for splitting MR DICOM series containing multiple echoes into logical sequences for 
    -- display.  However, it is also important for display protocols that need place different 
    -- echoes in the same study in consistent locations, regardless of order (or presence) 
    -- in the study.  This property will almost always be present in MR sequences, with the 
    -- possible exception of derived images like screen captures.
    -- 
    -- Standard Values:	T1, T2, PD
    -- Usage in DP:	Common
    -- Applicable Modalities:	MR, MRA, MS
    -- Related DICOM Tags:	Echo Time <0018,0081>, Repetition Time <0018, 0080>, and Flip Angle <0018,1314> 
    local valList = T1_VAL .. "," .. T2_VAL .. "," .. PD_VAL
    Register("ECHO_TYPE_PROP", "110C", "string", valList, "Echo Type",{1})
    
    -- INTENT property indicates the intent of .
    -- 
    -- Standard Values:	FOR PRESENTATION, FOR PROCESSING
    -- Usage in DP:	Rare
    -- Applicable Modalities:	All
    -- Related DICOM Tags:	Presentation Intent <0008,0068> 
    local valList = "FOR PRESENTATION,FOR PROCESSING"
    Register("INTENT_PROP", "110D", "string", valList, "Intent")
    
    -- IMAGE_LOCATION_PATIENT_PROP is a property of type double (numeric) computed 
    -- for axial, sagittal and coronal images. It is used for sorting 
    -- images within sequence (and it is not used for DP since it varies from image to image)
    Register("IMAGE_LOCATION_PATIENT_PROP","110E","double", "Image Location",{0}) 
    
    -- COMMON_SCRIPT_VER property is used to mark sequencing output has having been created
    -- with a particular common script verison.  This allows better detection in the field
    -- when a poor sequencing result is being caused by out-of-date scripts.
    -- 
    -- Standard Values:	As labeled by source code control
    -- Usage in DP:	Rare
    -- Applicable Modalities:	All
    -- Related DICOM Tags:	None 
    Register("COMMON_SCRIPT_VER_PROP", "110F", "string", "", "Common Script Version")
    
    -- MODALITY_SCRIPT_VER property is used to mark sequencing output has having been created
    -- with a particular modality script verison.  This allows better detection in the field
    -- when a poor sequencing result is being caused by out-of-date scripts.
    -- 
    -- Standard Values:	As labeled by source code control
    -- Usage in DP:	Rare
    -- Applicable Modalities:	All
    -- Related DICOM Tags:	None 
    Register("MODALITY_SCRIPT_VER_PROP", "1110", "string", "", "Modality Script Version")
    
    -- Delayed indicates the series is a delayed version of an earlier series in the same study.
    -- This is typically only done when time-release contrast agent is being used.
    -- This property would only be "Yes" when the appropriate keyword is detected in
    -- the series description, and "No" otherwise.
    --
    -- Standard Values: Yes
    -- Usage in DP: Common
    -- Applicable Modalities: CT, MR
    -- Related DICOM Tags: Series description. 
    Register("DELAYED_PROP", "1111", "string", "Yes,No", "Delayed", {1} )
    
    -- Position indicates the clinically relevant information about how
    -- the patient was positioned during the scan
    --
    -- Standard Values: Prone, Supine, Upright, Standing, Weight-Bearing, Flexion, Extension, ...
    -- Usage in DP: Common
    -- Applicable Modalities: Common
    -- Related DICOM Tags: ACQ Patient Position <0018,5100>, Series description. 
    local valList = ABER_VAL .. "," .. AXIAL_VAL .. "," .. AXILLARY_VAL .. "," .. 
                    CALDWELL_VAL .. "," .. CARPAL_TUNNEL_VAL .. "," .. CROSS_TABLE_VAL .. "," .. DIDIEE_VAL .. "," .. 
                    DECUBITUS_LEFT_VAL .. "," ..  DECUBITUS_RIGHT_VAL .. "," ..
                    BROW_DOWN_VAL ..",".. BROW_UP_VAL ..","..  DORSOPLANTAR_VAL .. "," .. EXTENSION_VAL ..",".. 
                    FLEXION_VAL .. "," .. FROG_LEG_VAL .. "," .. 
                    HALF_AXIAL_VAL .. "," .. HEINIG_VAL .. "," .. HERMODSSON_VAL .. "," .. HUGHSTON_VAL .. "," .. 
                    INFEROSUPERIOR_VAL .. "," .. LAURIN_VAL .. "," .. MERCHANT_VAL .. "," .. 
                    MORTISE_VAL .. "," .. NON_WEIGHT_BEARING_VAL .. "," .. NOTCH_VAL .. "," .. ODONTOID_VAL .. "," .. PILLAR_VAL .. "," .. PRONE_VAL .. "," .. 
                    SCHULLER_VAL .. "," .. SETTEGAST_VAL .. "," .. STANDING_VAL .. "," .. STENVERS_VAL .. "," .. STRESS_VAL .. "," .. 
                    SUBMENTOVERTEX_VAL .. "," .. SUPINE_VAL .. "," .. SWIMMER_VAL .. "," .. TANGENTIAL_VAL .. "," .. THALAMOCORTICAL_VAL .. "," .. 
                    TOWNE_VAL .. "," .. TUNNEL_VAL .. "," .. UPRIGHT_VAL .. "," .. VERTICOSUBMENTAL_VAL .. "," .. WATERS_VAL .. "," ..
                    WEIGHT_BEARING_VAL  .. "," ..  WEST_POINT_VAL
                    
    Register("POSITION_PROP", "1112", "string", valList, "Position", {1} )

    -- Color indicates the image contains color pixels.
    -- This is useful for modalities like ultrasound where there may
    -- be a mixture of grayscale and color images.
    --
    -- Standard Values: Yes,No
    -- Usage in DP: Common
    -- Applicable Modalities: All
    -- Related DICOM Tags: IMG Photometric Interpretation <0028,0004>
    Register("COLOR_PROP", "1113", "string", "Yes,No", "Color", {1} )
    
    -- Slice Type indicates the thickness class of the slices of
    -- a cross-sectional image.  This is useful for discerning lower
    -- resolution scans from higher resolutions ones.
    --
    -- Standard Values: Thick,Thin,Very Thin
    -- Usage in DP: Never Seen This!!!
    -- Applicable Modalities: MR,CT
    -- Related DICOM Tags: ACQ Slice Thickness <0018,0050>
    local valList = THICK_VAL .. "," .. THIN_VAL .. "," .. VERY_THIN_VAL
    Register("SLICE_TYPE_PROP", "1114", "string", valList, "Slice Type", {0} )
 
    -- Projectiong angle indicates the angle/spin used to present a
    -- series of projection images.
    --
    -- Standard Values: Rotated, Tumbled, Basilar, L, R, B
    -- Usage in DP: Common
    -- Applicable Modalities: MR, CT, PT
    -- Related DICOM Tags: Series Description
    local valList = ROTATED_VAL .. "," .. TUMBLED_VAL .. "," .. BASILAR_VAL .. ",L,R,B"
    Register("PROJ_ANGLE_PROP", "1115", "string", valList, "Projection Angle", {1} ) 
    
    -- THUMBNAIL_GRP_PROP was created because the image sequences needed to be sequenced in a specific 
    -- order; i.e. Originals had to come first followed by MIPs and MPRs and Scouts had to come right at the end.
    -- Regular alphabetical order failed in this regard. Thumbnail group numbers are assigned as follows.
    -- Standard Values: 1 - 26
    --       Image Type Value		Thumbnail Group Value
	--		Original					01
	--		Recon						05
	--		MRCP						06
	--		MIP						06
	--		MPR						11
	--		Reformat					11
	--		Derived					12
	--		Unknown					16
	--		Scout						21
	--		Screen Save				26
	--		Useless Series			31
    -- Usage in DP: Rare; useful only for Generic DP in loading image sequences into viewports in the proper order
    -- Related DICOM Tags: Image Type <0008, 0008> or Series Description <0008, 103e> 
    Register("THUMBNAIL_GRP_PROP", "1116", "string", "01,05,06,11,12,16,21,26,31", "Thumbnail Group", {0})
    
    -- SERIES_UID_PROP is simply the value of DICOM tag (0020, 000e)
    --
    -- Usage in DP:	        NEVER
    -- Related DICOM Tags:	REL Series Instance UID <0020,000e> 
    Register("SERIES_UID_PROP", "1117", "string", "", "DICOM Series UID",{0})     
    
    -- PHASE_NAME_PROP was created as a second level phase identification property; phase number is not always 
    -- reliable and some vendors like to use terms like "Venous" or "Capillary" to indicate phase
    --
    -- Standard Values:	(1) Before (2) Arterial (3) Capillary (4) Venous
    -- Usage in DP:	          POSSIBLE
    -- Related DICOM Tags:	(0008, 103e) ID Series Description
    valList = BEFORE_VAL .. "," .. ARTERIAL_VAL .. "," .. CAPILLARY_VAL .. "," .. VENOUS_VAL

    Register("PHASE_NAME_PROP", "1118", "string", valList, "Phase Name",{1})     
	
    -- STUDY_DESC_PROP was created specifically for Suburban Radiology to help with their display protocol 
    -- building; as they heavily relied on this information throughout their work-flow
    --
    -- Usage in DP:	          POSSIBLE
    -- Related DICOM Tags:	(0008, 1030) ID Study Description
    
    Register("STUDY_DESC_PROP", "1119", "string", "", "Study Description",{0})     

	--TEXT_OVERLAY_WARNING_PROP will be used to provid warning message which can be displayed as text overlay.
    --
    -- Usage in DP:	        NEVER	
	Register("TEXT_OVERLAY_WARNING_PROP", "111A", "string", "NOT FOR DIAGNOSIS", "Text Overlay Warning",{0})     
	
	-- Enhanced Multi-Frame Properties
	-- MaxN: This section requires better descriptions
   
   -- EMF_SORT_DETAILS_PROP stores the various available sort hints that can be used to sort an EMF image. The sort
   -- hints are obtained from either the (0020, 9165) Dimension Index Pointer or from the sort hint tags themselves i.e.
   -- (0020, 9057) In-Stack Number and/or (0020, 9128) Temporal Position Index
   --
   -- Usage in DP: NEVER
   -- Related DICOM Tags: (0020, 9165) Dimension Index Pointer; (0020, 9057) In-Stack Number; (0020, 9128) Temporal Position Index
   Register("EMF_SORT_DETAILS_PROP", "1701", "string", "", "EMF Sort Details",{0}) -- not for DP 
   
   -- EMF_NB_SORT_HINTS stores the number of sort hints found in (0020, 9165) Dimension Index Pointer when it exists
   --
   -- Usage in DP: NEVER
   -- Related DICOM Tags: (0020, 9165) Dimension Index Pointer
   Register("EMF_NB_SORT_HINTS", "1702", "int", "EMF Number of Sort Hints",{0}) -- not for DP 
   
   -- Register("EMF_NAVIGATION_TYPE_PROP", "17XX", "string","Stack-Id,In-stack-Position,Temporal-Position-Index,Trigger-Delay-Time","Navigation",{1})
   -- translates the sort order given through permutation into a property for the image
   -- this property is actually generated in the grouping script, since there the 
   -- subsequent tags between 1702 and 170F will contain the sorting hints and EMF_NB_SORT_HINTS will record 
   -- the number or such hints   
   -- Register("EMF_STACK_ID_PROP", "17XX", "string","", "Stack Id",{1})

   -- EMF_FRAME_TYPE_PROP describes the type of frame the EMF object is made up of. This information is found in
   -- the third value of the DICOM attribute (0008, 9007) Frame Type 
   -- 
   -- Usage in DP: Possible
   -- Related DICOM Tags: (0008, 9007) Frame Type   
   Register("EMF_FRAME_TYPE_PROP", "1710", "string","", "Frame Type",{1})
    
end

function DeclareTags()
-- ////////////////////////////////////////////////////////////////////////////    
-- definitions of the DICOM elements IDs used in this script
-- and available in the context when the modality extraction scripts are called
-- ////////////////////////////////////////////////////////////////////////////    
    IMAGE_TYPE_TAG = maketag("0008", "0008")            -- image type, mandatory, regulated, text
    MODALITY_TAG = maketag("0008", "0060")              -- modality, mandatory, regulated, string
	PRES_INTENT_TAG = maketag( "0008", "0068" )         -- Presentation Intent Type
	DEVICE_MANUFACTURER_TAG = maketag( "0008", "0070" ) -- Manufacturer of acquisition device
    INSTITUTION_NAME_TAG = maketag("0008", "0080")
	INSTITUTION_ADDR_TAG = maketag("0008", "0081")
	STUDY_DESC_TAG = maketag("0008","1030")             -- Description of the study
    SERIES_DESC_TAG = maketag("0008", "103e")           -- series description, optional, free, text
	DEVICE_MODEL_TAG = maketag("0008","1090")           -- Model name of acquisition device
    	
	CONTRAST_TAG = maketag("0018", "0010")              -- contrast bolus/agent
	BODY_PART_EX_TAG = maketag("0018", "0015")          -- body region, conditional / optional, regulated, string
	SCAN_OPTIONS_TAG = maketag("0018","0022")           -- Scan Options
	MR_ACQ_TYPE_TAG = maketag( "0018", "0023" )	        -- ACQ MR Acquisition type    
    SLICE_THICKNESS_TAG = maketag( "0018", "0050" )	    -- ACQ Slice Thickness
	ACQ_PROTOCOL_TAG = maketag( "0018", "1030" )        -- Acquisition Protocol Name	
	CONTRAST_ROUTE_TAG = maketag("0018", "1040")		-- ACQ contrast bolus/route
    FRAME_TIME_TAG = maketag("0018","1063")             -- Rate of cine acquision in fps
    FRAME_TIME_VECTOR_TAG = maketag("0018","1065")  
	ACQ_FILTER_TYPE_TAG = maketag( "0018", "1160" )
	ACQ_DEV_PROCESS_TAG = maketag("0018","1400")        -- Description of processing done on image
    PATIENT_POS_TAG = maketag( "0018", "5100" )         -- ACQ Patient Position (HFS, HFP, etc.)
	VIEW_POSITION_TAG = maketag("0018","5101")          -- View position for CR/DR/DX projection info
	DETECTOR_TYPE_TAG = maketag("0018","7004") 			-- Detector Type 
    
    SERIES_UID_TAG = maketag("0020", "000e") 				-- REL Series Instance UID
	SERIES_NBR_TAG = maketag("0020", "0011")            -- series number, conditional / optional, integer
    ACQ_NUMBER_TAG = maketag( "0020", "0012" )          -- Relative Acquisition Number
	IMAGE_POSITION_PATIENT_TAG = maketag("0020","0032") -- image position in patient space
    IMAGE_ORIENTATION_PATIENT_TAG = maketag("0020","0037") -- image orientation in patient space    	
	REL_LATERALITY_TAG = maketag("0020", "0060")	    -- REL Laterality
    IMG_LATERALITY_TAG = maketag("0020","0062")       -- Image Laterality
    TEMP_POSITION_IDENTIFIER_TAG = maketag("0020","0100")    
	NUM_TEMP_POS_TAG = maketag( "0020", "0105" ) -- REL No. of Temporal Positions
	POSITION_REF_TAG = maketag( "0020", "1040" )        -- REL Position Reference
    IMAGE_COMMENTS_TAG = maketag( "0020", "4000" )      -- REL Image Comments
    FRM_LATERALITY_TAG = maketag("0020","9072")       -- Frame laterality
    
	PHOTOMETRIC_INTERP_TAG = maketag( "0028", "0004" )	    -- IMG Photometric interpretation (Monochrome1, Monochrome2, Palette Color, etc.)
	NUMBER_OF_FRAMES_TAG = maketag("0028", "0008")      -- number of frames, conditional, positive integer
    FRAME_INC_PTR_TAG = maketag("0028","0009")          -- Time to next frame in cine acquisition
    BURNED_ANNOTATION_TAG = maketag( "0028", "0301" )   -- Burned in Annotation
	LOSSY_IMAGE_COMPRESSION_TAG = maketag("0028","2110")--Lossy Image Compression
    
    SCHED_PROC_TAG = maketag( "0040", "0007" ) 	    -- Scheduled procedure step
    WKL_PROC_STEP_DESC_TAG = maketag("0040", "0254") -- WKL Performed PRocedure Step Description
	
	NMI_SERIES_TYPE_TAG = maketag("0054", "1000")
	
	-- Enhanced CT/MR tag definitions
	SOP_CLASS_UID_TAG = maketag("0008","0016") -- the sop class identification tag  
    EMF_FRAME_TYPE_TAG = maketag("0008","9007") 
	
	EMF_FRAME_ACQ_DATE_TIME_TAG = maketag("0018","9074")
	EMF_CARDIAC_TRIGGER_SEQ = maketag("0018","9118")
	
	EMF_STACK_ID_TAG = maketag("0020","9056") -- identification of the stack
    EMF_IN_STACK_NUMBER_TAG = maketag("0020","9057") -- relative position in the stack
	EMF_FRAME_CONTENT_SEQ = maketag("0020","9111")
	EMF_PLANE_POSITION_SEQ = maketag("0020","9113")
	EMF_TEMPORAL_POS_INDEX_TAG = maketag("0020","9128")
	EMF_FRAME_REF_DATE_TIME_TAG = maketag("0018","9151")	
	EMF_TRIGGER_DELAY_TIME_TAG = maketag("0020","9153")
	EMF_FRAME_ACQ_NUMBER_TAG = maketag("0020","9156")
	EMF_DIM_INDEX_VALUES_TAG = maketag("0020","9157") -- the sorting hints
    EMF_FRAME_COMMENTS_TAG = maketag("0020","9158")
	EMF_DIMENSION_INDEX_POINTER_TAG = maketag("0020","9165")
    EMF_FUNCTIONAL_GROUP_SEQ_POINTER_TAG = maketag("0020","9167")
    EMF_FRAME_ACQ_DURATION_TAG = maketag("0018","9220")
    
	-- Private tag definitions go here
	PHILIPS_PRIVATE_DESC_TAG = maketag( "0019", "1960" )	-- Private x-ray description used by some Philips devices and gateways
	SIEMENS_VIEW_TAG = maketag( "0051", "1010" )        -- Special Siemens private tag for view
	MIG_FILENAME_TAG = maketag("3711","0001")           -- Name of file containing image data
    MODALITY_SCRIPT_VER_PROP = maketag("3711", "1110")
end


-- ////////////////////////////////////////////////////////////////////////////
-- ////////////////////////////////////////////////////////////////////////////
-- common property extractors
-- ////////////////////////////////////////////////////////////////////////////
-- ////////////////////////////////////////////////////////////////////////////

function ExtractImageType()
-- ////////////////////////////////////////////////////////////////////////////
-- This function extracts the MIG image type property.  Currently it only checks
-- for the presence of "Scout" or "Localizer" substrings in a couple of key elements
-- and if present sets image type to be "Scout".  Other image types
-- like original and derived should also be implemented in the future
-- so that display protocols can have some granularity for seq. placement.
--
-- ////////////////////////////////////////////////////////////////////////////

  TraceC("Extracting image type...\n")
  
    local matchStr = nil		-- Matched string value.
    local index, tag;                   -- loop variables
	
    -- List of tags we will examine for image type information...
    local searchTags = { SERIES_DESC_TAG, IMAGE_TYPE_TAG }
    
	-- 1. Checking DICOM Tags (0008, 103e) ID Series Description & (0008, 0008) ID Image Type for "Scout" indicators
    for index,tag in ipairs(searchTags) do 
    
        TraceC( " *********************************** Checking tag " .. index .. ": " .. tag .. "\n" )
    
    	-- if attribute exists for given tag in this image...
        local testAttr = GetAttr(tag)
        if (testAttr) then
            -- Look for substring matches for scout (including synonyms)
            matchStr = BestWordMatchInAttr( testAttr, SCOUT_VAL, LOCALIZER_VAL, LOCALIZE_VAL, LOC_VAL, SCANOGRAM_VAL, 
															SCANO_VAL, TOPOGRAM_VAL, LOCATOR_VAL, THREE_PL_VAL, THREE_PLANE_VAL )
                
            -- If we got a match...
            if (matchStr) then
                -- Adopt the value "Scout" regardless of value found
                TraceC( "Found scout string: " .. matchStr .. "\n" )
                return NewPropWithValue( IMAGE_TYPE_PROP, SCOUT_VAL );
                -- ===============================================
            end
			
			-- Special Check for Philips devices
			local DeviceName = GetAttr( DEVICE_MANUFACTURER_TAG )
			if ( DeviceName~=nil and ValueForTag(DEVICE_MANUFACTURER_TAG) == "Philips Medical Systems" ) then 

				matchStr = BestWordMatchInAttr( testAttr, SURVEY_VAL, SURVEY_MST_VAL )
				-- If we got a match...
				if (matchStr) then
					-- Adopt the value "Scout" regardless of value found
					TraceC( "Found scout string: " .. matchStr .. "\n" )
					return NewPropWithValue( IMAGE_TYPE_PROP, SCOUT_VAL );
					-- ===============================================
				end			
			end
        end
    end

    -- Not a scout, so look for other likely candidates...
    -- 2. Checking DICOM tag (0008, 0008) ID Image Type for "Screen Save" & "MIP" indicators
	local imageTypeAttr = GetAttr(IMAGE_TYPE_TAG)
    if (imageTypeAttr) then
    
		matchStr = BestWordMatchInAttr(imageTypeAttr, PROJIMAGE_VAL, PROJIMAG_VAL, DISPLAY_VAL,
														POS_DISP_VAL, SCREEN_SAVE_VAL, VOLR_VAL, VOLREN_VAL, 
														CSA_3DPROJ_VAL, CSA_VRT_VAL, MAX_IP_VAL, MIN_IP_VAL)
		
		if ( (matchStr == PROJIMAGE_VAL) or (matchStr == PROJIMAG_VAL) or 
			 (matchStr == MAX_IP_VAL)	or (matchStr == MIN_IP_VAL) ) then
			TraceC( "Found other image type string: " .. matchStr .. "\n" )	
			return NewPropWithValue( IMAGE_TYPE_PROP, MIP_VAL )
			-- ===============================================
		end
		
		if ( (matchStr == DISPLAY_VAL) or (matchStr == POS_DISP_VAL)  
			or (matchStr == SCREEN_SAVE_VAL)  or (matchStr == VOLR_VAL)
			or (matchStr == CSA_3DPROJ_VAL)  or (matchStr == CSA_VRT_VAL) 
			or (matchStr == VOLREN_VAL) ) then
			TraceC( "Found other image type string: " .. matchStr .. "\n" )	
			return NewPropWithValue( IMAGE_TYPE_PROP, SCREEN_SAVE_VAL )
			-- ===============================================
		end
    end
	
	-- 3. Checking DICOM tag (0020, 4000) REL Image Comments for "MRCP" or "MIP" indicators
    local imageCommentsAttr = GetAttr(IMAGE_COMMENTS_TAG)
    if (imageCommentsAttr) then
        matchStr = BestWordMatchInAttr( imageCommentsAttr, MRCP_VAL, MIP_VAL )
        if (matchStr) then
			TraceC( "Found other image type string: " .. matchStr .. "\n" )	
			return NewPropWithValue( IMAGE_TYPE_PROP, matchStr )
			-- ===============================================
        end
    end
	
	--$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
	-- 3.5 Site Customized code for San Luis Diagnostic Centre - the value "3D" in (0018,0023) ACQ MR Acquisition Type
	-- indicates that image type is "MIP". This isnt necessarily true but (1) changing this causes orientation correction
	-- problems, and the display protocols already created on site misbehave.
	local SiteName = GetAttr(INSTITUTION_NAME_TAG)
	if ( SiteName~=nil and BestWordMatchInAttr(SiteName, "San Luis Diagnostic") ) then
		local MRAcqTypeAttr = GetAttr(MR_ACQ_TYPE_TAG)
		if ( MRAcqTypeAttr~=nil and BestWordMatchInAttr( MRAcqTypeAttr, THREE_D_VAL) ) then
			TraceC( "Found other image type string: 3D\n" )		
			return NewPropWithValue( IMAGE_TYPE_PROP, MIP_VAL );
		end
	end	
	--$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
	
	-- 4. Checking DICOM Tags (0008, 103e) ID Series Description & (0008, 0008) ID Image Type for image type indicators
    for index,tag in ipairs(searchTags) do 
    
        TraceC( " *********************************** Checking tag " .. index .. ": " .. tag .. "\n" )
    
    	-- if attribute exists for given tag in this image...
        local testAttr = GetAttr(tag)
        if (testAttr) then
            -- Look for substring matches for common other values (w/o synonyms)
            matchStr = BestWordMatchInAttr( testAttr, MIP_VAL, MPR_VAL, ORIGINAL_VAL, RECON_VAL, 
                                            REFORMAT_VAL, REFORMATTED_VAL, DERIVED_VAL, MIPSERIES_VAL,
                                            SCREEN_SAVE_VAL, MRCP_VAL, MRACP_VAL, VI_SNAPSHOT_VAL, VC_SNAPSHOT_VAL,
											CAD_SNAPSHOT1_VAL, CAD_SNAPSHOT2_VAL)
                
            -- If we got a match...
            if (matchStr) then
            
                -- Remap "MIPSeries" to "MIP"
                if (matchStr == MIPSERIES_VAL)  then
                     matchStr = MIP_VAL
                end
                
                -- Remap "Reformatted" to "MPR"
                if (matchStr == REFORMATTED_VAL or matchStr == REFORMAT_VAL) then
                    matchStr = MPR_VAL
                end
                
                -- Remap "MRACP" to "MRCP"
                if (matchStr == MRACP_VAL) then
                    matchStr = MRCP_VAL
                end
				
                -- Remap "VI/VC SnapShot" to "Screen Save"
                if ( (matchStr == VI_SNAPSHOT_VAL) or (matchStr == VC_SNAPSHOT_VAL)
					 or (matchStr == CAD_SNAPSHOT1_VAL) or (matchStr == CAD_SNAPSHOT2_VAL) ) then
                    matchStr = SCREEN_SAVE_VAL
                end				
                                
                -- Adopt the value found directly
                TraceC( "Found other image type string: " .. matchStr .. "\n" )
                return NewPropWithValue( IMAGE_TYPE_PROP, matchStr )
                -- ===============================================
            end
        end
        
    end
	
    TraceC( "Defaulting to raw DICOM image type value.\n" ) 
    
    -- Not a common image type either, so just use whatever is the first value of DICOM Image Type
    return NewPropWithValue( IMAGE_TYPE_PROP, ValueForTag( IMAGE_TYPE_TAG ) );
    -- =============================================== 
end

-- ////////////////////////////////////////////////////////////////////////////
function ExtractThumbnailGroup()
-- Mahesh Nagarajan, 25 November 2005, Created.
-- Retrieves the image type property and assigns the apropriate value from the table
-- shown below.
--       Image Type Value	Thumbnail Group Value
--	Original			01
--	Recon				05
--	MRCP				06
--	MIP				06
--	MPR				11
--	Reformat			11
--	Derived			12
--	Unknown			16
--	Scout				21
--	Screen Save		26
--	Useless Series	31
-- ////////////////////////////////////////////////////////////////////////////
    local grpValue = nil
    
    --************************************************************************
    -- MaxN: Certain sequences are identified as useless based on their series 
    -- descriptions; such sequences are sent to the end of the thumbnail bar
    CALIBRATION_SCAN_VAL = "Calibration Scan"
    CAL_SCAN_VAL = "Cal Scan"
    CONTROL_SCAN_VAL = "Control Scan"
    SMART_PREP_VAL = "Smart Prep"
    TIMING_BOLUS_VAL = "Timing Bolus"
    SMARTSCOUT_VAL = "SmartScout"
    ASSET_CAL_VAL = "asset cal"
    DOSE_REPORT_VAL = "Dose Report"
	REPORTCARD_VAL = "ReportCARD"
    
    local seriesDescAttr = GetAttr(SERIES_DESC_TAG)
    if (seriesDescAttr) then
        if ( BestWordMatchInAttr( seriesDescAttr, CALIBRATION_SCAN_VAL, CAL_SCAN_VAL, CONTROL_SCAN_VAL, 
											SMART_PREP_VAL, TIMING_BOLUS_VAL, SMARTSCOUT_VAL, ASSET_CAL_VAL, 
											DOSE_REPORT_VAL, REPORTCARD_VAL  ) ) then
            
            grpValue = "31"
            return NewPropWithValue( THUMBNAIL_GRP_PROP, grpValue )            
            
        end
    end
    --************************************************************************
    
    --************************************************************************
    -- MaxN: Images with the value "FOR PROCESSING" in their presentation
    -- intent DICOM tags should be sent to the end of the thumbnail bar
    local intentStr = ValueForTag( PRES_INTENT_TAG )
    
    if ( intentStr ~= nil and intentStr == "FOR PROCESSING" ) then
        grpValue = "31"
        return NewPropWithValue( THUMBNAIL_GRP_PROP, grpValue )    
    end 
    --************************************************************************
    
    local imageTypeValue = ValueForTag(IMAGE_TYPE_PROP)
    
    if ( imageTypeValue == ORIGINAL_VAL ) then
    
        grpValue = "01"
    
	elseif ( imageTypeValue == RECON_VAL ) then

        grpValue = "05"
    
    elseif ( imageTypeValue == MIP_VAL or 
              imageTypeValue == MRCP_VAL ) then

        grpValue = "06"
    
    elseif ( imageTypeValue == MPR_VAL or 
			  imageTypeValue == REFORMAT_VAL ) then

        grpValue = "11"
    
	elseif ( imageTypeValue == DERIVED_VAL ) then

        grpValue = "12"
    	
    elseif ( imageTypeValue == SCOUT_VAL ) then

        grpValue = "21"
	
    elseif ( imageTypeValue == SCREEN_SAVE_VAL ) then

        grpValue = "26"
    
    else    --Unknown image type; place it before the Scout images
    
        grpValue = "16"
	
    end

    -- Return the Thumbnail Group property 
    return NewPropWithValue( THUMBNAIL_GRP_PROP, grpValue )
    -- ===============================================        
end

-- ////////////////////////////////////////////////////////////////////////////
function ExtractSeriesUID()
-- ////////////////////////////////////////////////////////////////////////////
    local seriesUIDValue = nil
    local seriesUIDTag = GetAttr(SERIES_UID_TAG)
    
    if (seriesUIDTag) then
	seriesUIDValue = seriesUIDTag:Get(1)
    end
    
    return NewPropWithValue( SERIES_UID_PROP, seriesUIDValue )
end

-- ////////////////////////////////////////////////////////////////////////////
function ExtractView()
-- ////////////////////////////////////////////////////////////////////////////
-- Cristian Bonciu, October 20, 2005, modified to extract the view from the 
-- Image Orientation in patient space DICOM element, whenever present
-- When the tag is not present, the old implementation is called

    TraceC("Extracting view...\n")
    
    local matchStr = nil		-- view property value.
    
    -- MaxN 4 OCT 06: First try a quick search in the series description
    matchStr = QuickSearchView()

    
    -- Quick search failed; try to extract the view from the image orientation
    if ( not matchStr ) then
		local imageOrientationAttr = GetAttr(IMAGE_ORIENTATION_PATIENT_TAG)
		if( imageOrientationAttr ) then
			-- compute here the view property based on the normal to the image plane
			-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
			local xx =  imageOrientationAttr:Get(1)
			local xy =  imageOrientationAttr:Get(2)
			local xz =  imageOrientationAttr:Get(3)
			local yx =  imageOrientationAttr:Get(4)
			local yy =  imageOrientationAttr:Get(5)
			local yz =  imageOrientationAttr:Get(6)
			-- compute the directional cosine values of the normal to the image plane with respect to the 
			-- patient coordinate system axis
			local zx = xy * yz - xz * yy
			local zy = xz * yx - xx * yz
			local zz = xx * yy - xy * yx
			-- take the largest directional cosine value (smallest angle) to get the major axis
			if( zx < 0 ) then zx = -zx end
			if( zy < 0 ) then zy = -zy end
			if( zz < 0 ) then zz = -zz end
			if( zx > zy and zx > zz ) then matchStr = SAGITTAL_VAL end
			if( zy > zx and zy > zz ) then matchStr = CORONAL_VAL end
			if( zz > zx and zz > zy ) then matchStr = AXIAL_VAL end
	 --       if( matchStr ) then
	 --         TraceC( " >>>>>>>>>>>>>>>>>>>>>>>> Extracted view from image orientation :" .. matchStr .. "\n" )
	 --      else
	 --         TraceC( " >>>>>>>>>>>>>>>>>>>>>>>> View from image orientation couldn't be extracted \n" )
	 --      end
		-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~         
		end
	end
    
    -- If no/invalid image orientation by math above or tie...
    if( not matchStr ) then  
      -- We will have to resort to old way of hunting for keyword labels
      matchStr = FindViewKeyword()
    end
    
    -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    -- CB, June 9, 2005: 
    -- once the view property string is defined, we try to compute 
    -- IMAGE_LOCATION_PATIENT_PROP to be used as sorting hint
    -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    local imagePositionAttr = GetAttr(IMAGE_POSITION_PATIENT_TAG)
    
    -- IMAGE_LOCATION_PATIENT_PROP needs IMAGE_POSITION_PATIENT_TAG
    if( not imagePositionAttr ) then
        return NewPropWithValue( VIEW_PROP, matchStr ) 
    end
    
    -- compute IMAGE_LOCATION_PATIENT_PROP by picking the patient position 
    -- coordinate corresponding to the axis perpendicular to the image plane
    local imageLocationProp = AttrValue:new()
 
    -- IMAGE_LOCATION_PATIENT_PROP for axials is the Z coordinate of the image position 
    -- (the axial plane is the X-Y plane in patient space)
    if (matchStr == AXIAL_VAL) then
        local dLocation = imagePositionAttr:Get(3) 
        imageLocationProp:AppendVal(dLocation)
        SetAttr( IMAGE_LOCATION_PATIENT_PROP, imageLocationProp ) 
    -- IMAGE_LOCATION_PATIENT_PROP for coronals is the Y coordinate of the image position 
    -- (the coronal plane is the X-Z plane in the patient space)
    elseif (matchStr == CORONAL_VAL) then
        local dLocation = imagePositionAttr:Get(2)
        imageLocationProp:AppendVal(dLocation)
        SetAttr( IMAGE_LOCATION_PATIENT_PROP, imageLocationProp ) 
    -- IMAGE_LOCATION_PATIENT_PROP for sagittals is the X coordinate of the image position 
    -- (the coronal plane is the Y-Z plane in the patient space)
    elseif (matchStr == SAGITTAL_VAL) then
        local dLocation = imagePositionAttr:Get(1)
        imageLocationProp:AppendVal(dLocation)
        SetAttr( IMAGE_LOCATION_PATIENT_PROP, imageLocationProp ) 
    end
    -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    -- Return the view property 
    return NewPropWithValue( VIEW_PROP, matchStr )
    -- ===============================================        
end

-- ////////////////////////////////////////////////////////////////////////////
function QuickSearchView()
-- This method returns a string representing the view keyword matched in the
-- (0008, 103e) Series Description. Only certain strings types are searched for
-- i.e. Coronal, Axial, Sagittal, Cor, Sag & Ax
-- ////////////////////////////////////////////////////////////////////////////
    local matchStr = nil		-- Matched string value.
    local seriesDescTag = GetAttr(SERIES_DESC_TAG)

    if (seriesDescTag) then
        matchStr = BestWordMatchInAttr( seriesDescTag, SAGITTAL_VAL, CORONAL_VAL,  AXIAL_VAL,
                                        OBLIQUE_VAL, COR_ABBR_VAL, SAG_ABBR_VAL, AX_ABBR_VAL)
    end

    -- Remap any abbreviations/misspellings into standard unabbreviated values
    if (matchStr == SAG_ABBR_VAL) then
            matchStr = SAGITTAL_VAL
    elseif (matchStr == COR_ABBR_VAL) then
            matchStr = CORONAL_VAL
    elseif (matchStr == AX_ABBR_VAL) then
            matchStr = AXIAL_VAL
    end

    return matchStr
end

-- ////////////////////////////////////////////////////////////////////////////
function FindViewKeyword()
-- This method returns a string representing the view keyword matched in the
-- various relevant tags of the DICOM header, or nil if no mach was found.
-- ////////////////////////////////////////////////////////////////////////////

    TraceC("Finding view by keyword...\n")

    local matchStr = nil		-- Matched string value.
    local index, tag;                   -- loop variables
    
   -- List of tags we will examine for view information...
   -- N.B.  Be sure to look in series description before IMAGE TYPE or Siemens Somaris/5 3D 
   -- workstaiton MPR's will all be considered Axial due to their bug
   local searchTags = { SIEMENS_VIEW_TAG, SERIES_DESC_TAG, IMAGE_TYPE_TAG, ACQ_DEV_PROCESS_TAG,
                        SCAN_OPTIONS_TAG, ACQ_PROTOCOL_TAG, IMAGE_COMMENTS_TAG }
    
    -- For each tag that could be checked...
    for index,tag in ipairs(searchTags) do 
    
        TraceC( " *********************************** Checking tag " .. index .. ": " .. tag .. "\n" )
    
    	-- if attribute exists for given tag in this image...
        local testAttr = GetAttr(tag)
        if (testAttr) then
        
            -- Look for literal substring matches of items that contain punctuation
            matchStr = BestMatchInAttr( testAttr, TRA_GT_VAL, COR_GT_VAL, SAG_GT_VAL, C_ZERO_R_VAL )
                
            if (matchStr == nil) then
                -- Look for whole word matches (including abbreviations)
                matchStr = BestWordMatchInAttr( testAttr, SAGITTAL_VAL, CORONAL_VAL,  AXIAL_VAL,  
                                                OBLIQUE_VAL, TRAN_ABBR_VAL, TRA_ABBR_VAL, TRS_ABBR_VAL, 
                                                COR_ABBR_VAL, SAG_ABBR_VAL, AX_ABBR_VAL, SAGITAL_VAL )
            end
                
            -- If we got a match...
            if (matchStr) then
                TraceC( "Found view string: " .. matchStr .. "\n" )
                break         -- stop searching
            end
        end
    end

    -- If nothing found above...
    if (not matchStr) then
        TraceC( "No view information found in common tags.\n" )
    	return nil	-- fail
	-- =======================================================
    end

    -- Remap any abbreviations/misspellings into standard unabbreviated values
    if (matchStr == SAG_ABBR_VAL or matchStr == SAGITAL_VAL or matchStr == SAG_GT_VAL) then
            matchStr = SAGITTAL_VAL
    elseif (matchStr == COR_ABBR_VAL or matchStr == C_ZERO_R_VAL or matchStr == COR_GT_VAL) then
            matchStr = CORONAL_VAL
    elseif (matchStr == AX_ABBR_VAL or matchStr == TRAN_ABBR_VAL or 
            matchStr == TRA_ABBR_VAL or matchStr == TRA_GT_VAL or matchStr == TRS_ABBR_VAL) then
            matchStr = AXIAL_VAL
    end

    return matchStr

end

-- ////////////////////////////////////////////////////////////////////////////
function ExtractBodyRegion()
-- This function extracts the body region covered in the series.
--
-- Allan Noordvyk, Nov. 16, 2005
--    Added search of series description BEFORE the BODY_PART_EX_TAG
--    in order to allow for more fine-grained results, since the
--    body region tag tends to be the same for all series in a study
--    in practice.
-- ////////////////////////////////////////////////////////////////////////////

    local testAttr           -- an attribute to be tested
    local matchStr = nil     -- string containing match
    
    -- List of primary tags we will examine for body region information...
    local searchTags = { SERIES_DESC_TAG }

    -- For each tag that could be checked...
    for index,tag in ipairs(searchTags) do 
    
        TraceC( "Checking primary tag " .. index .. ": " .. tag .. "\n" )
    
    	-- if attribute exists for given tag in this image...
        local testAttr = GetAttr( tag)
        if (testAttr) then
        
            -- Look for substring matches (including abbreviations)
            matchStr = BestMatchForBodyRegion(testAttr)
                
            -- If we got a match...
            if (matchStr) then
                TraceC( "Found body region match: " .. matchStr .. "\n" )
                return NewPropWithValue( BODY_REGION_PROP, matchStr )
                -- =====================================================
            end
        end
    end

    -- 2nd check  the Body Part Examined Tag
    testAttr = GetAttr(BODY_PART_EX_TAG)
    if (testAttr) then
    
        -- See if attribute matces either DICOM standard values or what site has in database
        matchStr = BestMatchForBodyRegion(testAttr)
        
        -- If we did not find a match above...
        if (not matchStr) then
            -- Otherwise treat tag value as special value (since this tag is designed for this purpose)
            -- and use verbatim.
            matchStr = testAttr:Get(1);
            
            -- If the string is simply "DEFAULT", however...
            if (matchStr == "DEFAULT") then
                matchStr = nil  -- stupid vendor - don't bother
            end
        end
            
        -- If we ended up with anything above...
        if (matchStr) then
            TraceC( "Found body region in BODY_PART_EX_TAG\n" );
            -- Use it as the body region.
            return NewPropWithValue( BODY_REGION_PROP, matchStr )
            -- ==============================================
        end
    end
        
    -- List of alternate tags we will examine for body region information...
    local searchTags = { IMAGE_TYPE_TAG, ACQ_DEV_PROCESS_TAG,
                         SCAN_OPTIONS_TAG, ACQ_PROTOCOL_TAG, POSITION_REF_TAG,
                         SCHED_PROC_TAG, STUDY_DESC_TAG }

    -- For each tag that could be checked...
    for index,tag in ipairs(searchTags) do 
    
        TraceC( "Checking alternate tag " .. index .. ": " .. tag .. "\n" )
    
    	-- if attribute exists for given tag in this image...
        local testAttr = GetAttr( tag)
        if (testAttr) then
        
            -- Look for substring matches (including abbreviations)
            matchStr = BestMatchForBodyRegion(testAttr)
                
            -- If we got a match...
            if (matchStr) then
                TraceC( "Found body region match: " .. matchStr .. "\n" )
                return NewPropWithValue( BODY_REGION_PROP, matchStr )
                -- =====================================================
            end
        end
    end

    TraceC( "No body region information found.\n" )   
    return nil
    
end

-- ////////////////////////////////////////////////////////////////////////////
function ExtractFrameType()
--
-- This function sets the frame type property to be either "Still" or "Cine"
-- based on whether the image is from a multi-frame file that has a frame
-- increment pointer or frame time value.   Ultrasound multi-frame images
-- are treated as a special case due to known vendor issues -- any multi-frame
-- US image file will be treated as being a cine, even if no time information
-- is included using either tag.
--
-- This as written will not work for the any CT or MR object that is 
-- to be treated as a cine even though the frames are in different files.
-- If we want to treat those as cine we should either add additional
-- logic to this routine, or add a 2nd routine in modality specific
-- scripts that is called after this one and resets the value.
--
-- This function should be called after ExtractModality
--
-- ////////////////////////////////////////////////////////////////////////////
    
    local bMultiFrame = false   -- will be set to true if frame is part of multi-frame file
    local bTimeBased = false    -- will be set to true if time relationship to other frames
    
    local nFrames = ValueForTag(NUMBER_OF_FRAMES_TAG)
    
    -- If the frame count attribute is present and has a value of more than 1..
    if (nFrames and nFrames > 1 ) then
          
        bMultiFrame = true
           
	--**********************************************************************************
	-- Now we have the thankless job of figuring out if the DICOM tag (0028, 0009) exists and
	-- if so, whether it points to either (0018, 1036) Frame Time or (0018, 1065) Frame Time Vector.
	-- If this is indeed the case, then the object is time based and definitely a Cine clip.
	if ( HasAttr(FRAME_INC_PTR_TAG) ) then
		local frameIncPtrAttr = GetAttr(FRAME_INC_PTR_TAG)
		if (frameIncPtrAttr) then
			local nIDs = 0
			if ( frameIncPtrAttr:IsMatrix() )then
				nIDs = frameIncPtrAttr:GetSize()
			else
				nIDs = 1
			end
		
			if ( nIDs == 1 ) then
				local element = frameIncPtrAttr:Get(1)
				local group = frameIncPtrAttr:Get(2)
				
				if ( (element == 4195 and group == 24) or	-- pointing to (0018, 1063) Frame Time?
				     (element == 4197 and group == 24) ) then	-- pointing to (0018, 1065) Frame Time Vector?
					
					bTimeBased = true;
				end
			else
				for i=1,nIDs,1 do
					local element = frameIncPtrAttr:Get(i,1)
					local group = frameIncPtrAttr:Get(i,2)
					
					if ( (element == 4195 and group == 24) or	-- pointing to (0018, 1063) Frame Time?
					     (element == 4197 and group == 24) ) then	-- pointing to (0018, 1065) Frame Time Vector?
					
						bTimeBased = true
						break
					end
				end
			end
		end
	end
	--**********************************************************************************
    
    end
       
    -- If the frame is part of a multi-frame file and it is time based...
    if( bMultiFrame == true and bTimeBased == true) then
        return NewPropWithValue( FRAME_TYPE_PROP, CINE_VAL )
        -- ==============================================
    end
    
    -- Otherwise we must be still frame image.
    return NewPropWithValue( FRAME_TYPE_PROP, STILL_VAL )

end

-- ////////////////////////////////////////////////////////////////////////////
function ExtractDelayed()
-- This function detects whether the series should be considered a delayed
-- contrast acquisition or not.   Unless the "DELAY" substring is found 
-- somewhere in the series description, it a value of "No" is assigned.
-- Otherwise, a value of "Yes" is assigned.
-- ////////////////////////////////////////////////////////////////////////////
 
    local anAttr
    
    -- If there is a series description and it contains the substring "DELAY"...
    anAttr = GetAttr( SERIES_DESC_TAG )
    if (anAttr and BestMatchInAttr( anAttr, "DELAY", "Late" )) then 
        -- We are dealing with a delayed series.
        return NewPropWithValue( DELAYED_PROP, "Yes" )
    end
    
    -- If there are image comments and they contains the substring "DELAY"...
    anAttr = GetAttr( IMAGE_COMMENTS_TAG )
    if (anAttr and BestMatchInAttr( anAttr, "DELAY" )) then 
        -- We are dealing with a delayed series.
        return NewPropWithValue( DELAYED_PROP, "Yes" )
    end
    
    -- Not a delayed series.
    return NewPropWithValue( DELAYED_PROP, "No" )

end

-- ////////////////////////////////////////////////////////////////////////////
function ExtractPosition()
-- This function detects whether the patient was supine, prone, upright, etc. when
-- the images in the series were acquired.
-- ////////////////////////////////////////////////////////////////////////////

    -- If the modality is a projection-type...
    local modAttr = GetAttr(MODALITY_PROP)
    if (BestMatchInAttr(modAttr,"CR","DR","DX","XR","RF") ~= nil) then
    
        -- Call the specialized function instead of this general one.
        return ExtractPositionProjection()
        -- ==============================================
    end

    local matchStr
    
    -- See if we have patient position information
    local anAttr = GetAttr(PATIENT_POS_TAG)
    if (anAttr) then
    
        -- Look for standard prone / supine or decubitus substrings (Head-First-Supine, Head-First-Prone,
        -- Feet-First-Supine, Feet-First-Prone, etc)
        matchStr = BestMatchInAttr( anAttr, HFS_VAL, HFP_VAL, FFS_VAL, FFP_VAL, HFDR_VAL, HFDL_VAL, FFDR_VAL, FFDL_VAL )
        
        if (matchStr == HFS_VAL or matchStr == FFS_VAL) then
            return NewPropWithValue( POSITION_PROP, SUPINE_VAL )
            --- ==================================================
        elseif (matchStr == HFP_VAL or matchStr == FFP_VAL) then
            return NewPropWithValue( POSITION_PROP, PRONE_VAL )
            --- ==================================================
        elseif( matchStr == HFDR_VAL or matchStr == FFDR_VAL ) then
            return NewPropWithValue( POSITION_PROP, DECUBITUS_RIGHT_VAL )        
            --- ==================================================            
        elseif( matchStr == HFDL_VAL or matchStr == FFDL_VAL ) then
            return NewPropWithValue( POSITION_PROP, DECUBITUS_LEFT_VAL )        
            --- ==================================================            
        end
        
    end 
	
	-- MaxN: Special searched done for MR studies to capture images with the "ABER Position. this is
	-- done for MR studies only to save time
	if (BestMatchInAttr(modAttr, "MR") ~= nil) then
    
	    local index, tag;                   -- loop variables
		local searchTags = { SERIES_DESC_TAG, ACQ_DEV_PROCESS_TAG, VIEW_POSITION_TAG,
									ACQ_PROTOCOL_TAG, SCHED_PROC_TAG, PHILIPS_PRIVATE_DESC_TAG }
		
		for index,tag in ipairs(searchTags) do

			TraceC( " *********************************** Checking tag " .. index .. ": " .. tag .. "\n" )
    		-- if attribute exists for given tag in this image...
			local testAttr = GetAttr(tag)
			if (testAttr) then
				matchStr = BestWordMatchInAttr( testAttr, ABER_VAL )
					
				-- If we got a match...
				if (matchStr) then
					TraceC( "Found Position string: " .. matchStr .. "\n" )
					return NewPropWithValue( POSITION_PROP, matchStr )
					-- ===============================================
				end
			end
		end
        
    end
	
    -- If we are here we didn't find an official tag, 
    -- so double-check the series description
    local anAttr = GetAttr(SERIES_DESC_TAG)
    if (anAttr) then
    
            -- Look for limited range of non-projection modality keywords within 
            -- the series description
            matchStr = BestWordMatchInAttr( anAttr, SUPINE_VAL, PRONE_VAL )
        
        if (matchStr ~= nil) then
            return NewPropWithValue( POSITION_PROP, matchStr )
            --- ==================================================
        end
        
    end
    
    return nil
    
end

-- ////////////////////////////////////////////////////////////////////////////
function ExtractPositionProjection()
---  This function extracts the Position property from projection images
---  and is called automatically from ExtractPosition for any projetion
---  modality (e.g. DX, CR, XR, DR).  
---  This function is needed because projection modalities often involve
---  a complex nomenclature that describes both the patient position and
---  specific projection information that is distinct from other modalites. 
-- ////////////////////////////////////////////////////////////////////////////

    TraceC( "Extracting position for a projection modality...\n" );
    
    -- Set up array of arrays variable to hold aliases for each type of position.
    -- First value of each array is the standard property value and the rest are the aliases.
    -- Order arrays with shortest substring aliases ones at the end (to avoid less precise early matching).
    -- For example TUNNEL_VAL needs to follow CARPAL_TUNNEL_VAL, "Tunnel" is a substring of "Carpal Tunnel".
    -- Words of 4 characters or less will require woord wrapping to prevent false substring positive matches, 
    -- so include plural forms for those.  Words 5 letters and longer will do substring matches.
    local aliases  = { { SUPINE_VAL, "SUP" },
                       { PRONE_VAL, "PRN" },
                       { HALF_AXIAL_VAL, "HALFAXIAL", "SEMIAXIAL", "HA", "1/2 AXIAL" },  -- must be before AXIAL below
                       { AXILLARY_VAL, "AXIL", "AXILL" },   -- must be before "AX" alias for "AXIAL" below
                       { AXIAL_VAL, "AX" },
                       { CALDWELL_VAL, "CALD" },
                       { CARPAL_TUNNEL_VAL, "CARPAL" },   -- must be before TUNNEL below
                       { CROSS_TABLE_VAL, "CROSSTABLE" },
                       { DIDIEE_VAL, "DIDEE", "DIDES", "DIDIE", "DIDE" },
                       { BROW_DOWN_VAL, "BROWDOWN" },
                       { BROW_UP_VAL, "BROWUP" },
                       { DORSOPLANTAR_VAL, "DORSO", "PLANTAR", "DP" },
                       { EXTENSION_VAL, "EXTEN" },
                       { FLEXION_VAL, "FLEX" },
                       { FROG_LEG_VAL, "FROGLEG", "FROG" },
                       { HEINIG_VAL, "HIENIG" },
                       { HERMODSSON_VAL, "HERMODSON", "HERM" },
                       { HUGHSTON_VAL, "HUGH" },
                       { INFEROSUPERIOR_VAL, "INFERO", "IS" },
                       { LAURIN_VAL, "LAURIN" },
                       { MERCHANT_VAL, "MERCH" },
                       { MORTISE_VAL, "MORTISSE" },
                       { ODONTOID_VAL, "MOUTH", "ODONT" },
                       { NOTCH_VAL, "STRYKER" },
                       { PILLAR_VAL, "PILL" },
                       { SCHULLER_VAL, "SCHUL", "SCHULER" },
                       { SETTEGAST_VAL, "SETEGAST", "SUNRISE", "SUN RISE", "SETTE" },
                       { STANDING_VAL, "STAND" },
                       { STENVERS_VAL, "STENVER", "STEN" },
                       { STRESS_VAL, "STRSS" },
                       { SUBMENTOVERTEX_VAL, "SUBMENTO" },
                       { SWIMMER_VAL, "SWIM" },
                       { TANGENTIAL_VAL, "TANGENT", "TAN" },
                       { THALAMOCORTICAL_VAL, "THALAMO", "FIBER" },
                       { TOWNE_VAL, "TOWNES", "TOWN" },
                       { TUNNEL_VAL, "TUN", "TNL" },
                       { UPRIGHT_VAL, "UP-RIGHT", "ERECT" },
                       { VERTICOSUBMENTAL_VAL, "VERTICO", "SUBMENTAL"},
                       { WATERS_VAL, "WATER" },
                       { NON_WEIGHT_BEARING_VAL, "NONWEIGHT" },   -- must be before weight-bearing below
                       { WEIGHT_BEARING_VAL, "BEARING" },
                       { WEST_POINT_VAL, "WESTPOINT", "WPOINT" }
                     }

    
    local matchStr = nil		-- Matched string value.
    local aliasWords
    local i, j, index, tag
    
    -- List of tags we will examine for view information...
    local searchTags = { SERIES_DESC_TAG, ACQ_DEV_PROCESS_TAG, VIEW_POSITION_TAG,
                         ACQ_PROTOCOL_TAG, SCHED_PROC_TAG, PHILIPS_PRIVATE_DESC_TAG }
    
    -- For each tag that could be checked...
    for index,tag in ipairs(searchTags) do 
      
      -- For each possible property value...
      for i,aliasWords in ipairs(aliases) do
      
          local testVal = aliasWords[1] 
    
          TraceC( "Checking tag " .. index .. ": " .. tag .. " for aliases of [" .. testVal .. "]\n" )
    
          -- if attribute exists for given tag in this image...
          local testAttr = GetAttr(tag)
          if (testAttr) then
              for j,word in ipairs(aliasWords) do
              
                  -- If the word is less than five letters long...
                  if (string.len(word) < 5) then

                      -- Look only for full word matches for this alias 
                      -- (to avoid false positives in substrings)
                      matchStr = BestWordMatchInAttr( testAttr, word ) 
                      
                  else
              
                      -- Look for substring matches for this alias
                      matchStr = BestMatchInAttr( testAttr, word )
                  end
                
                  -- If we got a match...
                  if (matchStr) then
                  
                      TraceC( "Found position string [" .. matchStr .. 
                              "] indicating value [" .. testVal .. "]\n" )
                              
                      -- If we didn't find an explicit projection value yet
                      if (ValueForTag( PROJECTION_PROP ) == nil) then
                      
                          -- See if we can infer it from the position information
                          InferProjectionFromPosition( testVal )
                          
                      end
                      
                      return NewPropWithValue( POSITION_PROP, testVal )
                      -- ===================================================
                  end
              end
          end
      end
    end
      
    TraceC( "No projection position alias was found.\n" )
    
end

-- ////////////////////////////////////////////////////////////////////////////
function InferProjectionFromPosition( posStr )
-- This function attempts to set the projection property based on a position
-- string.  This function is called automatically from ExtractPositionProjection
-- when that routine has deduced a position value (but has not yet set it)
-- and no explicit projection was already found and set).
--
-- Not all positions values have non-ambiguous general projecion values
-- associated, so this routine will only set the projeciton property for
-- those that do.

    if (posStr == BROW_DOWN_VAL or posStr == CALDWELL_VAL or 
        posStr == HARRIS_AND_BEATH_VAL or posStr == HERMODSSON_VAL or
        posStr == TUNNEL_VAL) then
        
        -- All of the above always involve posterior-anterior projections
        return NewPropWithValue( PROJECTION_PROP, PA_VAL )
        -- ===================================================    
    end
    
    if (posStr == BROW_UP_VAL or posStr == FROG_LEG_VAL or
        posStr == MORTISE_VAL or posStr == ODONTOID_VAL or
        posStr == TOWNE_VAL ) then
        
        -- All of the above always involve anterior-posterior projections
        return NewPropWithValue( PROJECTION_PROP, AP_VAL )
        -- ===================================================    
    end
    
    if (posStr == DIDIEE_VAL or posStr == STENVERS_VAL ) then
        
        -- All of the above always involve oblique projections
        return NewPropWithValue( PROJECTION_PROP, OBLIQUE_VAL )
        -- ===================================================    
    end
    
    return nil
    
end

-- ////////////////////////////////////////////////////////////////////////////
function ExtractContrast()
-- Cristian Bonciu,September 27, 2004,
--   added CONTRAST_PROP as per CRDB00024804: Sequence Contrast property should 
--   be defined as Yes or No    
-- Allan Noordvyk, September 14, 2005
---  Check bolus name vs. "N" and "NO" and ignore if matches.
-- Vladan Petrovic Oct 3, 2005
---  Check for CONTRAST_ROUTE_TAG added
-- ////////////////////////////////////////////////////////////////////////////
 
    local bolusName = ValueForTag( CONTRAST_TAG )
    local bolusRouteName = ValueForTag(CONTRAST_ROUTE_TAG)
    
    -- Look at REL Image Comments
    local relImageComments = GetAttr( IMAGE_COMMENTS_TAG )
    if (relImageComments) then
        if (BestWordMatchInAttr( relImageComments, OMNI_VAL )) then
              return NewPropWithValue( CONTRAST_PROP, "Yes" )
        elseif (BestMatchInAttr(relImageComments, UNENHANCED_VAL)) then
              return NewPropWithValue( CONTRAST_PROP, "No" )
        end
    end

    if (bolusName ~= nil and bolusName ~= "N" and bolusName ~= "NO" and bolusName ~= "NONE" and bolusName ~= "off") or
    (bolusRouteName ~= nil and bolusRouteName ~= "N" and bolusRouteName ~= "NO"and bolusRouteName ~= "NONE") then
        return NewPropWithValue( CONTRAST_PROP, "Yes" )
    end
    
	-- The following section was modified to enhance readibility - searching the series description for contrast indicators
	-- now follows three steps outlined below
	-- 1. If a series description was provided and it contains a non-ambiguous contrast substring
    -- (i.e. don't confuse "POST FOSSA" or "POST OP" with "POST" by itself)...
	if (seriesDescAttr ~= nil and (BestMatchInAttr( seriesDescAttr, POST_VAL ) and 
        BestMatchInAttr( seriesDescAttr, POST_FOS_VAL, POST_DASH_FOS_VAL, POST_OP_VAL, POST_DASH_OP_VAL ) == nil)) then
         -- Trust keyword in series description as indicator of contrast.
        return NewPropWithValue( CONTRAST_PROP, "Yes" )
    end
	
	-- 2. The keyword WGAD in the series description usually means with Gadolinium i.e. a contrast agent
	-- return contrast property with value "Yes"
	local seriesDescAttr = GetAttr( SERIES_DESC_TAG )
    if (seriesDescAttr ~= nil and BestMatchInAttr( seriesDescAttr, WGAD_VAL ) ) then
		return NewPropWithValue( CONTRAST_PROP, "Yes" )
	end
    
    -- 3. Looking for common contrast indicators +C & C+; however, the "BestWordMatchInAttr" function cannot be used as it 
	-- doesnt include "+" & "-" in its pattern searches resulting in false positive matching for C- or -C
	local seriesDescTagValue = ValueForTag(SERIES_DESC_TAG)
	if ( seriesDescTagValue ~= nil and 
		(string.find(seriesDescTagValue,"%sC%+$",1) or string.find(seriesDescTagValue,"%s%+C%s",1) or
		 string.find(seriesDescTagValue,"%sC%+%s",1) or string.find(seriesDescTagValue,"%s%+C%s",1) or
		 string.find(seriesDescTagValue,"^C%+%s",1) or string.find(seriesDescTagValue,"%s%+C%s",1)) ) then

		return NewPropWithValue( CONTRAST_PROP, "Yes" )
	end	
	
    return NewPropWithValue( CONTRAST_PROP, "No" )
end

-- ////////////////////////////////////////////////////////////////////////////
function ExtractAcqDevice()
-- This function identifies the acquisition device and sets the ACQ_DEVICE_PROP
-- appropriately.  Usually this just means copying the value in the Manufacturer
-- Model Number DICOM header element <0008/1090>, but could mean concatenating 
-- the Manufacturer and other elements in certain cases.
-- 
-- Author: Allan Noordvyk, Oct 1, 2004,
--     
-- ////////////////////////////////////////////////////////////////////////////
    
    local aStr = SmartAppendWords( ValueForTag( DEVICE_MANUFACTURER_TAG ), " ", 
                                   ValueForTag( DEVICE_MODEL_TAG ) )
    
    return NewPropWithValue( ACQ_DEVICE_PROP, aStr )

end

function ExtractModality()
-- ////////////////////////////////////////////////////////////////////////////
-- This function extracts the modality of the image.  So far we can get away
-- with a direct copy of hte modality as recorded in DICOM element <0008,0060>
-- ////////////////////////////////////////////////////////////////////////////
    
    return NewPropFromTag( MODALITY_PROP, MODALITY_TAG )

end

function ExtractColor()
-- ////////////////////////////////////////////////////////////////////////////
-- This function extracts whether the image contains color or not.  This
-- is done by consulting DICOM element <0028,0004> IMG Photometric Intrepretation.
-- ////////////////////////////////////////////////////////////////////////////
    
    local valStr
    local photoStr = ValueForTag( PHOTOMETRIC_INTERP_TAG )
    
    -- If there is no value (shouldn't happen, but..)...
    if (photoStr == nil) then
      return nil
    end
    
    -- if the interpretation is one of the monochrome values...
    if (photoStr == "MONOCHROME1" or photoStr == "MONOCHROME2") then
      valStr = "No"   -- not color
    else
      valStr = "Yes"  -- yes color
    end
    
    return NewPropWithValue( COLOR_PROP, valStr )

end

function ExtractSliceType()
-- ////////////////////////////////////////////////////////////////////////////
-- This function extracts a classification of the slice type based on its 
-- thickness.  This is done by consulting DICOM element <0018,0050> 
-- ACQ Slice Thickness and dividing into distsinct classes based on
-- numeric ranges.
-- The current numeric ranges used to determine slice thickness have been
-- changed with respect to Dr. Fred Vernacchia's input
-- ////////////////////////////////////////////////////////////////////////////
    
    local valStr
    local thickness = ValueForTag( SLICE_THICKNESS_TAG )
    
    -- If there is no thickness value...
    if (thickness == nil) then
      return nil
    end
    
    -- if the value is greater or equal to our upper bound (in mm)..
    if (thickness > 3.4) then
      valStr = THICK_VAL
    elseif (thickness >= 1.5) then -- if in the mid-range
      valStr = THIN_VAL
    else
      valStr = VERY_THIN_VAL
    end
    
    return NewPropWithValue( SLICE_TYPE_PROP, valStr )

end

function ExtractProjection()
-- ////////////////////////////////////////////////////////////////////////////
-- This function extracts the projection of the image.  For most projection
-- modalities we can get away with a direct copy of the ACQ View Position as 
-- recorded in DICOM element <0018,5101>, so that is all this routine does
-- for now.
-- However, many older CR devices don't use this element and instead bury
-- the projection information in other fields.  Thus, ExtractCR.tls has some
-- additional logic in its ExtractProjectionCR function to go digging in
-- the usual places favored by various vendors.
-- ////////////////////////////////////////////////////////////////////////////
    
    TraceC("Extracting projection...\n")
    
    -- First, check for alternate values of "Lateral"; map these to "Lat"
    local projType =  nil
    local projAttr = GetAttr(VIEW_POSITION_TAG)
    if (projAttr) then
          projType = BestWordMatchInAttr( projAttr, LATERAL_VAL, LATE_VAL)
          if (projType) then
                return NewPropWithValue( PROJECTION_PROP, LAT_VAL )
          end
          
          projType = BestWordMatchInAttr( projAttr, RLD_ABB_VAL)
          if (projType) then
                return NewPropWithValue( PROJECTION_PROP, RLD_VAL )
          end
          
          projType = BestWordMatchInAttr( projAttr, LLD_ABB_VAL)
          if (projType) then
                return NewPropWithValue( PROJECTION_PROP, LLD_VAL )
          end
    end
    
    -- Above checks failed so no mapping required, send value as is
    return NewPropFromTag( PROJECTION_PROP, VIEW_POSITION_TAG )

end

function ExtractIntent()
-- ////////////////////////////////////////////////////////////////////////////
-- This function extracts the intent of the image.  For most modalities
-- we can get away with a direct copy of the Presentation Intent Type as 
-- recorded in DICOM element <0008,0068>, so that is all this routine does
-- for now.
-- ////////////////////////////////////////////////////////////////////////////
    
    TraceC("Extracting intent...\n")
    
    -- 04 APR 2006, Mahesh "Max" Nagarajan; on input from Allan Noordvyk, added
    -- code to re-map non-standard values like to "PRESENTATION" & "PROCESSING" to 
    -- standard values i.e. "FOR PRESENTATION" & "FOR PROCESSING".
    local intentStr = ValueForTag( PRES_INTENT_TAG )
    
    if (intentStr == nil) then
        return nil
    end
    
    if (intentStr == "PRESENTATION") then
        intentStr = "FOR PRESENTATION"
    elseif (intentStr == "PROCESSING") then
        intentStr = "FOR PROCESSING"
    end
        
    return NewPropWithValue( INTENT_PROP, intentStr )
end


function ExtractLaterality()
-- ////////////////////////////////////////////////////////////////////////////
-- This function extracts the laterality of the image.  For most modalities 
-- we can get away with a direct copy of the DICOM Laterality element  <0020, 0062>
-- routine does for now.
-- However, many older CR devices don't use this element and instead bury
-- the laterality information in other fields.  Thus, ExtractCR.tls has some
-- additional logic in its ExtractLateralityCR function to go digging in
-- the usual places favored by various vendors.
-- ////////////////////////////////////////////////////////////////////////////
    
    TraceC("Extracting laterality...\n")
 
    -- STAGE 1  - Search the Laterality-Specific DICOM tags. If these are found, the values 
    -- can be used directly
    -- ****************************************************************************************
    local retProp1 = NewPropFromTag( LATERALITY_PROP, IMG_LATERALITY_TAG )
    if ( retProp1 and ValueForTag(IMG_LATERALITY_TAG) ~= "U" ) then
		TraceC( "Laterality tag (0020, 0062) provided specific laterality value.\n" )
		return retProp1
    end
    
    local retProp2 = NewPropFromTag( LATERALITY_PROP, REL_LATERALITY_TAG )
    if ( retProp2 and ValueForTag(REL_LATERALITY_TAG) ~= "U" ) then
		TraceC( "Laterality tag (0020, 0060) provided specific laterality value.\n" )
		return retProp2
    end    
    
    local retProp3 = NewPropFromTag( LATERALITY_PROP, FRM_LATERALITY_TAG )
    if (retProp3 and ValueForTag(FRM_LATERALITY_TAG) ~= "U" ) then
		TraceC( "Laterality tag (0020, 9072) provided specific laterality value.\n" )
		return retProp3
    end    
    -- ****************************************************************************************    
    
    TraceC( "Searching alternate tags for laterality...\n" )
    
    -- STAGE 2 - If we're here, the search in laterality-specific DICOM tags above failed; must search 
    -- through the alternate tags now
    -- ****************************************************************************************     
    local matchStr = nil		-- Matched string value.
    
    -- List of tags we will examine for laterality information...
    local searchTags = { SERIES_DESC_TAG, ACQ_DEV_PROCESS_TAG, 
			 SCAN_OPTIONS_TAG, ACQ_PROTOCOL_TAG, POSITION_REF_TAG,
			 STUDY_DESC_TAG, BODY_PART_EX_TAG, IMAGE_COMMENTS_TAG,
                         WKL_PROC_STEP_DESC_TAG }
    
    -- For each tag that could be checked...
    for index,tag in ipairs(searchTags) do 
    
        TraceC( "Checking tag " .. index .. ": " .. tag .. "\n" )
    
    	-- if attribute exists for given tag in this image...
        local testAttr = GetAttr(tag)
        if (testAttr) then
            local match1 = nil
	    local match2 = nil
	
	    -- Searching alternate tags, including some that are traditionally used more for projection than 
	    -- for laterality. We accept the string "left" to indicate 'L' laterality only if the string "left 
	    -- lateral" isnt found in the same tag.
	    match1 = BestWordMatchInAttr( testAttr, LEFT_VAL, RIGHT_VAL, LT_VAL, RT_VAL, 
							LL_VAL, RL_VAL)
	    match2 = BestWordMatchInAttr( testAttr, LL_LONG_VAL, RL_LONG_VAL, LLD_LONG_VAL, 
                                          RLD_LONG_VAL, LLO_LONG_VAL, RLO_LONG_VAL,
                                          LAO_LONG_VAL, RAO_LONG_VAL, LPO_LONG_VAL, RPO_LONG_VAL )
	
	    if (match1 and not(match2)) then
	    -- left or right is not obtained from the long form of lateral projection types; we got a match!
		    matchStr = match1
	    end

            -- If we got a match...
            if (matchStr) then
                TraceC( "Found laterality string: " .. matchStr .. "\n" )
                break         -- stop searching
            end
        end
    end
    -- ****************************************************************************************   

    -- If nothing found above...
    if (not matchStr) then
        TraceC( "No laterality information found in common alternate tags.\n" )
    	return nil	-- fail
	-- =======================================================
    end

    -- Remap any alternate forms into standard values
    if (matchStr == LEFT_VAL) then
            matchStr = L_VAL
    elseif (matchStr == RIGHT_VAL) then
            matchStr = R_VAL
    elseif (matchStr == LT_VAL) then
            matchStr = L_VAL
    elseif (matchStr == RT_VAL) then
            matchStr = R_VAL
    elseif (matchStr == LL_VAL) then
	    matchStr = L_VAL
    elseif (matchStr == RL_VAL) then
	    matchStr = R_VAL
    end

    -- Return the new attribute.
    return NewPropWithValue( LATERALITY_PROP, matchStr )
    -- ===============================================   
end

function ExtractProjAngle()
-- ////////////////////////////////////////////////////////////////////////////
-- This function extracts the angle/spin of a projection image.  
-- This only applies to a MIP or other kind of projection image.  
-- We search for common keywords indicating variation of angle 
-- within the series in some commonly used tags
--
-- IMPORTANT: This method should be called after image type property is
-- extracted, so that non-projection images can be skipped.
-- Likewise, laterality should be extracted already in the event its
-- value will be consulted.
--
-- ////////////////////////////////////////////////////////////////////////////
    
    TraceC("Extracting projection angle...\n")
    
    local imageType = ValueForTag( IMAGE_TYPE_PROP ) 
    if (imageType ~= MIP_VAL) then
      return nil
    end
    
    local matchStr = nil		-- Matched string value.
    
    -- List of tags we will examine for projection angle information...
    local searchTags = { SERIES_DESC_TAG, IMAGE_TYPE_TAG, ACQ_DEV_PROCESS_TAG,
                         SCAN_OPTIONS_TAG, POSITION_REF_TAG }
    
    -- For each tag that could be checked...
    for index,tag in ipairs(searchTags) do 
    
        TraceC( "Checking tag " .. index .. ": " .. tag .. "\n" )
    
    	-- if attribute exists for given tag in this image...
        local testAttr = GetAttr(tag)
        if (testAttr) then
            -- Look for whole word substring matches (including abbreviations)
            matchStr = BestMatchInAttr( testAttr, TUMBLED_VAL, ROTATED_VAL, 
                                        R_L_VAL, L_R_VAL, T_D_VAL, D_T_VAL,
                                        BASILAR_VAL )
            -- If we got a match...
            if (matchStr) then
                TraceC( "Found proj angle string: " .. matchStr .. "\n" )
                break         -- stop searching
            end
        end
    end
    
    -- If nothing found above...
    if (not matchStr) then
        -- Assume equivalence with laterality
        matchStr = ValueForTag( LATERALITY_PROP )
    end

    -- If nothing found above...
    if (not matchStr) then
        TraceC( "No projection angle information found in common tags.\n" )
    	return nil	-- fail
	-- =======================================================
    end

    -- Remap any alternate forms into standard values
    if (matchStr == R_L_VAL or matchStr == L_R_VAL) then
            matchStr = ROTATED_VAL
    elseif (matchStr == T_D_VAL or matchStr == D_T_VAL) then
            matchStr = TUMBLED_VAL
    end

    -- Return the new attribute.
    return NewPropWithValue( PROJ_ANGLE_PROP, matchStr )
    -- ===============================================   
end


function ExtractDescription()
-- ////////////////////////////////////////////////////////////////////////////
-- This function extracts a description for the image's series.  This routine
-- simply attempts to use the series description.  If not found, this method
-- sets the description to "?"  Each modality may wish to over-ride this behavior 
-- build more meaningful descriptions from discovered attributes (e.g. a CR
-- may wish to concatenate projection and body region, an MR may wish to
-- concatenate view, echo, and contrast, etc.)
-- ////////////////////////////////////////////////////////////////////////////

    -- Look-up DICOM series description directly.
    local descStr = ValueForTag( SERIES_DESC_TAG )
	local descAttr = GetAttr( SERIES_DESC_TAG )
	
	-- search Strings for Anonymous; these descriptions need to be discarded as well
	ANONYM_VAL = "Anonym"
	    
    -- If this is non-nil and hasn't been wiped by overzealous anonymization or
    -- just filled with something stupid by the device...
    if (descStr and not string.find(descStr,"CONFIDENTIAL")
		and not BestMatchInAttr(descAttr, ANONYM_VAL)
        and descStr ~= "DEFAULT") then
       return NewPropWithValue( SERIES_DESC_PROP, descStr )
       -- ==============================================================
    end
        
    -- If there is a DICOM image type...
    testAttr = GetAttr(IMAGE_TYPE_TAG)    
    if (testAttr) then
        
      -- If one of the common description values (e.g. "Screen Save", "3D", etc.)
      -- appears as a substring anywhere in the image type list...
      local commonVal = BestWordMatchInAttr( testAttr, SCREEN_SAVE_VAL, THREE_D_VAL, REFORMATTED_VAL, 
                                             MIP_VAL, MPR_VAL, MIPSERIES_VAL )
      if (commonVal) then
      
          -- Remap "MIPSeries" to "MIP" and "MPR\\SCR" to "MPR"
          if (commonVal == MIPSERIES_VAL) then
            commonVal = MIP_VAL
          end
          
          -- Use that to identify the series
          return NewPropWithValue( SERIES_DESC_PROP, commonVal );
          -- ===============================================
      end
 
    end      
       
    -- Just return our default place-holder.
    return NewPropWithValue( SERIES_DESC_PROP, "?" ); 

end

function ExtractStudyDescription()
-- ////////////////////////////////////////////////////////////////////////////
-- This function was written for Suburban radiology to assist with their display
-- protocol building; owing to their heavy reliance on the contents of the study
-- description tag
-- ////////////////////////////////////////////////////////////////////////////
	-- Nothing fance, just return the contents of the tag
	return NewPropFromTag( STUDY_DESC_PROP, STUDY_DESC_TAG ); 
	
end

-- ////////////////////////////////////////////////////////////////////////////
-- Enhanced Multi-Frame Extraction Routines
-- ////////////////////////////////////////////////////////////////////////////

function ExtractSortDetailsEMF()
-- ////////////////////////////////////////////////////////////////////////////
-- This function gives a name to an EMF CT sequence
-- based on stack ID and Dimension Index Sequence Information
-- ////////////////////////////////////////////////////////////////////////////
    local seriesDetailsProp = AttrValue:new()
    -- add the dimension index pointer tag in the detalied description (descriptive purpose only)
    local dimIndexPtrAttr = GetAttr(EMF_DIMENSION_INDEX_POINTER_TAG)
    if( dimIndexPtrAttr ) then -- this could be a matrix of pairs (eeee,gggg) identifying the sorting hints 
        local nIds = 0
        if( dimIndexPtrAttr:IsMatrix() ) then 
            nIds = dimIndexPtrAttr:GetSize() -- number of pairs eeee, gggg
        else
            nIds = 1 -- here we have one pair and GetSize will find 2 items
        end
         -- for each of the ids
        if( nIds == 1 ) then
            local element = dimIndexPtrAttr:Get(1)
            local group = dimIndexPtrAttr:Get(2)
            local dictName = GetAttrDescription(group,element)            
            seriesDetailsProp:AppendVal(dictName)    
        else
            local prevElement = 0 -- get rid of duplicates, if any
            local prevGroup = 0        
            for i=1,nIds,1 do 
                local element = dimIndexPtrAttr:GetM(i,1)
                local group = dimIndexPtrAttr:GetM(i,2)
                if (prevElement ~= element or prevGroup ~= group ) then
                    local dictName = GetAttrDescription(group,element)            
                    prevElement = element 
                    prevGroup = group
                    seriesDetailsProp:AppendVal(dictName)    
                end
            end
        end
    else
        if( HasAttr(EMF_IN_STACK_NUMBER_TAG) ) then 
            local dictName = GetAttrDescription(0,EMF_IN_STACK_NUMBER_TAG)  
            seriesDetailsProp:AppendVal(dictName)    
        end
        if( HasAttr(EMF_TEMPORAL_POS_INDEX_TAG) ) then 
           local dictName = GetAttrDescription(0,EMF_TEMPORAL_POS_INDEX_TAG)  
           seriesDetailsProp:AppendVal(dictName)    
        end
    end
    
    local nSortHints = seriesDetailsProp:GetSize()
    if( nSortHints > 0 ) then
        SetAttr( EMF_SORT_DETAILS_PROP, seriesDetailsProp )
    end
    
end

function ExtractSortHintsEMF()
-- ////////////////////////////////////////////////////////////////////////////
-- This function extracts the sorting hints from dimension index values, when present
-- by cycling the priority of the sort tags defined in dimension index values 
-- ////////////////////////////////////////////////////////////////////////////
   local dimIndexValuesAttr = GetAttr(EMF_DIM_INDEX_VALUES_TAG)
   if( not dimIndexValuesAttr ) then
        return -- nothing to do when no dimension index values found 
    end
      
    local nIds = dimIndexValuesAttr:GetSize()
    if( nIds == 0 ) then
        ReportError("ExtractSortHintsEMF","Empty dimension index values ")
        return -- nothing to do when no 
    end   
    
    local shufledValues = {} -- will hold the values from the dimension index
    -- copy the original values
    for i=1,nIds,1 do 
        local value = dimIndexValuesAttr:Get(i)
        shufledValues[i] = value
    end
    -- generate one sort property by promoting the inferion values in the first position
    for i=1,nIds,1 do 
        -- generate the property
        local sortHintTag = EMF_NB_SORT_HINTS + i        
        local sortHintProp =  AttrValue:new()
        local value = shufledValues[1]
        sortHintProp:AppendVal(value)    
        SetAttr( sortHintTag, sortHintProp )

        if( nIds > 1 ) then
            -- shuffle the values 
            local tmpVal = shufledValues[1]
            for j=1,nIds-1,1 do 
                shufledValues[j] = shufledValues[j+1];
            end    
            shufledValues[nIds] = tmpVal
        end
    end
    -- generate the sort hint count
    local sortHintCount = AttrValue:new()
    sortHintCount:AppendVal(nIds)    
    SetAttr( EMF_NB_SORT_HINTS, sortHintCount )
    
end


function ExtractFrameTypeEMF()
-- ////////////////////////////////////////////////////////////////////////////
-- This function extracts the third value of the frame type attribute (0008,9007)
-- ////////////////////////////////////////////////////////////////////////////
    local frameTypeAttr = GetAttr(EMF_FRAME_TYPE_TAG)
    if( not frameTypeAttr ) then
        return -- nothing to do when no frame type is present
    end
      
    local frameTypeValues = frameTypeAttr:GetSize()
    if( frameTypeValues < 3 ) then
        return -- less than three values, no third value
    end
    
    local frameTypeStr = frameTypeAttr:Get(3)
    if( not frameTypeStr ) then
        return -- empty third value
    end
    if( string.len(frameTypeStr) == 0 ) then
        return -- empty third value
    end
    
    return NewPropWithValue(EMF_FRAME_TYPE_PROP,frameTypeStr)
     
end


-- ////////////////////////////////////////////////////////////////////////////
-- common properties extracted from every attribute collection
-- ////////////////////////////////////////////////////////////////////////////

function Execute()

    TraceC("Extract common executing...\n")
    
    DeclareTags()
    
    -- Tag our output with the script version
    NewPropWithValue( COMMON_SCRIPT_VER_PROP, "$Revision: 2 $ - $Modtime: 9/11/09 1:04p $" )
    
    -- Get modality attribute of current image
    local modalityAttr = GetAttr(MODALITY_TAG)

    -- If not a CR, DX, or XR image...
    if( not BestMatchInAttr( modalityAttr, "CR", "DX", "XR", "DR" ) ) then
       -- Extract image type
       ExtractImageType()
    end
       
    ExtractModality()
    ExtractColor()
    ExtractAcqDevice()
    
    -- If this is a CT or MR image variant...
    if( BestMatchInAttr( modalityAttr, "CT", "MR" ) ) then
        -- Extract CT/MR specific attributes
        ExtractContrast()
        ExtractView()  
        ExtractDelayed()
		ExtractSeriesUID()
		ExtractStudyDescription()
        -- ... otherwise, if this is a CR or DX or XR image variant...
    elseif( BestMatchInAttr( modalityAttr, "CR", "DX", "XR" ) ) then
        -- Extract projection x-ray specific attributes
        ExtractProjection()
        -- CB: June 9, 2005, allow view extraction for PET images, too             
    elseif( BestMatchInAttr( modalityAttr, "PT" ) ) then
        ExtractView()            
    end
	
	-- Enhanced Multi-Frame extraction routines
    local EMF_CT_CLASS_UID = "1.2.840.10008.5.1.4.1.1.2.1" 
	local EMF_MR_CLASS_UID = "1.2.840.10008.5.1.4.1.1.4.1" 
	local sopClassAttr = GetAttr(SOP_CLASS_UID_TAG)
	
	if (sopClassAttr~=nil and BestMatchInAttr(sopClassAttr, EMF_MR_CLASS_UID, EMF_CT_CLASS_UID) ) then
		TraceC("Found Enhanced CT/MR Object\n")

		ExtractSortDetailsEMF()	-- identify the sorting attributes 
		ExtractSortHintsEMF()		-- define the sorting attributes
		ExtractFrameTypeEMF()	-- derive the frame type prop as the third value of the frame type attribute		

	end
	
    ExtractThumbnailGroup()
    ExtractBodyRegion()
    ExtractFrameType()
    ExtractLaterality()
    ExtractIntent()
    ExtractPosition()
    ExtractSliceType()
    ExtractProjAngle()
    ExtractDescription()

    TraceC("Extract common execution complete.\n")
    
end

