//***************************************************************************************
// 
// File name: defectdetectionprocfunc.cpp
// Location: See Matrox Example Launcher in the MIL Control Center
// 
//
// Synopsis: This file contains the processing functions declarations
//           that are used to perform some simple defect detection.
//
// Copyright (C) Matrox Electronic Systems Ltd., 1992-2020.
// All Rights Reserved

#include <mil.h>
#include <math.h>
#include "defectdetectionprocfunc.h"

//*****************************************************************************
// Remaps to an 8 bit image. 
//*****************************************************************************
void Remap8BitImage(MIL_ID MilSrcImage,
                    MIL_ID MilDestImage,
                    MIL_INT StartIndex,
                    MIL_INT EndIndex,
                    MIL_DOUBLE MinValue,
                    MIL_DOUBLE MaxValue)
   {
   // Work on the same system of the SrcBufferId.
   MIL_ID MilSystem = MbufInquire(MilSrcImage, M_OWNER_SYSTEM, M_NULL);
     
   // Alloc the LUT.
   MIL_ID MilLut = MbufAlloc1d(MilSystem, 256, 32+M_FLOAT, M_LUT, M_NULL);
   
   // Generate the ramp.
   MgenLutRamp(MilLut, 0, MinValue, StartIndex, MinValue);
   MgenLutRamp(MilLut, StartIndex, MinValue, EndIndex, MaxValue);
   MgenLutRamp(MilLut, EndIndex, MaxValue, 255, MaxValue);
   
   // Apply the mapping.
   MimLutMap(MilSrcImage, MilDestImage, MilLut);

   // Free the buffers.
   MbufFree(MilLut);
   }

