//***************************************************************************************
// 
// File name: TireStringRead.cpp  
// Location:  ...\Matrox Imaging\MILxxx\Examples\Processing\3dReconstruction\TireStringRead\C++
//             
//
// Synopsis: Example that reads characters on a tire using 3d data.
//
// Copyright (C) Matrox Electronic Systems Ltd., 1992-2015.
// All Rights Reserved

#include "TireStringRead.h"

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

   MosPrintf(MIL_TEXT("[SYNOPSIS]\n"));
   MosPrintf(MIL_TEXT("This example demonstrates the reading of strings on a tire using ")
             MIL_TEXT("3d\nsheet-of-light profiling. The system consists of two cameras ")
             MIL_TEXT("and one\nlaser. Note that during the setup of the grab, the ")
             MIL_TEXT("cameras were\nsynchronized so the same laser scan was provided ")
             MIL_TEXT("to all cameras\nat the same time.\n"));
   MosPrintf(MIL_TEXT("\n\n"));

   MosPrintf(MIL_TEXT("[MODULES USED]\n"));
   MosPrintf(MIL_TEXT("Modules used: Application, system, display, buffer, ")
             MIL_TEXT("graphic, \nimage processing, calibration,"));
   MosPrintf(MIL_TEXT(" 3d reconstruction, model finder, \n"));
   MosPrintf(MIL_TEXT("measurement, string reader. \n"));
   }

