//********************************************************************************************/
//
// File name:  MultipleDatamatrixCodeRead.cpp
// Location:  ...\Matrox Imaging\MILxxx\Examples\Processing\Code\MultipleDatamatrixCodeRead\C++
//             
//
// Synopsis:   This example shows three techniques to read several datamatrix codes in an image:
//
//             1- Performing a single reading in the whole image.
//
//             2- Performing multiple sequential readings in regions of interest (ROIs).
//
//             3- Performing multiple parallel readings in regions of interest (ROIs).
//
// In order to get meaningful timing benchmarks, this application must be compiled in 'Release' mode 
//
// Copyright (C) Matrox Electronic Systems Ltd., 1992-2015.
// All Rights Reserved

#include <mil.h>

// Example image path.  
#define  EXAMPLE_IMAGE_PATH   M_IMAGE_PATH MIL_TEXT("MultipleDatamatrixCodeRead/")
// Source image file name. 
#define  IMAGE_FILE           EXAMPLE_IMAGE_PATH MIL_TEXT("MultipleDatamatrix.mim")
// Expected maximum size of any string.
#define  MAXIMUM_STRING_SIZE  100
// Offset of the text in the MIL display.
#define  TEXT_OFFSET_X        -150 
#define  TEXT_OFFSET_Y_1      -70 
#define  TEXT_OFFSET_Y_2      -40 
// Timing loop iterations.
#define NB_LOOP               4

// Example functions declarations.
void SingleReadingExample(MIL_ID MilSystem, MIL_ID MilCodeContext, MIL_ID MilImage, MIL_ID MilDisplay);
void SequentialReadingsExample(MIL_ID MilSystem, MIL_ID MilCodeContext, MIL_ID MilImage, MIL_ID MilDisplay);
void ParallelReadingsExample(MIL_ID MilSystem, MIL_ID MilCodeContext, MIL_ID MilImage, MIL_ID MilDisplay);

//****************************************************************************
// Example description.
//****************************************************************************
void PrintHeader()   
   {
   MosPrintf(MIL_TEXT("[EXAMPLE NAME]\n")
             MIL_TEXT("MultipleDatamatrixCodeRead\n\n")

             MIL_TEXT("[SYNOPSIS]\n")
             MIL_TEXT("This example shows three techniques to locate and read several\n")
             MIL_TEXT("datamatrix codes in an image:\n")
             MIL_TEXT("1- Performing a single reading in the whole image.\n")
             MIL_TEXT("2- Performing multiple sequential readings in regions of interest (ROIs).\n")
             MIL_TEXT("3- Performing multiple parallel readings in regions of interest (ROIs).\n\n")
  
             MIL_TEXT("[MODULES USED]\n")
             MIL_TEXT("Modules used: application, system, thread, display, buffer,\n")
             MIL_TEXT("image processing, blob, code, graphics.\n\n")
  
             MIL_TEXT("Press <Enter> to continue.\n\n"));
   MosGetch();
   }

//*****************************************************************************
// Main.
//*****************************************************************************
int MosMain(void)
   {
   MIL_ID MilApplication,     // Application identifier.
          MilSystem,          // System identifier.
          MilDisplay,         // Display identifier.
          MilCodeContext,     // Code reader context identifier.
          MilCodeModel,       // Code reader model identifier.
          MilDispGraList,     // Graphic list identifier.
          MilImage;           // Image buffer identifier.

   // Allocate MIL objects. 
   MappAlloc(M_NULL, M_DEFAULT, &MilApplication);
   MsysAlloc(M_DEFAULT, M_SYSTEM_HOST, M_DEFAULT, M_DEFAULT, &MilSystem);
   MdispAlloc(MilSystem, M_DEFAULT, MIL_TEXT("M_DEFAULT"), M_WINDOWED, &MilDisplay);

   // Allocate a code context.
   McodeAlloc(MilSystem, M_DEFAULT, M_DEFAULT, &MilCodeContext);
   // Add a datamatrix code model.
   McodeModel(MilCodeContext, M_ADD, M_DATAMATRIX, M_NULL, M_DEFAULT, &MilCodeModel);

   // Restore source image into image buffer.
   MbufRestore(IMAGE_FILE, MilSystem, &MilImage);

   // Display the image buffer. Zoom it down first.
   MdispZoom(MilDisplay, 0.5, 0.5);
   MdispSelect(MilDisplay, MilImage);

   // Allocate a graphic list.
   MgraAllocList(MilSystem, M_DEFAULT, &MilDispGraList);
   // Associate the graphic list to the display.
   MdispControl(MilDisplay, M_ASSOCIATED_GRAPHIC_LIST_ID, MilDispGraList);

   // Print header. 
   PrintHeader();

   // Set the number of datamatrix to read to M_ALL.
   McodeControl(MilCodeModel, M_NUMBER, M_ALL);
   // Run the example that reads all the datamatrix at once.
   SingleReadingExample(MilSystem, MilCodeContext, MilImage, MilDisplay);

   // Set the number of datamatrix to read to 1.
   McodeControl(MilCodeModel, M_NUMBER, 1);
   // Run the example that reads all the datamatrix sequentially.
   SequentialReadingsExample(MilSystem, MilCodeContext, MilImage, MilDisplay);

   // Run the example that reads all the datamatrix in parallel.
   ParallelReadingsExample(MilSystem, MilCodeContext, MilImage, MilDisplay);

   // Free MIL objects.
   McodeFree(MilCodeContext);
   MgraFree(MilDispGraList);
   MbufFree(MilImage);
   MdispFree(MilDisplay);
   MsysFree(MilSystem);
   MappFree(MilApplication);

   return 0;
   }

