--[[
         This script identifies the image properties of computed radiography (CR) images
         Specific properties extracted are:
	
		- Projection: Based primarily on View Position 
		- Image Description: built from many tags
		- Laterality: standard tag or failing over to embedded text
		- Image Type: standard
     Allan Noordvyk,     August, 2004,        CREATED and 
     -	implemented the above described functionality
     Cristian Bonciu, September 27, 2004,        MODIFIED
     -	Moved common tag definitions in ExtractCommon.tls
     -	Called RegisterProperties for CR_PROJECTION_PROP
     -	Assigned range of IDs for CR properties (1600 to 16FF)
     -	Added 'then' to 'if' statements 
     -	Replaced attr.Get1(x) with attr:Get(x) 
     -	Replaced attr.AddValue with attr:AppendVal
     -	Replaced SmartAppendWords with MergeStrings
     -	Re-coded the parts confusing an attribute with its string value
     -	Removed sparsely used ';' for consistency of the style
     -	Removed the call to ExtractLaterality, since no implementation was found for the function
     -	Other minor syntax and variable names corrections
	-	Notes: PROJECTION_PROP from common is overwritten without checking its existence 
				CR_PROJECTION_PROP is registered as property but no extraction code is available 
     Allan Noordvyk,     October 11, 2004
     -	Added check of Acquisition Device Processing tag for projection information
	-	Removed commented out obsolete code.
     -	Added LL and LR as recognized projection values (n.b. "LAT" is still accepted).
     Allan Noordvyk,     April 27, 2005
     -	Added support for Philips CR devices found at Mercy Hospital, Iowa City, which
		put image description information in private element, rather in than in standard
		tags.   Also, added recognition of their variant of RLO, LLO, RLD, and LLD.
     Allan Noordvyk,     Sept 27, 2005
     -	Added detection of "OBL" word as short-hand for "Oblique" projection.
     Allan Noordvyk,     Oct, 1, 2005
     -	Added script version property setting.
     Allan Noordvyk      Oct 6, 2005
     -	The changes below are related to 
          STRQ1557 Sequencing Concerns for SIEMENS CR SIEMENS FD-X VA20L
		as well as other issues observed in situ at Gwinnett Medical
     -	Added "NAVICULAR" as alias for "OBLIQUE"
     -	Added use of script for scanned images labeled as "XR" modality
     Allan Noordvyk      Oct 18 2005
     -	Added ExtractPositionCR function to handle populating new Position
		property when ExtractCommon has no luck doing so (CRDB00031302)
     Allan Noordvyk      Oct 24 2005
     -	Added check of ACQ Protocol Name for projection information based on
          Minolta Konica device encountered at Kosciusko
     -	Added Axial, Odontoid, Waters, and Swimmers as recognized projections. 
     Mahesh Nagarajan    25 OCT 2005
     -	Looked for the strings "Port" and "Portable" and interpreted these as
		AP projection info
     Allan Noordvyk      26 Oct 2005
     -	With changes to ExtractCommon related to expanded CR projection related position
          values described in HRS11-STRQ1604, now include position in built series descriptions
     -	Note that this means the Axial, Odontoid, Waters, and Swimmers are no longer
          treated as pure projections, but as positions.
     Mahesh Nagarajan	30 OCT 2005
	-	Added "Navicul" as an alias for "Navicular" used in Prince William Hospital
     Mahesh Nagarajan    01 NOV 2005
     -	Added definitions for the smaller case long forms of AP(antero-posterior), PA(postero-anterior) 
          and Lat (lateral) as Mercy Chicago inserts these values in ACQ Device Processing Description
     Allan Noordvyk      13 NOV 2005
     -	Add Projection word search in WKL Scheduled Procedure Step
     Mahesh Nagarajan    01 DEC 2005
     -	Added "a.p." and "p.a." as aliases fro AP and PA projections respectively
     Mahesh Nagarajan    10 JAN 2006
     -	Added check for projection types in REL Comments tag for Philips devices only
     Brani Miklos           27 JAN 2006
     -	Removed search for projection types in ScheduledProcedure Step Description after
		retrieving incorrect projection values when the description is too general. May re-visit
	this later and allow searches in this tag for certain devices alone
     -	Added search for string "KUB" in DICOM tag (0018, 1400) ACQ Device Processing Description;
		this is remapped to AP projection type
     Mahesh Nagarajan   28 MAR 2006
     -	Introducing the concept of thumbnail grouping in CRs to ensure that CAD images go to the very end
     -	Created new CR-specific property "CAD Image" - set to 'yes' for CAD images and 'no' for others
     Mahesh Nagarajan   06 JULY 2006
     -	Congratulations to Michael Lai, the new sequencing guy!! Two days on the job and he spots his first
		bug in ExtractCR.tls where a certain GetAttr() function call takes two parameters instead of one.
	Mahesh Nagarajan   17 DEC 2006
	-	Signing in for Shudong owing to time constraints; taking one step towards eliminating the use of the "?" 
		for series description; trying to use modality+frame type instead.
-]]		

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

