summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNikita Kostovsky <nikita@kostovsky.me>2025-09-20 17:34:39 +0200
committerNikita Kostovsky <nikita@kostovsky.me>2025-09-20 17:34:39 +0200
commitb724e714d24b7623718016af921e1d9aab8d02ea (patch)
tree3d4efb61e43d8e6196c7eda061181897b27a9eee
parentad001563fda4a9061909bd09dcf51238138014d6 (diff)
check Scheimpflug principle; calculate distance to matrix and depth of fieldHEADmaster
-rw-r--r--src/graphicsscene.cpp199
-rw-r--r--src/graphicsscene.h4
-rw-r--r--src/mainwindow.cpp42
-rw-r--r--src/opticaldesign.h14
m---------third_party/goodies0
5 files changed, 254 insertions, 5 deletions
diff --git a/src/graphicsscene.cpp b/src/graphicsscene.cpp
index 52416d5..e918ccc 100644
--- a/src/graphicsscene.cpp
+++ b/src/graphicsscene.cpp
@@ -4,6 +4,8 @@
#include <QGraphicsEllipseItem>
#include <QGraphicsLineItem>
#include <QGraphicsPolygonItem>
+#include <QGraphicsTextItem>
+#include <QGraphicsView>
namespace orphex
{
@@ -31,7 +33,7 @@ GraphicsScene::GraphicsScene(QObject* parent)
QLineF{QPointF{0, -50}, QPointF{0, 50}},
m_lenseItem
};
- m_lineOfActionItem->setPen(QPen{Qt::gray, 0});
+ m_lineOfActionItem->setPen(QPen{Qt::green, 0});
m_opticalAxisItem = new QGraphicsLineItem{
QLineF{QPointF{-1000, 0}, QPointF{1000, 0}},
@@ -72,10 +74,47 @@ GraphicsScene::GraphicsScene(QObject* parent)
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";
@@ -229,4 +268,162 @@ void GraphicsScene::update(OpticalDesign* design)
);
// 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<int>(design->get_lenseAperture()) / 10.;
+
+ using FromSharpDistMm = double;
+ using BackSharpDistMm = double;
+ using DofResult = std::tuple<FromSharpDistMm, BackSharpDistMm>;
+ 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));
}
diff --git a/src/graphicsscene.h b/src/graphicsscene.h
index 289deba..1c50c78 100644
--- a/src/graphicsscene.h
+++ b/src/graphicsscene.h
@@ -30,4 +30,8 @@ private:
QGraphicsLineItem* m_actualRangeItem{nullptr};
QGraphicsPolygonItem* m_desiredRangeAreaItem{nullptr};
QGraphicsPolygonItem* m_actualRangeAreaItem{nullptr};
+
+ // debug
+ QGraphicsEllipseItem* m_sensorLenseIntersectionItem{nullptr};
+ QGraphicsEllipseItem* m_lenseLaserIntersectionItem{nullptr};
};
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
index 66274c1..e73df5a 100644
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -154,6 +154,13 @@ void MainWindow::initUi()
}
);
+ connect(
+ m_design,
+ qOverload<>(&OpticalDesign::lenseApertureChanged),
+ m_scene,
+ std::bind(&GraphicsScene::update, m_scene, m_design)
+ );
+
leftLayout->addRow(tr("Lense A&perture:"), comboBox);
};
@@ -184,6 +191,14 @@ void MainWindow::initUi()
MinValue{1.},
MaxValue{45.}
);
+ addFormPair(
+ tr("&Back Focal Distance (mm):"),
+ qOverload<double>(&OpticalDesign::backFocalDistanceMmChanged),
+ &OpticalDesign::get_backFocalDistanceMm,
+ &OpticalDesign::set_backFocalDistanceMm,
+ MinValue{1.},
+ MaxValue{45.}
+ );
createLenseApertureComboBox();
@@ -271,6 +286,30 @@ void MainWindow::initUi()
Decimals{2}
);
+ addFormLabel(
+ tr("Lense-Sensor Distance (mm):"),
+ qOverload<double>(&OpticalDesign::lenseSensorDistanceMmChanged),
+ Decimals{2}
+ );
+
+ addFormLabel(
+ tr("Front Sharp Distance (mm):"),
+ qOverload<double>(&OpticalDesign::frontSharpDistanceMmChanged),
+ Decimals{2}
+ );
+
+ addFormLabel(
+ tr("Back Sharp Distance (mm):"),
+ qOverload<double>(&OpticalDesign::backSharpDistanceMmChanged),
+ Decimals{2}
+ );
+
+ addFormLabel(
+ tr("Depth Of Field (mm):"),
+ qOverload<double>(&OpticalDesign::depthOfFieldMmChanged),
+ Decimals{2}
+ );
+
// graphics view
const auto openGlWidget = new QOpenGLWidget{centralWidget()};
QSurfaceFormat surfaceFormat{};
@@ -291,7 +330,8 @@ void MainWindow::initUi()
);
m_view->setTransformationAnchor(GraphicsView::AnchorViewCenter);
- m_view->scale(15, 15);
+ m_view->scale(5, 5);
+ m_view->setTransformationAnchor(GraphicsView::AnchorUnderMouse);
m_view->centerOn({0, -m_design->get_lenseYPosMm()});
m_scene->update(m_design);
diff --git a/src/opticaldesign.h b/src/opticaldesign.h
index e6fb2c6..5a7ebac 100644
--- a/src/opticaldesign.h
+++ b/src/opticaldesign.h
@@ -36,13 +36,13 @@ Q_ENUM_NS(LenseAperture)
// Q_DECLARE_METATYPE(orphex::LenseAperture)
-class OpticalDesign : public G_Object
+class OpticalDesign : public goodies::G_Object
{
Q_OBJECT
- DEFAULT_CONSTRUCTOR(G_Object, OpticalDesign)
+ DEFAULT_CONSTRUCTOR(goodies::G_Object, OpticalDesign)
DEFAULT_DESTRUCTOR(OpticalDesign);
- G_OBJECT(G_Object, OpticalDesign)
+ G_OBJECT(goodies::G_Object, OpticalDesign)
public:
void calculate() const;
@@ -51,12 +51,16 @@ protected:
INI_PROPERTY(double, opticalAxisAngleDegrees, 45.);
// the Y axis is directed from the laser to the lens
INI_PROPERTY(double, lenseYPosMm, 50.);
+ // default values for CS2006ZM06 lense
INI_PROPERTY(double, focalDistanceMm, 6.);
+ INI_PROPERTY(double, backFocalDistanceMm, 7.3);
+ // FIXME: loading doesn't work
INI_PROPERTY_VAR(
orphex::LenseAperture,
lenseAperture,
orphex::LenseAperture::F1_2
);
+ // default values for IMX287 sensor
INI_PROPERTY(double, sensorWidthMm, 4.98);
INI_PROPERTY(double, sensorHeightMm, 3.74);
INI_PROPERTY(double, sensorCellWidthUm, 6.9);
@@ -71,4 +75,8 @@ protected:
G_PROPERTY_DEFAULT(double, sensorLenseAngleDegrees, 0.);
G_PROPERTY_DEFAULT(double, actualZBaseMm, 0.);
G_PROPERTY_DEFAULT(double, actualZRangeMm, 0.);
+ G_PROPERTY_DEFAULT(double, lenseSensorDistanceMm, 0.);
+ G_PROPERTY_DEFAULT(double, frontSharpDistanceMm, 0.);
+ G_PROPERTY_DEFAULT(double, backSharpDistanceMm, 0.);
+ G_PROPERTY_DEFAULT(double, depthOfFieldMm, 0.);
};
diff --git a/third_party/goodies b/third_party/goodies
-Subproject e7019763076cbbe3d52c2a03133c3ded5558f01
+Subproject ffac2767127fe0bdb990187bbf9ec640e06933a