void SingleReadingExample(MIL_ID MilSystem, MIL_ID MilCodeContext, MIL_ID MilImage, MIL_ID MilDisplay)
   {
   MIL_ID         MilDispGraList,   // Graphic list identifier. Will be used to display results.
                  MilCodeResult;    // Code reader result buffer identifier.
   MIL_INT        NumberOfCodes,    // Number of codes found and read.
                  CodeIndex,        // Code index used to loop through the results.
                  ReadIndex;        // Read loop counter index.
   MIL_DOUBLE     Time;             // Processing time.

   MosPrintf(MIL_TEXT("--------------------------------------------------\n"));
   MosPrintf(MIL_TEXT("1- Performing a single reading in the whole image.\n\n"));

   MosPrintf(MIL_TEXT("A Code Reader context is set up to locate and read an unknown\n"));
   MosPrintf(MIL_TEXT("number of datamatrix codes in an image.\n\n"));
   MosPrintf(MIL_TEXT("Press <Enter> to continue.\n\n"));
   MosGetch();

   // Allocate a code result buffer.
   McodeAllocResult(MilSystem, M_DEFAULT, &MilCodeResult);

   // Read the datamatrix.
   McodeRead(MilCodeContext, MilImage, MilCodeResult);

   // Get the number of datamatrix that were read.
   McodeGetResult(MilCodeResult, M_NUMBER+M_TYPE_MIL_INT, &NumberOfCodes);

   // Disable the display update when the associated graphic list is modified.
   MdispControl(MilDisplay, M_UPDATE_GRAPHIC_LIST, M_DISABLE);

   // Get the graphic list identifier and clear the graphic list.
   MdispInquire(MilDisplay, M_ASSOCIATED_GRAPHIC_LIST_ID, &MilDispGraList);
   MgraClear(M_DEFAULT, MilDispGraList);

   // Draw results in the graphic list.
   MgraColor(M_DEFAULT, M_COLOR_YELLOW);
   McodeDraw(M_DEFAULT, MilCodeResult, MilDispGraList, M_DRAW_BOX, M_ALL, M_DEFAULT);

   // Print the result's header.
   MosPrintf(MIL_TEXT("\n\tString\t\t\tPosition\n\t------------------------------------------\n"));
   
   // Loop through the codes to get the results.
   for (CodeIndex=0; CodeIndex<NumberOfCodes; CodeIndex++)
      {
      MIL_DOUBLE     PositionX,                             // X coordinate of the code.
                     PositionY;                             // Y coordinate of the code.
      MIL_TEXT_CHAR  DecodedString[MAXIMUM_STRING_SIZE];    // Decoded string.
      MIL_TEXT_CHAR  CharBuffer[MAXIMUM_STRING_SIZE];       // String to display.

      // Get the decoded strings and their position.
      McodeGetResultSingle(MilCodeResult, CodeIndex, M_POSITION_X, &PositionX);
      McodeGetResultSingle(MilCodeResult, CodeIndex, M_POSITION_Y, &PositionY);
      McodeGetResultSingle(MilCodeResult, CodeIndex, M_STRING, &DecodedString[0]);

      // Display the string using the graphic list.
      MosSprintf(CharBuffer, MAXIMUM_STRING_SIZE, MIL_TEXT("Code%d"), CodeIndex+1);
      MgraText(M_DEFAULT, MilDispGraList, PositionX+TEXT_OFFSET_X, PositionY+TEXT_OFFSET_Y_1, CharBuffer);
      MosSprintf(CharBuffer, MAXIMUM_STRING_SIZE, MIL_TEXT("%s"),DecodedString);
      MgraText(M_DEFAULT, MilDispGraList, PositionX+TEXT_OFFSET_X, PositionY+TEXT_OFFSET_Y_2, CharBuffer);

      // Print the results.
      MosPrintf(MIL_TEXT("Code%d:\t%s\t(%.2f, %.2f)\n"), CodeIndex+1, DecodedString, PositionX, PositionY);
      }

   // Enable the display updates.
   MdispControl(MilDisplay, M_UPDATE_GRAPHIC_LIST, M_ENABLE);

   MosPrintf(MIL_TEXT("\n%d datamatrix codes were read.\n"), NumberOfCodes);

   //*******************************************************************************
   // Now, time the code reading. Do it in a loop to get the average processing time.
   //*******************************************************************************
   MosPrintf(MIL_TEXT("\nTiming benchmark in progress; please wait ...\n"));
   MappTimer(M_DEFAULT, M_TIMER_RESET+M_SYNCHRONOUS, M_NULL);
   for (ReadIndex=0; ReadIndex<NB_LOOP; ReadIndex++)
      McodeRead(MilCodeContext, MilImage, MilCodeResult);
   MappTimer(M_DEFAULT, M_TIMER_READ+M_SYNCHRONOUS, &Time);

   // Print the average processing time.
   MosPrintf(MIL_TEXT("\nThe %d codes were read in %.2f msec.\n\n"), NumberOfCodes, Time*1000/NB_LOOP);
   MosPrintf(MIL_TEXT("Press <Enter> to continue.\n\n"));
   MosGetch();

   // Free the code result buffer.
   McodeFree(MilCodeResult);
   }


// Expected size of each datamatrix cell.
#define EXPECTED_CELL_SIZE             4.9
#define EXPECTED_CELL_NUMBER_X         16
#define EXPECTED_CELL_NUMBER_Y         16
// Maximum expected number of codes to read.
#define EXPECTED_MAX_NUMBER_OF_CODES   50

// Processing function parameters structure.
typedef struct 
   {
   MIL_ID      MilImage;               // Image buffer identifier.
   MIL_ID      MilResizedImage;        // Image buffer identifier.
   MIL_ID      MilResizedBinImage;     // Image buffer identifier.
   MIL_ID      MilStructElement;       // Structuring element buffer identifier.
   MIL_ID      MilBlobFeatureList;     // Blob feature list identifier.
   MIL_ID      MilBlobResult;          // Blob result buffer identifier.
   MIL_ID      MilCodeContext;         // Code context identifier.
   MIL_ID      MilCodeResult;          // Code result identifier.
   MIL_ID      MilDispGraList;         // Graphic list identifier.
   MIL_ID      MilRoiGraList;          // Graphic list identifier.
   MIL_DOUBLE  ResizeFactor;           // Image resize factor.
   MIL_INT     NumberOfCodes;          // Number of codes found.
   bool        IsTimerActive;          // Indicates if a timing benchmark is in progress.
   } SEQUENTIAL_PROC_PARAM;

// Sequential processing functions declaration.
void SequentialInit(MIL_ID MilSystem, MIL_ID MilCodeContext, MIL_ID MilDisplay, MIL_ID MilImage, SEQUENTIAL_PROC_PARAM &ProcParamPtr);
void SequentialFree(SEQUENTIAL_PROC_PARAM &ProcParamPtr);
void SequentialProcessing(SEQUENTIAL_PROC_PARAM &ProcParamPtr);

void SequentialReadingsExample(MIL_ID MilSystem, MIL_ID MilCodeContext, MIL_ID MilImage, MIL_ID MilDisplay)
   {
   MIL_INT     ReadIndex;                    // Read loop counter index.
   MIL_DOUBLE  Time;                         // Processing time.
   SEQUENTIAL_PROC_PARAM  ProcessingParam;   // Processing parameters.

   // Initialize the processing structure.
   SequentialInit(MilSystem, MilCodeContext, MilDisplay, MilImage, ProcessingParam);

   MosPrintf(MIL_TEXT("---------------------------------------------------\n"));
   MosPrintf(MIL_TEXT("2- Performing multiple sequential readings in ROIs.\n\n"));

   MosPrintf(MIL_TEXT("A Code Reader context is set up to read a single datamatrix code.\n"));
   MosPrintf(MIL_TEXT("A custom preprocessing algorithm is used to locate potential\n"));
   MosPrintf(MIL_TEXT("datamatrix codes and to define an ROI around each one.\n"));
   MosPrintf(MIL_TEXT("The reading is performed for each ROI sequentially.\n\n"));
   MosPrintf(MIL_TEXT("Press <Enter> to continue.\n\n"));
   MosGetch();

   // Print the result's header.
   MosPrintf(MIL_TEXT("\n\tString\t\t\tPosition\n\t------------------------------------------\n"));

   // Disable the display update when the associated graphic list is modified.
   MdispControl(MilDisplay, M_UPDATE_GRAPHIC_LIST, M_DISABLE);

   // Perform the processing.
   SequentialProcessing(ProcessingParam);

   // Enable the display update.
   MdispControl(MilDisplay, M_UPDATE_GRAPHIC_LIST, M_ENABLE);

   MosPrintf(MIL_TEXT("\nA total of %d datamatrix codes were read.\n"), ProcessingParam.NumberOfCodes);

   //*******************************************************************************
   // Now, time the code reading. Do it in a loop to get the average processing time.
   //*******************************************************************************
   ProcessingParam.IsTimerActive = true;
   MosPrintf(MIL_TEXT("\nTiming benchmark in progress; please wait ...\n"));
   MappTimer(M_DEFAULT, M_TIMER_RESET+M_SYNCHRONOUS, M_NULL);
   for (ReadIndex=0; ReadIndex<NB_LOOP; ReadIndex++)
      {
      // Reset the number of codes found.
      ProcessingParam.NumberOfCodes = 0;
      // Perform the processing.
      SequentialProcessing(ProcessingParam);
      }
   MappTimer(M_DEFAULT, M_TIMER_READ+M_SYNCHRONOUS, &Time);

   // Print the average processing time.
   MosPrintf(MIL_TEXT("\nThe %d codes were read with sequential readings in %.2f msec.\n\n"), ProcessingParam.NumberOfCodes, Time*1000/NB_LOOP);
   MosPrintf(MIL_TEXT("Press <Enter> to continue.\n\n"));
   MosGetch();

   // Free the processing resources.
   SequentialFree(ProcessingParam);
   }