function RegisterProperties()
-- ////////////////////////////////////////////////////////////////////////////
--- This function is called automatically at load of script to register any
--- modality specific properties.  Prior to IP Library build 39 it must be 
--- present even if empty.
--- Modality specific property IDs (3711,1601)- (3711,16FF) range
-- /////////////////////////////////////////////////////////////////////////////

    -- CAD_IMG_PROP indicates the diffusion coefficient (if any) employed to acquire the MR image.
    --
    -- Standard Values:	        Yes, No
    -- Usage in DP:	        Common
    -- Related DICOM Tags:	ID Institution Name (0008, 0080)
               
    Register("CAD_IMG_PROP", "1601", "string", "Yes,No", "CAD Image",{1})

end

-- ////////////////////////////////////////////////////////////////////////////
-- string definitions 
-- ////////////////////////////////////////////////////////////////////////////
    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
    
    LATERAL_VAL = "Lateral" -- long form of Lat
    
    AP_LONG_SC_VAL = "antero-posterior"         -- long form of AP in small case
    PA_LONG_SC_VAL = "postero-anterior"         -- long form of PA in small case
    OBLIQUE_SC_VAL = "oblique"                  -- Oblique but in lower case

    DOTTED_AP_VAL = "a.p."
    DOTTED_PA_VAL = "p.a."

    LT_OBLIQUE_VAL = "Lt Oblique"         -- long variation of LLO
    RT_OBLIQUE_VAL = "Rt Oblique"         -- long variation of RLO
    LT_DECUBITUS_VAL = "Lt Decubitus"     -- long variation of LLD
    RT_DECUBITUS_VAL = "Rt Decubitus"     -- long variation of RLD
    
    NAVICULAR_VAL = "Navicular" -- a kind of oblique view
    NAVICUL_VAL = "Navicul" -- Shoter version of "Navicular"
    
    OBL_VAL = "Obl"             -- Abbreviation of Oblique (w/o L or R indication)
    PORT_VAL = "Port"           -- Can assume indicator of AP view
    PORTABLE_VAL = "Portable"   -- As above.
    
    KUB_VAL = "KUB"	-- Can be interpreted as AP projection type
    
-- ////////////////////////////////////////////////////////////////////////////
-- CR property extraction functions 
-- ////////////////////////////////////////////////////////////////////////////

-- ////////////////////////////////////////////////////////////////////////////
function ExtractProjectionCR()
	-- This function sets the Projection property if the image based
	-- on an examination of the header values (if possible) and returns
	-- the string value assigned to this property.
	--
	-- If there wasn't a recognizable CR Projection value, no property
	-- is set and nil is returned.
	--
	-- Note that we can't just use the standard ExtractProjection function
	-- from ExtractCommon, because CR doesn't use Sagital/Coronal/Axial
	-- and because many of the CR vendors embed the projection information
	-- some where non-standard (and often in violation of the DICOM standard).
        --
	-- Essentially, we are looking for indication of "PA", "AP", or "Lat"
        --
    -- CB: September 27, 2004, PROJECTION_PROP will be overwritten by this implementation
    -- even when it is generated by the common script
