import 'dart:io'; import 'dart:math'; List extractEdgeCoordinate( String imageFilePath, String outputFileName, double syringeWidth, ) { // Detect the edges of the image. // All edge pixels will white(255) and rest of the pixels will be black(0). List> Image = CV2.imread(imageFilePath, CV2.IMREAD_COLOR); List> EdgeImage = CV2.Canny(Image, 100, 200); // Find the first edge point (white pixel). // It is assumed that first edge point part of the syringe. int NumOfRow = EdgeImage.length; int NumOfCol = EdgeImage[0].length; List EdgePoint = [0, 0]; int EdgePointFlag = 0; for (int RowIndex = 0; RowIndex < NumOfRow; RowIndex++) { for (int ColIndex = 0; ColIndex < NumOfCol; ColIndex++) { if (EdgeImage[RowIndex][ColIndex] == 255) { EdgePoint = [RowIndex, ColIndex]; EdgePointFlag = 1; break; } } if (EdgePointFlag == 1) { break; } } // Color the all the syringe and droplet pixels differently (70). // Determine the left-most, right-most, and bottom droplet pixels. EdgeImage[EdgePoint[0]][EdgePoint[1]] = 70; List> DropletEdgePoints = []; DropletEdgePoints.add(EdgePoint); int LeftMostCol = 9999999999; int RightMostCol = 0; int BottomMostRow = 0; int BottomMostRow2 = 0; int CenterCol = 0; while (DropletEdgePoints.length != 0) { List CurrentPoint = DropletEdgePoints[0]; DropletEdgePoints.remove(CurrentPoint); List> NeighborPoints = []; for (int RowOffset = -3; RowOffset < 4; RowOffset++) { for (int ColOffset = -3; ColOffset < 4; ColOffset++) { if (RowOffset != 0 || ColOffset != 0) { NeighborPoints.add([ CurrentPoint[0] + RowOffset, CurrentPoint[1] + ColOffset, ]); } } } for (List point in NeighborPoints) { int PointRow = point[0]; int PointCol = point[1]; if ((0 <= PointRow && PointRow < NumOfRow) && (0 <= PointCol && PointCol < NumOfCol)) { if (EdgeImage[PointRow][PointCol] == 255) { DropletEdgePoints.add([PointRow, PointCol]); EdgeImage[PointRow][PointCol] = 70; if (PointRow > BottomMostRow) { BottomMostRow = PointRow; } if (PointCol > RightMostCol) { RightMostCol = PointCol; } if (PointCol < LeftMostCol) { LeftMostCol = PointCol; } } } } } CenterCol = (LeftMostCol + RightMostCol) ~/ 2; for (int Row = 0; Row < BottomMostRow + 1; Row++) { if (EdgeImage[Row][CenterCol] == 70) { BottomMostRow2 = Row; } } BottomMostRow = BottomMostRow2; // Determine the transition row between the syringe pixels and the droplet pixels. int PreviousWidth = 0; int WidthIncCount = 5; List WidthHistory = List.filled(WidthIncCount + 1, 0); int TransitionRow = 0; for (int Row = 0; Row < BottomMostRow; Row++) { int LeftEdgeCol = 0; int RightEdgeCol = 9999999; for (int Col = LeftMostCol - 1; Col < RightMostCol + 1; Col++) { if (EdgeImage[Row][Col] == 70) { LeftEdgeCol = Col; break; } } for (int Col = RightMostCol + 1; Col >= LeftMostCol - 1; Col--) { if (EdgeImage[Row][Col] == 70) { RightEdgeCol = Col; break; } } int CurrentWidth = RightEdgeCol - LeftEdgeCol; if ((CurrentWidth != PreviousWidth) && (0 < CurrentWidth) && (CurrentWidth < (9999999 - NumOfCol))) { for (int i = 0; i < WidthIncCount; i++) { WidthHistory[i] = WidthHistory[i + 1]; } WidthHistory[WidthIncCount] = CurrentWidth; } int WidthIncreaseCount = 0; for (int i = 0; i < WidthIncCount; i++) { if (WidthHistory[i] < WidthHistory[i + 1]) { WidthIncreaseCount += 1; } } if (WidthIncreaseCount == WidthIncCount) { TransitionRow = Row; break; } if ((0 < CurrentWidth) && (CurrentWidth < (9999999 - NumOfCol))) { PreviousWidth = CurrentWidth; } } // Determine the width of the syringe in terms of pixel count int WidthSum = 0; int WidthCount = 0; int SyringeLevel = (0.75 * TransitionRow).toInt(); for (int Row = 0; Row < TransitionRow; Row++) { int RightEdgeCol = 9999999; int LeftEdgeCol = 0; for (int Col = LeftMostCol; Col < CenterCol + 1; Col++) { if (EdgeImage[Row][Col] == 70) { EdgeImage[Row][Col] = 255; LeftEdgeCol = Col; break; } } for (int Col = RightMostCol; Col > CenterCol - 1; Col--) { if (EdgeImage[Row][Col] == 70) { EdgeImage[Row][Col] = 255; RightEdgeCol = Col; break; } } int CurrentWidth = RightEdgeCol - LeftEdgeCol; if (WidthCount <= SyringeLevel) { if ((0 < CurrentWidth) && (CurrentWidth < (9999999 - NumOfCol))) { WidthSum += CurrentWidth; WidthCount += 1; } } } double AverageWidth = (WidthSum.toDouble()) / (WidthCount.toDouble()); // Extract the pixel coordinates of all droplet pixels (color: 70) List> PositiveEdgePoints = []; List> NegativeEdgePoints = []; for (int Row = BottomMostRow; Row > TransitionRow - 1; Row--) { for (int Col = LeftMostCol; Col < CenterCol + 1; Col++) { if (EdgeImage[Row][Col] == 70) { NegativeEdgePoints.add([Row, Col]); break; } } for (int Col = RightMostCol; Col > CenterCol; Col--) { if (EdgeImage[Row][Col] == 70) { PositiveEdgePoints.add([Row, Col]); break; } } } // Scale the point coordinates by using the syringe length double SyringeWidth = syringeWidth; double ScalingFactor = SyringeWidth / AverageWidth; List> ScaledNegativeEdgePoints = []; String negativeFileName = outputFileName + "_Negative.dat"; IOSink NegativeCoordinatesFile = File(negativeFileName).openWrite(); for (List point in NegativeEdgePoints) { int Row = point[0]; int Col = point[1]; double Row2 = (ScalingFactor * (BottomMostRow - Row).toDouble()) + 0.0001; double Col2 = ScalingFactor * (Col - CenterCol).toDouble(); NegativeCoordinatesFile.write("$Col2 $Row2\n"); ScaledNegativeEdgePoints.add([Col2, Row2]); } NegativeCoordinatesFile.close(); List> ScaledPositiveEdgePoints = []; String positiveFileName = "${outputFileName}_Positive.dat"; IOSink PositiveCoordinatesFile = File(positiveFileName).openWrite(); for (List point in PositiveEdgePoints) { int Row = point[0]; int Col = point[1]; double Row2 = (ScalingFactor * (BottomMostRow - Row).toDouble()) + 0.0001; double Col2 = ScalingFactor * (Col - CenterCol).toDouble(); PositiveCoordinatesFile.write("$Col2 $Row2\n"); ScaledPositiveEdgePoints.add([Col2, Row2]); } PositiveCoordinatesFile.close(); // Draw the detected edge points of the droplet. // List> Image2 = deepCopy(Image); for (int Row = 0; Row < NumOfRow; Row++) { for (int Col = 0; Col < NumOfCol; Col++) { Image[Row][Col] = 0; // For EdgeImage, since it is a separate 2D list of ints, set to 0. EdgeImage[Row][Col] = 0; } } for (List Point in PositiveEdgePoints) { Image[Point[0]][Point[1]] = [0, 0, 255]; EdgeImage[Point[0]][Point[1]] = 255; } for (List Point in NegativeEdgePoints) { Image[Point[0]][Point[1]] = [0, 0, 255]; EdgeImage[Point[0]][Point[1]] = 255; } // Draw the center column of the drop CV2.line( Image, Point(CenterCol.toDouble(), TransitionRow.toDouble()), Point(CenterCol.toDouble(), BottomMostRow.toDouble()), [0, 255, 0], 1, ); // image, start, end, color(BGR), thickness double MaxWidthRatio = 0.0; for (int Row = BottomMostRow; Row > TransitionRow - 1; Row--) { int RightCol = 99999; int LeftCol = -1; for (int Col = LeftMostCol; Col < CenterCol + 1; Col++) { if (EdgeImage[Row][Col] == 255) { LeftCol = Col; break; } } for (int Col = RightMostCol; Col > CenterCol; Col--) { if (EdgeImage[Row][Col] == 255) { RightCol = Col; break; } } double WidthRatio = (RightCol - LeftCol).toDouble() / (RightMostCol - LeftMostCol).toDouble(); if (WidthRatio < 1.0 && WidthRatio > MaxWidthRatio) { MaxWidthRatio = WidthRatio; } } List> RowWithMaxWidth = []; for (int Row = BottomMostRow; Row > TransitionRow - 1; Row--) { int RightCol = 99999; int LeftCol = -1; for (int Col = LeftMostCol; Col < CenterCol + 1; Col++) { if (EdgeImage[Row][Col] == 255) { LeftCol = Col; break; } } for (int Col = RightMostCol; Col > CenterCol; Col--) { if (EdgeImage[Row][Col] == 255) { RightCol = Col; break; } } double WidthRatio = (RightCol - LeftCol).toDouble() / (RightMostCol - LeftMostCol).toDouble(); if (WidthRatio == MaxWidthRatio) { RowWithMaxWidth.add([Row, LeftCol, RightCol]); } } // Draw the main diameter int n = RowWithMaxWidth.length; n = (n / 2).toInt(); int Row = RowWithMaxWidth[n][0]; int LeftCol = RowWithMaxWidth[n][1]; int RightCol = RowWithMaxWidth[n][2]; CV2.line( Image, Point(LeftCol.toDouble(), Row.toDouble()), Point(RightCol.toDouble(), Row.toDouble()), [0, 255, 0], 1, ); // Measure and draw the ten diameters List Diameters = []; int MaxWidth = RightMostCol - LeftMostCol; for (int i = 1; i < 13; i++) { for (int Col = LeftMostCol; Col < CenterCol + 1; Col++) { if (EdgeImage[BottomMostRow - (i * MaxWidth ~/ 10)][Col] == 255) { LeftCol = Col; break; } } for (int Col = RightMostCol; Col > CenterCol; Col--) { if (EdgeImage[BottomMostRow - (i * MaxWidth ~/ 10)][Col] == 255) { RightCol = Col; break; } } Diameters.add(RightCol - LeftCol); CV2.line( Image, Point( LeftCol.toDouble(), (BottomMostRow - (i * MaxWidth ~/ 10)).toDouble(), ), Point( RightCol.toDouble(), (BottomMostRow - (i * MaxWidth ~/ 10)).toDouble(), ), [255, 0, 0], 1, ); } // Scale the 12 drop diameters for (int i = 0; i < 12; i++) { Diameters[i] = (Diameters[i] * ScalingFactor).toInt(); } double ScaledMaxWidth = ScalingFactor * MaxWidth.toDouble(); // double ScaledUpWidth = ScalingFactor * (RightCol - LeftCol).toDouble(); return [ ScaledPositiveEdgePoints, ScaledNegativeEdgePoints, ScaledMaxWidth, Diameters, ]; }