void SequentialInit(MIL_ID MilSystem, MIL_ID MilCodeContext, MIL_ID MilDisplay, MIL_ID MilImage, SEQUENTIAL_PROC_PARAM &ProcParamPtr)
   {
   MIL_INT  BufSizeX,         // Image buffer size X.
            BufSizeY,         // Image buffer size Y.
            BufType,          // Image buffer type.
            NewBufSizeX,      // Image buffer new size X.
            NewBufSizeY;      // Image buffer new size Y.

   // Allocate a code result buffer.
   McodeAllocResult(MilSystem, M_DEFAULT, &ProcParamPtr.MilCodeResult);

   // Allocate a blob feature list.
   MblobAllocFeatureList(MilSystem, &ProcParamPtr.MilBlobFeatureList);
   // Add the bounding box and the area to the feature list.
   MblobSelectFeature(ProcParamPtr.MilBlobFeatureList, M_AREA);
   MblobSelectFeature(ProcParamPtr.MilBlobFeatureList, M_BOX);

   // Allocate a blob result buffer.
   MblobAllocResult(MilSystem, &ProcParamPtr.MilBlobResult);
   MblobControl(ProcParamPtr.MilBlobResult, M_IDENTIFIER_TYPE, M_BINARY);

   // Get the size and type of the image buffer.
   MbufInquire(MilImage, M_SIZE_X, &BufSizeX);
   MbufInquire(MilImage, M_SIZE_Y, &BufSizeY);
   MbufInquire(MilImage, M_TYPE, &BufType);

   // The image will be resized so that a datamatrix cell is represented by a single pixel.
   ProcParamPtr.ResizeFactor = 1/EXPECTED_CELL_SIZE;
   NewBufSizeX = (MIL_INT) (BufSizeX * ProcParamPtr.ResizeFactor);
   NewBufSizeY = (MIL_INT) (BufSizeY * ProcParamPtr.ResizeFactor);

   // Allocate a smaller processing buffer.
   MbufAlloc2d(MilSystem, NewBufSizeX, NewBufSizeY, BufType, M_IMAGE+M_PROC, &ProcParamPtr.MilResizedImage);

   // Allocate a binary image that will be used for blob analysis.
   MbufAlloc2d(MilSystem, NewBufSizeX, NewBufSizeY, 1+M_UNSIGNED, M_IMAGE+M_PROC, &ProcParamPtr.MilResizedBinImage);

   // Allocate a 3x3 structuring element and clear it with 1.
   MbufAlloc2d(MilSystem, 3, 3, 32+M_UNSIGNED, M_STRUCT_ELEMENT, &ProcParamPtr.MilStructElement);
   MbufClear(ProcParamPtr.MilStructElement, 1);

   // Get the graphic list identifier and clear the graphic list used to display the results.
   MdispInquire(MilDisplay, M_ASSOCIATED_GRAPHIC_LIST_ID, &ProcParamPtr.MilDispGraList);
   MgraClear(M_DEFAULT, ProcParamPtr.MilDispGraList);

   // Allocate a second graphic list and clear it. It will be used to define a ROI for the code reading.
   MgraAllocList(MilSystem, M_DEFAULT, &ProcParamPtr.MilRoiGraList);

   // Reset the number of codes read.
   ProcParamPtr.NumberOfCodes = 0;

   // Specify the timer is not active at first.
   ProcParamPtr.IsTimerActive = false;

   // Finish filling the processing parameter structure .
   ProcParamPtr.MilImage = MilImage;
   ProcParamPtr.MilCodeContext = MilCodeContext;
   }

void SequentialFree(SEQUENTIAL_PROC_PARAM &ProcParamPtr)
   {
   // Free MIL objects.
   McodeFree(ProcParamPtr.MilCodeResult);
   MgraFree(ProcParamPtr.MilRoiGraList);
   MbufFree(ProcParamPtr.MilResizedBinImage);
   MbufFree(ProcParamPtr.MilStructElement);
   MbufFree(ProcParamPtr.MilResizedImage);
   MblobFree(ProcParamPtr.MilBlobResult);
   MblobFree(ProcParamPtr.MilBlobFeatureList);
   }