-- ////////////////////////////////////////////////////////////////////////////

    local sValue = nil
	-- This will be set to non-nil if we find a 
	-- recognizable projection in the header somewhere.
    local rawAttr = nil
	-- A raw DICOM attribute value to be tested
        
    -- If the common function was already successful finding a projection value...
    local existingProp = GetAttr( PROJECTION_PROP )
    if (existingProp) then
          TraceC( "Well-behaved device: No need for special CR projection logic.\n" )
          return existingProp
          --==============================================
    end
    
    TraceC( "Ill-behaved device: Hunting down projection with special CR logic.\n" )
    
    	-- If we haven't found a value yet and this is a Philips device...
	if (sValue == nil and ValueForTag(DEVICE_MANUFACTURER_TAG) == "Philips Medical Systems") then
        
        	-- Some Phillips CR units put this information in a private element.
                local PHILIPS_PRIVATE_DESC_TAG = maketag( "0019", "1960" )

		-- So we try to dig out a value there.
		rawAttr = GetAttr( PHILIPS_PRIVATE_DESC_TAG )
		if (rawAttr) then
			-- Match it to the closest CR projection value
			-- (if possible, otherwise nil)
			sValue = BestMatchInAttr( rawAttr, PA_VAL, AP_VAL, LLD_VAL, RLD_VAL, LLO_VAL, 
                                                  RLO_VAL, LATERAL_VAL, LAT_VAL, LL_VAL, RL_VAL,
                                                  LT_OBLIQUE_VAL, RT_OBLIQUE_VAL, 
                                                  LT_DECUBITUS_VAL, RT_DECUBITUS_VAL, OBLIQUE_VAL, OBL_VAL,
                                                  NAVICULAR_VAL, NAVICUL_VAL, PORT_VAL, PORTABLE_VAL,
                                                  DOTTED_AP_VAL, DOTTED_PA_VAL )
			-- (note that we ignore this string if we didn't find a
			--  good match, unlike VIEW_POSITION_TAG above, since it
			-- could be anything, not necessarily projection related).
		end
                
                -- Mahesh Nagarajan, 10 JAN 2006: A quick check for projection types in (0020, 4000) 
                -- REL Image Comments tag
                if (sValue == nil) then
                       rawAttr = GetAttr( IMAGE_COMMENTS_TAG )
                        if (rawAttr) then
                              -- Match it to the closest CR projection value
                              -- (if possible, otherwise nil)
                              sValue = BestWordMatchInAttr( rawAttr, PA_VAL, AP_VAL, LLD_VAL, RLD_VAL, LLO_VAL, 
                                                  RLO_VAL, LATERAL_VAL, LAT_VAL, LL_VAL, RL_VAL, LT_OBLIQUE_VAL, 
                                                  RT_OBLIQUE_VAL, LT_DECUBITUS_VAL, RT_DECUBITUS_VAL, OBLIQUE_VAL, 
                                                  OBL_VAL, NAVICULAR_VAL, NAVICUL_VAL, PORT_VAL, PORTABLE_VAL,
                                                  DOTTED_AP_VAL, DOTTED_PA_VAL )
                        end
                end
                
	end
	
	-- If we haven't found a value yet ...
	if (sValue == nil) then
 		-- Some CR units actually fill in the series description and these
		-- may have the body part somewhere in that string (e.g. "Lat Chest").
		-- So we try to dig out a value there.
		rawAttr = GetAttr( SERIES_DESC_TAG )
		if ( rawAttr ) then
			-- Match it to the closest CR projection value
			-- (if possible, otherwise nil)
			sValue = BestWordMatchInAttr( rawAttr, PA_VAL, AP_VAL, LLD_VAL,  RLD_VAL, LLO_VAL, 
                                                      RLO_VAL, LATERAL_VAL, LAT_VAL, LL_VAL, RL_VAL,
                                                      LT_OBLIQUE_VAL, RT_OBLIQUE_VAL, 
                                                      LT_DECUBITUS_VAL, RT_DECUBITUS_VAL, OBLIQUE_VAL, OBL_VAL,
                                                      NAVICULAR_VAL, NAVICUL_VAL, PORT_VAL, PORTABLE_VAL,
                                                      DOTTED_AP_VAL, DOTTED_PA_VAL)
			-- (note that we ignore this string if we didn't find a
			--  good match, unlike VIEW_POSITION_TAG above, since it
			-- could be anything, not necessarily projection related).
		end
	end

	-- If we haven't found a value yet ...
	if (sValue == nil) then
		-- Some CR units include the projection information in the
		-- description of the processing function used.  So we
		-- will try to dig out a value there.
                -- Dejarnette uses "Port" or "Portable" to indicate PA projection
		rawAttr = GetAttr( ACQ_DEV_PROCESS_TAG )
		if (rawAttr) then
			-- Match it to the closest CR projection value
			-- (if possible, otherwise nil)
			sValue = BestWordMatchInAttr( rawAttr, PA_VAL, AP_VAL, LLD_VAL, RLD_VAL, LLO_VAL, 
                                                      RLO_VAL, LATERAL_VAL, LAT_VAL, LL_VAL, RL_VAL,
                                                      LT_OBLIQUE_VAL, RT_OBLIQUE_VAL, LT_DECUBITUS_VAL, 
                                                      RT_DECUBITUS_VAL, OBLIQUE_VAL, OBL_VAL, NAVICULAR_VAL, 
                                                      NAVICUL_VAL, PORT_VAL, PORTABLE_VAL, AP_LONG_SC_VAL, 
                                                      PA_LONG_SC_VAL, OBLIQUE_SC_VAL, DOTTED_AP_VAL, DOTTED_PA_VAL )
			-- (note that we ignore this string if we didn't find a
			--  good match, unlike VIEW_POSITION_TAG above, since it
			-- could be anything, not necessarily projection related).
		end
	end
	
	-- If we haven't found a value yet ...
	if (sValue == nil) then
		-- Some CR units include the projection information in the
		-- description of the processing function used.  So we
		-- will try to dig out a value there.
                -- Dejarnette uses "Port" or "Portable" to indicate PA projection
		rawAttr = GetAttr( BODY_PART_EX_TAG )
		if (rawAttr) then
			-- Match it to the closest CR projection value
			-- (if possible, otherwise nil)
			sValue = BestWordMatchInAttr( rawAttr, PA_VAL, AP_VAL, LLD_VAL, RLD_VAL, LLO_VAL, 
                                                      RLO_VAL, LATERAL_VAL, LAT_VAL, LL_VAL, RL_VAL,
                                                      LT_OBLIQUE_VAL, RT_OBLIQUE_VAL, LT_DECUBITUS_VAL, 
                                                      RT_DECUBITUS_VAL, OBLIQUE_VAL, OBL_VAL, NAVICULAR_VAL, 
                                                      NAVICUL_VAL, PORT_VAL, PORTABLE_VAL, AP_LONG_SC_VAL, 
                                                      PA_LONG_SC_VAL, OBLIQUE_SC_VAL, DOTTED_AP_VAL, DOTTED_PA_VAL )
			-- (note that we ignore this string if we didn't find a
			--  good match, unlike VIEW_POSITION_TAG above, since it
			-- could be anything, not necessarily projection related).
		end
	end
	
	-- If we haven't found a value yet ...
	if (sValue == nil) then
		-- Some CR units include the projection information in the
		-- description of the acquisiton protocol used.  So we
		-- will try to dig out a value there.
		rawAttr = GetAttr( ACQ_PROTOCOL_TAG )
		if (rawAttr) then
			-- Match it to the closest CR projection value
			-- (if possible, otherwise nil)
			sValue = BestWordMatchInAttr( rawAttr, PA_VAL, AP_VAL, LLD_VAL, RLD_VAL, LLO_VAL, 
                                                      RLO_VAL, LATERAL_VAL, LAT_VAL, LL_VAL, RL_VAL,
                                                      LT_OBLIQUE_VAL, RT_OBLIQUE_VAL, 
                                                      LT_DECUBITUS_VAL, RT_DECUBITUS_VAL, OBLIQUE_VAL, OBL_VAL,
                                                      NAVICULAR_VAL, NAVICUL_VAL, PORT_VAL, PORTABLE_VAL,
                                                      DOTTED_AP_VAL, DOTTED_PA_VAL )
			-- (note that we ignore this string if we didn't find a
			--  good match, unlike VIEW_POSITION_TAG above, since it
			-- could be anything, not necessarily projection related).
		end
	end        