//*****************************************************************************
// Creates a gradient mask.
//*****************************************************************************
void CreateGradientMaskImage(MIL_ID MilTemplateImage,
                            MIL_ID MilTemplateLumImage,
                            MIL_DOUBLE GradientSmoothness,
                            MIL_ID *MilGradientMaskImagePtr,
                            MIL_ID *MilGradientLumMaskImagePtr)
   {
   // Get the owner system of the template image.
   MIL_ID MilSystem = MbufInquire(MilTemplateLumImage, M_OWNER_SYSTEM, M_NULL);

   // Get the size of the template image.
   MIL_INT TemplateSizeX = MbufInquire(MilTemplateLumImage, M_SIZE_X, M_NULL);
   MIL_INT TemplateSizeY = MbufInquire(MilTemplateLumImage, M_SIZE_Y, M_NULL);

   // Allocate the gradient mask images.
   MIL_ID MilGradientMaskColorImage = MbufAllocColor(MilSystem, 3, TemplateSizeX, TemplateSizeY, 8+M_UNSIGNED, M_IMAGE+M_PROC, M_NULL);
   MIL_ID MilGradientLumMaskImage = MbufAlloc2d(MilSystem, TemplateSizeX, TemplateSizeY, 8+M_UNSIGNED, M_IMAGE+M_PROC, M_NULL);
   MIL_ID MilGradientMaskImage = MbufAlloc2d(MilSystem, TemplateSizeX, TemplateSizeY, 8+M_UNSIGNED, M_IMAGE+M_PROC, M_NULL);

   MIL_ID MilLinearFilterIIRContext = MimAlloc(MilSystem, M_LINEAR_FILTER_IIR_CONTEXT, M_DEFAULT, M_NULL);
   MimControl(MilLinearFilterIIRContext, M_FILTER_TYPE, M_SHEN);
   MimControl(MilLinearFilterIIRContext, M_FILTER_SMOOTHNESS, GradientSmoothness);
   MimControl(MilLinearFilterIIRContext, M_FILTER_RESPONSE_TYPE, M_STEP);

   // Create the luminance gradient mask.
   MimDifferential(MilLinearFilterIIRContext, MilTemplateLumImage, M_NULL, M_NULL, M_NULL, MilGradientLumMaskImage, M_NULL, M_DEFAULT, M_GRADIENT, M_DEFAULT);

   // Create the gradient mask of each band.
   MimDifferential(MilLinearFilterIIRContext, MilTemplateImage, M_NULL, M_NULL, M_NULL, MilGradientMaskColorImage, M_NULL, M_DEFAULT, M_GRADIENT, M_DEFAULT);

   MimFree(MilLinearFilterIIRContext);
      
   // Create the gradient mask total image.
   MIL_ID MilGradientTotal  = MbufAlloc2d(MilSystem, TemplateSizeX, TemplateSizeY, 32+M_UNSIGNED, M_IMAGE+M_PROC, M_NULL);
   MIL_ID MilGradientSquare = MbufAlloc2d(MilSystem, TemplateSizeX, TemplateSizeY, 16+M_UNSIGNED, M_IMAGE+M_PROC, M_NULL);
   MbufClear(MilGradientTotal, 0);
   for(MIL_INT BandIdx = 0; BandIdx < 3; BandIdx++)
      {
      MIL_ID MilGradientBand = MbufChildColor(MilGradientMaskColorImage, M_RED << BandIdx, M_NULL);
      MimArith(MilGradientBand, M_NULL, MilGradientSquare, M_SQUARE);
      MimArith(MilGradientTotal, MilGradientSquare, MilGradientTotal, M_ADD);
      MbufFree(MilGradientBand);
      }
   
   // Get the square root of the total of the gradient.
   MimArith(MilGradientTotal, M_NULL, MilGradientMaskImage, M_SQUARE_ROOT);
  
   // Since the IIR filter maximum value of the IIR filter is 127, we stretch back the two buffers to 255.
   static const MIL_INT MaxColorGradient = (MIL_INT)sqrt(127.0*127.0*3.0);
   Remap8BitImage(MilGradientMaskImage, MilGradientMaskImage, 0, MaxColorGradient, 0, 255);
   Remap8BitImage(MilGradientLumMaskImage, MilGradientLumMaskImage, 0, 127, 0, 255);

   if(MilGradientMaskImagePtr != M_NULL)
      *MilGradientMaskImagePtr = MilGradientMaskImage;
   else
      MbufFree(MilGradientMaskImage);

   if(MilGradientLumMaskImagePtr != M_NULL)
      *MilGradientLumMaskImagePtr = MilGradientLumMaskImage;
   else
      MbufFree(MilGradientLumMaskImage);

   MbufFree(MilGradientTotal);
   MbufFree(MilGradientSquare);
   MbufFree(MilGradientMaskColorImage);
   }

//*****************************************************************************
// Defines the model and the fixturing offset.
//*****************************************************************************
bool DefineModelAndFixture(MIL_ID MilTemplateLumImage,
                           MIL_ID MilModContext,
                           MIL_ID MilFixturingOffset,
                           MIL_DOUBLE ModelOffsetX,
                           MIL_DOUBLE ModelOffsetY,
                           MIL_DOUBLE ModelSizeX,
                           MIL_DOUBLE ModelSizeY)
   {
   // Remove the model from the context if there is one
   if(MmodInquire(MilModContext, M_CONTEXT, M_NUMBER_MODELS + M_TYPE_MIL_INT, M_NULL))
      MmodDefine(MilModContext, M_DELETE, M_ALL, M_DEFAULT, M_DEFAULT, M_DEFAULT, M_DEFAULT);

   // Define the model image from the template image.
   MmodDefine(MilModContext, M_IMAGE, MilTemplateLumImage, ModelOffsetX, ModelOffsetY, ModelSizeX, ModelSizeY);

   // Check if the model is not empty.
   MIL_INT NbModelEdges = MmodInquire(MilModContext, 0, M_NUMBER_OF_CHAINED_EDGELS, M_NULL);
   if (NbModelEdges == 0)
      {
      MosPrintf(MIL_TEXT("Invalid template image. The resulting model is empty.\n\n"));
      return false;
      }

   // Set the number of models to find to M_ALL and preprocess.
   MmodControl(MilModContext, 0, M_NUMBER, M_ALL);
   MmodPreprocess(MilModContext, M_DEFAULT);

   // Learn the fixture offset from the model.
   McalFixture(M_NULL, MilFixturingOffset, M_LEARN_OFFSET, M_MODEL_MOD, MilModContext, 0, M_DEFAULT, M_DEFAULT, M_DEFAULT);
   
   return true;
   }

