345 lines
10 KiB
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,
|
|
];
|
|
}
|