--[[bmiklos 05-Dec-2005
	This must have been commented out as sites in Abilene and Brownwood
	is putting there general description of a procedure which
	contains all requested projections
	-- If we haven't found a value yet ...
	if (sValue == nil) then
		-- Some CR units will only include the information in the scheduled
          -- procedure step tag
		rawAttr = GetAttr( SCHED_PROC_TAG )
		if (rawAttr) then
			-- Match it to the closest CR projection value
			-- (if possible, otherwise nil)
			sValue = BestWordMatchInAttr( rawAttr, PA_VAL, AP_VAL, LLD_VAL, RLD_VAL, LLO_VAL, 
                                                      RLO_VAL, LATERAL_VAL, LAT_VAL, LL_VAL, RL_VAL,
                                                      LT_OBLIQUE_VAL, RT_OBLIQUE_VAL, 
                                                      LT_DECUBITUS_VAL, RT_DECUBITUS_VAL, OBLIQUE_VAL, OBL_VAL,
                                                      NAVICULAR_VAL, NAVICUL_VAL, PORT_VAL, PORTABLE_VAL )
			-- (note that we ignore this string if we didn't find a
			--  good match, unlike VIEW_POSITION_TAG above, since it
			-- could be anything, not necessarily projection related).
		end
	end]]
        
    -- If we haven't found a value yet ...
	if (sValue == nil) then
		-- Some CR units have distinct study descriptions per image 
		-- in the same study (pretty stupid, eh!) and will stash
		-- the projection somewhere in that string (e.g. "Lat Chest").
		-- So we try to dig out a value there.
		rawAttr = GetAttr( STUDY_DESC_TAG )
		if (rawAttr) then
			-- Match it to the closest CR projection value
			-- (if possible, otherwise nil)
			sValue = BestWordMatchInAttr( rawAttr, PA_VAL, AP_VAL, LLD_VAL, RLD_VAL, LLO_VAL, 
                                                      RLO_VAL, LATERAL_VAL, LAT_VAL, LL_VAL, RL_VAL,
                                                      LT_OBLIQUE_VAL, RT_OBLIQUE_VAL, 
                                                      LT_DECUBITUS_VAL, RT_DECUBITUS_VAL, OBLIQUE_VAL, OBL_VAL,
                                                      NAVICULAR_VAL, NAVICUL_VAL, PORT_VAL, PORTABLE_VAL,
                                                      DOTTED_AP_VAL, DOTTED_PA_VAL )
			-- (note that we ignore this string if we didn't find a
			--  good match, unlike VIEW_POSITION_TAG above, since it
			-- could be anything, not necessarily projection related).
		end
	end

	-- Brani's one last check for projection type in (0018, 1400) ACQ Acquisition Processing Device
	-- Description for the string "KUB"; this usually denotes AP projection
	if (sValue == nil) then
		rawAttr = GetAttr( ACQ_DEV_PROCESS_TAG )
		if (rawAttr) then
			sValue = BestWordMatchInAttr( rawAttr, KUB_VAL )
		end
	end 
	
	-- If we found a projection string matching long form of LATERAL...
	if( sValue == LATERAL_VAL ) then
            sValue = LAT_VAL    -- force to standard value
	end
        
        -- If we found a projection string matching long form of LLO...
	if( sValue == LT_OBLIQUE_VAL) then
            sValue = LLO_VAL    -- force to standard value
	end
        
        -- If we found a projection string matching long form of RLO...
	if( sValue == RT_OBLIQUE_VAL) then
            sValue = RLO_VAL    -- force to standard value
	end
        
        -- If we found a projection string matching long form of LLD...
	if( sValue == LT_DECUBITUS_VAL) then
            sValue = LLD_VAL    -- force to standard value
	end
        
        -- If we found a projection string matching long form of RLD...
	if( sValue == RT_DECUBITUS_VAL) then
            sValue = RLD_VAL    -- force to standard value
	end
        
        -- If we found a projection string matching alternate form of Oblique...
	if( sValue == OBL_VAL or sValue == NAVICULAR_VAL or sValue == NAVICUL_VAL) then
            sValue = OBLIQUE_VAL    -- force to standard value
	end
  
        -- If we found a projection string matching portable, we can assume AP as
        -- as patient immobility is the usual reason to do a portable...
	if( sValue == PORT_VAL or sValue == PORTABLE_VAL or sValue == AP_LONG_SC_VAL or sValue == DOTTED_AP_VAL) then
            sValue = AP_VAL    -- force to standard value
	end        

        -- If we found a projection string matching KUB (kidney,uterus,bladder), we can assume AP
	if( sValue == KUB_VAL) then
            sValue = AP_VAL    -- force to standard value
	end        	
	
        -- If we found a projection string matching alternate form of PA...
	if( sValue == PA_LONG_SC_VAL or sValue == DOTTED_PA_VAL) then
            sValue = PA_VAL    -- force to standard value
	end        
        
	-- Return new property with value matching string found abouve (or nil if none).
	return NewPropWithValue( PROJECTION_PROP, sValue )

