//***************************************************************************************/
// 
// File name: CircleMeasurement.cpp  
// Location: See Matrox Example Launcher in the MIL Control Center
// 
//
// Synopsis:  This program contains an example of circle measurement using the measurement
//            module. See the PrintHeader() function below for detailed description.
//
// Copyright (C) Matrox Electronic Systems Ltd., 1992-2020.
// All Rights Reserved

#include <mil.h>

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

   MosPrintf(MIL_TEXT("[SYNOPSIS]\n"));
   MosPrintf(MIL_TEXT("This example shows how to use some controls and score functions\n"));
   MosPrintf(MIL_TEXT("to measure four circles.\n\n"));

   MosPrintf(MIL_TEXT("[MODULES USED]\n"));
   MosPrintf(MIL_TEXT("Modules used: application, system, display, buffer, graphic, measurement.\n\n"));

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

// Circle measurement function prototypes.
void MeasureCircle(MIL_ID MilSystem, MIL_ID MilDisplay, MIL_INT ImageIndex);

void IntroMessageForCircle0();
void IntroMessageForCircle1();
void IntroMessageForCircle2();
void IntroMessageForCircle3();

void SetupForCircle1(MIL_ID MilCircleMarker);
void SetupForCircle2(MIL_ID MilCircleMarker);
void SetupForCircle3(MIL_ID MilCircleMarker);

void FindMarkerAndShowResults(MIL_ID MilDisplay,
                              MIL_ID MilImage,
                              MIL_ID MilOverlayImage,
                              MIL_ID MilCircleMarker);

//****************************************************************************
// Constants.
//****************************************************************************

// Filenames.
#define EXAMPLE_IMAGE_PATH    M_IMAGE_PATH MIL_TEXT("CircleMeasurement/")

static const MIL_INT NB_IMAGES = 4;
static MIL_CONST_TEXT_PTR IMAGE_FILENAME[NB_IMAGES] =
   {
   EXAMPLE_IMAGE_PATH MIL_TEXT("circle0.mim"),
   EXAMPLE_IMAGE_PATH MIL_TEXT("circle1.mim"),
   EXAMPLE_IMAGE_PATH MIL_TEXT("circle2.mim"),
   EXAMPLE_IMAGE_PATH MIL_TEXT("circle3.mim")
   };

// Draw colors.
static const MIL_DOUBLE POSITION_COLOR      = M_COLOR_YELLOW;
static const MIL_DOUBLE SUB_POSITIONS_COLOR = M_COLOR_GREEN;
static const MIL_DOUBLE RING_COLOR          = M_COLOR_BLUE;
static const MIL_DOUBLE EDGES_COLOR         = M_COLOR_RED;

// Search settings.
static const MIL_DOUBLE POLARITY         [NB_IMAGES] = {M_ANY, M_NEGATIVE, M_NEGATIVE, M_POSITIVE};
static const MIL_DOUBLE RING_CENTER_POS_X[NB_IMAGES] = {228.0,      333.0,      260.0,      270.0};
static const MIL_DOUBLE RING_CENTER_POS_Y[NB_IMAGES] = {282.0,      240.0,      230.0,      225.0};
static const MIL_DOUBLE RING_INNER_RADIUS[NB_IMAGES] = { 15.0,       50.0,       20.0,       50.0};
static const MIL_DOUBLE RING_OUTER_RADIUS[NB_IMAGES] = {150.0,      235.0,      200.0,      200.0};

// Maximum number of pixels of deviation from the circle radius.
static const MIL_DOUBLE MAX_DEVIATION = 2.0;

// Edge threshold to use in noisy images
static const MIL_DOUBLE HIGHER_EDGEVALUE = 5.0;

