Skip to content

Scan Configuration Files

Thomas Euler edited this page Sep 6, 2019 · 1 revision

Relevant files

  • Scan configuration files describe a scan configuration, they are Igor .ipf files and located in \WaveMetrics\Igor Pro 6 User Files\ScanM\ScM_Configs. The variables used to describe a scan configuration are explained below. Standard xy scans require no additional files.

  • Scan path function file. Scans that require more complicated scan paths (e.g. spiral scans) in the following referred to as "arbitrary scans" require an additional .ipf file that contains a defined set of standardized functions. For details, see section Arbitrary Scans below. Scan path function files live in \WaveMetrics\Igor Pro 6 User Files\User Procedures\ScanM.

General Definitions

Where applicable, the relevant corresponding variables in scan configuration files (here, e.g. for a 64x64 image scan) are given for the different definitions.

  • AI channel := analogue input channel (#0 .. #3). Typically, AI #0 and #1 are used for fluorescence (e.g. green OGB-1 and red SR101), while AI #2 records trigger signals for offline synchronization with light stimulus. AI #3 is currently unused.

  • AO channel := analogue output channel (#0 .. #3). Currently, AO #0 and #1 are used for the x and y galvo scanners, respectively, AO #2 is providing a blanking signal (to turn down the laser via the Pockels Cell during the retrace and simultaneously allow for stimulus LEDs to be on). AO 3 will be used to drive an electrically tunable lens (ETL) and, thus, the z axis (to be implemented).

  • sample := an single voltage measurement of an AI channel.

  • pixel := voltage (intensity) value for a given scan position as defined by the x-y(-z) scanner paths. A pixel represents a single sample or the average of multiple samples (if oversampling is activated). The time bin (=pixel) for intensity "collection" along the scan path is given by the targeted pixel duration (in µs), the number of measurements (=samples) that are averaged for the pixel value is given by the oversampling factor:

    cp.targetedPixelDur_us    = 25.0    // total pixel duration
    cp.nSubPixOversample      = 25      // oversampling factor
    

    Note that the smallest time unit allowed is 1 µs.

  • scan line := a line of pixels, e.g. a row in an x-y image scan. Usually defined by the path of the fastest scan axis. For "historical" reasons, we typically use lines of 2 ms duration. Each scan line consists of 3 sections (all measured in number of pixels): offset, pixel data, and retrace. They correspond to the following variables in scan configuration files (here, e.g. for a 64x64 image scan):

    cp.nXPixLineOffs          = 6       // # of no-data pixels at line start
    cp.dXDataPixels           = 64      // # of data pixels per line
    cp.nPixRetrace            = 10      // # of no-data pixels for line retrace
    

    Only the pixel data contains image information; offset and retrace are used to move the scanner to the next scan line. Offset pixels should also be used to move the scanners from their parking position to the beginning of the first scan line, whereas retrace pixels should be used at the end of a frame to move the scanners into parking position.

  • frame := multiple scan lines, e.g. an individual "image" of an xy image scan. On the scan path side, it corresponds to a full scan pattern that is repeated in case of time-lapsed recordings.

    cp.dYPixels               = 64      // # of lines per frame
    

    In principle, a frame can also contain a 3D volume (to be implemented).

  • pixel buffer := the buffer that ScanM uses to fetch pixel data during a scan for display purpose. The pixel buffer can be the size of a frame, then display is updated once per recorded frame. A frame can also be divided into smaller partitions (e.g. 1 frame = 4 pixel buffers), i.e. to display already parts of the image before the scanning of the full frame is finished. The following variable defines the number of pixel buffers per frame:

    cp.nDivFrameBuf            = 4      // # of pixel buffers used per frame
    
  • display wave := the Igor wave that represents the current frame recorded from an AI channel in the ScanM GUI. Its aspect ratio is defined by:

    cp.aspectRatioFrame        = 1.0    // Aspect ratio display wave
    
  • stimulation buffer := the buffer that contains the AO voltages traces send to the x-y scanners (AO #0,1), serves as blanking signal (AO #2), and drives, for instance, an ETL (AO #3). Usually, the stimulation buffer contains the scan path for a full frame. However, if the frames are rather small (e.g. 64x16 image scans), then writing these buffers to the NI cards can generate too much communication overhead between ScanM, the drivers and the NI hardware. To ameliorate this, the scan buffer can also contain multiple copies of the scan path for a single frame. This behavior is controlled by the following parameter:

    cp.stimBufPerFr            = 1      // # of stimulus buffers per frame
    

Other Variables Used in Scan Configuration Files

  • Description text for GUI and scan mode (currently: ScM_scanMode_XYImage or ScM_scanMode_TrajectArb):

    cp.sConfigDesc             = "64x64 image, 2ms/ln, os25"
    cp.ScanMode                = ScM_scanMode_XYImage
    
  • For scan mode = ScM_scanMode_XYImage, an internal scan path function can be used - it generates simple rectangular image scans. In this case, no scan path function name must be given:

    cp.sExtScanPathFuncName    = ""
    

    For scan mode = ScM_scanMode_TrajectArb the name of a scan path function must be given. For details, see section Arbitrary Scans below.

  • Parameters defining the AO and AI voltage range, the preselected AI channels and the number of available AI channels (up to 4). For AO channel definitions, see ScM_USER_DEFINITIONS.ipf.

    cp.minAI_V                 = -1.0   // AI voltage range minimum
    cp.maxAI_V                 = +5.0   // AI voltage range maximum
    cp.AIChannelSelect         = 0x01   // AI channel pre-selection
    cp.nAIChansPossible        = 4      // # of AI channels that can be recorded
    cp.minAO_V                 = -5.0   // AO voltage range minimum
    cp.maxAO_V                 = +5.0   // AO voltage range maximum
    
  • Park positions for the galvo scanners (AO #0,1), default level for the blanking signal (AO #2) and ...

    cp.parkScannerX_V          =  0.0    // Park positions
    cp.parkScannerY_V          =  0.0
    cp.parkBlankLaser_V        =  4.0
    
  • All four AO channels are enabled by default. To change this, the user can selectively disable AO #2 and/or #3 using the following parameters. This can be useful to safe memory, i.e. in the case of high-resolution anatomy scans that do not require a z axis.

    cp.noAOCh2_Blanking        = 0    // 0=enabled (default), 1=disabled
    cp.noAOCh3_Z               = 1
    

    EDIT: AO channel #3 needs to be activated in order to use the ETL, even when only using it to change the focal plane with the GUI during a focus scan.

  • Currently, there is no dedicated line scan mode. As a work-around, one can "switch-off" the y scanner during normal x-y scans (mode ScM_scanMode_XYImage) using the following parameter:

    cp.noYScan                 = 1    // 0=ignored, 1=switch off y scanner
    
  • Note: There are a couple of z scan-related parameters defined; these may still be objected to changes and therefore should not be used for now.

    cp.nZPixLineOffs           = 0    // # of no-data pixels at line start
    cp.nZPixRetrace            = 0    // # of no-data pixels for line retrace
    cp.dZDataPixels            = 0    // # of data pixels per line
    cp.usesZForFastScan        = 0
    

Arbitrary Scans

To define an new arbitrary scan pattern, two files must be created: a scan configuration file and a scan path function file.

1. The scan configuration file

It that describes the scan configuration and refers to a user-defined, external scan path function. The name of this Igor .ipf must be unique and reside in and located in \WaveMetrics\Igor Pro 6 User Files\ScanM\ScM_Configs. Its layout must follow that of "standard" scan configuration files.

For an example, see stim_r8_2msLn_1os.ipf, which defines a spiral scan (by Luke Rogerson) and is listed in the following. For simplicity, only the relevant sections are commented. For further explanations on the parameters, see above.

#pragma rtGlobals = 1

// ---> START OF USER SECTION

The module name must be unique and the same as the .ipf file name.

#pragma ModuleName	= stim_r8_2msLn_1os
// <--- END OF USER SECTION

// ------------------------------------------------------
static function	ScM_defineStim (sName)
   string   sName

   struct   ScM_ConfigParams cp
   ScM_initConfigParamsStruct(cp, sName)

Do not change the code outside the sections marked USER SECTION.

   // ---> START OF USER SECTION
   //
   cp.sConfigDesc          = "Radial Scan"

Set scan mode to ScM_scanMode_TrajectArb and give the name of the scan path function (here radialScan) defined in the respective scan path function file (see below).

   cp.ScanMode             = ScM_scanMode_TrajectArb
   cp.sExtScanPathFuncName = "radialScan"
   cp.aspectRatioFrame     = 1.0           // Aspect ratio display wave

The following parameters define a scan line with 1664 data pixels, followed by a 336 pixel retrace period. A frame consists of 8 scan lines and the scan path for a full frame is written in one piece to the NI card.

   cp.nXPixLineOffs        = 0             // # of no-data pixels at line start
   cp.nPixRetrace          = 336           // # of no-data pixels for line retrace
   cp.dXDataPixels         = 1664          // # of data pixels per line
   cp.dYPixels             = 8             // # of lines per frame
   cp.stimBufPerFr         = 1             // # of stimulus buffers per frame

The duration of each scan line is 2 ms, with a pixel duration of 1 µs and no over-sampling.

   cp.targetedPixelDur_us  = 1             // total pixel duration
   cp.nSubPixOversample    = 1             // oversampling factor

For displaying purposes, ScanM requests full frames.

   cp.nDivFrameBuf         = 1             // # of pixel buffers used per frame

Set the voltage range for the AI channels, pre-select AI channel #0 as a minimum requirement but allow the user to select all 4 AI channels, if needed.

   cp.minAI_V              = -1.0          // AI voltage range minimum
   cp.maxAI_V              = +5.0          // AI voltage range maximum
   cp.AIChannelSelect      = 0x01          // AI channel pre-selection
   cp.nAIChansPossible     = 4             // # of AI channels that can be recorded

Set the allowed voltage range of the AO channels.

   cp.minAO_V              = -4.0*sqrt(pi) // AO voltage range minimum
   cp.maxAO_V              = +4.0*sqrt(pi) // AO voltage range maximum

Set park positions of the scanners, the state of the blanking signal between scans, and disable AO #3 (no z axis).

   cp.parkScannerX_V       = 0.0           // Park positions
   cp.parkScannerY_V       = 0.0
   cp.parkBlankLaser_V     = 0.0
   cp.noAOCh3_Z            = 1

Here come the parameters specific for scan mode ScM_scanMode_TrajectArb. Since individual pixels may not be easily assigned to distinct positions and/or certain positions may be scanned a different number of times during a frame, a virtual frame size for decoding/display purposes is defined:

   cp.dxFrDecoded          = 32            // Frame size for decoded / reconstructed image
   cp.dyFrDecoded          = 32

Default voltage range for the scan path of a frame:

   cp.trajDefVRange_V      = 1             // default voltage range in V

Since different scan paths may need different parameters, the user can here define an arbitrary set of (max. 100) additional parameters to be used in the scan path function. As an example, here is the set of parameters for Luke's spiral scans:

   cp.nTrajParams          = 5             // 0="standard" spiral, following parameters ignored
   cp.trajParams[0]        = 8             // Number of rotated offsets
   cp.trajParams[1]        = 0.015         // Step size
   cp.trajParams[2]        = 1/7.5*pi      // Tightness of coils
   cp.trajParams[3]        = 64            // nPixels before end of spiral to start blanking
   cp.trajParams[4]        = 128           // nPixels pause in the centre before spiral start
   // <--- END OF USER SECTION

   ScM_createStim(cp)
end
// ------------------------------------------------------

2. The scan path function file

It contains a defined set of standardized functions: (a) to generate the scan path, (b) to prepare a decoder, and (c) to decode/reconstruct a frame for display.

Scan path function files are .ipf files, live in \WaveMetrics\Igor Pro 6 User Files\User Procedures\ScanM and their name must start with spf_.

For an example, see spf_Spirals.ipf, which defines the scan path and decoder functions for a spiral scan (by Luke Rogerson) and is listed in the following. For simplicity, only the relevant sections are commented in detail. Critical sections are marked. For the full example, see file.

CRITICAL - Define module name:

// ---> START OF USER SECTION
#pragma ModuleName	= spf_Spirals
// <--- END OF USER SECTION

(a) Scan path generating function

This function is called once when the configuration file is loaded and the scan path is generated. Its name must be unique in ScanM and identical to the string assigned to cp.sExtScanPathFuncName in the scan configuration file.

As input, it receives ...

  • wScanPathFuncParams[] with the main scan path parameters from the scan configuration file. For details, see below.

All input parameters are read-only - they must not be changed!!

As output, it must generate in the current data folder 4 temporary waves (float) containing the scan path voltages for all 4 AO channels for the duration of one frame. Note that even for disabled AO channels, a wave each must be generated (and set to zero). The waves need to be named as follows:

  • StimX[], AO channel 0, x scanner
  • StimY[], AO channel 1, y scanner
  • StimPC[], AO channel 2, laser blanking signal (TTL levels)
  • StimZ[], AO channel 3, z axis (i.e. ETL)

CRITICAL - The head of the first function has to look as follows:

function radialScan(wScanPathFuncParams)
    wave		wScanPathFuncParams

From here on, it is up to the user how to structure the function. In this example, first some variables are defined and parameters are retrieved from wScanPathFuncParams.

    variable	numberOfPoints, flyback, MaxAO_V, dxFrDecoded, dyFrDecoded
    variable    nOffsets, stepSize, coilFactor, earlyRetrace, pauseRetrace
    variable 	totalSize, normFactor, angle, away, itx

    variable	awayStep, theta, delta
    variable 	x_max, y_max

    // Retrieve general parameters about the scan configuration
    //
    numberOfPoints  = wScanPathFuncParams[0]  // cp.dXDataPixels, number of pixels per line (w/o retrace)
    flyback         = wScanPathFuncParams[1]  // cp.nPixRetrace, number of pixels for retrace
    totalsize       = wScanPathFuncParams[2]  // cp.trajDefaultVRange, default total voltage range
    MaxAO_V         = wScanPathFuncParams[3]  // cp.maxAO_V, maximal AO voltage
    dxFrDecoded     = wScanPathFuncParams[4]  // cp.dxFrDecoded, frame width for reconstructed/decoded frame
    dyFrDecoded     = wScanPathFuncParams[5]  // cp.dyFrDecoded, frame height for reconstructed/decoded frame

    // Retrieve scan function-specific, user-defined parameters
    // with the number of parameters defined in cp.nTrajParams and
    // the parameters in cp.trajParams[]
    //
    nOffsets        = wScanPathFuncParams[6]  // Number of rotated offsets
    stepSize        = wScanPathFuncParams[7]  // Step size
    coilFactor      = wScanPathFuncParams[8]  // Tightness of coils
    earlyRetrace    = wScanPathFuncParams[9]  // nPixels before end of spiral to start blanking
    pauseRetrace    = wScanPathFuncParams[10] // nPixels pause in the centre before spiral start

In the example file, a number variables are initialized:

    numberOfPoints += flyback
    awayStep       = 1 /(coilFactor *2 *pi)
    theta          = stepSize /coilFactor

CRITICAL - Generate the four output waves (as decribed above). In this example, NumberofPointscontaines the number of pixels in a scan line and nOffsets the number of scan lines per frame.

    make/O/N=(NumberofPoints*nOffsets) 	StimX  = 0
    make/O/N=(NumberofPoints*nOffsets) 	StimY  = 0
    make/O/N=(NumberofPoints*nOffsets) 	StimZ  = 0
    make/O/N=(NumberofPoints*nOffsets) 	StimPC = ScM_TTLhigh

In the rest of the function, these waves are filled with the scan path voltages. For details, see spf_Spirals.ipf.

CRITICAL - Make sure that you clean up temporary waves at the end of the function to reduce the risk that Igor runs out of memory.

    killWaves/z StimX1Offs, StimY1Offs, StimPC1Offs
    killWaves/z pix_x, pix_y, pix_r, pix_theta
end

(b) Decoder preparation function

This function prepares the decoding of the pixel data generated with the respective scan path function. This will be called once when the stimulus configuration is loaded, right after calling the scan path function and after copying StimX, StimY, StimZ and StimPC into the scan stimulus buffer of the scan configuration. This function can be empty if not needed; it is meant to be used to create waves and global variables (in the datafolder of that particular scan configuration) that are needed for decoding or to accelerate decoding during the scan.

This function's name must be <scan path function>_prepareDecode, in this example radialScan_prepareDecode.

As input, it receives ...

  • the scan stimulus buffer wStimBufData[nCh][]
  • the scan parameters wScanPathFuncParams (for details on content, see scan path function above).

All input parameters are read-only - they must not be changed!!

As output, the function must return one of two constants:

  • SCM_PixDataResorted if pixel data is just resorted (w/o loss of information) by the scan decoder or
  • SCM_PixDataDecoded if the reconstruction/decoding of the pixel data involves some kind of information loss.

The return value determines if ScM_FileIO.ipf retains two sets of data waves (SCM_PixDataDecoded), one with the raw and one with the decoded pixe data, or just one set (SCM_PixDataResorted).

CRITICAL - The head of this function has to look as follows:

function radialScan_prepareDecode(wStimBufData, wScanPathFuncParams)
   wave  wStimBufData, wScanPathFuncParams

From here on, it is up to the user how to structure the function. In this example, first some variables are defined and parameters are retrieved from wScanPathFuncParams.

   variable	dxFrDecoded, dyFrDecoded

   // Retrieve parameters about the scan configuration
   //
   dxFrDecoded   = wScanPathFuncParams[4]	// cp.dxFrDecoded, frame width for reconstructed/decoded frame
   dyFrDecoded   = wScanPathFuncParams[5] 	// cp.dyFrDecoded, frame height for reconstructed/decoded frame

In the example, waves are generated that later help/accelerate decoding/reconstructing the frames for display, i.e. projecting the spirals onto a 32x32 pixel frame.

CRITICAL - Again make sure that you clean up temporary waves at the end of the function to reduce the risk that Igor runs out of memory.

(c) Decoder function

This function is called frequently (for each pixel buffer and AI channel retrieved) during the scanning. Its job is to decode the pixel data on the fly and it is responsible for populating the display wave.

Note that the function should be very fast; it should not take more than a few milliseconds per call, otherwise the display will more and more lag behind the recoding. If nescessary, an external XOP function can be implemented to speed up decoding - this is, in fact, done in the example.

This function's name must be <scan path function>_decode, that is in the example radialScan_decode.

As input, it receives ...

  • the linearized display wave wImgFrame[dx*dy] for the current AI channel
  • a copy of the display wave, wImgFrameAv[dx*dy], that can be used e.g. for averaging
  • the new pixel data block wPixelDataBlock[] (contains the data for all recorded AI channels)
  • a string sCurrConfPath with the path to the current scan configuration folder in case waves need to be accessed there.
  • a numeric wave wParams, which currently contains:
    • wParams[0] - the number of AI channels recorded (1..4)
    • wParams[1] - the index of the AI channel to be decoded (0..3)
    • wParams[2] - the line offset (in pixels)
    • wParams[3] - the length of a frame (in pixels)
    • wParams[4] - the length of a pixel data block per channel (in pixels)
    • wParams[5] - the number of frames per step (only used for z-stack averaging)
    • wParams[6] - 1=only complete frames are shown, 0=pixel blocks are shown as they arrive

As output, the display wave wImgFrame[] (see input parameters) must be populated.

CRITICAL - The head of this function has to look as follows:

function radialScan_decode(wImgFrame, wImgFrameAv, wPixelDataBlock, sCurrConfPath, wParams)
   wave   wImgFrame, wImgFrameAv, wPixelDataBlock
   string sCurrConfPath
   wave   wParams

In the example, a number of local variables are defined:

   variable  dxFrDecoded, dyFrDecoded, nAICh, iAICh
   variable  dataLength, start, stop
   variable  nPoints, nOffsets, offset_pixel, data_pixels
   variable	 flyback

sCurrConfPath can be employed to get access to waves and globale variables in the current scan configuration data folder, if needed.

   wave pCountMatrix         = $(sCurrConfPath +"countMatrix")
   wave pwStimBufData        = $(sCurrConfPath +"wStimBufData")
   wave pwScanPathFuncParams = $(sCurrConfPath +"wScanPathFuncParams")

Retrieve the required parameters about the scan configuration etc.

   data_pixels  = pwScanPathFuncParams[0]  // cp.dXDataPixels, number of pixels per line (w/o retrace)
   flyback      = pwScanPathFuncParams[1]  // cp.nPixRetrace, number of pixels for retrace
   dxFrDecoded  = pwScanPathFuncParams[4]  // cp.dxFrDecoded, frame width for reconstructed/decoded frame
   dyFrDecoded  = pwScanPathFuncParams[5]  // cp.dyFrDecoded, frame height for reconstructed/decoded frame
   nOffsets     = pwScanPathFuncParams[6]  // Number of rotated offsets
   offset_pixel = pwScanPathFuncParams[9]  // nPixels before end of spiral to start blanking

To interpret the pixel buffer, the number of recorded AI channels and the index of the AI channel to reconstruct/decode is needed:

   nAICh        = wParams[0]              // number of AI channels recorded (1..4)
   iAICh        = wParams[1]              // index of AI channel to reconstruct (0..3)

From here on, the script starts decoding the pixel buffer to reconstruct the current frame for display. For details, see spf_Spirals.ipf.

Note that spf_Spirals.ipf uses an external function ( ScanDecoder(...)) from an XOP. The function was written in C++ (by Luke Rogerson) and compiled into an XOP.