end


-- ////////////////////////////////////////////////////////////////////////////
function ExtractBodyRegionCR() 
	-- This function sets the Body Region property if the image based
	-- on an examination of the header values (if possible) and returns
	-- the string value assigned to this property.
	--
	-- If there wasn't a recognizable Body Region value, no property
	-- is set and nil is returned.
-- ////////////////////////////////////////////////////////////////////////////
	local sValue = nil
			-- This string will be set to non-nil if we find a 
			-- recognizable body region in the header somewhere.
	local rawAttr = nil
			-- A raw DICOM attribute whose value is to be tested
	local aProp = nil
			-- Property to be returned.

	-- See if the common body region is already present 
	commonBodyRegionProp = GetAttr(BODY_REGION_PROP)
	if (commonBodyRegionProp) then
                TraceC( "Common script found body region... skipping special CR logic.\n" )
		-- No need to do special handling.  This must be one
		-- of the minority of CR units that bothers to use the
		-- standard tag for this.
		return commonBodyRegionProp
	end

        TraceC( "No good body region found by common script... using special CR logic.\n" )      

	-- Unfortunately, CR vendor coders are typically very sloppy with
	-- respect to DICOM conformance.
	-- So images from most CR units will end up here, digging through
	-- other tags for keywords that look like body regions.

 	-- Some CR units actually fill in the series description and
	-- these may have the body part somewhere in that string (e.g. "Lat Chest").
	-- So we try to dig out a value there.

	rawAttr = GetAttr( SERIES_DESC_TAG )
	if ( rawAttr ~= nil) then
		-- Match it to the closest body region defined for the site
		-- (if possible, otherwise nil)
		sValue = BestMatchForBodyRegion( rawAttr )
		-- (note that we ignore this string if we didn't find a
		--  good match, unlike BODY_PART_EX_TAG above, since it
		-- could be anything, not necessarily body region related).
	end
	
	-- If we haven't found a value yet ...
	if ( sValue == nil) then
		-- Some CR units have processing algorithm names that indicate
		-- the body part being imaged (e.g. "CLAVICLE/AC JOINTS").
		-- So we try to dig out a value there.
		rawAttr = GetAttr( ACQ_DEV_PROCESS_TAG )
		if ( rawAttr ) then
			-- Match it to the closest body region defined for the site
			-- (if possible, otherwise nil)
			sValue = BestMatchForBodyRegion( rawAttr )
			-- (note that we ignore this string if we didn't find a
			--  good match, unlike BODY_PART_EX_TAG above, since it
			-- could be anything, not necessarily body region related).
		end
	end		
	
	-- If we haven't found a value yet ...
	if ( sValue == nil) then
		-- Some CR units have distinct study descriptions per image 
		-- in the same study (pretty stupid, eh!) and will stash
		-- the body part somewhere in that string (e.g. "Lat Chest").
		-- So we try to dig out a value there.
		rawAttr = GetAttr( STUDY_DESC_TAG )
		if ( rawAttr ) then

			-- Match it to the closest body region defined for the site
			-- (if possible, otherwise nil)
			sValue = BestMatchForBodyRegion( rawAttr )
			-- (note that we ignore this string if we didn't find a
			--  good match, unlike BODY_PART_EX_TAG above, since it
			-- could be anything, not necessarily body region related).
		end
	end

        -- Return new property with matching string from above (or return nil if no match)
        return NewPropWithValue( BODY_REGION_PROP, sValue )

