Skip to content

Commit bf8499b

Browse files
Optical Flow Segmentation (#20)
* first commit * initial commit * adding better caching * Verbose Cache Purging * Rename chain seg base class * (VC) Add ability to swap segmentation algorithms * cleanup * Improved Segmentation in Black Areas. Set Alpha between 0.0 (consider black areas) and 1.0 (only consider totaly white areas). * updated blackness region computation * added more parametrization to set when a point is inside/outside the sheet * detect outside of sheet by mean window pixel values instead of only current pixel * check mean and single pixel brightness value to determine if the pixel is outside the sheet * merging conflicts that were forgotten * updated VC to educelab newest version. and GUI implementation in progress * porting to own segmentation class and adding GUI * Adding GUI and documentation. Readme for Optical Flow Segmentation. Renaming Variables. * Optical Flow Segmentation Author * Requested Changes * Requested Changes * Requested Changes * (ofs) Interface cleanup * (ofs) Cleanup to compute * (ofs) Cleanup compute * (ofs) Rename class * (ofs) Cleanup gui stuff * (Volume) Use exclusive mutex for slice cache * Added Name to creators --------- Co-authored-by: Seth Parker <csparker247@gmail.com>
1 parent 66b8f52 commit bf8499b

File tree

10 files changed

+753
-11
lines changed

10 files changed

+753
-11
lines changed

.zenodo.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@
4545
{"name": "Shankle, Melissa"},
4646
{"name": "Syamil, Raiffa"},
4747
{"name": "Taber, Ryan"},
48-
{"name": "Posma, JP"}
48+
{"name": "Posma, JP"},
49+
{"name": "Schilliger, Julian"}
4950
],
5051
"keywords" : [
5152
"c-plus-plus",

CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,4 +104,4 @@ endif()
104104
# Setup the config files #
105105
include(VCPackageConfig)
106106
# Install to system directories
107-
include(VCInstall)
107+
include(VCInstall)

apps/VC/CWindow.cpp

Lines changed: 73 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include "vc/core/util/Logging.hpp"
1515
#include "vc/meshing/OrderedPointSetMesher.hpp"
1616
#include "vc/segmentation/LocalResliceParticleSim.hpp"
17+
#include "vc/segmentation/OpticalFlowSegmentation.hpp"
1718

1819
namespace vc = volcart;
1920
namespace vcs = volcart::segmentation;
@@ -101,6 +102,10 @@ CWindow::CWindow()
101102
fSegParams.fPeakDistanceWeight = 50;
102103
fSegParams.fWindowWidth = 5;
103104
fSegParams.targetIndex = 5;
105+
fSegParams.ofsSmoothBrightnessThreshold = 180;
106+
fSegParams.ofsOutsideThreshold = 80;
107+
fSegParams.ofsPixelThreshold = 80;
108+
fSegParams.ofsDisplacementThreshold = 10;
104109

105110
// create UI widgets
106111
CreateWidgets();
@@ -220,15 +225,65 @@ void CWindow::CreateWidgets(void)
220225
// segmentation methods
221226
auto* aSegMethodsComboBox = this->findChild<QComboBox*>("cmbSegMethods");
222227
aSegMethodsComboBox->addItem(tr("Local Reslice Particle Simulation"));
228+
aSegMethodsComboBox->addItem(tr("Optical Flow Segmentation"));
223229
connect(
224230
aSegMethodsComboBox, SIGNAL(currentIndexChanged(int)), this,
225231
SLOT(OnChangeSegAlgo(int)));
226232

233+
227234
// ADD NEW SEGMENTATION ALGORITHM NAMES HERE
228235
// aSegMethodsComboBox->addItem(tr("My custom algorithm"));
229236

237+
// Optical Flow Segmentation Parameters
238+
auto* edtOutsideThreshold = new QSpinBox();
239+
edtOutsideThreshold->setMinimum(0);
240+
edtOutsideThreshold->setMaximum(255);
241+
edtOutsideThreshold->setValue(80);
242+
auto* edtOpticalFlowPixelThreshold = new QSpinBox();
243+
edtOpticalFlowPixelThreshold->setMinimum(0);
244+
edtOpticalFlowPixelThreshold->setMaximum(255);
245+
edtOpticalFlowPixelThreshold->setValue(80);
246+
auto* edtOpticalFlowDisplacementThreshold = new QSpinBox();
247+
edtOpticalFlowDisplacementThreshold->setMinimum(0);
248+
edtOpticalFlowDisplacementThreshold->setValue(10);
249+
auto* edtSmoothenPixelThreshold = new QSpinBox();
250+
edtSmoothenPixelThreshold->setMinimum(0);
251+
edtSmoothenPixelThreshold->setMaximum(256);
252+
edtSmoothenPixelThreshold->setValue(180);
253+
254+
connect(edtOutsideThreshold, &QSpinBox::valueChanged, [&](int v) {
255+
fSegParams.ofsOutsideThreshold = static_cast<std::uint8_t>(v);
256+
});
257+
connect(edtOpticalFlowPixelThreshold, &QSpinBox::valueChanged, [&](int v) {
258+
fSegParams.ofsPixelThreshold = static_cast<std::uint8_t>(v);
259+
});
260+
connect(
261+
edtOpticalFlowDisplacementThreshold, &QSpinBox::valueChanged,
262+
[&](int v) {
263+
fSegParams.ofsDisplacementThreshold = static_cast<std::uint32_t>(v);
264+
});
265+
connect(edtSmoothenPixelThreshold, &QSpinBox::valueChanged, [&](int v) {
266+
fSegParams.ofsSmoothBrightnessThreshold = static_cast<std::uint8_t>(v);
267+
});
268+
269+
auto* opticalFlowParamsContainer = new QWidget();
270+
auto* opticalFlowParamsLayout = new QVBoxLayout(opticalFlowParamsContainer);
271+
272+
opticalFlowParamsLayout->addWidget(new QLabel("Optical Flow Displacement Threshold"));
273+
opticalFlowParamsLayout->addWidget(edtOpticalFlowDisplacementThreshold);
274+
opticalFlowParamsLayout->addWidget(new QLabel("Optical Flow Dark Pixel Threshold"));
275+
opticalFlowParamsLayout->addWidget(edtOpticalFlowPixelThreshold);
276+
opticalFlowParamsLayout->addWidget(
277+
new QLabel("Smooth Curve at Dark Points"));
278+
opticalFlowParamsLayout->addWidget(edtOutsideThreshold);
279+
opticalFlowParamsLayout->addWidget(
280+
new QLabel("Smooth Curve at Bright Points"));
281+
opticalFlowParamsLayout->addWidget(edtSmoothenPixelThreshold);
282+
283+
this->ui.segParamsStack->addWidget(opticalFlowParamsContainer);
284+
230285
// LRPS segmentation parameters
231-
// all of these are contained in the this->ui.lrpsParams
286+
// all of these are contained in this->ui.lrpsParams
232287
fEdtAlpha = this->findChild<QLineEdit*>("edtAlphaVal");
233288
fEdtBeta = this->findChild<QLineEdit*>("edtBetaVal");
234289
fEdtDelta = this->findChild<QLineEdit*>("edtDeltaVal");
@@ -267,6 +322,10 @@ void CWindow::CreateWidgets(void)
267322
connect(
268323
fEdtEndIndex, SIGNAL(editingFinished()), this,
269324
SLOT(OnEdtEndingSliceValChange()));
325+
326+
327+
// INSERT OTHER SEGMENTATION PARAMETER WIDGETS HERE
328+
// this->ui.segParamsStack->addWidget(new QLabel("Parameter widgets here"));
270329

271330
// INSERT OTHER SEGMENTATION PARAMETER WIDGETS HERE
272331
// this->ui.segParamsStack->addWidget(new QLabel("Parameter widgets here"));
@@ -284,7 +343,7 @@ void CWindow::CreateWidgets(void)
284343
fLabImpactRange = this->findChild<QLabel*>("labImpactRange");
285344
fLabImpactRange->setText(QString::number(fEdtImpactRng->value()));
286345

287-
// Setup the status bar
346+
// Set up the status bar
288347
statusBar = this->findChild<QStatusBar*>("statusBar");
289348
}
290349

