surface-tension-calculator/lib/scripts/extract_edge_coordinate.dart

345 lines
10 KiB
Dart

import 'dart:io';
import 'dart:math';
List<dynamic> 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<List<dynamic>> Image = CV2.imread(imageFilePath, CV2.IMREAD_COLOR);
List<List<int>> 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<int> 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<List<int>> DropletEdgePoints = [];
DropletEdgePoints.add(EdgePoint);
int LeftMostCol = 9999999999;
int RightMostCol = 0;
int BottomMostRow = 0;
int BottomMostRow2 = 0;
int CenterCol = 0;
while (DropletEdgePoints.length != 0) {
List<int> CurrentPoint = DropletEdgePoints[0];
DropletEdgePoints.remove(CurrentPoint);
List<List<int>> 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<int> 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<int> 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<List<int>> PositiveEdgePoints = [];
List<List<int>> 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<List<double>> ScaledNegativeEdgePoints = [];
String negativeFileName = outputFileName + "_Negative.dat";
IOSink NegativeCoordinatesFile = File(negativeFileName).openWrite();
for (List<int> 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<List<double>> ScaledPositiveEdgePoints = [];
String positiveFileName = "${outputFileName}_Positive.dat";
IOSink PositiveCoordinatesFile = File(positiveFileName).openWrite();
for (List<int> 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<List<dynamic>> 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<int> Point in PositiveEdgePoints) {
Image[Point[0]][Point[1]] = [0, 0, 255];
EdgeImage[Point[0]][Point[1]] = 255;
}
for (List<int> 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<List<int>> 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<int> 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,
];
}