end

function ExtractDescriptionCR() 
-- ////////////////////////////////////////////////////////////////////////////
	-- This function sets the image description property of the image based
	-- on an examination of the header values and previously derived image
	-- properties.  The set property is returned.
	--
	-- This method will always return some kind of property.  If it can't
	-- come up with anything else, it will use a question mark for the
	-- image description just so that we have something to show in the UI.
	--
	-- IMPORTANT: As this function makes use of derived properties, it should
	-- be called LAST, after all other extraction functions have been run.
-- ///////////////////////////////////////////////////////////////////////////

    -- If the common function was already successful finding a good description value...
    local seriesDescProp = GetAttr( SERIES_DESC_PROP )
    if (seriesDescProp) then
        -- If it isn't just the special no-info placeholder...
        if (seriesDescProp:Get(1) ~= "?") then
            TraceC( "Common script found description... skipping special CR logic.\n" )
            return seriesDescProp;
            --==============================================
        end
    end
    
    TraceC( "No good description found by common script... using special CR logic.\n" )  


    -- If this is a Philips device...
    if (ValueForTag(DEVICE_MANUFACTURER_TAG) == "Philips Medical Systems") then
        
          -- Some Phillips CR units put this information in a private element.
          local PHILIPS_PRIVATE_DESC_TAG = maketag( "0019", "1960" )
          
          -- If this tag is present with a value...
          local privValue = ValueForTag( PHILIPS_PRIVATE_DESC_TAG )
          if (privValue ~= nil) then
              -- Just use it directly
              return NewPropWithValue( SERIES_DESC_PROP, privValue )
              -- ==============================================
          end
          
    end
  
    -- Try to build a descriptive sentence by using derived properties
    local seriesDescStr
    seriesDescStr = SmartAppendWords( ValueForTag(PROJECTION_PROP), " ", ValueForTag(LATERALITY_PROP) )
    seriesDescStr = SmartAppendWords( seriesDescStr, " ", ValueForTag(BODY_REGION_PROP) )
    seriesDescStr = SmartAppendWords( seriesDescStr, " ", ValueForTag(POSITION_PROP) )

      
    -- If we have an image type other than boring old "Original"...
    local imageTypeStr = ValueForTag(IMAGE_TYPE_PROP) 
    if (imageTypeStr and imageTypeStr ~= "Original" and imageTypeStr ~= "ORIGINAL") then 
        -- Append non-boring image type to custom series description
        seriesDescStr = SmartAppendWords( seriesDescStr, " ", imageTypeStr )
    end
        
	if (not seriesDescStr or string.len(seriesDescStr) == 0) then
        -- Nothing useful found; using a modality+frame type instead
  		local modalityType = ValueForTag(MODALITY_PROP)
		local frameType = ValueForTag(FRAME_TYPE_PROP)
		seriesDescStr = SmartAppendWords(modalityType," ",frameType)
    end	
		
    -- If we had no success above...
    if (not seriesDescStr or string.len(seriesDescStr) == 0) then
        TraceC( "No components available for building series description.\n" )
        seriesDescStr = "?"
    end
       
    return NewPropWithValue( SERIES_DESC_PROP, seriesDescStr )