@@ -607,6 +666,18 @@ void CWindow::DoSegmentation(void)
607666
lrps->setConsiderPrevious(fSegParams.fIncludeMiddle);
608667
segmenter = lrps;
609668
}
669+
// Setup OFSC
670+
else if (segIdx == 1) {
671+
auto ofs = vcs::OpticalFlowSegmentation::New();
672+
ofs->setMaterialThickness(fVpkg->materialThickness());
673+
ofs->setTargetZIndex(fSegParams.targetIndex);
674+
ofs->setOutsideThreshold(fSegParams.ofsOutsideThreshold);
675+
ofs->setOFThreshold(fSegParams.ofsPixelThreshold);
676+
ofs->setOFDispThreshold(fSegParams.ofsDisplacementThreshold);
677+
ofs->setSmoothBrightnessThreshold(
678+
fSegParams.ofsSmoothBrightnessThreshold);
679+
segmenter = ofs;
680+
}
610681
// ADD OTHER SEGMENTER SETUP HERE. MATCH THE IDX TO THE IDX IN THE
611682
// DROPDOWN LIST
612683

apps/VC/CWindow.hpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,11 @@ class CWindow : public QMainWindow
5656
int fWindowWidth{5};
5757
bool fIncludeMiddle;
5858
int targetIndex;
59+
// Optical Flow Segmentation Parameters
60+
std::uint8_t ofsSmoothBrightnessThreshold{180};
61+
std::uint8_t ofsOutsideThreshold{80};
62+
std::uint8_t ofsPixelThreshold{80};
63+
std::uint32_t ofsDisplacementThreshold{10};
5964
};
6065