void SequentialProcessing(SEQUENTIAL_PROC_PARAM &ProcParamPtr)
   {
   MIL_INT     NumberOfBlobs;    // Number of blobs found.
   MIL_DOUBLE  ExpectedAreaMin,  // Minimum expected size of the area of a blob.
               ExpectedAreaMax;  // Maximum expected size of the area of a blob.

   // Resize the image in order to reduce the processing time. A datamatrix cell will be represented by a single pixel..
   MimResize(ProcParamPtr.MilImage, ProcParamPtr.MilResizedImage, ProcParamPtr.ResizeFactor, ProcParamPtr.ResizeFactor, M_NEAREST_NEIGHBOR+M_OVERSCAN_ENABLE+M_FAST);

   // Perform a bottom-hat filtering to make the background uniform.
   MimMorphic(ProcParamPtr.MilResizedImage, ProcParamPtr.MilResizedImage, ProcParamPtr.MilStructElement, M_BOTTOM_HAT, 5, M_GRAYSCALE);

   // Binarize the image.
   MimBinarize(ProcParamPtr.MilResizedImage, ProcParamPtr.MilResizedBinImage, M_BIMODAL+M_GREATER_OR_EQUAL, M_NULL, M_NULL);

   // Perform a dilate filtering to merge broken datamatrix.
   MimDilate(ProcParamPtr.MilResizedBinImage, ProcParamPtr.MilResizedBinImage, 1, M_BINARY);

   // Compute the blobs.
   MblobCalculate(ProcParamPtr.MilResizedBinImage, M_NULL, ProcParamPtr.MilBlobFeatureList, ProcParamPtr.MilBlobResult);

   // Exclude the blobs that are smaller than a minimum expected area.
   ExpectedAreaMin = (EXPECTED_CELL_NUMBER_X * EXPECTED_CELL_NUMBER_Y * 0.8);
   MblobSelect(ProcParamPtr.MilBlobResult, M_EXCLUDE, M_AREA, M_LESS, ExpectedAreaMin, M_NULL);
   // Exclude the blobs that are larger than a maximum expected area.
   ExpectedAreaMax = EXPECTED_CELL_NUMBER_X * EXPECTED_CELL_NUMBER_Y*1.3;
   MblobSelect(ProcParamPtr.MilBlobResult, M_EXCLUDE, M_AREA, M_GREATER, ExpectedAreaMax, M_NULL);
   // Exclude the blobs that have a bounding box aspect ratio too far from 1.
   MblobSelect(ProcParamPtr.MilBlobResult, M_EXCLUDE, M_BOX_ASPECT_RATIO, M_OUT_RANGE, 0.85, 1.15);

   // Get the number of included blobs.
   MblobGetNumber(ProcParamPtr.MilBlobResult, &NumberOfBlobs);

   if (NumberOfBlobs > 0)
      {
      MIL_INT  BlobIndex,     // Blob Index used to loop through blobs.
               *BoxXMin,      // X coordinate of the blobs upper left corner.
               *BoxXMax,      // X coordinate of the blobs lower right corner.
               *BoxYMin,      // Y coordinate of the blobs upper left corner.
               *BoxYMax;      // Y coordinate of the blobs lower right corner.

      // Allocate memory space to hold the bounding coordinates of the blobs bounding box.
      BoxXMin = new MIL_INT[NumberOfBlobs];
      BoxXMax = new MIL_INT[NumberOfBlobs];
      BoxYMin = new MIL_INT[NumberOfBlobs];
      BoxYMax = new MIL_INT[NumberOfBlobs];

      // Get the bounding box of each blob.
      MblobGetResult(ProcParamPtr.MilBlobResult, M_BOX_X_MIN+M_TYPE_MIL_INT, &BoxXMin[0]);
      MblobGetResult(ProcParamPtr.MilBlobResult, M_BOX_X_MAX+M_TYPE_MIL_INT, &BoxXMax[0]);
      MblobGetResult(ProcParamPtr.MilBlobResult, M_BOX_Y_MIN+M_TYPE_MIL_INT, &BoxYMin[0]);
      MblobGetResult(ProcParamPtr.MilBlobResult, M_BOX_Y_MAX+M_TYPE_MIL_INT, &BoxYMax[0]);

      // Loop through the blobs and read the codes in ROI.
      for(BlobIndex=0; BlobIndex<NumberOfBlobs; BlobIndex++)
         {
         MIL_INT     CodeReadStatus;   // Read status.

         // Define a ROI around the blob. Leave a margin around the blob.
         MIL_INT  Margin = 3;
         MIL_INT  BoxStartX = (MIL_INT)((BoxXMin[BlobIndex] * (1/ProcParamPtr.ResizeFactor))-(Margin*EXPECTED_CELL_SIZE));
         MIL_INT  BoxStartY = (MIL_INT)((BoxYMin[BlobIndex] * (1/ProcParamPtr.ResizeFactor))-(Margin*EXPECTED_CELL_SIZE));
         MIL_INT  BoxSizeX = (MIL_INT)(((BoxXMax[BlobIndex] - BoxXMin[BlobIndex])*(1/ProcParamPtr.ResizeFactor))+(2*Margin*EXPECTED_CELL_SIZE));
         MIL_INT  BoxSizeY = (MIL_INT)(((BoxYMax[BlobIndex] - BoxYMin[BlobIndex])*(1/ProcParamPtr.ResizeFactor))+(2*Margin*EXPECTED_CELL_SIZE));

         // Clear the graphic list.
         MgraClear(M_DEFAULT, ProcParamPtr.MilRoiGraList);
         // Draw the ROI in the graphic list.
         MgraRectAngle(M_DEFAULT, ProcParamPtr.MilRoiGraList, BoxStartX, BoxStartY, BoxSizeX, BoxSizeY, 0, M_CORNER_AND_DIMENSION+M_FILLED);

         // Associate the modified graphic list with the image to process.
         MbufSetRegion(ProcParamPtr.MilImage, ProcParamPtr.MilRoiGraList, M_DEFAULT, M_NO_RASTERIZE, M_DEFAULT);

         // Read the code.
         McodeRead(ProcParamPtr.MilCodeContext, ProcParamPtr.MilImage, ProcParamPtr.MilCodeResult);

         McodeGetResult(ProcParamPtr.MilCodeResult, M_STATUS+M_TYPE_MIL_INT, &CodeReadStatus);
         if (CodeReadStatus == M_STATUS_READ_OK)
            {
            // If a timing benchmark is not in progress, get the results and display/print them.
            if (!ProcParamPtr.IsTimerActive)
               {
               MIL_DOUBLE     PositionX,                             // X coordinate of the code.
                              PositionY;                             // Y coordinate of the code.
               MIL_TEXT_CHAR  DecodedString[MAXIMUM_STRING_SIZE];    // Decoded string.
               MIL_TEXT_CHAR  CharBuffer[MAXIMUM_STRING_SIZE];       // String to display.

               // Get the decoded string and position of the code.
               McodeGetResult(ProcParamPtr.MilCodeResult, M_STRING, &DecodedString[0]);
               McodeGetResult(ProcParamPtr.MilCodeResult, M_POSITION_X, &PositionX);
               McodeGetResult(ProcParamPtr.MilCodeResult, M_POSITION_Y, &PositionY);

               // Draw results in the graphic list.
               MgraColor(M_DEFAULT, M_COLOR_CYAN);
               McodeDraw(M_DEFAULT, ProcParamPtr.MilCodeResult, ProcParamPtr.MilDispGraList, M_DRAW_BOX, M_ALL, M_DEFAULT);
               // Display the string using the graphic list.
               MosSprintf(CharBuffer, MAXIMUM_STRING_SIZE, MIL_TEXT("Code%d"), ProcParamPtr.NumberOfCodes+1);
               MgraText(M_DEFAULT, ProcParamPtr.MilDispGraList, PositionX+TEXT_OFFSET_X, PositionY+TEXT_OFFSET_Y_1, CharBuffer);
               MosSprintf(CharBuffer, MAXIMUM_STRING_SIZE, MIL_TEXT("%s"), DecodedString);
               MgraText(M_DEFAULT, ProcParamPtr.MilDispGraList, PositionX+TEXT_OFFSET_X, PositionY+TEXT_OFFSET_Y_2, CharBuffer);

               // Print the results.
               MosPrintf(MIL_TEXT("Code%d:\t%s\t(%.2f, %.2f)\n"), ProcParamPtr.NumberOfCodes+1, DecodedString, PositionX, PositionY);
               }

            // Increment the number of codes that were read.
            ProcParamPtr.NumberOfCodes++;
            }
         }

      // Delete the ROI information from the image buffer.
      MbufSetRegion(ProcParamPtr.MilImage, M_NULL, M_DEFAULT, M_DELETE, M_DEFAULT);

      // Free allocated memory space.
      delete []BoxXMin;
      delete []BoxXMax;
      delete []BoxYMin;
      delete []BoxYMax;
      }
   }


#define MAXIMUM_NUMBER_OF_CODES 50

// ROI structure.
typedef struct SBoxParam
   {
   MIL_INT  MinX;    // Upper left x coordinate of the blob's bounding box.
   MIL_INT  MinY;    // Upper left y coordinate of the blob's bounding box.
   MIL_INT  MaxX;    // Lower right x coordinate of the blob's bounding box.
   MIL_INT  MaxY;    // Lower right y coordinate of the blob's bounding box.
   }BOX_PARAM;