end

function ExtractModalityCR()
-- ////////////////////////////////////////////////////////////////////////////
-- This function extracts the modality of the image.  We over-ride what
-- the generic extract common function does, because OT's frequently are
-- really CR's that were routed through an intermediary (e.g. MIMS) and thus
-- have been configured to go through this script.
-- ////////////////////////////////////////////////////////////////////////////
    
    return NewPropWithValue( MODALITY_PROP, "CR" )

end

function ExtractThumbnailGroupCR()
-- ////////////////////////////////////////////////////////////////////////////
-- This function assigns a thumbnail group value to al CR images. All images are assigned 
-- "01" while CAD images are assigned "06"
-- ///////////////////////////////////////////////////////////////////////////
    local grpValue = "01"
    local isCAD = nil
	
    -- Currently, the only indicator of CAD images is that the manufacturer tag 
    -- would have the value "R2 Technology".
    RTWO_TECH_VAL = "R2 Technology"
    local manufacturerTag = GetAttr(DEVICE_MANUFACTURER_TAG)
    if (manufacturerTag) then
	    isCAD = BestMatchInAttr(manufacturerTag, RTWO_TECH_VAL)
	    if (isCAD) then
		    grpValue = "06"
	    end
    end
	
    return NewPropWithValue(THUMBNAIL_GRP_PROP, grpValue)