//*****************************************************************************
// Main.
//*****************************************************************************
int MosMain(void)
   {
   // Allocate MIL objects. 
   MIL_ID MilApplication = MappAlloc(M_NULL, M_DEFAULT, M_NULL);
   MIL_ID MilSystem      = MsysAlloc(M_DEFAULT, M_SYSTEM_HOST, M_DEFAULT, M_DEFAULT, M_NULL);
   MIL_ID MilDisplay     = MdispAlloc(MilSystem, M_DEFAULT, MIL_TEXT("M_DEFAULT"), M_WINDOWED, M_NULL);

   PrintHeader();

   for (MIL_INT ImageIndex = 0; ImageIndex < NB_IMAGES; ++ImageIndex)
      MeasureCircle(MilSystem, MilDisplay, ImageIndex);

   // Free MIL objects.
   MdispFree(MilDisplay);
   MsysFree(MilSystem);
   MappFree(MilApplication);

   return 0;
   }

//***************************************************************************
// Create and setup a circle marker to measure the center and radius of a
// circle in a given image.
//***************************************************************************
void MeasureCircle(MIL_ID MilSystem, MIL_ID MilDisplay, MIL_INT ImageIndex)
   {
   MosPrintf(MIL_TEXT("CIRCLE %d\n")
             MIL_TEXT("========\n\n"),
             ImageIndex);

   switch (ImageIndex)
      {
      case 0: IntroMessageForCircle0(); break;
      case 1: IntroMessageForCircle1(); break;
      case 2: IntroMessageForCircle2(); break;
      case 3: IntroMessageForCircle3(); break;
      }

   // Restore and display the source image.
   MIL_ID MilImage = MbufRestore(IMAGE_FILENAME[ImageIndex], MilSystem, M_NULL);
   MdispSelect(MilDisplay, MilImage);

   // Prepare for overlay annotation.
   MdispControl(MilDisplay, M_OVERLAY, M_ENABLE);
   MIL_ID MilOverlayImage = MdispInquire(MilDisplay, M_OVERLAY_ID, M_NULL);

   // Allocate circle marker and setup search region and polarity.
   MIL_ID MilCircleMarker = MmeasAllocMarker(MilSystem, M_CIRCLE, M_DEFAULT, M_NULL);

   MmeasSetMarker(MilCircleMarker, M_POLARITY   , POLARITY[ImageIndex]         , M_NULL                       );
   MmeasSetMarker(MilCircleMarker, M_RING_CENTER, RING_CENTER_POS_X[ImageIndex], RING_CENTER_POS_Y[ImageIndex]);
   MmeasSetMarker(MilCircleMarker, M_RING_RADII , RING_INNER_RADIUS[ImageIndex], RING_OUTER_RADIUS[ImageIndex]);

   if (ImageIndex != 0)
      {
      MosPrintf(MIL_TEXT("If you measure the circle with default settings, results are not as expected.\n\n"));
      }

   // Measure circle and print results.
   FindMarkerAndShowResults(MilDisplay, MilImage, MilOverlayImage, MilCircleMarker);

   // Pause to show results.
   if (ImageIndex == 0)
      {
      MosPrintf(MIL_TEXT("Press <Enter> to continue.\n\n"));
      }
   else
      {
      MosPrintf(MIL_TEXT("Press <Enter> to measure again, using custom settings.\n\n"));
      }
   MosGetch();

   // Clear the overlay image.
   MdispControl(MilDisplay, M_OVERLAY_CLEAR, M_DEFAULT);

   // Except for the first case, measure again using new settings.
   if (ImageIndex != 0)
      {
      MosPrintf(MIL_TEXT("----------\n\n"));

      // Set up controls and score functions that are specific to each circle.
      switch (ImageIndex)
         {
         case 1: SetupForCircle1(MilCircleMarker); break;
         case 2: SetupForCircle2(MilCircleMarker); break;
         case 3: SetupForCircle3(MilCircleMarker); break;
         }

      // Measure circle and print results.
      FindMarkerAndShowResults(MilDisplay, MilImage, MilOverlayImage, MilCircleMarker);

      // Pause to show results.
      if (ImageIndex < NB_IMAGES-1)
         MosPrintf(MIL_TEXT("Press <Enter> to continue.\n\n"));
      else
         MosPrintf(MIL_TEXT("Press <Enter> to end.\n\n"));
      MosGetch();

      // Clear the overlay image.
      MdispControl(MilDisplay, M_OVERLAY_CLEAR, M_DEFAULT);
      }

   // Free all allocations.
   MmeasFree(MilCircleMarker);
   MbufFree(MilImage);
   }