// Thread parameters structure.
typedef struct SThreadParam
   {
   MIL_ID         MilThread;                          // Thread identifier.
   MIL_ID         MilImage;                           // Image buffer identifier.
   MIL_ID         MilCodeContext;                     // Code context identifier.
   MIL_ID         MilCodeResult;                      // Code result identifier.
   MIL_ID         MilRoiGraList;                      // Graphic list identifier.
   MIL_ID         ReadyEvent;                         // Event identifier used to indicate the thread is ready to process.
   MIL_ID         DoneEvent;                          // Event identifier used to indicate the thread has finished processing.
   MIL_INT        NumberOfCodes;                      // Number of codes found.
   MIL_INT        ReadStatus;                         // Status of the read operation.
   bool           DoExit;                             // Indicates to exit the processing thread.
   MIL_DOUBLE     ResizeFactor;                       // Image resize factor.
   BOX_PARAM      BlobBox;                            // Bounding box of the blob.
   MIL_TEXT_CHAR* CodesRead[MAXIMUM_NUMBER_OF_CODES]; // Array of decoded string.
   MIL_DOUBLE     PosX[MAXIMUM_NUMBER_OF_CODES];      // Position X of the code.
   MIL_DOUBLE     PosY[MAXIMUM_NUMBER_OF_CODES];      // Position Y of the code.
   } THREAD_PARAM;

// Processing function parameters structure.
typedef struct 
   {
   MIL_ID         MilImage;               // Image buffer identifier.
   MIL_ID         MilResizedImage;        // Image buffer identifier.
   MIL_ID         MilResizedBinImage;     // Image buffer identifier.
   MIL_ID         MilStructElement;       // Structuring element buffer identifier.
   MIL_ID         MilBlobFeatureList;     // Blob feature list identifier.
   MIL_ID         MilBlobResult;          // Blob result buffer identifier.
   MIL_ID         MilDispGraList;         // Graphic list identifier.
   MIL_ID*        DoneEventPtr;           // Pointer on an array of event identifier.
   MIL_INT        NumProcCores;           // Number of cores available.
   MIL_INT        InitialMpUse;           // Initial state of the Use Mp functionality.
   MIL_DOUBLE     ResizeFactor;           // Image resize factor.
   bool           IsTimerActive;          // Indicates if a timing benchmark is in progress.
   THREAD_PARAM   *ThreadParam;           // Parameter structure sent to each thread.
   } PARALLEL_PROC_PARAM;

// Parallel processing functions declaration.
void ParallelInit(MIL_ID MilSystem, MIL_ID MilCodeContext, MIL_ID MilDisplay, MIL_ID MilImage, PARALLEL_PROC_PARAM &ProcParamPtr);
void ParallelFree(PARALLEL_PROC_PARAM &ProcParamPtr);
void ParallelProcessing(PARALLEL_PROC_PARAM &ProcParamPtr);
void ParallelGetResults(THREAD_PARAM &ThreadParamPtr, MIL_ID MilDispGraList);
MIL_UINT32 MFTYPE ParallelProcessingThread(void *ThreadParam);

   
void ParallelReadingsExample(MIL_ID MilSystem, MIL_ID MilCodeContext, MIL_ID MilImage, MIL_ID MilDisplay)
   {
   MIL_INT              ReadIndex,           // Read loop counter index.
                        ThreadIndex,         // Thread loop counter index.
                        TotalNumberOfCodes;  // The total number of codes that were read.
   MIL_DOUBLE           Time;                // Processing time.
   PARALLEL_PROC_PARAM  ProcessingParam;     // Processing parameters.

   // Initialize the processing structure.
   ParallelInit(MilSystem, MilCodeContext, MilDisplay, MilImage, ProcessingParam);

   MosPrintf(MIL_TEXT("-------------------------------------------------\n"));
   MosPrintf(MIL_TEXT("3- Performing multiple parallel readings in ROIs.\n\n"));

   MosPrintf(MIL_TEXT("A Code Reader context is set up to read a single datamatrix code.\n"));
   MosPrintf(MIL_TEXT("A custom preprocessing algorithm is used to locate potential\n"));
   MosPrintf(MIL_TEXT("datamatrix codes and to define an ROI around each one.\n"));
   MosPrintf(MIL_TEXT("Readings are then performed for several ROIs on parallel threads.\n"));
   MosPrintf(MIL_TEXT("The number of threads used is equal to the number of cores\n"));
   MosPrintf(MIL_TEXT("available on the system.\n\n"));
   MosPrintf(MIL_TEXT("Press <Enter> to continue.\n\n"));
   MosGetch();

   // Print the result's header.
   MosPrintf(MIL_TEXT("\n\t\tString\t\t\tPosition\n\t\t------------------------------------------\n"));

   // Disable the display update when the associated graphic list is modified.
   MdispControl(MilDisplay, M_UPDATE_GRAPHIC_LIST, M_DISABLE);

   // Perform the processing.
   ParallelProcessing(ProcessingParam);

   // Enable the display update.
   MdispControl(MilDisplay, M_UPDATE_GRAPHIC_LIST, M_ENABLE);

   TotalNumberOfCodes = 0;
   MosPrintf(MIL_TEXT("\n"));
   for (ThreadIndex=0; ThreadIndex<ProcessingParam.NumProcCores; ThreadIndex++)
      {
      MosPrintf(MIL_TEXT("A total of %d datamatrix codes were read in thread %d.\n"), ProcessingParam.ThreadParam[ThreadIndex].NumberOfCodes, ThreadIndex+1);
      TotalNumberOfCodes+=ProcessingParam.ThreadParam[ThreadIndex].NumberOfCodes;
      }

   //*******************************************************************************
   // Now, time the code reading. Do it in a loop to get the average processing time.
   //*******************************************************************************
   ProcessingParam.IsTimerActive = true;
   MosPrintf(MIL_TEXT("\nTiming benchmark in progress; please wait ...\n"));
   MappTimer(M_DEFAULT, M_TIMER_RESET+M_SYNCHRONOUS, M_NULL);
   for (ReadIndex=0; ReadIndex<NB_LOOP; ReadIndex++)
      {
      // Perform the processing.
      ParallelProcessing(ProcessingParam);
      }
   MappTimer(M_DEFAULT, M_TIMER_READ+M_SYNCHRONOUS, &Time);
 
   // Print the average processing time.
   MosPrintf(MIL_TEXT("\nThe %d codes were read with parallel readings in %.2f msec.\n\n"), TotalNumberOfCodes, Time*1000/NB_LOOP);
   MosPrintf(MIL_TEXT("Press <Enter> to end.\n"));
   MosGetch();

   // Free the processing resources.
   ParallelFree(ProcessingParam);
   }