//*****************************************************************************
// Main.
//*****************************************************************************
int MosMain(void)
   {
   PrintHeader();

   // Allocate the MIL application.
   MIL_ID MilApplication = MappAlloc(M_NULL, M_DEFAULT, M_NULL);

   // Initialization.
   CExampleManagerFor3D* pExampleMngrFor3D = MakeExampleManager();
   if (!pExampleMngrFor3D)
      {
      MappFree(MilApplication);
      return -1;
      }

   MosPrintf(MIL_TEXT("Press <Enter> to start.\n\n"));
   MosGetch();

   //.......................................................................
   // 1. To calibrate the setup, the first step is to calibrate the cameras.
   // Camera calibration specifications.
   const MIL_DOUBLE COL_SPACING                [NUM_CAMERAS] = { 10.0, 10.0 };
   const MIL_DOUBLE ROW_SPACING                [NUM_CAMERAS] = { 10.0, 10.0 };
   const MIL_INT    NB_ROWS                    [NUM_CAMERAS] = { 22, 22 };
   const MIL_INT    NB_COLS                    [NUM_CAMERAS] = { 18, 18 };
   const MIL_DOUBLE CORNER_HINT_X              [NUM_CAMERAS] = { 1142, 1420 };
   const MIL_DOUBLE CORNER_HINT_Y              [NUM_CAMERAS] = { 259,    16 };
   const MIL_DOUBLE OFFSET_Z                   [NUM_CAMERAS] = { -28.49, -28.49 };
   const MIL_INT64  CALIBRATION_TYPE           [NUM_CAMERAS] = { M_CHESSBOARD_GRID, M_CHESSBOARD_GRID};
   const MIL_CONST_TEXT_PTR GRID_IMG_FILENAME  [NUM_CAMERAS] = { EX_PATH("Cam1_grid.mim"), EX_PATH("Cam2_grid.mim") };

   // Initialize data.
   SCameraCalibrationInfo CAMERA_CALIBRATION_INFO[NUM_CAMERAS];
   for (MIL_INT c = 0; c < NUM_CAMERAS; c++)
      {
      SCameraCalibrationInfo& CCI = CAMERA_CALIBRATION_INFO[c];
      CCI.CornerHintX          = CORNER_HINT_X[c];
      CCI.CornerHintY          = CORNER_HINT_Y[c];
      CCI.OffsetZ              = OFFSET_Z[c];
      CCI.NbRows               = NB_ROWS[c];
      CCI.NbCols               = NB_COLS[c];
      CCI.RowSpacing           = ROW_SPACING[c];
      CCI.ColSpacing           = COL_SPACING[c];
      CCI.CalibrationType      = CALIBRATION_TYPE[c];
      CCI.GridImageFilename    = GRID_IMG_FILENAME[c];
      CCI.Relocate             = NO_RELOCATE;
      CCI.RelocatedGridImageFilename = NULL;
      }

   //.................................
   // 1.1 Execute cameras calibration.
   MIL_ID CameraCalibrations[NUM_CAMERAS];
   bool CameraCalibrationOk = pExampleMngrFor3D->CalibrateCameras(CAMERA_CALIBRATION_INFO, NUM_CAMERAS, &CameraCalibrations[0]);

   //..................................................................
   // 2. Then continue to calibrate the laser planes (sheets-of-light).
   if(CameraCalibrationOk)
      {
      MosPrintf(MIL_TEXT("Press <Enter> to calibrate laser planes.\n\n"));
      MosGetch();

      // Sheet-of-Light (laser plane) calibration      
      const MIL_INT    NUM_REF_PLANES                    = 7;
      const MIL_DOUBLE CAL_MIN_CONTRAST    [NUM_CAMERAS] = {  30,  30 };
      const MIL_INT    CAL_NB_REF_PLANES   [NUM_CAMERAS] = { NUM_REF_PLANES, NUM_REF_PLANES };
      const MIL_INT    CAL_SCAN_ORIENTATION[NUM_CAMERAS] = { M_HORIZONTAL, M_HORIZONTAL };
      const MIL_INT    CAL_PEAK_WIDTH      [NUM_CAMERAS] = { 15, 15 };
      const MIL_INT    CAL_PEAK_WIDTH_DELTA[NUM_CAMERAS] = { 14, 14 };
      const MIL_INT    LASER_LABELS        [NUM_CAMERAS] = { 1, 1 };
      const MIL_INT    CAMERA_LABELS       [NUM_CAMERAS] = { 1, 2 };

      const MIL_DOUBLE PLANE_Z[NUM_CAMERAS][MAX_NB_REF_PLANES] =
         {  { -5.05, -10.91, -16.77, -22.63, -28.49, -34.35, -40.21 },
            { -5.05, -10.91, -16.77, -22.63, -28.49, -34.35, -40.21 } };

      const SRefPlaneInfo LASER_CALIBRATION_PLANES[NUM_CAMERAS][MAX_NB_REF_PLANES] = 
         { { // first camera
               // RefImageName                                Zs
               { EX_PATH("Cam1RefPlanes\\Cam1_laser_h0.mim"), PLANE_Z[0][0] },
               { EX_PATH("Cam1RefPlanes\\Cam1_laser_h1.mim"), PLANE_Z[0][1] },
               { EX_PATH("Cam1RefPlanes\\Cam1_laser_h2.mim"), PLANE_Z[0][2] },
               { EX_PATH("Cam1RefPlanes\\Cam1_laser_h3.mim"), PLANE_Z[0][3] },
               { EX_PATH("Cam1RefPlanes\\Cam1_laser_h4.mim"), PLANE_Z[0][4] },
               { EX_PATH("Cam1RefPlanes\\Cam1_laser_h5.mim"), PLANE_Z[0][5] },
               { EX_PATH("Cam1RefPlanes\\Cam1_laser_h6.mim"), PLANE_Z[0][6] }
            },
            { // second camera
               // RefImageName                                Zs
               { EX_PATH("Cam2RefPlanes\\Cam2_laser_h0.mim"), PLANE_Z[1][0] },
               { EX_PATH("Cam2RefPlanes\\Cam2_laser_h1.mim"), PLANE_Z[1][1] },
               { EX_PATH("Cam2RefPlanes\\Cam2_laser_h2.mim"), PLANE_Z[1][2] },
               { EX_PATH("Cam2RefPlanes\\Cam2_laser_h3.mim"), PLANE_Z[1][3] },
               { EX_PATH("Cam2RefPlanes\\Cam2_laser_h4.mim"), PLANE_Z[1][4] },
               { EX_PATH("Cam2RefPlanes\\Cam2_laser_h5.mim"), PLANE_Z[1][5] },
               { EX_PATH("Cam2RefPlanes\\Cam2_laser_h6.mim"), PLANE_Z[1][6] }
            } };
      
      const MIL_INT NUM_LASERS_PER_IMAGE = 1;

      SLineExtractionInROI CHILD_EXTRACTION_INFO;
      CHILD_EXTRACTION_INFO.OffsetX =  520;
      CHILD_EXTRACTION_INFO.OffsetY =    0;
      CHILD_EXTRACTION_INFO.SizeX   =  370;
      CHILD_EXTRACTION_INFO.SizeY   = 1200;

      SCameraLaserInfo LASER_CALIBRATION_INFO[NUM_CAMERAS * NUM_LASERS_PER_IMAGE];
      for(MIL_INT c = 0; c < NUM_CAMERAS; c++)
         {
         SCameraLaserInfo& LCI = LASER_CALIBRATION_INFO[c];

         LCI.NumLasersPerImage  = NUM_LASERS_PER_IMAGE;
         LCI.NumRefPlanes       = NUM_REF_PLANES;
         LCI.CalMinContrast     = CAL_MIN_CONTRAST[c];
         LCI.CalNbRefPlanes     = CAL_NB_REF_PLANES[c];
         LCI.CalScanOrientation = CAL_SCAN_ORIENTATION[c];
         LCI.CalPeakWidthNominal= CAL_PEAK_WIDTH[c];
         LCI.CalPeakWidthDelta  = CAL_PEAK_WIDTH_DELTA[c];
         for(MIL_INT l = 0; l < LCI.CalNbRefPlanes; l++)
            {
            LCI.LaserCalibrationPlanes[l] = LASER_CALIBRATION_PLANES[c][l];
            }
         LCI.LaserLabel  = LASER_LABELS[c];
         LCI.CameraLabel = CAMERA_LABELS[c];

         LCI.LineExtractionInROI = eLineChildROI;
         LCI.LineExtractionInROIInfo = CHILD_EXTRACTION_INFO;
         }

      //............................................................
      // 2.1 Execute the calibration of the laser planes.
      // Generates the needed calibrated camera-laser pair contexts.
      MIL_ID CameraLaserCtxts[NUM_CAMERAS * NUM_LASERS_PER_IMAGE];
      bool SheetOfLightOk = pExampleMngrFor3D->CalibrateSheetOfLight(&LASER_CALIBRATION_INFO[0],
                                                                     &CameraCalibrations[0],
                                                                     &CameraLaserCtxts[0]);
      if (SheetOfLightOk)
         {
         // Map generation specifications.
         const MIL_DOUBLE D3D_DISPLAY_REFRESH_PER_SEC = 0.75; // 3d Display FPS
         const MIL_INT    CAMERA_MAP_MIN_CONTRAST[]   = {  7,  7 };
         const MIL_INT    CAMERA_MAP_PEAK_WIDTH[]     = { 20 , 20 };
         const MIL_INT    CAMERA_MAP_PEAK_DELTA[]     = { 19 , 19 };
         const MIL_DOUBLE CAMERA_MAP_SCAN_SPEED[]     = { 0.2727, 0.2727 };
         const MIL_DOUBLE CAMERA_MAX_FRAMES           = 1024;
         const MIL_DOUBLE CAMERA_DISPLACEMENT_MODE    = M_CURRENT;

         // Visualization volume information.
         SMapGeneration MapData;
         MapData.BoxCornerX       = - 29.80;
         MapData.BoxCornerY       = -  0.21;
         MapData.BoxCornerZ       =    1.86;
         MapData.BoxSizeX         =  229.00;
         MapData.BoxSizeY         =  247.00;
         MapData.BoxSizeZ         = - 19.00;         
         MapData.MapSizeX         = 842;
         MapData.MapSizeY         = 906;
         MapData.PixelSizeX       = 0.273;
         MapData.PixelSizeY       = 0.273;
         MapData.GrayScaleZ       = (MapData.BoxSizeZ / 65534.0);
         MapData.IntensityMapType = 8 + M_UNSIGNED;
         MapData.SetExtractOverlap= false;
         MapData.ExtractOverlap   = M_DEFAULT;
         MapData.FillXThreshold   = 1.0;
         MapData.FillYThreshold   = 1.0;

         // Scan and analyze information.
         SPointCloudAcquisitionInfo SCAN_INFO =
            {
            // SD3DSysInfo
            { D3D_DISPLAY_REFRESH_PER_SEC, SHOW_NO_COLOR }, 
            { CAMERA_MAP_MIN_CONTRAST[0] , CAMERA_MAP_MIN_CONTRAST[1] },
            { CAMERA_MAP_PEAK_WIDTH[0]   , CAMERA_MAP_PEAK_WIDTH[1]   },
            { CAMERA_MAP_PEAK_DELTA[0]   , CAMERA_MAP_PEAK_DELTA[1]   },
            { CAMERA_MAP_SCAN_SPEED[0]   , CAMERA_MAP_SCAN_SPEED[1]   },
            CAMERA_MAX_FRAMES,
            CAMERA_DISPLACEMENT_MODE,
            eLineOffsetOnly,
            // SLineExtractionInROI
            { CHILD_EXTRACTION_INFO, CHILD_EXTRACTION_INFO },
            MapData,
            {  // DigInfo
               // DigFormat                SX  SY  SB Type NbFrames
               { EX_PATH("Cam1_tire1.avi"), 0,  0,  0,  0,  0 },
               { EX_PATH("Cam2_tire1.avi"), 0,  0,  0,  0,  0 }
            },
            MIL_TEXT("") // ScanDisplayText
            };

         // Update some information from the sequences on disk.
         for(MIL_INT d = 0; d < NUM_CAMERAS; d++)
            { SCAN_INFO.DigInfo[d].UpdateInfoFromDisk(); }

         //....................................................
         // 3. Acquire a 3d point cloud by scanning the object.
         //    The point cloud container will hold one point cloud per camera-laser pair.
         MIL_ID PointCloudContainer = M_NULL;
         bool PointCloudOk = pExampleMngrFor3D->AcquirePointCloud(eScan, &SCAN_INFO, CameraLaserCtxts, &PointCloudContainer);

         //.....................................................................................
         // 4. Generate the depth map (orthogonal 2d-projection) of the acquired 3d point cloud.
         MIL_ID TireDepthmap = M_NULL;
         pExampleMngrFor3D->GenerateDepthMap(PointCloudContainer, SCAN_INFO.MapVisualizationData, &TireDepthmap);

         //....................................
         // 5. Analyze the generated depth map.
         CTireStringRead ProbObj;
         pExampleMngrFor3D->AnalyzeDepthMap(&ProbObj, TireDepthmap);

         // Free camera-laser contexts.
         for (MIL_INT c = 0; c < NUM_CAMERAS; c++)
            {
            for (MIL_INT l = 0; l < NUM_LASERS_PER_IMAGE; l++)
               {
               MIL_ID& CameraLaserCtx = CameraLaserCtxts[(c*NUM_LASERS_PER_IMAGE) + l];
               if (CameraLaserCtx != M_NULL)
                  {
                  M3dmapFree(CameraLaserCtx);
                  CameraLaserCtx = M_NULL;
                  }
               }
            }

         M3dmapFree(PointCloudContainer);
         if(TireDepthmap != M_NULL)
            { MbufFree(TireDepthmap); }
         }
      }
   else
      {
      // A problem occurred calibrating the cameras.
      MosPrintf(MIL_TEXT("Press <Enter> to end.\n\n"));
      MosGetch();
      }

   // Free camera calibrations.
   for (MIL_INT c = 0; c < NUM_CAMERAS; c++)
      {
      if(CameraCalibrations[c] != M_NULL)
         {
         McalFree(CameraCalibrations[c]);
         CameraCalibrations[c] = M_NULL;
         }
      }

   delete pExampleMngrFor3D;
   pExampleMngrFor3D = NULL;

   // Free the MIL application.
   MappFree(MilApplication);

   return 0;
   }