//***************************************************************************
// Print explanations for circle 0
//***************************************************************************
void IntroMessageForCircle0()
   {
   MosPrintf(MIL_TEXT("In simple cases, only the search region (in blue) needs to be set, using\n")
             MIL_TEXT("M_RING_CENTER and M_RING_RADII; all other settings can be left to their\n")
             MIL_TEXT("default.\n\n"));
   }

//***************************************************************************
// Print explanations for circle 1
//***************************************************************************
void IntroMessageForCircle1()
   {
   MosPrintf(MIL_TEXT("In this case, the contour of the outer circle forms a double edge; however,\n")
             MIL_TEXT("the circle must be measured along its outer edge only.\n\n"));
   }

//***************************************************************************
// Specific circle marker setup to measure circle 1.
//***************************************************************************
void SetupForCircle1(MIL_ID MilCircleMarker)
   {
   // Ensures that all edges contributing to the measurement are near the circle.
   MmeasSetMarker(MilCircleMarker, M_MAX_ASSOCIATION_DISTANCE, MAX_DEVIATION, M_NULL);

   MosPrintf(MIL_TEXT("For more accurate measurements, the following setting is used:\n\n")
             MIL_TEXT("   M_MAX_ASSOCIATION_DISTANCE\n")
             MIL_TEXT("      This ensures that all edges contributing to the measurement are near\n")
             MIL_TEXT("      the circle.\n\n"));
   }

//***************************************************************************
// Print explanations for circle 2
//***************************************************************************
void IntroMessageForCircle2()
   {
   MosPrintf(MIL_TEXT("The object in this image is textured; therefore, many edges could be detected\n")
             MIL_TEXT("inside the circle. It is also an incomplete circle, so edges on the straight\n")
             MIL_TEXT("side must not be considered.\n\n"));
   }

//***************************************************************************
// Specific circle marker setup to measure circle 2.
//***************************************************************************
void SetupForCircle2(MIL_ID MilCircleMarker)
   {
   // Ensures all edges contributing to measurement are near the circle.
   MmeasSetMarker(MilCircleMarker, M_MAX_ASSOCIATION_DISTANCE, MAX_DEVIATION, M_NULL);

   // Only consider stronger edges, useful in noisy images.
   MmeasSetMarker(MilCircleMarker, M_EDGEVALUE_MIN, HIGHER_EDGEVALUE, M_NULL);

   MosPrintf(MIL_TEXT("For more accurate measurements, the following settings are used:\n\n")
             MIL_TEXT("   M_MAX_ASSOCIATION_DISTANCE\n")
             MIL_TEXT("      This ensures that all edges contributing to the measurement are near\n")
             MIL_TEXT("      the circle.\n")
             MIL_TEXT("   M_EDGEVALUE_MIN\n")
             MIL_TEXT("      This ensures that only stronger edges are considered, which is useful\n")
             MIL_TEXT("      in noisy images.\n\n"));
   }

//***************************************************************************
// Print explanations for circle 3
//***************************************************************************
void IntroMessageForCircle3()
   {
   MosPrintf(MIL_TEXT("This image contains concentric circles. A criterion must be added to\n")
             MIL_TEXT("select a specific circle; in this case, the innermost circle.\n\n"));
   }