void ParallelInit(MIL_ID MilSystem, MIL_ID MilCodeContext, MIL_ID MilDisplay, MIL_ID MilImage, PARALLEL_PROC_PARAM &ProcParamPtr)
   {
   MIL_INT        BufSizeX,            // Image buffer size X.
                  BufSizeY,            // Image buffer size Y.
                  BufType,             // Image buffer type.
                  NewBufSizeX,         // Image buffer new size X.
                  NewBufSizeY,         // Image buffer new size Y.
                  ContextByteSize,     // Memory size required to store the code context.
                  ThreadIndex;         // Thread loop counter index.
   MIL_TEXT_CHAR* MemPtr = NULL;       // Pointer on the memory used to store the code context.

   // Allocate a blob feature list.
   MblobAllocFeatureList(MilSystem, &ProcParamPtr.MilBlobFeatureList);
   // Add the bounding box and the area to the feature list.
   MblobSelectFeature(ProcParamPtr.MilBlobFeatureList, M_AREA);
   MblobSelectFeature(ProcParamPtr.MilBlobFeatureList, M_BOX);

   // Allocate a blob result buffer.
   MblobAllocResult(MilSystem, &ProcParamPtr.MilBlobResult);
   MblobControl(ProcParamPtr.MilBlobResult, M_IDENTIFIER_TYPE, M_BINARY);

   // Copy the image identifier in the processing structure.
   ProcParamPtr.MilImage = MilImage;

   // Get the size and type of the image buffer.
   MbufInquire(MilImage, M_SIZE_X, &BufSizeX);
   MbufInquire(MilImage, M_SIZE_Y, &BufSizeY);
   MbufInquire(MilImage, M_TYPE, &BufType);

   // The image will be resized so that a datamatrix cell is represented by a single pixel.
   // Compute this new size.
   ProcParamPtr.ResizeFactor = 1/EXPECTED_CELL_SIZE;
   NewBufSizeX = (MIL_INT) (BufSizeX * ProcParamPtr.ResizeFactor);
   NewBufSizeY = (MIL_INT) (BufSizeY * ProcParamPtr.ResizeFactor);

   // Allocate a smaller processing buffer.
   MbufAlloc2d(MilSystem, NewBufSizeX, NewBufSizeY, BufType, M_IMAGE+M_PROC, &ProcParamPtr.MilResizedImage);

   // Allocate a binary image that will be used for blob analysis.
   MbufAlloc2d(MilSystem, NewBufSizeX, NewBufSizeY, 1+M_UNSIGNED, M_IMAGE+M_PROC, &ProcParamPtr.MilResizedBinImage);

   // Allocate a 3x3 structuring element and clear it with 1.
   MbufAlloc2d(MilSystem, 3, 3, 32+M_UNSIGNED, M_STRUCT_ELEMENT, &ProcParamPtr.MilStructElement);
   MbufClear(ProcParamPtr.MilStructElement, 1);

   // Get the graphic list identifier and clear the graphic list used to display the results.
   MdispInquire(MilDisplay, M_ASSOCIATED_GRAPHIC_LIST_ID, &ProcParamPtr.MilDispGraList);
   MgraClear(M_DEFAULT, ProcParamPtr.MilDispGraList);

   // Specify the timer is not active at first.
   ProcParamPtr.IsTimerActive = false;

   // ************************************************
   // Initialization of the Thread parameter structure.
   // ************************************************

   // Inquire the initial state of the Use MP functionality.
   MappInquireMp(M_DEFAULT, M_MP_USE, M_DEFAULT, M_DEFAULT, &ProcParamPtr.InitialMpUse);

   // Inquire the number of cores available for processing.
   MappInquireMp(M_DEFAULT, M_CORE_NUM_PROCESS, M_DEFAULT, M_NULL, &ProcParamPtr.NumProcCores);

   if ((ProcParamPtr.NumProcCores > 1) && (ProcParamPtr.InitialMpUse == M_ENABLE))
      {
      // Disable MP processing to avoid contention between users threads and MP.
      MappControlMp(M_DEFAULT, M_MP_USE, M_DEFAULT, M_DISABLE, M_NULL);
      }

   // Allocate an array of thread parameter structures; one per core.
   ProcParamPtr.ThreadParam = new THREAD_PARAM[ProcParamPtr.NumProcCores];

    // Allocate an array of event identifiers; one per core.
   ProcParamPtr.DoneEventPtr = new MIL_ID[ProcParamPtr.NumProcCores];

   // A copy of the code context must be made for each thread.
   // Inquire the memory size require to stream the code context in memory.
   McodeStream(M_NULL , MilSystem, M_INQUIRE_SIZE_BYTE, M_MEMORY, M_DEFAULT, M_DEFAULT, &MilCodeContext, &ContextByteSize);
   // Allocate the required memory.
   MemPtr = new MIL_TEXT_CHAR[ContextByteSize];
   // Stream the context to memory.
   McodeStream(MemPtr, M_NULL, M_SAVE, M_MEMORY, M_DEFAULT, M_DEFAULT, &MilCodeContext, &ContextByteSize);

   // Initialize the thread parameter structures and start the threads.
   for (ThreadIndex=0; ThreadIndex<ProcParamPtr.NumProcCores; ThreadIndex++)
      {
      MIL_INT  CodeIndex;     // Code loop counter index.

      // Stream the code context from memory.
      McodeStream(MemPtr , MilSystem, M_RESTORE, M_MEMORY, M_DEFAULT, M_DEFAULT, &ProcParamPtr.ThreadParam[ThreadIndex].MilCodeContext, &ContextByteSize);

      // Set the initial exit state to false.
      ProcParamPtr.ThreadParam[ThreadIndex].DoExit = false;

      // Set the resize factor in the thread structure.
      ProcParamPtr.ThreadParam[ThreadIndex].ResizeFactor = ProcParamPtr.ResizeFactor;

      // Allocate a code result buffer.
      McodeAllocResult(MilSystem, M_DEFAULT, &ProcParamPtr.ThreadParam[ThreadIndex].MilCodeResult);
      
      // Allocate one image per thread.
      MbufAlloc2d(MilSystem, BufSizeX, BufSizeY, BufType, M_IMAGE+M_PROC, &ProcParamPtr.ThreadParam[ThreadIndex].MilImage);

      // Copy the original image in each thread.
      MbufCopy(MilImage, ProcParamPtr.ThreadParam[ThreadIndex].MilImage);

      // Allocate a graphic list and clear it. It will be used to define a ROI for the code reading.
      MgraAllocList(MilSystem, M_DEFAULT, &ProcParamPtr.ThreadParam[ThreadIndex].MilRoiGraList);

      // Allocate the synchronization events.
      MthrAlloc(MilSystem, M_EVENT, M_NOT_SIGNALED+M_AUTO_RESET, M_NULL, M_NULL, &ProcParamPtr.ThreadParam[ThreadIndex].ReadyEvent);
      ProcParamPtr.DoneEventPtr[ThreadIndex] = MthrAlloc(MilSystem, M_EVENT, M_NOT_SIGNALED+M_AUTO_RESET, M_NULL, M_NULL, &ProcParamPtr.ThreadParam[ThreadIndex].DoneEvent);

      // Set the string pointers to NULL.
      for (CodeIndex=0; CodeIndex<MAXIMUM_NUMBER_OF_CODES; CodeIndex++)
         ProcParamPtr.ThreadParam[ThreadIndex].CodesRead[CodeIndex] = NULL;

      // Start the thread.
      MthrAlloc(MilSystem, M_THREAD, M_DEFAULT, &ParallelProcessingThread, &ProcParamPtr.ThreadParam[ThreadIndex], &ProcParamPtr.ThreadParam[ThreadIndex].MilThread);
      }

   // Free the memory used to copy the code context.
   delete []MemPtr;
   }