//*******************************************************************************
// Function that analyzes the scanned object.
//*******************************************************************************
void CTireStringRead::Analyze(SCommonAnalysisObjects& CommonAnalysisObjects)
   {
   // Processing display zoom factor.
   const MIL_DOUBLE PROC_DISPLAY_ZOOM_FACTOR_X = 1;
   const MIL_DOUBLE PROC_DISPLAY_ZOOM_FACTOR_Y = 1;

   // Color specifications.
   const MIL_DOUBLE MEAS_COLOR        = M_COLOR_GREEN;
   const MIL_DOUBLE PROC_TEXT_COLOR   = M_COLOR_BLUE;


   const MIL_DOUBLE MEAS_RING_CENTER_X   = -220;
   const MIL_DOUBLE MEAS_RING_CENTER_Y   = 465;
   const MIL_DOUBLE MEAS_INNER_RADIUS    = 800;
   const MIL_DOUBLE MEAS_OUTER_RADIUS    = 870;
   const MIL_DOUBLE MEAS_NUM_SUB_REGIONS = 20;

   const MIL_DOUBLE POLAR_DELTA_RADIUS = 250;
   const MIL_DOUBLE POLAR_START_ANGLE  = 25;
   const MIL_DOUBLE POLAR_END_ANGLE    =-15;

   const MIL_INT FIRST_CHILD_OFFSET_X = -391;
   const MIL_INT FIRST_CHILD_OFFSET_Y = -20;
   const MIL_INT FIRST_CHILD_SIZE_X   = 340;
   const MIL_INT FIRST_CHILD_SIZE_Y   = 40;

   const MIL_INT SECOND_CHILD_OFFSET_X = 0;
   const MIL_INT SECOND_CHILD_OFFSET_Y = -13;
   const MIL_INT SECOND_CHILD_SIZE_X   = 295;
   const MIL_INT SECOND_CHILD_SIZE_Y   = 33;

   MIL_ID MilSystem        = CommonAnalysisObjects.MilSystem;
   MIL_ID MilGraphics      = CommonAnalysisObjects.MilGraphics;
   MIL_ID MilGraphicList   = CommonAnalysisObjects.MilGraphicList;
   MIL_ID MilDepthMap      = CommonAnalysisObjects.MilDepthMap;
   CMILDisplayManager* MilDisplayMngr  = CommonAnalysisObjects.MilDisplays;

   MilDisplayMngr->UpdateEnabled(false);

   // Setup the display.
   MgraClear(M_DEFAULT, MilGraphicList);   
   MilDisplayMngr->Zoom(PROC_DISPLAY_ZOOM_FACTOR_X, PROC_DISPLAY_ZOOM_FACTOR_Y);

   // Allocate the necessary buffers for processing.
   MIL_ID MilEqualizedImage, MilRemapped8BitImage;
   MbufAlloc2d(MilSystem,
               MbufInquire(MilDepthMap, M_SIZE_X, M_NULL),
               MbufInquire(MilDepthMap, M_SIZE_Y, M_NULL),
               16 + M_UNSIGNED, M_IMAGE + M_PROC + M_DISP, &MilEqualizedImage);

   MbufAlloc2d(MilSystem,
               MbufInquire(MilDepthMap, M_SIZE_X, M_NULL),
               MbufInquire(MilDepthMap, M_SIZE_Y, M_NULL),
               8 + M_UNSIGNED, M_IMAGE + M_PROC + M_DISP, &MilRemapped8BitImage);

   MbufClear(MilEqualizedImage, 0);
   MbufClear(MilRemapped8BitImage, 0);

   // Do an adaptive equalize of the depth map image.
   MimHistogramEqualizeAdaptive(m_MilAdaptiveEqualizeContext, 
                                MilDepthMap, 
                                MilEqualizedImage, M_DEFAULT);

   // Remap to 8 bit.
   MimShift(MilEqualizedImage, MilRemapped8BitImage, -8);

   // Find the arc using measurement.
   MmeasSetMarker(m_MilCircleMarker, M_POLARITY, M_NEGATIVE, M_NEGATIVE);

   MmeasSetMarker(m_MilCircleMarker, M_RING_CENTER, MEAS_RING_CENTER_X, MEAS_RING_CENTER_Y);
   MmeasSetMarker(m_MilCircleMarker, M_RING_RADII , MEAS_INNER_RADIUS, MEAS_OUTER_RADIUS);
   MmeasSetMarker(m_MilCircleMarker, M_SUB_REGIONS_NUMBER, MEAS_NUM_SUB_REGIONS, M_NULL);  

   // Find the circle and measure its position and radius.
   MmeasFindMarker(M_DEFAULT, MilRemapped8BitImage, m_MilCircleMarker, M_DEFAULT);

   // If occurrence is found, show the results.
   MIL_INT NumberOccurrencesFound = 0;
   MmeasGetResult(m_MilCircleMarker, M_NUMBER + M_TYPE_MIL_INT, &NumberOccurrencesFound, M_NULL);

   if (NumberOccurrencesFound >= 1)
      {
      MIL_DOUBLE CircleCenterX, CircleCenterY, CircleRadius;
      MmeasSetMarker(m_MilCircleMarker, M_RESULT_OUTPUT_UNITS, M_PIXEL, M_NULL);
      MmeasGetResult(m_MilCircleMarker, M_POSITION, &CircleCenterX, &CircleCenterY);
      MmeasGetResult(m_MilCircleMarker, M_RADIUS  , &CircleRadius, M_NULL);

      // Using the circle, unwrap with a polar transform.
      MIL_DOUBLE  SizeRadius,SizeAngle;
      MimPolarTransform(MilRemapped8BitImage, M_NULL, 
                        CircleCenterX, CircleCenterY, 
                        CircleRadius-POLAR_DELTA_RADIUS, 
                        CircleRadius+POLAR_DELTA_RADIUS, 
                        POLAR_START_ANGLE, POLAR_END_ANGLE, 
                        M_RECTANGULAR_TO_POLAR, 
                        M_NEAREST_NEIGHBOR + M_OVERSCAN_ENABLE, 
                        &SizeAngle, &SizeRadius);
   
      MIL_INT SizeX = (MIL_INT)ceil(SizeAngle);
      MIL_INT SizeY = (MIL_INT)ceil(SizeRadius);

      MIL_ID MilUnWrappedImage =
      MbufAlloc2d(MilSystem, SizeX, SizeY, 8, M_IMAGE + M_PROC + M_DISP, M_NULL);
      MbufClear(MilUnWrappedImage, 0);

      MimPolarTransform(MilRemapped8BitImage, MilUnWrappedImage, 
                        CircleCenterX, CircleCenterY, 
                        CircleRadius-POLAR_DELTA_RADIUS, 
                        CircleRadius+POLAR_DELTA_RADIUS, 
                        POLAR_START_ANGLE, POLAR_END_ANGLE, 
                        M_RECTANGULAR_TO_POLAR, 
                        M_NEAREST_NEIGHBOR + M_OVERSCAN_ENABLE, 
                        &SizeAngle, &SizeRadius);

      MilDisplayMngr->Show(MilUnWrappedImage);

      // Clear the graphics list.
      MgraClear(M_DEFAULT, MilGraphicList);

      MilDisplayMngr->UpdateEnabled(true);

      // Find the model shape around the second string.
      MmodFind(m_MilModel, MilUnWrappedImage, m_MilModelResult);

      MIL_INT NumOfOccurences = 0;
      MmodGetResult(m_MilModelResult, M_DEFAULT, M_NUMBER + M_TYPE_MIL_INT, &NumOfOccurences);

      if (NumOfOccurences >= 1)
         {
         // Get the reference point position.
         MIL_DOUBLE RefPointX, RefPointY;

         MmodControl(m_MilModelResult, M_DEFAULT, M_RESULT_OUTPUT_UNITS, M_PIXEL);

         MmodGetResult(m_MilModelResult, M_DEFAULT, M_POSITION_X + M_TYPE_MIL_DOUBLE, &RefPointX);
         MmodGetResult(m_MilModelResult, M_DEFAULT, M_POSITION_Y + M_TYPE_MIL_DOUBLE, &RefPointY);

         // Create a child around the two strings relative to the reference point.
         MIL_INT FirstOffsetX = (MIL_INT)(RefPointX + FIRST_CHILD_OFFSET_X);
         MIL_INT FirstOffsetY = (MIL_INT)(RefPointY + FIRST_CHILD_OFFSET_Y);

         MIL_ID MilFirstStringChildImage = 
         MbufChild2d(MilUnWrappedImage, FirstOffsetX, FirstOffsetY, FIRST_CHILD_SIZE_X, FIRST_CHILD_SIZE_Y, M_NULL);

         MIL_INT SecondOffsetX = (MIL_INT)RefPointX + SECOND_CHILD_OFFSET_X;
         MIL_INT SecondOffsetY = (MIL_INT)(RefPointY + SECOND_CHILD_OFFSET_Y);

         MIL_ID MilSecondStringChildImage =
         MbufChild2d(MilUnWrappedImage, SecondOffsetX, SecondOffsetY, SECOND_CHILD_SIZE_X, SECOND_CHILD_SIZE_Y, M_NULL);

         // Read the first string.
         MstrRead(m_MilFirstStringReader, MilFirstStringChildImage, m_MilFirstStringReaderResult);
         // Read the second string.
         MstrRead(m_MilSecondStringReader, MilSecondStringChildImage, m_MilSecondStringReaderResult);

         MIL_INT NumberOfStringRead = 0;
         MstrGetResult(m_MilFirstStringReaderResult, M_GENERAL, M_STRING_NUMBER + M_TYPE_MIL_INT, &NumberOfStringRead);

         MgraControl(MilGraphics, M_BACKGROUND_MODE, M_OPAQUE);
         MgraColor(MilGraphics, M_COLOR_GREEN);
         MgraControl(MilGraphics, M_FONT_SIZE, TEXT_FONT_SIZE_SMALL);
         MgraText(MilGraphics, MilGraphicList, TEXT_OFFSET_X, TEXT_OFFSET_Y, MIL_TEXT("Read strings in unwrapped depth map"));

         // Show the wrapped tire zoomed out in the top right corner.
         const MIL_DOUBLE ZoomFactor = 0.3;

         MIL_INT ZoomSizeX = (MIL_INT)(MbufInquire(MilRemapped8BitImage, M_SIZE_X, M_NULL) * ZoomFactor);
         MIL_INT ZoomSizeY = (MIL_INT)(MbufInquire(MilRemapped8BitImage, M_SIZE_Y, M_NULL) * ZoomFactor);

         MIL_ID MilResizedImage = 
         MbufAlloc2d(MilSystem, ZoomSizeX, ZoomSizeY, 8, M_IMAGE + M_PROC, M_NULL);
         MimResize(MilRemapped8BitImage, MilResizedImage, M_FILL_DESTINATION, M_FILL_DESTINATION, M_BICUBIC);

         MIL_ID MilRotatedImage = MbufAlloc2d(MilSystem, ZoomSizeY, ZoomSizeX, 8, M_IMAGE + M_PROC, M_NULL);
         MimRotate(MilResizedImage, MilRotatedImage, 90, (MIL_DOUBLE)(ZoomSizeX/2), 
                  (MIL_DOUBLE)(ZoomSizeY/2), (MIL_DOUBLE)(ZoomSizeY/2), 
                  (MIL_DOUBLE)(ZoomSizeX/2), M_BICUBIC);

         const MIL_INT TireOffsetY = 50;
         const MIL_INT TireSizeY = 550;

         MIL_ID MilUnWrappedImageChild =
         MbufChild2d(MilUnWrappedImage,
                     MbufInquire(MilUnWrappedImage, M_SIZE_X, M_NULL)-ZoomSizeY,         
                     0, ZoomSizeY, (MIL_INT)(TireSizeY*ZoomFactor), M_NULL);

         MbufCopyColor2d(MilRotatedImage, MilUnWrappedImageChild, M_ALL_BANDS, 0, 
                         (MIL_INT)(TireOffsetY*ZoomFactor), M_ALL_BANDS, 0, 0, ZoomSizeY, 
                         (MIL_INT)(TireSizeY*ZoomFactor));

         MbufFree(MilResizedImage);
         MbufFree(MilRotatedImage);

         // Annotate the acquired depth map.
         MgraControl(MilGraphics, M_BACKGROUND_MODE, M_OPAQUE);
         MgraColor(MilGraphics, M_COLOR_GREEN);
         MgraControl(MilGraphics, M_FONT_SIZE, TEXT_FONT_SIZE_SMALL);
         MgraText(MilGraphics, MilGraphicList, 
                  MbufInquire(MilUnWrappedImage, M_SIZE_X, M_NULL)-ZoomSizeY+TEXT_OFFSET_X, 
                  TEXT_OFFSET_Y, MIL_TEXT("Acquired depth map"));

         MgraControl(MilGraphics, M_BACKGROUND_MODE, M_TRANSPARENT);

         if(NumberOfStringRead >= 1)
            {
            // Draw the first string.
            MgraColor(MilGraphics, PROC_TEXT_COLOR);
            MgraText(MilGraphics, MilGraphicList, FirstOffsetX, FirstOffsetY-30, MIL_TEXT("Embossed"));

            MgraControl(MilGraphics, M_DRAW_OFFSET_X, -FirstOffsetX);
            MgraControl(MilGraphics, M_DRAW_OFFSET_Y, -FirstOffsetY);
            MstrDraw(MilGraphics, m_MilFirstStringReaderResult, MilGraphicList, M_DRAW_STRING, M_ALL, M_NULL, M_DEFAULT);
            }
         else
            {
            MosPrintf(MIL_TEXT("Required string was not found.\n"));
            MosPrintf(MIL_TEXT("Press <Enter> to continue.\n\n"));
            MosGetch();
            }

         NumberOfStringRead = 0;
         MstrGetResult(m_MilSecondStringReaderResult, M_GENERAL, M_STRING_NUMBER + M_TYPE_MIL_INT, &NumberOfStringRead);

         if( NumberOfStringRead >= 1)
            {
            // Draw the second string.
            MgraControl(MilGraphics, M_DRAW_OFFSET_X, M_DEFAULT);
            MgraControl(MilGraphics, M_DRAW_OFFSET_Y, M_DEFAULT);

            MgraColor(MilGraphics, PROC_TEXT_COLOR);
            MgraText(MilGraphics, MilGraphicList, SecondOffsetX, SecondOffsetY-30, 
               MIL_TEXT("Imprinted"));

            MgraControl(MilGraphics, M_DRAW_OFFSET_X, -SecondOffsetX);
            MgraControl(MilGraphics, M_DRAW_OFFSET_Y, -SecondOffsetY);

            MstrDraw(MilGraphics, m_MilSecondStringReaderResult, MilGraphicList, M_DRAW_STRING, M_ALL, M_NULL, M_DEFAULT);

            MosPrintf(MIL_TEXT("A polar transform was done to unroll the tire's ")
                      MIL_TEXT("sidewall and\nthe two strings have been read.\n"));
            MosPrintf(MIL_TEXT("Press <Enter> to continue.\n\n"));
            MosGetch();
            }
         else
            {
            MosPrintf(MIL_TEXT("Required string was not found.\n"));
            MosPrintf(MIL_TEXT("Press <Enter> to continue.\n\n"));
            MosGetch();
            }

         MbufFree(MilUnWrappedImageChild);
         MbufFree(MilFirstStringChildImage);
         MbufFree(MilSecondStringChildImage);
         }
      else
         {
         MosPrintf(MIL_TEXT("Required model was not found.\n"));
         MosPrintf(MIL_TEXT("Press <Enter> to continue.\n\n"));
         MosGetch();
         }

      MbufFree(MilUnWrappedImage);
      }
   else
      {
      MosPrintf(MIL_TEXT("Required circle was not found.\n"));
      MosPrintf(MIL_TEXT("Press <Enter> to continue.\n\n"));
      MosGetch();
      }

   MbufFree(MilEqualizedImage);
   MbufFree(MilRemapped8BitImage);
   }

