/*
* This file is part of the GreasePad distribution (https://github.com/FraunhoferIOSB/GreasePad).
* Copyright (c) 2022-2026 Jochen Meidow, Fraunhofer IOSB
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#include "qassert.h"
#include "qcolor.h"
#include "qcontainerfwd.h"
#include "qhashfunctions.h"
#include "qlogging.h"
#include "qsharedpointer.h"
#include "qtypes.h"
#include
#include
#include
#include
#include
#include "adjustment.h"
#include "conncomp.h"
#include "constants.h"
#include "constraints.h"
#include "find.h"
#include "geometry/acute.h"
#include "global.h"
#include "graphics/qconstraints.h"
#include "graphics/qsegment.h"
#include "graphics/qstroke.h"
#include "gui/mainscene.h"
#include "matfun.h"
#include "matrix.h"
#include "quantiles.h"
#include "state.h"
#include "statistics/prob.h"
#include "uncertain/quncertain.h"
#include "uncertain/udistance.h"
#include "uncertain/upoint.h"
#include "uncertain/usegment.h"
#include "uncertain/ustraightline.h"
#include "unique.h"
#include
#include
#include
#include
#include
#include
#include
#include
using Eigen::Array;
using Eigen::ArrayXi;
using Eigen::VectorXd;
using Eigen::VectorXi;
using Eigen::Vector;
using Eigen::Vector3d;
using Eigen::Matrix3d;
using Eigen::Matrix;
using Eigen::Index;
using Eigen::SparseMatrix;
using Eigen::ColMajor;
using Eigen::indexing::last;
using Eigen::Dynamic;
using Eigen::seq;
using Constraint::ConstraintBase;
using Constraint::Parallel;
using Constraint::Orthogonal;
using Constraint::Copunctual;
using Constraint::Vertical;
using Constraint::Horizontal;
using Constraint::Diagonal;
using Uncertain::uPoint;
using Uncertain::uStraightLine;
using Uncertain::uStraightLineSegment;
using Uncertain::uDistance;
using Graph::conncomp;
using Graph::IncidenceMatrix;
using Matfun::find;
using Matfun::sign;
using Matfun::unique;
using TextColor::black;
using TextColor::blue;
bool State::considerOrthogonal_ = true;
bool State::considerParallel_ = true;
bool State::considerCopunctual_ = true;
bool State::considerVertical_ = false;
bool State::considerHorizontal_ = false;
bool State::considerDiagonal_ = false;
Quantiles::Recognition State::recogn_;
Quantiles::Snapping State::snap_;
namespace {
//! Serialization of sparse incidence matrix
QDataStream & operator<< (QDataStream & out, const IncidenceMatrix & AA)
{
// qDebug() << Q_FUNC_INFO;
out << static_cast(AA.rows());
out << static_cast(AA.cols());
out << static_cast(AA.nonZeros());
for( Index c=0; c::InnerIterator it( AA, c);
for( ; it; ++it) {
out << static_cast(it.row()) << static_cast(it.col());
}
}
return out;
}
//! Deserialization of sparse incidence matrix
QDataStream & operator>> (QDataStream & in, IncidenceMatrix & AA)
{
// qDebug() << Q_FUNC_INFO;
uint nrows = 0;
uint ncols = 0;
uint nnz = 0;
in >> nrows >> ncols >> nnz;
// fill vector with triplets (i,j,value)
std::vector< Eigen::Triplet > tripletList;
tripletList.reserve( nnz );
int r = 0;
int c = 0;
for ( uint i=0; i> r >> c;
tripletList.emplace_back( r, c, 1 );
}
// fill sparse matrix
AA.resize(static_cast(nrows), static_cast(ncols));
AA.setFromTriplets( tripletList.begin(),
tripletList.end() );
return in;
}
//! Serialization of geometric constraint
QDataStream & operator<< ( QDataStream & out, const ConstraintBase & c)
{
out << c.type_name()[0]; // first character, {'v','h','d','o','p','c'}
return out;
}
//! Serialization of arrays
template
QDataStream & operator<< (QDataStream & out, const Array & v)
{
out << v.size();
for (const auto x : v) {
out << x;
}
return out;
}
template
QDataStream & operator>> (QDataStream & in, Array & v)
{
Index sz = 0;
in >> sz;
v.resize(sz);
for (Index i=0; i> v(i);
}
return in;
}
} // namespace
//! Implementation details of class 'state' (pImpl idiom)
class impl {
public:
bool deserialize( QDataStream & in ); //!< Serialization
void serialize( QDataStream & out ) const; //!< Deserialization
[[nodiscard]] QString StatusMsg() const; //!< Create message for status bar
void toggleVisibilityStrokes(); //!< Toggle the visibility of strokes (point sequences)
void toggleVisibilityConstraints(); //!< Toggle the visibility of markers depicting geometric constraints
void toggleVisibilityConstrained(); //!< Toggle the visibility of constrained segments
void toggleVisibilityUnconstrained(); //!< Toggle the visibility of unconstrained segments
void clearAll(); //!< Clear all, create blank state
// augment
void append( const QPolygonF & track); //!< Augment state: append one track (segment)
void reasoning_augment_and_adjust( const Quantiles::Snapping &snap); //!< Augment state: reasoning & adjustment
// reduce
void remove_elements(); //!< Reduce state: delete selected elements
void reasoning_reduce_and_adjust(); //!< Reduce state: reasoning & adjustment
// augment & reduce
void replaceGraphics(); //!< Graphics: Replace where necessary
void graphicItemsAdd(QGraphicsScene *sc) const; //!< Graphics: add items
private:
void identify_subtasks();
// augment
Index findNewConstraints();
void findAdjacenciesRecentSegment(const Quantiles::Snapping &snap);
void merge_segment ( Index s );
bool identities_removed();
void snap_endpoints( Index numNewConstr );
void update_segments( const ArrayXi & maps, const AdjustmentFramework & a);
// reduce
void remove_constraint( Index i );
void remove_segment( Index i );
// augment & reduce
void solve_subtask_greedy(int cc);
//! Estimation of two points delimiting an uncertain straight line segment
static std::pair uEndPoints( const VectorXd & xi,
const VectorXd & yi);
static std::pair trackCoords(const QPolygonF &poly);
void setAltColors() const;
void lookOutForOrthogonality( Index c);
void lookOutForParallelism( Index c);
void lookOutForCopunctuality( Index c);
bool is_vertical( Index a);
bool is_horizontal( Index a);
bool is_diagonal( Index a);
bool are_parallel( Index a, Index b);
bool are_orthogonal( Index a, Index b);
bool are_identical( Index a, Index b);
bool are_copunctual( Index a, Index b, Index c);
void establish_vertical( Index a);
void establish_horizontal( Index a);
void establish_diagonal( Index a);
void establish_parallel( Index a, Index b );
void establish_orthogonal( Index a, Index b );
void establish_copunctual( Index a, Index b, Index c );
void snapThisToOthers(Index s);
void snapOthersToThis(Index s, Index cc);
void augment();
[[nodiscard]] std::pair >
a_Maker( const ArrayXi & maps_ ) const;
QVector< std::shared_ptr< const uStraightLineSegment> > m_segm;
QVector< std::shared_ptr< Constraint::ConstraintBase> > m_constr;
QVector< std::shared_ptr< QEntity::QStroke> > m_qStroke;
QVector< std::shared_ptr< QEntity::QUnconstrained> > m_qUnconstrained;
QVector< std::shared_ptr< QEntity::QConstrained> > m_qConstrained;
QVector< std::shared_ptr< QConstraint::QConstraintBase> > m_qConstraint;
IncidenceMatrix Adj; // adjacency of straight line segments
IncidenceMatrix Rel; // relation of segment (row) and constraint (column)
IncidenceMatrix x_touches_l; // "End-point x touches straight line l."
IncidenceMatrix y_touches_l; // "End-point y touches straight line l."
IncidenceMatrix PP; // parallelism of straight lines
ArrayXi arr_segm; // connected components of the segments, e.g., 0,1,1,0,2,1,2,2
ArrayXi arr_constr; // connected components of the constraints, e.g., 0,1,2,2,1
Array m_status;
Array m_enforced;
static constexpr double m_scale = 1000;
};
State::State( const State & other)
: m_pImpl( std::make_unique(*other.m_pImpl) )
{
// qDebug() << Q_FUNC_INFO;
}
State::~State()
{
// qDebug() << Q_FUNC_INFO;
clearAll();
}
State::State() : m_pImpl( std::make_unique())
{
// qDebug() << Q_FUNC_INFO;
}
State & State::operator= ( const State & other )
{
// qDebug() << Q_FUNC_INFO;
if ( &other==this ) {
return *this;
}
m_pImpl = std::make_unique( *other.m_pImpl);
return *this;
}
bool State::augment( const QPolygonF &track)
{
// qDebug() << Q_FUNC_INFO;
pImpl()->append( track);
pImpl()->reasoning_augment_and_adjust( snap_ );
pImpl()->replaceGraphics();
return true;
}
void State::clearAll()
{
pImpl()->clearAll();
}
//! Set significance level for recognition tasks
void State::setAlphaRecognition( const double alpha) {
recogn_.setAlpha( Stats::Prob(alpha) ); }
//! Set significance level for snapping of end points
void State::setAlphaSnapping( const double alpha) {
snap_.setAlpha( Stats::Prob(alpha) ); }
bool impl::deserialize( QDataStream & in )
{
// qDebug() << Q_FUNC_INFO;
// st_clear(); // required? st: new()
qDebug().noquote() << "(1) reading topology...";
in >> Adj;
in >> Rel;
in >> x_touches_l;
in >> y_touches_l;
in >> PP;
qDebug().noquote() << "(2) reading geometry...";
qDebug().noquote() << "(2.1) reading uncertain straight line segments...";
for ( Index s=0; s> *seg;
if ( in.status()!=0) {
return false;
}
m_segm.append( seg );
}
qDebug().noquote() << "(2.2) reading geometric constraints...";
for ( Index i=0; i> type_code;
// qDebug() << Q_FUNC_INFO << type_name;
if ( in.status()!=0) {
return false;
}
std::shared_ptr c;
c = Constraint::Factory::getInstance()->create(type_code);
if ( c==nullptr ) {
return false;
}
m_constr.append( c );
}
qDebug().noquote() << "(3) graphics...";
qDebug().noquote() << "(3.1) constraints...";
for ( const auto & con : std::as_const( m_constr)) {
auto q = QConstraint::Factory::getInstance()->create( con->type_name());
if ( !q->deserialize( in ) ) {
return false;
}
m_qConstraint.append( q);
}
qDebug().noquote() << "(3.2) strokes ...";
for ( Index i=0; i();
if( !q->deserialize(in) ) {
return false;
}
m_qStroke.append( q);
}
qDebug().noquote() << "(3.3) unconstrained segments...";
for ( Index i=0; i();
if( !q->deserialize(in) ) {
return false;
}
m_qUnconstrained.append( q);
}
qDebug().noquote() << "(3.4) constrained segments...";
for ( Index i=0; i();
if( !q->deserialize(in) ) {
return false;
}
m_qConstrained.append( q);
}
qDebug().noquote() << "(4) attributes of constraints...";
in >> m_status;
in >> m_enforced;
// determine subtasks, i.e., connected components
identify_subtasks();
return true;
}
void State::graphicItemsAdd( QGraphicsScene *sc) const
{
pImpl()->graphicItemsAdd( sc);
}
void impl::graphicItemsAdd( QGraphicsScene *sc) const
{
setAltColors();
for ( const auto & item : m_qStroke)
{
assert( !item->scene() );
sc->addItem( item.get() );
}
for ( const auto & item : m_qUnconstrained)
{
assert( !item->scene() );
sc->addItem( item.get() );
}
for ( const auto & item : m_qConstrained)
{
assert( !item->scene() );
sc->addItem( item.get() );
}
for ( const auto & item : m_qConstraint)
{
assert( !item->scene() );
sc->addItem( item.get() );
}
}
bool State::reduce()
{
// qDebug() << Q_FUNC_INFO;
pImpl()->remove_elements(); // ... but do not delete
pImpl()->reasoning_reduce_and_adjust();
pImpl()->replaceGraphics();
return true;
}
void State::serialize( QDataStream & out ) const
{
pImpl()->serialize( out );
}
void impl::serialize( QDataStream &out ) const
{
// qDebug() << Q_FUNC_INFO;
// (1) topology
out << Adj;
out << Rel;
out << x_touches_l;
out << y_touches_l;
out << PP;
// (2) geometry
for ( const auto & item : m_segm) {
out << *item;
}
for ( const auto & item : m_constr) {
out << *item;
}
// (3) graphics
for ( const auto & item : m_qConstraint) {
item->serialize( out );
}
for ( const auto & item : m_qStroke) {
item->serialize( out );
}
for ( const auto & item : m_qUnconstrained) {
item->serialize( out );
}
for ( const auto & item : m_qConstrained) {
item->serialize( out );
}
// (4) evaluations
out << m_status;
out << m_enforced;
qDebug().noquote() << "Export finished.";
}
void impl::findAdjacenciesRecentSegment(const Quantiles::Snapping &snap)
{
for ( int i=0; ibounding_box().overlaps(
m_segm.last()->bounding_box()) ) {
continue;
}
bool are_adjacent = false;
if ( m_segm.last()->touchedBy( m_segm.at(i)->ux(),
snap.quantile_stdNormDistr(),
snap.quantile_chi2_1dof()) ) {
x_touches_l.set(i,last);
are_adjacent = true;
}
if ( m_segm.last()->touchedBy( m_segm.at(i)->uy(),
snap.quantile_stdNormDistr(),
snap.quantile_chi2_1dof()) ) {
y_touches_l.set(i,last);
are_adjacent = true;
}
if ( m_segm.at(i)->touchedBy( m_segm.last()->ux() ,
snap.quantile_stdNormDistr(),
snap.quantile_chi2_1dof() ) ) {
x_touches_l.set(last,i);
are_adjacent = true;
}
if ( m_segm.at(i)->touchedBy( m_segm.last()->uy(),
snap.quantile_stdNormDistr(),
snap.quantile_chi2_1dof() ) ) {
y_touches_l.set(last,i);
are_adjacent = true;
}
if ( m_segm.at(i)->intersects( *m_segm.last()) )
{
are_adjacent = true;
}
if ( are_adjacent ) {
Adj.set(i, last);
Adj.set(last, i);
}
}
}
void impl::remove_segment(const Index i)
{
// qDebug() << "removing segment #" << i+1;
m_segm.removeAt(i);
Adj.reduce(i,i); // A(i,:)= []; A(:,i)=[]
PP.reduce(i,i);
Rel.remove_row(i); // B(i,:) = [];
x_touches_l.reduce(i,i);
y_touches_l.reduce(i,i);
m_qStroke.removeAt( i );
m_qUnconstrained.removeAt( i );
m_qConstrained.removeAt( i );
}
void impl::reasoning_augment_and_adjust( const Quantiles::Snapping & snap)
{
// (0) find adjacencies / connectivity
findAdjacenciesRecentSegment( snap);
if ( identities_removed() ) {
findAdjacenciesRecentSegment( snap); // ! 2nd time
}
// (1) find constraints .....................................
const Index num_new_constraints_ = findNewConstraints();
assert( num_new_constraints_ >= 0 );
qDebug().noquote() << QString( num_new_constraints_==1 ?
"%1 new constraint found." : "%1 new constraints found.").arg(num_new_constraints_);
// (2) check for independence and consistency ...............
identify_subtasks(); // connected components
if (num_new_constraints_ > 0) {
const VectorXi LabelsNewConstrIndividual = arr_constr.tail( num_new_constraints_);
const VectorXi LabelsNewConstrUnique = unique( LabelsNewConstrIndividual);
for ( const auto cc : LabelsNewConstrUnique) {
qDebug().noquote() << blue << QStringLiteral("Reasoning for connected component #%1/%2...")
.arg(cc).arg(LabelsNewConstrUnique.size())
<< black;
solve_subtask_greedy(cc); // in [augment state]
}
} // if ( num_new_constraints_ > 0 )
// (3) snap all segments adjacent to new segment .............
snap_endpoints( num_new_constraints_ );
}
Index impl::findNewConstraints()
{
const Index previously = m_constr.length();
const Index c = Adj.rows()-1;
// unary relations ......................................
if ( State::considerVertical() ) {
if ( is_vertical(c)) {
establish_vertical(c);
}
}
if ( State::considerHorizontal() ) {
if ( is_horizontal(c)) {
establish_horizontal(c);
}
}
if ( State::considerDiagonal() ) {
if ( is_diagonal(c)) {
establish_diagonal(c);
}
}
if ( State::considerOrthogonal() ) {
lookOutForOrthogonality(c);
}
if ( State::considerParallel() ) {
lookOutForParallelism(c);
}
if ( State::considerCopunctual() ) {
lookOutForCopunctuality(c);
}
return m_constr.length() -previously;
}
void impl::lookOutForOrthogonality( const Index c )
{
for ( SparseMatrix::InnerIterator it(Adj,c) ; it; ++it) {
assert( it.value()==1 );
const int a = it.index(); // a is direct neighbor of c.
if ( are_orthogonal( a, c) ) {
establish_orthogonal( a, c);
}
}
}
void impl::lookOutForParallelism(const Index c)
{
const SparseMatrix WW = (Adj*Adj).eval();
// case: straight line segment connects two segments
const Vector idx = find( Adj.col(c).eval() ); // neighbors of c
// Check all pairs of direct neighbors.
for ( Index i=0; i nbs = find( WW.col(c).eval() );
for ( const auto a : nbs) { // Index n=0; n nbs = find( WW.col(c).eval() );
for ( const auto a : nbs) {
// a and c are adjacent and have at least one common neighbor.
// Find all common neighbors of a and c.
const Vector nbnb = find( Adj.col(a).eval() ); // neighbors of a.
for ( const auto b : nbnb ) {
if ( !Adj.isSet( b,c ) ) {
continue; // b is not a common neighbor of a and c.
}
// b is neighbor of a and c.
if ( b > a ) {
// establish copunctuality
// if straight lines not pairwise identical
if ( !are_identical(b,c) && !are_identical(a,c) && !are_identical(a,b) ) {
if ( are_copunctual( a, b, c) ) {
establish_copunctual( a, b, c );
}
}
}
}
}
}
void impl::solve_subtask_greedy( const int cc )
{
const ArrayXi maps_ = find( arr_segm==cc );
const ArrayXi mapc_ = find( arr_constr==cc );
assert( mapc_.size()>0 );
const IncidenceMatrix relsub = Rel(maps_,mapc_);
AdjustmentFramework a{ a_Maker( maps_)};
//qDebug().noquote().nospace() << red << "greedy search..." << black;
bool last_constraint_required = false;
for ( Index c=0; ctype_name()) << black;
// add constraint tentative and check dependency/consistency:
m_status(mapc_(c)) = Attribute::Required;
// enforce constraints (adjustment)
last_constraint_required = a.enforceConstraints( m_constr, relsub, mapc_, m_status, m_enforced );
if ( !last_constraint_required ) {
m_status(mapc_(c)) = Attribute::Redundant;
}
}
}
// adjustment with consistent set of constraints ..................
if ( !last_constraint_required ) {
const Index C = (m_status==Attribute::Required).count();
qDebug().noquote() << blue << QString( C==1 ?
"final adjustment with %1 constraint" :
"final adjustment with %1 consistent constraints" ).arg( C)
<< black;
last_constraint_required = a.enforceConstraints( m_constr, relsub, mapc_,m_status, m_enforced);
}
if ( !last_constraint_required ) {
qDebug().noquote() << "-> final adjustment with inconsistent set";
QMessageBox msg(nullptr);
msg.setIcon( QMessageBox::Warning );
msg.setWindowTitle( QApplication::applicationName() );
msg.setText( QStringLiteral("Ooops, final adjustment with inconsistent set." ));
msg.setStandardButtons( QMessageBox::Ok );
msg.exec();
}
// update segments: project end points onto adjusted line ..............................
qDebug() << "update segments...";
update_segments( maps_ , a);
}
//! project end points of segments onto adjusted lines
void impl::update_segments( const ArrayXi & maps_,
const AdjustmentFramework & a)
{
for ( Index s=0; s p = a.getEntity(s);
const uStraightLine ul( p.first, p.second );
const uPoint ux = m_segm.at( maps_(s) )->ux();
const uPoint uy = m_segm.at( maps_(s) )->uy();
const uPoint ua = ul.project(ux).sphericalNormalized();
const uPoint ub = ul.project(uy).sphericalNormalized();
// qDebug() << QString("subtask: replace segment %1 due to adjustment").arg(s);
const auto us = std::make_shared(ua,ub);
m_segm.replace( maps_(s), us);
}
}
void impl::snap_endpoints( const Index numNewConstr)
{
ArrayXi newLabels(numNewConstr+1);
newLabels << arr_constr.tail(numNewConstr), arr_segm.tail(1);
newLabels = unique(newLabels);
for ( const int cc : newLabels) {
qDebug().noquote() << blue
<< QString(" snap subtask %1/%2").arg( cc+1 ).arg( newLabels.size() );
for ( const auto s : find( arr_segm==cc) ) {
snapThisToOthers(s); // is touching
snapOthersToThis(s, cc); // touched by
}
}
}
bool impl::is_vertical( const Index a)
{
return m_segm.at(a)->ul().isVertical( State::recogn_.quantile_chi2_1dof() );
}
bool impl::is_horizontal( const Index a)
{
return m_segm.at(a)->ul().isHorizontal( State::recogn_.quantile_chi2_1dof() );
}
bool impl::is_diagonal( const Index a)
{
return m_segm.at(a)->ul().isDiagonal( State::recogn_.quantile_chi2_1dof() );
}
bool impl::are_copunctual( const Index a, const Index b, const Index c)
{
return m_segm.at(c)->ul().isCopunctualWith( m_segm.at(a)->ul(),
m_segm.at(b)->ul(),
State::recogn_.quantile_chi2_1dof() );
}
bool impl::are_parallel( const Index a, const Index b)
{
return m_segm.at(a)->ul().isParallelTo( m_segm.at(b)->ul(),
State::recogn_.quantile_chi2_1dof() );
}
bool impl::are_orthogonal( const Index a, const Index b)
{
return m_segm.at(a)->ul().isOrthogonalTo( m_segm.at(b)->ul(),
State::recogn_.quantile_chi2_1dof() );
}
bool impl::are_identical( const Index a, const Index b)
{
// (1) pre-check with acute angle
const double alpha = Geometry::acute( m_segm.at(a)->ul().v(),
m_segm.at(b)->ul().v() );
constexpr double rho = 180./3.141592653589793; // = 180°/pi = 57.2958°
constexpr double T_deg_pre_check = 20.0;
if ( alpha*rho > T_deg_pre_check ) {
return false;
}
// (2) statistical test
return m_segm.at(a)->ul().isIdenticalTo( m_segm.at(b)->ul(),
State::recogn_.quantile_chi2_2dof() );
}
// augment state with new, unevaluated constraint
void impl::augment()
{
// append an empty column
Rel.conservativeResize(Rel.rows(), Rel.cols()+1);
m_status.conservativeResize(m_status.size()+1);
m_status(last) = Attribute::Unevaluated;
m_enforced.conservativeResize(m_enforced.size()+1);
m_enforced(last) = false;
}
void impl::establish_parallel( const Index a,
const Index b)
{
if ( PP.isSet(a,b) ) {
// qDebug() << "already parallel!";
// TODO(meijoc) set constraint to be unevaluated! but how??
return;
}
PP.set( a,b);
PP.set( b,a);
m_qConstraint.append( QConstraint::QParallel::create() );
m_constr.append( std::make_shared() );
augment();
Rel.set( a, last );
Rel.set( b, last );
}
void impl::establish_vertical( const Index a)
{
m_qConstraint.append( QConstraint::QAligned::create());
m_constr.append( std::make_shared() );
augment();
Rel.set( a, last );
}
void impl::establish_horizontal( const Index a)
{
m_qConstraint.append( QConstraint::QAligned::create());
m_constr.append( std::make_shared() );
augment();
Rel.set( a, last );
}
void impl::establish_diagonal( const Index a)
{
m_qConstraint.append( QConstraint::QAligned::create());
m_constr.append( std::make_shared() );
augment();
Rel.set( a, last );
}
void impl::establish_orthogonal( const Index a,
const Index b)
{
m_qConstraint.append( QConstraint::QOrthogonal::create());
m_constr.append( std::make_shared() );
augment();
Rel.set( a, last );
Rel.set( b, last );
}
void impl::establish_copunctual( const Index a,
const Index b,
const Index c)
{
m_qConstraint.append( QConstraint::QCopunctual::create() );
m_constr.append( std::make_shared() );
augment();
Rel.set( a, last );
Rel.set( b, last );
Rel.set( c, last );
}
/*
void impl::establish_identical( const Index a, const Index b)
{
m_qConstraint.append( QConstraint::QIdentical::create() );
m_constr.append( std::make_shared() );
Bi.conservativeResize( Bi.rows(), Bi.cols()+1 ); // append column
Bi.set( a, Bi.cols()-1 ); // B(a,end) = 1;
Bi.set( b, Bi.cols()-1 ); // B(b,end) = 1;
}
*/
std::pair >
impl::a_Maker(const ArrayXi & maps) const
{
const Index S = maps.size(); // number of segments
const Index N = 3*S; // number of observations
assert( S>0 );
VectorXd l(N); // vector of observations
SparseMatrix Sigma_ll(N,N); // covariance matrix observations
Sigma_ll.reserve(3*N);
int idx = 0; // [~,idx] = max( abs(l(1:2)) )
for (Index s=0; sul().sphericalNormalized();
// align signs consistently
const Vector3d m = ul.v();
m.head<2>().cwiseAbs().maxCoeff(&idx); // [~,idx] = max( abs(l(1:2)) )
const Index offset3 = 3*s;
l.segment<3>(offset3) = sign(m(idx))*m; // spherically normalized
for (Index i=0; i<3; i++) {
for (Index j=0; j<3; j++) {
Sigma_ll.insert( offset3+i, offset3+j ) = ul.Cov(i,j);
}
}
}
return {l, Sigma_ll};
}
void impl::remove_constraint( const Index i )
{
assert( i >= 0 );
assert( i < m_constr.length() );
if ( m_constr.at(i)->isInstanceOf() )
{
const Vector idx = find( Rel.col(i).eval() );
Q_ASSERT_X( idx.size() == 2, Q_FUNC_INFO,
QStringLiteral("parallel with %1 entities")
.arg( QString::number(idx.size())).toUtf8() );
PP.unset( idx(0), idx(1));
PP.unset( idx(1), idx(0));
}
m_constr.removeAt(i);
Rel.remove_column(i); // B(:,i)=[];
m_qConstraint.removeAt(i);
m_status(seq(i,last-1)) = m_status(seq(i+1,last));
m_status.conservativeResize(m_status.size()-1);
m_enforced(seq(i,last-1)) = m_enforced(seq(i+1,last));
m_enforced.conservativeResize(m_enforced.size()-1);
}
bool impl::identities_removed()
{
// qDebug() << Q_FUNC_INFO;
// loop neighbors a of current segment c.
bool found = false;
for ( Index a=Adj.rows()-1; a>=0 ; a-- ) // ! decrement
{
// if a and c are neighbors, check for identity
if ( Adj.isSet( a,last ) ) {
if ( are_identical(a, Adj.cols()-1)) {
found = true;
// merge segment "a" with last segment in list
// remove involved constraints
merge_segment(a);
} // identical
} // if adjacent
}
return found;
}
// uncertain segment via merged tracks
void impl::merge_segment( const Index s)
{
const QPolygonF merged_track
= m_qStroke.at(s)->polygon()
+ m_qStroke.last()->polygon();
m_qStroke.last() = std::make_shared( merged_track );
const std::pair xiyi = trackCoords(merged_track); // {x_i, y_i}
const std::pair uxuy = uEndPoints(xiyi.first, xiyi.second); // ux, uy
m_segm.last() = std::make_shared(
uxuy.first, uxuy.second);
m_qUnconstrained.last() = std::make_shared(
m_segm.last()->ux(),
m_segm.last()->uy() );
m_qConstrained.last() = std::make_shared(
m_segm.last()->ux(),
m_segm.last()->uy() );
// inherit adjacencies of [a]
for ( Index i=0; i= 0; c--) { // hint: Rel is sparse, but *ColMajor*, loop is inefficient...
if ( Rel.isSet(s,c) ) {
remove_constraint(c);
}
}
for ( Index t=0; tStatusMsg();
}
QString impl::StatusMsg() const
{
const Index S = m_segm.length();
const Index C = m_constr.length();
const Index R = (m_status==Attribute::Required).count(); // number of required_constraints
const int CC = arr_segm.size()>0 ? arr_segm.maxCoeff()+1 : 0;
const QString s0 = QApplication::tr("%1 connected component%2, ").arg(CC).arg(CC == 1 ? "" : "s");
const QString s1 = QApplication::tr("%1 segment%2, ").arg(S).arg(S == 1 ? "" : "s");
const QString s2 = QApplication::tr("%1 of %2 constraint%3 required.").arg(R).arg(C).arg(C == 1 ? "" : "s");
return s0 + s1 + s2;
}
void impl::identify_subtasks()
{
const VectorXi bicoco = conncomp( Rel.biadjacency());
arr_segm = bicoco.head( m_segm.length() ).array();
arr_constr = bicoco.tail( m_constr.length()).array();
}
void impl::reasoning_reduce_and_adjust()
{
// connected components / subtasks
identify_subtasks();
const int number_of_subtasks_ = arr_segm.size()>0 ? arr_segm.maxCoeff()+1 : 0;
// greedy search
for ( int cc=0; ccclone() );
m_status(c) = Attribute::Unevaluated;
}
}
if ( greedySearchRequired ) {
qDebug().nospace() << QString("Update of connected component #%1").arg(cc+1);
solve_subtask_greedy(cc); // reduce
}
}
}
void impl::remove_elements()
{
for ( Index c=m_qConstraint.size()-1; c>=0; c--) {
if ( m_qConstraint.at(c)->isSelected() ) {
remove_constraint(c);
}
}
for ( Index s=m_qConstrained.size()-1; s>=0; s--) {
if ( m_qConstrained.at(s)->isSelected()
|| m_qStroke.at(s)->isSelected()
|| m_qUnconstrained.at(s)->isSelected() ) {
for (int c=0; c(
m_segm.at(s)->ux(),
m_segm.at(s)->uy() );
q->setPen( m_qConstrained.at(s)->pen() );
m_qConstrained.replace( s, q);
}
}
// *** Check constraints. ***
// If any of the involved segements have been modified,
// the constraint has to be modified/replaced, too.
for (Index c=0; c idx = find( Rel.col(c).eval() );
if ( m_constr.at(c).use_count()==1 ) {
modified = true; // actually not modified, but added
}
else {
for (Index s=0; sclone();
assert( idx.size()>0 && idx.size()<4 ); // {1,2,3}-ary
q->setStatus( m_status(c)==Attribute::Required, m_enforced(c));
q->setGeometry( m_segm, idx );
m_qConstraint.replace( c, q);
}
}
assert( m_status.size()==m_enforced.size() );
}
void impl::append( const QPolygonF & track)
{
m_qStroke.append( std::make_shared( track) );
// end-points of straight line segment approximating the stroke
const std::pair xiyi = trackCoords(track);
const std::pair uxuy = uEndPoints(xiyi.first, xiyi.second);
// initially constrained==unconstrained
m_qUnconstrained.append( std::make_shared(
uxuy.first, uxuy.second
) );
m_qConstrained.append( std::make_shared(
uxuy.first, uxuy.second
) );
m_segm.append( std::make_shared( uxuy.first, uxuy.second ) );
Adj.augment(); // adjacency matrix
x_touches_l.augment();
y_touches_l.augment();
Rel.conservativeResize( Rel.rows()+1, Rel.cols() ); //append 1 row, relations: +1 segment
PP.augment();
}
void impl::setAltColors() const
{
const int N = arr_segm.size()>0 ? arr_segm.maxCoeff()+1 : 0;
for (int cc=0; cc=0 && hue<= 359 );
const QColor col = QColor::fromHsv( hue,255,255, 255); // hue, saturation, value, alpha
// (1) segments ...
const ArrayXi idx_s = find( arr_segm==cc );
for ( const auto s : idx_s ) {
m_qConstrained.at( s )->setAltColor(col);
}
// (2) constraints ...
const ArrayXi idx_c = find( arr_constr==cc );
for ( const auto c : idx_c ) {
m_qConstraint.at( c )->setAltColor( col );
}
}
}
bool State::deserialize( QDataStream & in)
{
return pImpl()->deserialize( in);
}
void State::toggleVisibilityStrokes()
{
pImpl()->toggleVisibilityStrokes();
}
void impl::toggleVisibilityStrokes()
{
for ( auto & item : m_qStroke) {
item->setVisible( !item->isVisible() );
}
}
void State::toggleVisibilityConstraints()
{
pImpl()->toggleVisibilityConstraints();
}
void impl::toggleVisibilityConstraints()
{
for ( auto & item : m_qConstraint) {
item->setVisible( !item->isVisible() );
}
}
void State::toggleVisibilityConstrained()
{
pImpl()->toggleVisibilityConstrained();
}
void impl::toggleVisibilityConstrained()
{
for ( auto & item : m_qConstrained) {
item->setVisible( !item->isVisible() );
}
}
void State::toggleVisibilityUnconstrained()
{
pImpl()->toggleVisibilityUnconstrained();
}
void impl::toggleVisibilityUnconstrained()
{
for ( auto & item : m_qUnconstrained) {
item->setVisible( !item->isVisible());
}
}
void impl::clearAll()
{
m_segm.clear();
m_constr.clear();
m_qStroke.clear();
m_qConstrained.clear();
m_qUnconstrained.clear();
m_qConstraint.clear();
x_touches_l.resize(0,0);
y_touches_l.resize(0,0);
Adj.resize(0,0);
PP.resize(0,0);
Rel.resize(0,0);
}
std::pair impl::trackCoords(const QPolygonF &poly)
{
const Index N = poly.length();
VectorXd xi(N);
VectorXd yi(N);
for ( int i=0; i
impl::uEndPoints(const VectorXd & xi, const VectorXd & yi)
{
assert( xi.size() > 0 );
assert( yi.size() == xi.size() );
// (1) uncertain straight line
const uStraightLine ul = uStraightLine::estim(xi,yi);
// (2) end-points with isotropic uncertrainty
const double phi = ul.angle_rad();
const VectorXd zi = std::sin(phi)*xi -std::cos(phi)*yi;
static const Matrix3d Zeros = Matrix3d::Zero(3,3);
int argmin = 0;
zi.minCoeff( &argmin);
const Vector3d x( xi(argmin), yi(argmin), 1);
const uDistance udx = uPoint(x, Zeros).distanceEuclideanTo(ul);
const Matrix3d Sigma_xx = Vector3d( udx.var_d(), udx.var_d(), 0).asDiagonal();
const uPoint ux = ul.project(uPoint(x, Sigma_xx));
int argmax = 0;
zi.maxCoeff( &argmax);
const Vector3d y( xi(argmax), yi(argmax), 1);
const uDistance udy = uPoint(y, Zeros).distanceEuclideanTo(ul);
const Matrix3d Sigma_yy = Vector3d( udy.var_d(), udy.var_d(), 0.).asDiagonal();
const uPoint uy = ul.project(uPoint(y, Sigma_yy));
return {ux, uy};
}
void impl::snapThisToOthers(const Index s)
{
uStraightLineSegment useg( *m_segm.at(s) );
bool changed = false;
for ( Index n=0; nhl())) {
changed = true;
break;
}
}
}
for ( Index n=0; nhl())) {
changed = true;
break;
}
}
}
if ( changed ) {
auto us = std::make_shared(useg);
m_segm.replace( s, us);
}
};
void impl::snapOthersToThis(const Index s, const Index cc)
{
for ( SparseMatrix::InnerIterator it( x_touches_l, s) ; it; ++it) {
const Index n = it.row(); // neighbor of segment s
if ( ( arr_segm(n) != cc) && ( !y_touches_l.isSet(it.index(),s) ) ) {
auto us = std::make_shared( *m_segm.at(n) );
if (us->move_x_to(m_segm.at(s)->hl())) {
m_segm.replace( n, us);
}
}
}
for (SparseMatrix::InnerIterator it(y_touches_l, s); it; ++it) {
const Index n = it.row() ; // neighbor of segment s
if ( ( arr_segm(n) != cc) && ( !x_touches_l.isSet(it.index(),s) )) {
auto us = std::make_shared( *m_segm.at(n) );
if (us->move_y_to(m_segm.at(s)->hl())) {
m_segm.replace( n, us);
}
}
}
};