void ParallelFree(PARALLEL_PROC_PARAM &ProcParamPtr)
   {
   MIL_ID   ThreadIndex;   // Thread loop counter index.

   if (ProcParamPtr.InitialMpUse == M_ENABLE)
      // Re-enable MP processing.
      MappControlMp(M_DEFAULT, M_MP_USE, M_DEFAULT, M_ENABLE, M_NULL);

   // Free MIL objects.
   MbufFree(ProcParamPtr.MilResizedBinImage);
   MbufFree(ProcParamPtr.MilStructElement);
   MbufFree(ProcParamPtr.MilResizedImage);
   MblobFree(ProcParamPtr.MilBlobResult);
   MblobFree(ProcParamPtr.MilBlobFeatureList);

   // Free the MIL object allocated in each thread.
   for (ThreadIndex=0; ThreadIndex<ProcParamPtr.NumProcCores; ThreadIndex++)
      {
      MIL_INT  CodeIndex;     // Code loop counter index.

      // Stop the thread.
      ProcParamPtr.ThreadParam[ThreadIndex].DoExit = true;
      MthrControl(ProcParamPtr.ThreadParam[ThreadIndex].ReadyEvent, M_EVENT_SET, M_SIGNALED);
      MthrWait(ProcParamPtr.ThreadParam[ThreadIndex].DoneEvent, M_EVENT_WAIT, M_NULL);

      // Free the MIL resources that were allocated per thread.
      McodeFree(ProcParamPtr.ThreadParam[ThreadIndex].MilCodeContext);
      McodeFree(ProcParamPtr.ThreadParam[ThreadIndex].MilCodeResult);
      MgraFree(ProcParamPtr.ThreadParam[ThreadIndex].MilRoiGraList);
      MbufFree(ProcParamPtr.ThreadParam[ThreadIndex].MilImage);
      MthrFree(ProcParamPtr.ThreadParam[ThreadIndex].ReadyEvent);
      MthrFree(ProcParamPtr.ThreadParam[ThreadIndex].DoneEvent);

      // Free the memory allocated to hold the strings.
      for (CodeIndex=0; CodeIndex<MAXIMUM_NUMBER_OF_CODES; CodeIndex++)
         {
         if (ProcParamPtr.ThreadParam[ThreadIndex].CodesRead[CodeIndex] != NULL)
            {
            delete []ProcParamPtr.ThreadParam[ThreadIndex].CodesRead[CodeIndex];
            ProcParamPtr.ThreadParam[ThreadIndex].CodesRead[CodeIndex] = NULL;
            }
         }

      // Free the thread.
      MthrFree(ProcParamPtr.ThreadParam[ThreadIndex].MilThread);
      }

   // Free the arrays of events identifiers and thread parameter structures.
   delete []ProcParamPtr.DoneEventPtr;
   delete []ProcParamPtr.ThreadParam;
   }


void ParallelProcessing(PARALLEL_PROC_PARAM &ProcParamPtr)
   {
   MIL_INT     NumberOfBlobs;    // Number of blobs found.
   MIL_ID      ThreadIndex;      // Thread loop counter index.
   MIL_DOUBLE  ExpectedAreaMin,  // Minimum expected size of the area of a blob.
               ExpectedAreaMax;  // Maximum expected size of the area of a blob.

   // Reset some processing parameters.
   for (ThreadIndex=0; ThreadIndex<ProcParamPtr.NumProcCores; ThreadIndex++)
      {
      // Set the number of codes found to 0.
      ProcParamPtr.ThreadParam[ThreadIndex].NumberOfCodes = 0;
      // Set the read status to 'not found'.
      ProcParamPtr.ThreadParam[ThreadIndex].ReadStatus = M_STATUS_NOT_FOUND;
      // Signal the 'done' event for each thread.
      MthrControl(ProcParamPtr.ThreadParam[ThreadIndex].DoneEvent, M_EVENT_SET, M_SIGNALED);
      }

   // Resize the image in order to reduce the processing time. A datamatrix cell will be represented by a single pixel.
   MimResize(ProcParamPtr.MilImage, ProcParamPtr.MilResizedImage, ProcParamPtr.ResizeFactor, ProcParamPtr.ResizeFactor, M_NEAREST_NEIGHBOR+M_OVERSCAN_ENABLE+M_FAST);

   // Perform a bottom-hat filtering to make the background uniform.
   MimMorphic(ProcParamPtr.MilResizedImage, ProcParamPtr.MilResizedImage, ProcParamPtr.MilStructElement, M_BOTTOM_HAT, 5, M_GRAYSCALE);

   // Binarize the image.
   MimBinarize(ProcParamPtr.MilResizedImage, ProcParamPtr.MilResizedBinImage, M_BIMODAL+M_GREATER_OR_EQUAL, M_NULL, M_NULL);

   // Perform a dilate filtering to merge broken datamatrix.
   MimDilate(ProcParamPtr.MilResizedBinImage, ProcParamPtr.MilResizedBinImage, 1, M_BINARY);

   // Compute the blobs.
   MblobCalculate(ProcParamPtr.MilResizedBinImage, M_NULL, ProcParamPtr.MilBlobFeatureList, ProcParamPtr.MilBlobResult);

   // Exclude the blobs that are smaller than a minimum expected area.
   ExpectedAreaMin = (EXPECTED_CELL_NUMBER_X * EXPECTED_CELL_NUMBER_Y * 0.8);
   MblobSelect(ProcParamPtr.MilBlobResult, M_EXCLUDE, M_AREA, M_LESS, ExpectedAreaMin, M_NULL);
   // Exclude the blobs that are larger than a maximum expected area.
   ExpectedAreaMax = EXPECTED_CELL_NUMBER_X * EXPECTED_CELL_NUMBER_Y*1.3;
   MblobSelect(ProcParamPtr.MilBlobResult, M_EXCLUDE, M_AREA, M_GREATER, ExpectedAreaMax, M_NULL);
   // Exclude the blobs that have a bounding box aspect ratio too far from 1.
   MblobSelect(ProcParamPtr.MilBlobResult, M_EXCLUDE, M_BOX_ASPECT_RATIO, M_OUT_RANGE, 0.85, 1.15);

   // Get the number of included blobs.
   MblobGetNumber(ProcParamPtr.MilBlobResult, &NumberOfBlobs);

   if (NumberOfBlobs > 0)
      {
      MIL_INT  BlobIndex,     // Blob loop counter index.
               ThreadIndex,   // Thread loop counter index.
               EventIndex,    // Index of the signaled event.
               *BoxXMin,      // X coordinate of the blobs upper left corner.
               *BoxXMax,      // X coordinate of the blobs lower right corner.
               *BoxYMin,      // Y coordinate of the blobs upper left corner.
               *BoxYMax;      // Y coordinate of the blobs lower right corner.

      // Allocate memory space to hold the bounding coordinates of the blobs bounding box.
      BoxXMin = new MIL_INT[NumberOfBlobs];
      BoxXMax = new MIL_INT[NumberOfBlobs];
      BoxYMin = new MIL_INT[NumberOfBlobs];
      BoxYMax = new MIL_INT[NumberOfBlobs];

      // Get the bounding box of each blob.
      MblobGetResult(ProcParamPtr.MilBlobResult, M_BOX_X_MIN+M_TYPE_MIL_INT, &BoxXMin[0]);
      MblobGetResult(ProcParamPtr.MilBlobResult, M_BOX_X_MAX+M_TYPE_MIL_INT, &BoxXMax[0]);
      MblobGetResult(ProcParamPtr.MilBlobResult, M_BOX_Y_MIN+M_TYPE_MIL_INT, &BoxYMin[0]);
      MblobGetResult(ProcParamPtr.MilBlobResult, M_BOX_Y_MAX+M_TYPE_MIL_INT, &BoxYMax[0]);

      // Read inside each blob region.
      for (BlobIndex=0; BlobIndex<NumberOfBlobs; BlobIndex++ )
         {
         // Wait for one of the thread to be ready to process.
         MthrWaitMultiple(&ProcParamPtr.DoneEventPtr[0], ProcParamPtr.NumProcCores, M_EVENT_WAIT, &EventIndex);

         // If a timing benchmark is not in progress, get the results.
         if (!ProcParamPtr.IsTimerActive)
            {
            if (ProcParamPtr.ThreadParam[EventIndex].ReadStatus == M_STATUS_READ_OK)
               {
               ParallelGetResults(ProcParamPtr.ThreadParam[EventIndex], ProcParamPtr.MilDispGraList);
               }
            }

         // Set the bounding box parameters in the thread parameter structure.
         ProcParamPtr.ThreadParam[EventIndex].BlobBox.MinX = BoxXMin[BlobIndex];
         ProcParamPtr.ThreadParam[EventIndex].BlobBox.MinY = BoxYMin[BlobIndex];
         ProcParamPtr.ThreadParam[EventIndex].BlobBox.MaxX = BoxXMax[BlobIndex];
         ProcParamPtr.ThreadParam[EventIndex].BlobBox.MaxY = BoxYMax[BlobIndex];

         // Set the 'ready' event to tell the thread it can process.
         MthrControl(ProcParamPtr.ThreadParam[EventIndex].ReadyEvent, M_EVENT_SET, M_SIGNALED);
         }

      // Wait for all of the thread to be finished.
      MthrWaitMultiple(&ProcParamPtr.DoneEventPtr[0], ProcParamPtr.NumProcCores, M_EVENT_WAIT+M_ALL_OBJECTS, M_NULL);

      // If a timing benchmark is not in progress, get the last results of each thread.
      if (!ProcParamPtr.IsTimerActive)
         {
         MIL_INT        CodeIndex;                       // Code loop counter index.
         MIL_TEXT_CHAR  CharBuffer[MAXIMUM_STRING_SIZE]; // String to display.

         for (ThreadIndex=0; ThreadIndex<ProcParamPtr.NumProcCores; ThreadIndex++)
            {
            // Get the results.
            ParallelGetResults(ProcParamPtr.ThreadParam[ThreadIndex], ProcParamPtr.MilDispGraList);

            // Display the decoded strings and print them in the console.
            for (CodeIndex=0; CodeIndex<ProcParamPtr.ThreadParam[ThreadIndex].NumberOfCodes; CodeIndex++)
               {
               MIL_DOUBLE  PositionX = ProcParamPtr.ThreadParam[ThreadIndex].PosX[CodeIndex];
               MIL_DOUBLE  PositionY = ProcParamPtr.ThreadParam[ThreadIndex].PosY[CodeIndex];

               // Display the strings using the graphic list.
               MosSprintf(CharBuffer, MAXIMUM_STRING_SIZE, MIL_TEXT("Thread%d_Code%d"), ThreadIndex+1, CodeIndex);
               MgraText(M_DEFAULT, ProcParamPtr.MilDispGraList, PositionX+TEXT_OFFSET_X, PositionY+TEXT_OFFSET_Y_1, CharBuffer);
               MosSprintf(CharBuffer, MAXIMUM_STRING_SIZE, MIL_TEXT("%s"),ProcParamPtr.ThreadParam[ThreadIndex].CodesRead[CodeIndex]);
               MgraText(M_DEFAULT, ProcParamPtr.MilDispGraList, PositionX+TEXT_OFFSET_X, PositionY+TEXT_OFFSET_Y_2, CharBuffer);

               // Print the results.
               MosPrintf(MIL_TEXT("Thread%d Code%d\t%s\t(%.2f, %.2f)\n"), ThreadIndex+1, CodeIndex+1, ProcParamPtr.ThreadParam[ThreadIndex].CodesRead[CodeIndex], PositionX, PositionY);
               }
            }
         }

      // Free allocated memory space.
      delete []BoxXMin;
      delete []BoxXMax;
      delete []BoxYMin;
      delete []BoxYMax;
      }
   }