//*****************************************************************************
// Finds the model and returns the number of occurrences.
//*****************************************************************************
MIL_INT FindModel(MIL_ID MilModContext,
                  MIL_ID MilTargetImage,
                  MIL_ID MilModResult)
   {   
   // Find the model in the target image.
   MmodFind(MilModContext, MilTargetImage, MilModResult);

   // Get the number of occurrences.
   MIL_INT NbOfOccurrences;
   MmodGetResult(MilModResult, M_GENERAL, M_NUMBER+M_TYPE_MIL_INT, &NbOfOccurrences);

   return NbOfOccurrences;
   }

//*****************************************************************************
// Aligns the SrcImage in the destination image based on the fixturing info.
//*****************************************************************************
void AlignImageBasedOnFixture(MIL_ID MilSrcImage,
                              MIL_ID MilDestImage,
                              MIL_ID MilFixturingOffset,
                              MIL_ID MilFixtureProvider,
                              MIL_INT ResultType,
                              MIL_INT OccurenceIdx)
   {
   // Fixture the current occurrence.
   McalFixture(MilSrcImage, MilFixturingOffset, M_MOVE_RELATIVE, ResultType, MilFixtureProvider, OccurenceIdx, M_DEFAULT, M_DEFAULT, M_DEFAULT);

   // Warp the occurrence in the inspection image.
   McalTransformImage(MilSrcImage, MilDestImage, M_DEFAULT, M_BILINEAR+M_OVERSCAN_CLEAR, M_DEFAULT, M_WARP_IMAGE+M_USE_DESTINATION_CALIBRATION);
   }


//*****************************************************************************
// Extracts the defects from the difference image.
//*****************************************************************************
MIL_INT ExtractDefects(MIL_ID MilDifferenceGrayImage,
                       MIL_ID MilBlobResult,
                       MIL_ID MilBlobContext,
                       MIL_DOUBLE TriangleLowerCutoff,
                       MIL_DOUBLE TriangleUpperCutoff,
                       MIL_DOUBLE BinCumulativeValue,
                       MIL_DOUBLE NormalVariation,
                       MIL_DOUBLE FixedDiffThreshold,
                       MIL_INT    CleanMorphSize,
                       const BinarizationMethod BinMethod)
   {
   // Get the owner system and the size of the difference image.
   MIL_ID MilSystem     = MbufInquire(MilDifferenceGrayImage, M_OWNER_SYSTEM, M_NULL);
   MIL_INT ImageSizeX   = MbufInquire(MilDifferenceGrayImage, M_SIZE_X, M_NULL);
   MIL_INT ImageSizeY   = MbufInquire(MilDifferenceGrayImage, M_SIZE_Y, M_NULL);

   // Allocate the binary image.
   MIL_ID MilInspectionBinImage = MbufAlloc2d(MilSystem, ImageSizeX, ImageSizeY, 1+M_UNSIGNED, M_IMAGE+M_PROC, M_NULL);
  
   // Binarize the image.
   MIL_INT BinValue;
   switch(BinMethod)
      {
      case enCumulHistPercentage:
         BinValue = MimBinarize(MilDifferenceGrayImage, M_NULL, M_PERCENTILE_VALUE, BinCumulativeValue, M_NULL);
         break;

      case enTriangleBisection:
         NormalVariation = (MIL_DOUBLE) MimBinarize(MilDifferenceGrayImage, M_NULL, M_TRIANGLE_BISECTION_BRIGHT, TriangleLowerCutoff, TriangleUpperCutoff);
      case enFixed:
         BinValue = (MIL_INT) (((NormalVariation + FixedDiffThreshold) > 255) ? 255 : (NormalVariation + FixedDiffThreshold));
         break;

      case enBiModal:
      default:
         BinValue = MimBinarize(MilDifferenceGrayImage, M_NULL, M_BIMODAL, M_NULL, M_NULL);
         break;
      }
   MimBinarize(MilDifferenceGrayImage, MilInspectionBinImage, M_FIXED + M_GREATER_OR_EQUAL, (MIL_DOUBLE) BinValue, M_NULL);

   // Clean the binary image.
   MimOpen(MilInspectionBinImage, MilInspectionBinImage, CleanMorphSize, M_BINARY);

   // Calculate the blobs.
   MblobCalculate(MilBlobContext, MilInspectionBinImage, M_NULL, MilBlobResult);

   // Free the binary image.
   MbufFree(MilInspectionBinImage);

   return BinValue;
   }