//***************************************************************************
// Specific circle marker setup to measure circle 3.
//***************************************************************************
void SetupForCircle3(MIL_ID MilCircleMarker)
   {
   // Ensures that all edges contributing to the measurement are near the circle.
   MmeasSetMarker(MilCircleMarker, M_MAX_ASSOCIATION_DISTANCE, MAX_DEVIATION, M_NULL);

   // Removes strength criterion (replaces it by a flat function).
   MmeasSetScore(MilCircleMarker, M_STRENGTH_SCORE,
                 0, 0, M_MAX_POSSIBLE_VALUE, M_MAX_POSSIBLE_VALUE,  // flat score function
                 M_DEFAULT, M_DEFAULT, M_DEFAULT);

   // Adds radius criterion, chooses smallest circle (decreasing ramp function).
   MmeasSetScore(MilCircleMarker, M_RADIUS_SCORE,
                 0, 0, 0, M_MAX_POSSIBLE_VALUE,   // decreasing ramp score function
                 M_DEFAULT, M_DEFAULT, M_DEFAULT);

   MosPrintf(MIL_TEXT("For more accurate measurements, the following settings are used:\n\n")
             MIL_TEXT("   M_MAX_ASSOCIATION_DISTANCE\n")
             MIL_TEXT("      This ensures that all edges contributing to the measurement are near\n")
             MIL_TEXT("      the circle.\n")
             MIL_TEXT("   M_STRENGTH_SCORE\n")
             MIL_TEXT("      This removes the strength criterion (replaces it by a flat function).\n")
             MIL_TEXT("   M_RADIUS_SCORE\n")
             MIL_TEXT("      This adds a radius criterion, which chooses the smallest circle\n")
             MIL_TEXT("      (decreasing ramp function).\n\n"));
   }

//***************************************************************************
// Measure circle and print results.
//***************************************************************************
void FindMarkerAndShowResults(MIL_ID MilDisplay,
                              MIL_ID MilImage,
                              MIL_ID MilOverlayImage,
                              MIL_ID MilCircleMarker)
   {
   // Draw the contour of the measurement search region.
   MgraColor(M_DEFAULT, RING_COLOR);
   MmeasDraw(M_DEFAULT, MilCircleMarker, MilOverlayImage, M_DRAW_SEARCH_REGION, M_DEFAULT, M_MARKER);

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

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

   if (NumberOccurrencesFound >= 1)
      {
      MIL_DOUBLE CircleCenterX, CircleCenterY, CircleRadius;
      MmeasGetResult(MilCircleMarker, M_POSITION, &CircleCenterX, &CircleCenterY);
      MmeasGetResult(MilCircleMarker, M_RADIUS  , &CircleRadius , M_NULL        );

      // Draw a cross on the center of the found circle.
      MgraColor(M_DEFAULT, POSITION_COLOR);
      MmeasDraw(M_DEFAULT, MilCircleMarker, MilOverlayImage, M_DRAW_POSITION, M_DEFAULT, M_RESULT);

      // Draw the found circle itself.
      MgraColor(M_DEFAULT, EDGES_COLOR);
      MmeasDraw(M_DEFAULT, MilCircleMarker, MilOverlayImage, M_DRAW_EDGES, M_DEFAULT, M_RESULT);

      // Draw the edges used to fit the circle.
      MgraColor(M_DEFAULT, SUB_POSITIONS_COLOR);
      MmeasDraw(M_DEFAULT, MilCircleMarker, MilOverlayImage, M_DRAW_SUB_POSITIONS, M_DEFAULT, M_RESULT);

      // Print the result.
      MosPrintf(MIL_TEXT("The circle was found (in red):\n")
                MIL_TEXT("   Center position (in yellow): (%.2f, %.2f)\n")
                MIL_TEXT("   Radius: %.2f\n\n"),
                CircleCenterX, CircleCenterY, CircleRadius);
      }
   else
      {
      // Print the result.
      MosPrintf(MIL_TEXT("The circle was not found.\n\n"));
      }
   }