8 const std::vector<std::vector<int>> &trajTime,
9 const std::vector<std::vector<int>> &trajVertexId,
10 const std::vector<std::vector<double>> &coordsX,
11 const std::vector<std::vector<double>> &coordsY,
12 const std::vector<int> &trajCriticalType,
13 std::vector<LinearTrajectory> &linearTraj,
14 std::vector<LinearTrajectory> &outputTraj,
15 std::vector<FuseRecord> &fuseRecords) {
18 const int numTraj =
static_cast<int>(trajTime.size());
19 const bool useTypeConstraint
20 = (
static_cast<int>(trajCriticalType.size()) == numTraj);
21 this->
printMsg(
"Linear regression + chaining (input: "
22 + std::to_string(numTraj) +
" t." +
")");
24 auto temporalOk = [&](
int sFrame,
int eFrame) ->
bool {
29 auto dist2AtStartFrame
31 int sFrame) ->
double {
32 const double xTh = coefI.
evalX(sFrame);
33 const double yTh = coefI.
evalY(sFrame);
34 const double xJ = coefJ.evalX(sFrame);
35 const double yJ = coefJ.evalY(sFrame);
36 const double dx = xJ - xTh, dy = yJ - yTh;
37 return dx * dx + dy * dy;
40 auto buildSamplesForChain
41 = [&](
const std::vector<FuseRecord> &chain, std::vector<int> &T,
42 std::vector<double> &X, std::vector<double> &Y) {
43 const int capacity =
static_cast<int>(chain.size()) * 2 + 2;
47 for(
const auto &r : chain) {
48 const std::vector<int> T2{trajTime[r.i].front(), r.endFrame};
49 const auto &cI = linearTraj[r.i];
50 for(
const int t : T2) {
51 X.push_back(cI.evalX(t));
52 Y.push_back(cI.evalY(t));
57 const auto &cJ = linearTraj[r.
j];
59 X.push_back(cJ.evalX(trajTime[r.
j].back()));
61 Y.push_back(cJ.evalY(trajTime[r.
j].back()));
63 T.push_back(trajTime[r.
j].back());
66 auto fitLineCoefForChain
69 std::vector<double> X, Y;
70 buildSamplesForChain(chain, T, X, Y);
72#ifdef TTK_ENABLE_EIGEN
73 linearRegression(T, X, Y, lineCoef);
75 lineCoef = linearTraj[chain.front().i];
77 lineCoef.
startFrame = trajTime[chain[0].i].front();
78 lineCoef.
endFrame = trajTime[chain.back().j].back();
83 for(
int i = 0; i < numTraj; ++i) {
84 if(trajTime[i].empty())
86 linearTraj[i].startFrame = trajTime[i].front();
87 linearTraj[i].endFrame = trajTime[i].back();
89#ifdef TTK_ENABLE_EIGEN
90 linearRegression(trajTime[i], coordsX[i], coordsY[i], linearTraj[i]);
92 this->
printWrn(
"Eigen unavailable: skipping linear regression");
95 const int tA = trajTime[i].front();
96 const int tB = trajTime[i].back();
97 const double xA = coordsX[i].front();
98 const double xB = coordsX[i].back();
99 const double yA = coordsY[i].front();
100 const double yB = coordsY[i].back();
102 linearTraj[i].ax = (xB - xA) /
static_cast<double>(tB - tA);
103 linearTraj[i].ay = (yB - yA) /
static_cast<double>(tB - tA);
104 linearTraj[i].bx = xA - linearTraj[i].ax * tA;
105 linearTraj[i].by = yA - linearTraj[i].ay * tA;
107 linearTraj[i].ax = 0.0;
108 linearTraj[i].bx = xA;
109 linearTraj[i].ay = 0.0;
110 linearTraj[i].by = yA;
119 outputTraj.reserve(numTraj);
120 for(
int i = 0; i < numTraj; ++i) {
121 if(trajTime[i].empty())
126 for(
size_t k = 0; k < trajTime[i].size(); ++k) {
130 linearTraj[i].finalChainId =
static_cast<int>(outputTraj.size());
132 outputTraj.push_back(std::move(lt));
134 this->
printMsg(
"Output: " + std::to_string(outputTraj.size()) +
" t.", 1.0,
139 std::vector<std::array<double, 3>> meanDir(numTraj);
142 fuseRecords.reserve(numTraj);
143 std::vector<char> usedAsStart(numTraj,
false), usedAsEnd(numTraj,
false);
145 const double similarityThreshold =
cosCol_;
148 for(
int i = 0; i < numTraj; ++i) {
149 if(usedAsStart[i] || trajTime[i].empty())
152 const int endFrame = trajTime[i].back();
155 double bestScore = std::numeric_limits<double>::infinity();
157 const double dist2Denom = (maxLinkDist2 > 0.0) ? maxLinkDist2 : 1.0;
158 const double dotDenom
159 = (1.0 - similarityThreshold > 1e-12) ? (1.0 - similarityThreshold) : 1.0;
160 const double timeDenom
163 for(
int j = 0; j < numTraj; ++j) {
164 if(usedAsEnd[j] || j == i || trajTime[j].empty())
167 if(useTypeConstraint && trajCriticalType[i] != trajCriticalType[j])
170 const int startFrame = trajTime[j].front();
173 = dist2AtStartFrame(linearTraj[i], linearTraj[j], startFrame);
174 if(dist2 > maxLinkDist2)
178 meanDir[i].data(), meanDir[j].data(), 3);
179 if(dot < similarityThreshold)
182 if(!temporalOk(startFrame, endFrame))
185 const double penDist2 = dist2 / dist2Denom;
186 const double penDot = (1.0 - dot) / dotDenom;
188 =
static_cast<double>(std::abs(endFrame - startFrame)) / timeDenom;
190 const double score = penDist2 + penDot + penTime;
192 if(score < bestScore) {
198 fuseRecords.push_back(
199 {i, bestJ, trajTime[i].back(), trajTime[bestJ].front(), -1});
200 usedAsStart[i] =
true;
201 usedAsEnd[bestJ] =
true;
205 outputTraj.reserve(numTraj);
206 std::vector<bool> used(fuseRecords.size(),
false);
208 for(
size_t idx1 = 0; idx1 < fuseRecords.size(); ++idx1) {
212 auto &r1 = fuseRecords[idx1];
213 const int finalId =
static_cast<int>(outputTraj.size());
215 std::vector<FuseRecord> chain{r1};
216 r1.finalContrib = finalId;
217 linearTraj[r1.i].finalChainId = finalId;
218 linearTraj[r1.j].finalChainId = finalId;
222 bool prepended =
true;
225 for(
size_t idx2 = 0; idx2 < fuseRecords.size(); ++idx2) {
228 auto &r2 = fuseRecords[idx2];
229 if(r2.j == chain.front().i) {
230 chain.insert(chain.begin(), r2);
231 r2.finalContrib = finalId;
232 linearTraj[r2.i].finalChainId = finalId;
233 linearTraj[r2.j].finalChainId = finalId;
242 bool extended =
true;
245 for(
size_t idx2 = 0; idx2 < fuseRecords.size(); ++idx2) {
248 auto &r2 = fuseRecords[idx2];
249 if(chain.back().j == r2.i) {
251 r2.finalContrib = finalId;
252 linearTraj[r2.i].finalChainId = finalId;
253 linearTraj[r2.j].finalChainId = finalId;
266 const int firstTraj = chain[0].i;
267 for(
size_t k = 0; k < trajTime[firstTraj].size(); ++k) {
269 trajTime[firstTraj][k],
272 for(
const auto &rec : chain) {
273 const int tj = rec.j;
274 for(
size_t k = 0; k < trajTime[tj].size(); ++k) {
276 trajTime[tj][k],
static_cast<ttk::SimplexId>(trajVertexId[tj][k]));
280 outputTraj.push_back(std::move(lineCoef));
282 const int firstTraj = chain[0].i;
286 seg.
endFrame = trajTime[firstTraj].back();
290 for(
size_t k = 0; k < trajTime[firstTraj].size(); ++k) {
292 trajTime[firstTraj][k],
295 outputTraj.push_back(std::move(seg));
298 for(
const auto &rec : chain) {
299 const int iSeg = rec.i;
300 const int jSeg = rec.j;
301 const int tEnd = trajTime[iSeg].back();
302 const int tStart = trajTime[jSeg].front();
303 const auto &cI = linearTraj[iSeg];
304 const auto &cJ = linearTraj[jSeg];
307 const double xA = cI.
evalX(tEnd);
308 const double yA = cI.evalY(tEnd);
309 const double xB = cJ.evalX(tStart);
310 const double yB = cJ.evalY(tStart);
312 const double dt =
static_cast<double>(tStart - tEnd);
313 junction.
ax = (xB - xA) / dt;
314 junction.
ay = (yB - yA) / dt;
315 junction.
bx = xA - junction.
ax *
static_cast<double>(tEnd);
316 junction.
by = yA - junction.
ay *
static_cast<double>(tEnd);
327 outputTraj.push_back(std::move(junction));
331 seg.
endFrame = trajTime[jSeg].back();
335 for(
size_t k = 0; k < trajTime[jSeg].size(); ++k) {
340 outputTraj.push_back(std::move(seg));
345 for(
int i = 0; i < numTraj; ++i) {
346 if(usedAsStart[i] || usedAsEnd[i] || trajTime[i].empty())
350 lineCoef.
endFrame = trajTime[i].back();
353 for(
size_t k = 0; k < trajTime[i].size(); ++k) {
357 linearTraj[i].finalChainId =
static_cast<int>(outputTraj.size());
359 outputTraj.push_back(std::move(lineCoef));
362 for(
auto &c : outputTraj) {
363 if(c.endFrame < c.startFrame)
364 std::swap(c.startFrame, c.endFrame);
367 this->
printMsg(
"Output: " + std::to_string(outputTraj.size()) +
" t.", 1.0,
int correctTrajectory(const std::vector< std::vector< int > > &trajTime, const std::vector< std::vector< int > > &trajVertexId, const std::vector< std::vector< double > > &coordsX, const std::vector< std::vector< double > > &coordsY, const std::vector< int > &trajCriticalType, std::vector< LinearTrajectory > &linearTraj, std::vector< LinearTrajectory > &outputTraj, std::vector< FuseRecord > &fuseRecords)
Linearize + (optional) chain input per-trajectory point clouds.