end

function ExtractCADImageProp()
-- ////////////////////////////////////////////////////////////////////////////
-- This function asssigns values for the property "CAD Image"; yes if the CR image has 
-- CAD and no otherwise.
-- ///////////////////////////////////////////////////////////////////////////
    local propValue = "No"
    local isCAD = nil
	
    -- Currently, the only indicator of CAD images is that the manufacturer tag 
    -- would have the value "R2 Technology".
    RTWO_TECH_VAL = "R2 Technology"
    local manufacturerTag = GetAttr(DEVICE_MANUFACTURER_TAG)
    if (manufacturerTag) then
	    isCAD = BestMatchInAttr(manufacturerTag, RTWO_TECH_VAL)
	    if (isCAD) then
		    propValue = "Yes"
	    end
    end
	
    return NewPropWithValue(CAD_IMG_PROP, propValue)
end

function ExtractPositionCR() 
-- ////////////////////////////////////////////////////////////////////////////
-- This function extracts a position property value if one was not
-- found by the common script.
-- ///////////////////////////////////////////////////////////////////////////

    -- If the common function was already successful finding a good description value...
    local positionProp = GetAttr( POSITION_PROP )
    if (positionProp) then

        TraceC( "Common script found position... skipping special CR logic.\n" )
        return positionProp;
        --==============================================

    end
    
    TraceC( "No good position found by common script... using special CR logic.\n" )  

    -- If this is a Philips device...
    if (ValueForTag(DEVICE_MANUFACTURER_TAG) == "Philips Medical Systems") then
        
        -- Some Phillips CR units put this information in a private element.
        local PHILIPS_PRIVATE_DESC_TAG = maketag( "0019", "1960" )
          
        local anAttr = GetAttr(PHILIPS_PRIVATE_DESC_TAG)
        if (anAttr) then
    
            -- Look for keywords within the sspecial tag
            local matchStr = BestWordMatchInAttr( anAttr, SUPINE_VAL, PRONE_VAL, UPRIGHT_VAL, 
                                                  STANDING_VAL, WEIGHT_BEARING_VAL, FLEXION_VAL,
                                                  EXTENSION_VAL )
        
            if (matchStr ~= nil) then
                return NewPropWithValue( POSITION_PROP, matchStr )
                --- ==================================================
            end
        
        end
          
    end
    
    -- No other tricks so just give up.
    return nil
  
end

-- ////////////////////////////////////////////////////////////////////////////
-- extract CR method
-- ////////////////////////////////////////////////////////////////////////////

function Execute()

    TraceC("Extract CR executing...\n")

    -- No modality check since also used for scanned films and other "modalities"

    -- Tag our output with the script version
    NewPropWithValue( MODALITY_SCRIPT_VER_PROP, "$Revision: 1 $ - $Modtime: 12/24/07 10:46a $ - CR" )
    	
    -- Set assorted priorties appropriate to CR
    local modalityProp = ExtractModalityCR()
    local projProp = ExtractProjectionCR()
    local bodyRegionProp = ExtractBodyRegionCR()
    local positionProp = ExtractPositionCR()
    local thumbnailGroupProp = ExtractThumbnailGroupCR()
    local CADimgProp = ExtractCADImageProp()
	
    -- Cristian Bonciu, September 27, 2004, commented out,since it is not defined
    -- local laterality = ExtractLaterality()
	-- Build description last since it depends on other properties discovered / overwritten above.
    local seriesDescProp = ExtractDescriptionCR()

    TraceC("Extract CR complete.\n")
    --==============================================
end
