#include "graphicsscene.h" // qt #include #include #include #include #include namespace orphex { namespace constants { const QLineF laserPlane{QPointF{-1000, 0}, QPointF{1000, 0}}; } } // namespace orphex GraphicsScene::GraphicsScene(QObject* parent) : QGraphicsScene{parent} { setSceneRect(QRectF{QPointF{-1000, -1000}, QPointF{1000, 1000}}); m_lenseItem = addEllipse( QRectF{QPointF{-1, -15}, QPointF{1, 15}}, QPen{Qt::yellow, 0} ); m_lenseItem->setTransformOriginPoint(m_lenseItem->boundingRect().center()); m_sensorItem = new QGraphicsLineItem(QLineF{}, m_lenseItem); m_sensorItem->setPen(QPen{Qt::red, 0}); m_lineOfActionItem = new QGraphicsLineItem{ QLineF{QPointF{0, -50}, QPointF{0, 50}}, m_lenseItem }; m_lineOfActionItem->setPen(QPen{Qt::green, 0}); m_opticalAxisItem = new QGraphicsLineItem{ QLineF{QPointF{-1000, 0}, QPointF{1000, 0}}, m_lenseItem }; m_opticalAxisItem->setPen(QPen{Qt::gray, 0}); using namespace orphex::constants; m_laserPlaneItem = addLine(laserPlane, QPen{Qt::black, 0}); m_laserPlaneItem->stackBefore(m_lenseItem); m_reverseLaserPlaneItem = new QGraphicsLineItem(QLineF{}, m_lenseItem); m_reverseLaserPlaneItem->setPen(QPen{Qt::magenta, 2}); m_reverseLaserPlaneItem->setVisible(false); m_desiredLaserPlaneItem = new QGraphicsLineItem(QLineF{}, m_lenseItem); m_desiredLaserPlaneItem->setPen(QPen{Qt::yellow, 0.1}); m_desiredLaserPlaneItem->setOpacity(0.5); m_desiredImagePlaneItem = new QGraphicsLineItem(QLineF{}, m_lenseItem); m_desiredImagePlaneItem->setPen(QPen{Qt::yellow, 0.15}); m_desiredImagePlaneItem->stackBefore(m_sensorItem); m_desiredRangeAreaItem = new QGraphicsPolygonItem{m_lenseItem}; m_desiredRangeAreaItem->setPen( QPen{m_desiredLaserPlaneItem->pen().color(), 0} ); m_desiredRangeAreaItem->setBrush( QBrush{m_desiredLaserPlaneItem->pen().color()} ); m_desiredRangeAreaItem->setOpacity(0.1); m_actualRangeItem = new QGraphicsLineItem(QLineF{}, m_lenseItem); m_actualRangeItem->setPen(QPen{Qt::red, 0.1}); m_actualRangeItem->setOpacity(0.5); m_actualRangeAreaItem = new QGraphicsPolygonItem{m_lenseItem}; m_actualRangeAreaItem->setPen(QPen{m_sensorItem->pen().color(), 0}); m_actualRangeAreaItem->setBrush(QBrush{m_sensorItem->pen().color()}); m_actualRangeAreaItem->setOpacity(0.1); m_sensorLenseIntersectionItem = new QGraphicsEllipseItem{ QRectF{QPointF{-1, -1}, QPointF{1, 1}}, m_lenseItem }; m_sensorLenseIntersectionItem->setPen(QPen{Qt::red, 0.1}); const auto sensorLenseTextItem = new QGraphicsTextItem{ tr("Scheimpflug: sensor/lense"), m_sensorLenseIntersectionItem }; sensorLenseTextItem->setPos(QPointF{-6, -8}); auto font = sensorLenseTextItem->font(); font.setPixelSize(2); sensorLenseTextItem->setFont(font); sensorLenseTextItem->setDefaultTextColor( m_sensorLenseIntersectionItem->pen().color() ); m_lenseLaserIntersectionItem = new QGraphicsEllipseItem{ QRectF{QPointF{-1, -1}, QPointF{1, 1}}, m_lenseItem }; m_lenseLaserIntersectionItem->setPen(QPen{Qt::blue, 0}); const auto lenseLaserTextItem = new QGraphicsTextItem{ tr("Scheimpflug: lense/laser"), m_lenseLaserIntersectionItem }; lenseLaserTextItem->setPos(QPointF{-6, -2}); lenseLaserTextItem->setFont(font); lenseLaserTextItem->setDefaultTextColor( m_lenseLaserIntersectionItem->pen().color() ); } void GraphicsScene::update(OpticalDesign* design) { qDebug() << "update"; if (!design) { qCritical() << Q_FUNC_INFO << "design is nullptr"; return; } using namespace orphex::constants; const auto F = design->get_focalDistanceMm(); const auto H = design->get_lenseYPosMm(); const auto w = design->get_sensorPixelsWidth(); const auto h = design->get_sensorPixelsHeight(); const auto wMm = design->get_sensorWidthMm(); const auto hMm = design->get_sensorHeightMm(); const auto oAngle = design->get_opticalAxisAngleDegrees(); m_lenseItem->setRotation(m_lenseItem->rotation()); m_lenseItem->setRotation(-oAngle); m_lenseItem->setPos(QPointF{0, -H}); const auto objectToImage = [F](const QPointF& objectPoint) -> QPointF { // please note that object X is negative const auto imagePointX = 1. / (1. / F - 1. / -objectPoint.x()); const auto magnification = imagePointX / objectPoint.x(); const auto imagePointY = magnification * objectPoint.y(); return QPointF{imagePointX, imagePointY}; }; const auto imageToObject = [F](const QPointF& imagePoint) -> QPointF { const auto objectPointX = 1. / (1. / imagePoint.x() - 1. / F); const auto magnification = imagePoint.x() / objectPointX; const auto objectPointY = imagePoint.y() / magnification; return QPointF{objectPointX, objectPointY}; }; // calculate sensor plane const auto& oai = m_opticalAxisItem; const auto& oail = m_opticalAxisItem->line(); const QLineF opticalAxisScene{ oai->mapToScene(oail.p1()), oai->mapToScene(oail.p2()) }; QPointF intersection{}; const auto type = m_laserPlaneItem->line().intersects(opticalAxisScene, &intersection); if (type == QLineF::NoIntersection) { qCritical() << "no intersection between laser plane and optical axis"; return; } if (!qFuzzyCompare(intersection.y(), 0.)) { qCritical() << "laser plane/optical axis intersection point y != 0"; return; } // take 2 points - to the left and to the right from intersection point; const auto leftP = intersection / 4; // const auto rightP = intersection + leftP; const auto rightP = intersection * 1.5; // create desired line on laser plane m_desiredLaserPlaneItem->setLine( {m_lenseItem->mapFromScene(QPointF{-design->get_zBaseMm(), 0}), m_lenseItem->mapFromScene( QPointF{-(design->get_zBaseMm() + design->get_zRangeMm()), 0} )} ); // calculate image plane const auto debugL = m_desiredLaserPlaneItem->line(); const QLineF debugImageL{ objectToImage(debugL.p1()), objectToImage(debugL.p2()) }; m_desiredImagePlaneItem->setLine(debugImageL); // create sensor line on image plane QPointF sensorCenterPos{}; if (m_opticalAxisItem->line().intersects( m_desiredImagePlaneItem->line(), &sensorCenterPos ) == QLineF::NoIntersection) { qCritical() << "no intersection between sensor plane and optical axis"; return; } const auto sensorHalfHeight = design->get_sensorHeightMm() / 2.; QLineF sensorLine{QPointF{0, 0}, QPointF{0, sensorHalfHeight}}; sensorLine.translate(sensorCenterPos); sensorLine.setAngle(m_desiredImagePlaneItem->line().angle()); sensorLine.setP1(sensorCenterPos + (sensorCenterPos - sensorLine.p2())); // at this momet sensor is centered and rotated. apply vertical offset const auto offsetRatio = design->get_sensorVerticalOffsetMm() / sensorLine.length(); const auto offset = sensorLine.pointAt(1 + offsetRatio) - sensorLine.p2(); sensorLine.setPoints(sensorLine.p1() + offset, sensorLine.p2() + offset); m_sensorItem->setLine(sensorLine); // find laser plane range which corresponds to sensor position m_actualRangeItem->setLine({ imageToObject(m_sensorItem->line().p1()), imageToObject(m_sensorItem->line().p2()), }); // not that object area has negative coords design->set_actualZBaseMm( -m_actualRangeItem->mapToScene(m_actualRangeItem->line().p1()).x() ); design->set_actualZRangeMm(m_actualRangeItem->line().length()); // convert debug image plane to laser plane just to verify they are the same // TODO: check with code and log errors? const QLineF reverseLaserPlaneLine{ imageToObject(m_desiredImagePlaneItem->line().p1()), imageToObject(m_desiredImagePlaneItem->line().p2()) }; m_reverseLaserPlaneItem->setLine(reverseLaserPlaneLine); // fill desired range area m_desiredRangeAreaItem->setPolygon( QPolygonF{ {m_desiredImagePlaneItem->line().p1(), m_desiredImagePlaneItem->line().p2(), m_desiredLaserPlaneItem->line().p2(), m_desiredLaserPlaneItem->line().p1()} } ); // fill actual range area m_actualRangeAreaItem->setPolygon( QPolygonF{ {m_sensorItem->line().p1(), m_sensorItem->line().p2(), m_actualRangeItem->line().p2(), m_actualRangeItem->line().p1()} } ); design->set_sensorLenseAngleDegrees( m_desiredImagePlaneItem->line().angleTo(m_lineOfActionItem->line()) ); // TODO: take back focal length into account // check Scheimpflug principle // TODO: draw lines? QPointF sensorLenseIntersection{}; if (m_sensorItem->line().intersects( m_lineOfActionItem->line(), &sensorLenseIntersection ) == QLineF::NoIntersection) { qWarning() << "no intersection between sensor plane and lense plane"; return; } m_sensorLenseIntersectionItem->setPos(sensorLenseIntersection); QPointF lenseLaserIntersection{}; if (m_lineOfActionItem->line().intersects( m_actualRangeItem->line(), &lenseLaserIntersection ) == QLineF::NoIntersection) { qWarning() << "no intersection between laser plane and lense plane"; return; } m_lenseLaserIntersectionItem->setPos(lenseLaserIntersection); if (!qFuzzyCompare(sensorLenseIntersection, lenseLaserIntersection)) { qWarning() << "The Scheimpflug principle is not observed:" << Qt::endl << "sensor/lense intersection:" << sensorLenseIntersection << Qt::endl << "lense/laser intersection:" << lenseLaserIntersection; } // TODO: move to settings // doesn't work (doesn't zoom) constexpr bool autoScale{false}; if (autoScale) { // region of interest QRectF ROI{}; ROI |= m_actualRangeAreaItem->boundingRect(); ROI |= m_desiredRangeAreaItem->boundingRect(); ROI |= m_lenseItem->boundingRect(); ROI |= m_sensorItem->boundingRect(); ROI |= m_sensorLenseIntersectionItem->boundingRect(); ROI |= m_lenseLaserIntersectionItem->boundingRect(); for (const auto& view : views()) { // TODO: move to settings constexpr double margin{5.}; view->setSceneRect(ROI); } } // calculate distance between sensor and lense on optical axis QPointF sensorAxisIntersection{}; if (m_sensorItem->line().intersects( m_opticalAxisItem->line(), &sensorAxisIntersection ) == QLineF::NoIntersection) { qCritical() << "no intersection between sensor and optical axis"; return; } design->set_lenseSensorDistanceMm( QLineF{QPointF{0, 0}, sensorAxisIntersection}.length() ); // calculate depth of field QPointF axisLaserIntersection{}; if (m_opticalAxisItem->line().intersects( m_actualRangeItem->line(), &axisLaserIntersection ) == QLineF::NoIntersection) { qCritical() << "no intersection between laser and optical axis"; return; } // const auto zM = std::min( // design->get_sensorPixelsHeight(), // design->get_sensorPixelsWidth() // ) / // 1000. / 1000.; // try z based on sensor cell size // const auto zMm = std::min( // design->get_sensorCellHeightUm(), // design->get_sensorCellHeightUm() // ) / // 1000.; // try z based on 1/16 sensor cell size (calibration step) const auto zMm = std::min( design->get_sensorCellHeightUm(), design->get_sensorCellWidthUm() ) / 1000. / 16.; // try z based on E. Goldovsky method - 0.2% of sensor height // const auto zMm = design->get_sensorHeightMm() * 0.2 / 100.; // const auto RM = // QLineF{QPointF{0., 0.}, axisLaserIntersection}.length() * 1000.; const auto RMidMm = QLineF{QPointF{0., 0.}, axisLaserIntersection}.length(); // const auto RFrontMm = QLineF{QPointF{0., 0.}, // axisLaserIntersection}.length(); const auto fM = // design->get_backFocalDistanceMm() * 1000.; // Gordijchuk & Pell, 1979, say to use focal distance. // Wikipedia, based on the same book, says to use back focal distance. WTF? // const auto fMm = design->get_backFocalDistanceMm(); const auto fMm = design->get_focalDistanceMm(); const auto K = static_cast(design->get_lenseAperture()) / 10.; using FromSharpDistMm = double; using BackSharpDistMm = double; using DofResult = std::tuple; const auto calculateDof = [zMm, fMm, K](const auto RMm) -> DofResult { const auto ff = fMm * fMm; const auto Rff = RMm * ff; const auto Kfz = K * fMm * zMm; const auto KRz = K * RMm * zMm; const auto R1Mm = Rff / (ff - Kfz + KRz); const auto R2Mm = Rff / (ff + Kfz - KRz); return {R1Mm, R2Mm}; }; const auto ff = fMm * fMm; const auto Rff = RMidMm * ff; const auto Kfz = K * fMm * zMm; const auto KRz = K * RMidMm * zMm; // const auto R1M = Rff / (ff - Kfz + KRz); // const auto R2M = Rff / (ff + Kfz - KRz); // const auto R1Mm = Rff / (ff - Kfz + KRz); // const auto R2Mm = Rff / (ff + Kfz - KRz); const auto [R1Mm, R2Mm] = calculateDof(RMidMm); qDebug() << "AAAAA: update" << R1Mm << R2Mm << design->get_sensorCellHeightUm(); design->set_frontSharpDistanceMm(R1Mm); design->set_backSharpDistanceMm(R2Mm); design->set_depthOfFieldMm((R2Mm - R1Mm)); }