6166
using Segmenter = volcart::segmentation::ChainSegmentationAlgorithm;

core/CMakeLists.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,6 @@ target_link_libraries(vc_core
8787
${VC_FS_LIB}
8888
Eigen3::Eigen
8989
opencv_core
90-
opencv_highgui
9190
opencv_imgproc
9291
opencv_imgcodecs
9392
ITKCommon

core/include/vc/core/types/Volume.hpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
/** @file */
44

5+
#include <mutex>
6+
57
#include "vc/core/filesystem.hpp"
68
#include "vc/core/types/BoundingBox.hpp"
79
#include "vc/core/types/Cache.hpp"
@@ -217,6 +219,8 @@ class Volume : public DiskBasedObjectBaseClass,
217219
bool cacheSlices_{true};
218220
/** Slice cache */
219221
mutable SliceCache::Pointer cache_{DefaultCache::New(DEFAULT_CAPACITY)};
222+
/** Cache mutex for thread-safe access */
223+
mutable std::mutex cacheMutex_;
220224

221225
/** Load slice from disk */
222226
cv::Mat load_slice_(int index) const;

core/src/Volume.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,7 @@ cv::Mat Volume::load_slice_(int index) const
208208

209209
cv::Mat Volume::cache_slice_(int index) const
210210
{
211+
const std::lock_guard<std::mutex> lock(cacheMutex_);
211212
if (cache_->contains(index)) {
212213
return cache_->get(index);
213214
}

segmentation/CMakeLists.txt

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,13 @@ set(srcs
88
src/FloodFill.cpp
99
src/IntensityMap.cpp
1010
src/LocalResliceParticleSim.cpp
11+
src/OpticalFlowSegmentation.cpp
1112
src/Particle.cpp
1213
src/ParticleChain.cpp
1314
src/StructureTensorParticleSim.cpp
1415
src/ThinnedFloodFillSegmentation.cpp
1516
src/ComputeVolumetricMask.cpp
1617
)
17-
set(deps
18-
VC::core
19-
opencv_highgui
20-
Eigen3::Eigen
21-
)
2218

2319
add_library(vc_segmentation ${srcs})
2420
add_library(VC::segmentation ALIAS "vc_segmentation")
@@ -33,8 +29,11 @@ target_compile_options(vc_segmentation
3329
)
3430
target_link_libraries(vc_segmentation
3531
PUBLIC
36-
${deps}
32+
VC::core
33+
Eigen3::Eigen
3734
PRIVATE
35+
opencv_highgui
36+
opencv_video
3837
${VC_FS_LIB}
3938
)
4039
target_compile_features(vc_segmentation PUBLIC cxx_std_17)
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
#pragma once
2+
3+
/** @file */
4+
5+
#include <optional>
6+
7+
#include "vc/core/types/OrderedPointSet.hpp"
8+
#include "vc/core/types/VolumePkg.hpp"
9+
#include "vc/segmentation/ChainSegmentationAlgorithm.hpp"
10+
#include "vc/segmentation/lrps/FittedCurve.hpp"
11+
12+
namespace volcart::segmentation
13+
{
14+
/**
15+
* @brief Optical Flow Segmentation
16+
*
17+
* @author Julian Schilliger (May 2023)
18+
*
19+
* This algorithm propagates a chain of points forward through a volume from a
20+
* starting z-index to an ending z-index (inclusive). It uses optical flow to
21+
* track the shape of the layer. Each seed point is assumed to be placed within
22+
* the layer rather than on its surface boundary.
23+
*
24+
* @warning This algorithm is non-deterministic and yields slightly different
25+
* results each run.
26+
*
27+
* @ingroup Segmentation
28+
*/
29+
class OpticalFlowSegmentation : public ChainSegmentationAlgorithm
30+
{
31+
public:
32+
/** Pointer */
33+
using Pointer = std::shared_ptr<OpticalFlowSegmentation>;
34+
35+
/** @brief Default constructor */
36+
OpticalFlowSegmentation() = default;
37+
38+
/** Default destructor */
39+
~OpticalFlowSegmentation() override = default;
40+
41+
/** Make a new shared instance */
42+
template <typename... Args>
43+
static auto New(Args... args) -> Pointer
44+
{
45+
return std::make_shared<OpticalFlowSegmentation>(
46+
std::forward<Args>(args)...);
47+
}
48+
49+
/** @brief Set the target z-index */
50+
void setTargetZIndex(int z);
51+
52+
/**
53+
* @brief Set the threshold of what pixel brightness is considered inside a
54+
* sheet (higher as threshold) and outside (lower as threshold)
55+
*/
56+
void setOutsideThreshold(std::uint8_t outside);
57+
58+
/**
59+
* @brief Set the threshold of what pixel brightness is considered while
60+
* calculating optical flow, darker pixels OF is interpolated from brighter
61+
* ones in the area
62+
*/
63+
void setOFThreshold(std::uint8_t ofThr);
64+
65+
/**
66+
* @brief Set the maximum single pixel optical flow displacement before
67+
* interpolating a pixel region
68+
*/
69+
void setOFDispThreshold(std::uint32_t ofDispThrs);
70+
71+
/**
72+
* @brief Set the threshold for what pixel brightness is considered as
73+
* being outside the sheet. Pixels above this threshold are considered
74+
* outside the sheet and are smoothed in an attempt to get them tracking
75+
* the sheet again.
76+
*/
77+
void setSmoothBrightnessThreshold(std::uint8_t brightness);
78+
79+
/**
80+
* @brief Set the estimated thickness of the substrate (in um)
81+
*
82+
* Used to generate the radius of the structure tensor calculation
83+
*/
84+
void setMaterialThickness(double m);
85+
86+
/** @brief Set the maximum number of threads */
87+
void setMaxThreads(std::uint32_t t);
88+
89+
/** @brief Clear the maximum number of threads */
90+
void resetMaxThreads();
91+
92+
/** Debug: Shows intensity maps in GUI window */
93+
void setVisualize(bool b);
94+
95+
/** Debug: Dumps reslices and intensity maps to disk */
96+
void setDumpVis(bool b);
97+
98+
/** @brief Compute the segmentation */
99+
auto compute() -> PointSet override;
100+
101+
/** @brief Returns the maximum progress value */
102+
[[nodiscard]] auto progressIterations() const -> size_t override;
103+
104+
private:
105+
/** @brief Compute the segmentation 1 Line */
106+
auto compute_curve_(const FittedCurve& currentCurve, int zIndex)
107+
-> std::vector<Voxel>;
108+
109+
/**
110+
* @brief Debug: Draw curve on slice image
111+
* @param curve Input curve
112+
* @param sliceIndex %Slice on which to draw
113+
* @param particleIndex Highlight point at particleIndex
114+
* @param showSpline Draw interpolated curve. Default only draws points
115+
*/
116+
[[nodiscard]] auto draw_particle_on_slice_(
117+
const FittedCurve& curve,
118+
int sliceIndex,
119+
int particleIndex = -1,
120+
bool showSpline = false) const -> cv::Mat;
121+
122+
/** @brief Convert the internal storage array into a final PointSet */
123+
auto create_final_pointset_(const std::vector<std::vector<Voxel>>& points)
124+
-> PointSet;
125+
126+
/** Target z-index */
127+
int endIndex_{0};
128+
/**
129+
* Darker pixels are considered outside the sheet. This parameter sets the
130+
* threshold of what pixel brightness is considered too deep inside a sheet
131+
* (higher than the threshold) and then tries to smoothen those points back
132+
* towards the edge of the sheet. Range: 0-255.
133+
*/
134+
std::uint8_t outsideThreshold_{80};
135+
/**
136+
* Disregarding pixel that are darker during optical flow computation. This
137+
* parameter sets the threshold for what pixel brightness is considered
138+
* while calculating optical flow. Darker pixels' optical flow is
139+
* interpolated from brighter ones in the area. Range: 0-255. Higher values
140+
* disregard more dark pixels during computation, while lower values
141+
* include more dark pixels.
142+
*/
143+
std::uint8_t opticalFlowPixelThreshold_{80};
144+
/**
145+
* Threshold of how many pixel optical flow can displace a point, if
146+
* higher, recompute optical flow with region's average flow. This
147+
* parameter sets the maximum single pixel optical flow displacement before
148+
* interpolating a pixel region. Range minimum: 0. Higher values allow more
149+
* displacement before interpolation, while lower values trigger
150+
* interpolation more frequently.
151+
*/
152+
std::uint32_t opticalFlowDisplacementThreshold_{10};
153+
/**
154+
* This parameter sets the threshold for what pixel brightness is considered
155+
* as being outside the sheet. Pixels considered outside the sheet are
156+
* smoothed in an attempt to get them tracking the sheet again.
157+
* Range: 0-255. Smooth curve at pixels above this threshold.
158+
*/
159+
std::uint8_t smoothByBrightness_{180};
160+
/** Estimated material thickness in um */
161+
double materialThickness_{100};
162+
/** Maximum number of threads */
163+
std::optional<std::uint32_t> maxThreads_;
164+
/** Dump visualization to disk flag */
165+
bool dumpVis_{false};
166+
/** Show visualization in GUI flag */
167+
bool visualize_{false};
168+
};
169+
} // namespace volcart::segmentation

0 commit comments

Comments
 (0)