MIL_UINT32 MFTYPE ParallelProcessingThread(void *ThreadParameters)
   {
   THREAD_PARAM   *ThreadParam = (THREAD_PARAM *)ThreadParameters;

   while(!ThreadParam->DoExit)
      {
      // Wait for the "ready" event to be signaled.
      MthrWait(ThreadParam->ReadyEvent, M_EVENT_WAIT, M_NULL);

      if (!ThreadParam->DoExit)
         {
         // Define a ROI around the blob. Leave a margin around the blob.
         MIL_INT  Margin = 3;
         MIL_INT  BoxStartX = (MIL_INT)((ThreadParam->BlobBox.MinX * (1/ThreadParam->ResizeFactor))-(Margin*EXPECTED_CELL_SIZE));
         MIL_INT  BoxStartY = (MIL_INT)((ThreadParam->BlobBox.MinY * (1/ThreadParam->ResizeFactor))-(Margin*EXPECTED_CELL_SIZE));
         MIL_INT  BoxSizeX = (MIL_INT)(((ThreadParam->BlobBox.MaxX - ThreadParam->BlobBox.MinX)*(1/ThreadParam->ResizeFactor))+(2*Margin*EXPECTED_CELL_SIZE));
         MIL_INT  BoxSizeY = (MIL_INT)(((ThreadParam->BlobBox.MaxY - ThreadParam->BlobBox.MinY)*(1/ThreadParam->ResizeFactor))+(2*Margin*EXPECTED_CELL_SIZE));

         // Clear the graphic list.
         MgraClear(M_DEFAULT, ThreadParam->MilRoiGraList);
         // Draw the ROI in the graphic list.
         MgraRectAngle(M_DEFAULT, ThreadParam->MilRoiGraList, BoxStartX, BoxStartY, BoxSizeX, BoxSizeY, 0, M_CORNER_AND_DIMENSION+M_FILLED);

         // Associate the modified graphic list with the image to process.
         MbufSetRegion(ThreadParam->MilImage, ThreadParam->MilRoiGraList, M_DEFAULT, M_NO_RASTERIZE, M_DEFAULT);

         // Read the code.
         McodeRead(ThreadParam->MilCodeContext, ThreadParam->MilImage, ThreadParam->MilCodeResult);

         // Get the read status.
         McodeGetResult(ThreadParam->MilCodeResult, M_STATUS+M_TYPE_MIL_INT, &ThreadParam->ReadStatus);
         
         // Increment the number of codes that were read on the current thread.
         if (ThreadParam->ReadStatus == M_STATUS_READ_OK)
            ThreadParam->NumberOfCodes++;
         }

      // Signal the "done" event.
      MthrControl(ThreadParam->DoneEvent, M_EVENT_SET, M_SIGNALED);
      }

   return(1);
   }

void ParallelGetResults(THREAD_PARAM &ThreadParamPtr, MIL_ID MilDispGraList)
   {
   MIL_INT CodeIndex = ThreadParamPtr.NumberOfCodes-1;

   // If necessary, allocate the memory to store the string.
   if(ThreadParamPtr.CodesRead[CodeIndex] == NULL)
      ThreadParamPtr.CodesRead[CodeIndex] = new MIL_TEXT_CHAR[MAXIMUM_STRING_SIZE];

   // Get the decoded string and position of the code.
   McodeGetResult(ThreadParamPtr.MilCodeResult, M_STRING, ThreadParamPtr.CodesRead[CodeIndex]);
   McodeGetResult(ThreadParamPtr.MilCodeResult, M_POSITION_X, &ThreadParamPtr.PosX[CodeIndex]);
   McodeGetResult(ThreadParamPtr.MilCodeResult, M_POSITION_Y, &ThreadParamPtr.PosY[CodeIndex]);

   // Draw results in the graphic list.
   MgraColor(M_DEFAULT, M_COLOR_GREEN);
   McodeDraw(M_DEFAULT, ThreadParamPtr.MilCodeResult, MilDispGraList, M_DRAW_BOX, M_ALL, M_DEFAULT);
   }