//*******************************************************************************
// Function that allocates processing objects.
//*******************************************************************************
void CTireStringRead::AllocProcessingObjects(MIL_ID MilSystem)
   {
   const MIL_TEXT_CHAR* FIRST_STRING_FONT   = EX_PATH("FirstStringFont.msr");
   const MIL_TEXT_CHAR* SECOND_STRING_FONT  = EX_PATH("SecondStringFont.msr");
   const MIL_TEXT_CHAR* SECOND_STRING_MODEL = EX_PATH("SecondStringModel.mmf");

   MimAlloc(MilSystem, M_HISTOGRAM_EQUALIZE_ADAPTIVE_CONTEXT, M_DEFAULT, &m_MilAdaptiveEqualizeContext);
   MmeasAllocMarker(MilSystem, M_CIRCLE, M_DEFAULT, &m_MilCircleMarker);      
   MmodAllocResult(MilSystem, M_DEFAULT, &m_MilModelResult);

   MmodRestore(SECOND_STRING_MODEL, MilSystem, M_DEFAULT, &m_MilModel);
   MmodPreprocess(m_MilModel, M_DEFAULT);

   // Restore the first string reader context.
   MstrRestore(FIRST_STRING_FONT, MilSystem, M_DEFAULT, &m_MilFirstStringReader);
   MstrAllocResult(MilSystem, M_DEFAULT, &m_MilFirstStringReaderResult);
   MstrPreprocess(m_MilFirstStringReader, M_DEFAULT);

   // Restore the second string reader context.
   MstrRestore(SECOND_STRING_FONT, MilSystem, M_DEFAULT, &m_MilSecondStringReader);
   MstrAllocResult(MilSystem, M_DEFAULT, &m_MilSecondStringReaderResult);
   MstrPreprocess(m_MilSecondStringReader, M_DEFAULT);
   }

//*******************************************************************************
// Function that frees processing objects.
//*******************************************************************************
void CTireStringRead::FreeProcessingObjects()
   {
   MimFree(m_MilAdaptiveEqualizeContext);    m_MilAdaptiveEqualizeContext  = M_NULL;
   MmeasFree(m_MilCircleMarker);             m_MilCircleMarker             = M_NULL;
   MmodFree(m_MilModel);                     m_MilModel                    = M_NULL;
   MmodFree(m_MilModelResult);               m_MilModelResult              = M_NULL;

   MstrFree(m_MilFirstStringReader);         m_MilFirstStringReader        = M_NULL;
   MstrFree(m_MilFirstStringReaderResult);   m_MilFirstStringReaderResult  = M_NULL;
   MstrFree(m_MilSecondStringReader);        m_MilSecondStringReader       = M_NULL;
   MstrFree(m_MilSecondStringReaderResult);  m_MilSecondStringReaderResult = M_NULL;
   }