//*****************************************************************************
// Extracts the differences. 
//*****************************************************************************
void ExtractDifferences(MIL_ID MilTemplateImage,
                        MIL_ID MilTemplateLumImage,
                        MIL_ID MilTemplateGradientMask,
                        MIL_ID MilTemplateLumGradientMask,
                        MIL_ID MilWarpedTarget,
                        MIL_ID MilDifferenceGrayImage, 
                        MIL_ID MilStructElement,
                        const DifferenceExtractionMethod DiffExtractMethod)
   {
   // Get the owner system and the size of the difference image.
   MIL_ID MilSystem = MbufInquire(MilDifferenceGrayImage, M_OWNER_SYSTEM, M_NULL);
   MIL_INT ImageSizeX = MbufInquire(MilDifferenceGrayImage, M_SIZE_X, M_NULL);
   MIL_INT ImageSizeY = MbufInquire(MilDifferenceGrayImage, M_SIZE_Y, M_NULL);   

   // Create the luminance version of the Warped target if the difference method is not the color distance.
   MIL_ID MilWarpedTargetLum = M_NULL;
   if(DiffExtractMethod != enColDistance)
      {
      if(MbufInquire(MilWarpedTarget, M_SIZE_BAND, M_NULL) == 3)
         {
         // Allocate the warped target luminance image. 
         MbufAlloc2d(MilSystem, ImageSizeX, ImageSizeY, 8+M_UNSIGNED, M_IMAGE+M_PROC, &MilWarpedTargetLum);

         // Get the luminance of the warped target.
         MimConvert(MilWarpedTarget, MilWarpedTargetLum, M_RGB_TO_L);
         }
      else
         MilWarpedTargetLum = MilWarpedTarget;
      }

   // Extract the differences.
   switch(DiffExtractMethod)
      {
      case enAbsoluteDiff:
         MimArith(MilTemplateLumImage, MilWarpedTargetLum, MilDifferenceGrayImage, M_SUB_ABS);
         break;

      case enColDistance:
         McolDistance(MilTemplateImage, MilWarpedTarget, MilDifferenceGrayImage, M_NULL, M_DEFAULT, M_EUCLIDEAN, M_NO_NORMALIZE, M_DEFAULT);
         break;

      case enTopHat:
         MimMorphic(MilWarpedTargetLum, MilDifferenceGrayImage, MilStructElement, M_TOP_HAT, 1, M_GRAYSCALE);
         break;

      case enBottomHat:
      default:
         MimMorphic(MilWarpedTargetLum, MilDifferenceGrayImage, MilStructElement, M_BOTTOM_HAT, 1, M_GRAYSCALE);
         break;
      }

   // Substract the gradient mask to prevent edge effect.
   MimArith(MilDifferenceGrayImage, DiffExtractMethod == enColDistance ? MilTemplateGradientMask : MilTemplateLumGradientMask, MilDifferenceGrayImage, M_SUB+M_SATURATION);

   if(MilWarpedTargetLum && MilWarpedTargetLum != MilWarpedTarget)
      MbufFree(MilWarpedTargetLum);
   }