From 61321e78f26c4937358da1d75febeff66975d699 Mon Sep 17 00:00:00 2001
From: zeisele <zeisele@iastate.edu>
Date: Wed, 23 Mar 2022 04:18:58 +0100
Subject: [PATCH] log block tab changes and graphing preparation

---
 .../gui/MicroCART/crazyflieworker.cpp         |  12 +
 groundStation/gui/MicroCART/crazyflieworker.h |   1 +
 groundStation/gui/MicroCART/logworker.cpp     |  45 ++++
 groundStation/gui/MicroCART/logworker.h       |  43 ++++
 groundStation/gui/MicroCART/mainwindow.cpp    |  91 ++++++--
 groundStation/gui/MicroCART/mainwindow.h      |   9 +
 groundStation/gui/MicroCART/mainwindow.ui     | 207 +++++++++++++++++-
 7 files changed, 387 insertions(+), 21 deletions(-)
 create mode 100644 groundStation/gui/MicroCART/logworker.cpp
 create mode 100644 groundStation/gui/MicroCART/logworker.h

diff --git a/groundStation/gui/MicroCART/crazyflieworker.cpp b/groundStation/gui/MicroCART/crazyflieworker.cpp
index cb241a10c..5fa186c7c 100644
--- a/groundStation/gui/MicroCART/crazyflieworker.cpp
+++ b/groundStation/gui/MicroCART/crazyflieworker.cpp
@@ -35,6 +35,18 @@ void CrazyflieWorker::connectBackend()
         paramGroupList.append(qgName);
     }
     emit(gotParamGroups(paramGroupList));
+    QStringList logVariablesList;
+    struct toc_group curGroup;
+    for(int i = 0; i < logGroups.size(); i++) {
+        curGroup = logGroups.at(i);
+        for(int j = 0; j < logGroups.at(i).entryNames.size(); j++) {
+            QString qvName(logGroups.at(i).groupName.c_str());
+            qvName = qvName.append(".");
+            qvName = qvName.append(logGroups.at(i).entryNames.at(j).c_str());
+            logVariablesList.append(qvName);
+        }
+    }
+    emit(gotLogVariables(logVariablesList));
 }
 
 /**
diff --git a/groundStation/gui/MicroCART/crazyflieworker.h b/groundStation/gui/MicroCART/crazyflieworker.h
index 43d37fc49..42dca39f0 100644
--- a/groundStation/gui/MicroCART/crazyflieworker.h
+++ b/groundStation/gui/MicroCART/crazyflieworker.h
@@ -42,6 +42,7 @@ public:
 signals:
     void gotParamValue(QString paramName, float value);
     void gotParamGroups(QStringList paramGroupList);
+    void gotLogVariables(QStringList logVariables);
     void gotGroupEntries(int box, QStringList groupEntries);
     void connected();
     void disconnected();
diff --git a/groundStation/gui/MicroCART/logworker.cpp b/groundStation/gui/MicroCART/logworker.cpp
new file mode 100644
index 000000000..f09c4b9b9
--- /dev/null
+++ b/groundStation/gui/MicroCART/logworker.cpp
@@ -0,0 +1,45 @@
+#include "logworker.h"
+#include <iostream>
+#include <unistd.h>
+#include <sys/stat.h>
+
+LogWorker::LogWorker(QObject *parent) : QObject(parent), conn(NULL)
+{
+}
+
+LogWorker::~LogWorker()
+{
+    disconnectBackend();
+}
+
+/**
+ * @brief CrazyflieWorker::connectBackend
+ * Command the worker to connect to the backend
+ * will set the private class struct, conn, once done.
+ * The connected() signal is emitted once complete.
+ */
+void LogWorker::connectBackend()
+{
+    if (conn == NULL) {
+        conn = ucart_backendConnect();
+        emit (connected());
+    } else {
+        qInfo() << "Attempted to connect crazyflieworker when already connected!";
+    }
+    
+}
+
+/**
+ * @brief CrazyflieWorker::disconnectBackend
+ * Command the worker to disconnect from the backend,
+ * sets the conn struct to null and emits the
+ * disconnected() signal once complete
+ */
+void LogWorker::disconnectBackend()
+{
+    if (conn) {
+         ucart_backendDisconnect(conn);
+         conn = NULL;
+         emit (disconnected());
+    }
+}
\ No newline at end of file
diff --git a/groundStation/gui/MicroCART/logworker.h b/groundStation/gui/MicroCART/logworker.h
new file mode 100644
index 000000000..539a168c6
--- /dev/null
+++ b/groundStation/gui/MicroCART/logworker.h
@@ -0,0 +1,43 @@
+#ifndef LOGWORKER_H
+#define LOGWORKER_H
+
+#include <QObject>
+#include <fstream>
+#include <sstream>
+#include <vector>
+#include <QStringList>
+#include "frontend_common.h"
+#include "frontend_param.h"
+#include "frontend_override.h"
+#include "frontend_logfile.h"
+#include "setpoint.h"
+#include <QDebug>
+
+
+/**
+ * @brief The crazyflieworker class
+ * This class handles communication for the crazyflie,
+ * reading log values and setting parameter values
+ */
+class LogWorker : public QObject
+{
+    Q_OBJECT
+public:
+    explicit LogWorker(QObject *parent = nullptr);
+    ~LogWorker();
+    
+
+signals:
+    void connected();
+    void disconnected();
+
+
+public slots:
+    void connectBackend();
+    void disconnectBackend();
+
+private:
+    struct backend_conn * conn;
+};
+
+#endif // LOGWORKER_H
diff --git a/groundStation/gui/MicroCART/mainwindow.cpp b/groundStation/gui/MicroCART/mainwindow.cpp
index 7835d8577..47d5ca653 100644
--- a/groundStation/gui/MicroCART/mainwindow.cpp
+++ b/groundStation/gui/MicroCART/mainwindow.cpp
@@ -55,24 +55,24 @@ MainWindow::MainWindow(QWidget *parent) :
     workerStartTimer->setSingleShot(true);
 
     /* Create a thread for workers */
-    QThread* workerThread = new QThread(this);
+    //QThread* workerThread = new QThread(this);
 
     /* Create a worker to update the tracker */
-    TrackerWorker * trackerWorker = new TrackerWorker();
+   // TrackerWorker * trackerWorker = new TrackerWorker();
 
     /* Move it to the worker thread. This means that slots of this worker
      * will run in the worker thread, and not block the UI */
-    trackerWorker->moveToThread(workerThread);
+    //trackerWorker->moveToThread(workerThread);
 
     /* Connect tracker worker */
-    connect(trackerWorker, SIGNAL (finished(float, float, float, float, float, float)),
-            this, SLOT (updateTracker(float, float, float, float, float, float)));
+    //connect(trackerWorker, SIGNAL (finished(float, float, float, float, float, float)),
+    //        this, SLOT (updateTracker(float, float, float, float, float, float)));
 
-    connect(trackerWorker, SIGNAL (finished(float, float, float, float, float, float)),
-            posIndicator, SLOT(updatePos(float,float,float,float,float,float)));
+    //connect(trackerWorker, SIGNAL (finished(float, float, float, float, float, float)),
+    //        posIndicator, SLOT(updatePos(float,float,float,float,float,float)));
 
-    connect(trackerWorker, SIGNAL (finished(float, float, float, float, float, float)),
-            attIndicator, SLOT(updateAttitude(float, float, float, float, float, float)));
+    //connect(trackerWorker, SIGNAL (finished(float, float, float, float, float, float)),
+    //        attIndicator, SLOT(updateAttitude(float, float, float, float, float, float)));
 
     backendProcess->setProcessChannelMode(QProcess::MergedChannels);
 
@@ -102,7 +102,19 @@ MainWindow::MainWindow(QWidget *parent) :
     crazyflieWorker = new CrazyflieWorker();
     crazyflieWorker->moveToThread(cfThread);
 
+    QThread * logThread = new QThread(this);
+    logWorker = new LogWorker();
+    logWorker->moveToThread(logThread);
+
     /* connect signals for crazyflie worker */
+    connect(ui->logEntriesComboBox1, SIGNAL (currentIndexChanged(int)), this, SLOT (getLogEntryBoxChange1(int)));
+    connect(ui->logEntriesComboBox2, SIGNAL (currentIndexChanged(int)), this, SLOT (getLogEntryBoxChange2(int)));
+    connect(ui->logEntriesComboBox3, SIGNAL (currentIndexChanged(int)), this, SLOT (getLogEntryBoxChange3(int)));
+    connect(ui->logEntriesComboBox4, SIGNAL (currentIndexChanged(int)), this, SLOT (getLogEntryBoxChange4(int)));
+    connect(ui->logEntriesComboBox5, SIGNAL (currentIndexChanged(int)), this, SLOT (getLogEntryBoxChange5(int)));
+    connect(ui->pb_startLog, SIGNAL (clicked()), logWorker, SLOT (getLogValues(QString, QString, QString, QString, QString)));
+    connect(logWorker, SIGNAL (gotLogValues(QStringList)), this, SLOT (graphLogs(QStringList)));
+
     connect(this, SIGNAL (rateSetpointSignal(float, float, float, float)), crazyflieWorker, SLOT (setCurrAttRateSetpoint(float, float, float, float)));
     connect(this, SIGNAL (angleSetpointSignal(float, float, float, float)), crazyflieWorker, SLOT (setCurrAttSetpoint(float, float, float, float)));
     connect(crazyflieTimer, SIGNAL(timeout()), this, SLOT(trigger_send_setpoint()));
@@ -110,6 +122,7 @@ MainWindow::MainWindow(QWidget *parent) :
     connect(this, SIGNAL(triggerAttRateSetpointSend()), crazyflieWorker, SLOT(sendAttRateSetpoint()));
     connect(this, SIGNAL(triggerMixedAttSetpointSend()), crazyflieWorker, SLOT(sendMixedAttSetpoint()));
     connect(crazyflieWorker, SIGNAL (gotParamGroups(QStringList)), this, SLOT (setParamGroups(QStringList)));
+    connect(crazyflieWorker, SIGNAL (gotLogVariables(QStringList)), this, SLOT (setLoggingVariableBox(QStringList)));
 
     connect(ui->paramGroupComboBox, SIGNAL (currentIndexChanged(int)), this, SLOT (getGroupBoxChange(int)));
     connect(ui->paramGroupComboBox_2, SIGNAL (currentIndexChanged(int)), this, SLOT (getGroupBox2Change(int)));
@@ -125,15 +138,18 @@ MainWindow::MainWindow(QWidget *parent) :
     connect(crazyflieWorker, SIGNAL (gotParamValue(QString, float)), this, SLOT (displayParamValue(QString, float)));
 
     /* Connect and disconnect from backend when signals emitted */
-    connect(workerStartTimer, SIGNAL (timeout()), trackerWorker, SLOT (connectBackend()));
+    //connect(workerStartTimer, SIGNAL (timeout()), trackerWorker, SLOT (connectBackend()));
+    connect(workerStartTimer, SIGNAL (timeout()), logWorker, SLOT (connectBackend()));
     connect(workerStartTimer, SIGNAL (timeout()), controlWorker, SLOT (connectBackend()));
     connect(workerStartTimer, SIGNAL (timeout()), crazyflieWorker, SLOT (connectBackend()));
 
-    connect(this, SIGNAL (connectWorkers()), trackerWorker, SLOT (connectBackend()));
+    //connect(this, SIGNAL (connectWorkers()), trackerWorker, SLOT (connectBackend()));
+    connect(this, SIGNAL (connectWorkers()), logWorker, SLOT (connectBackend()));
     connect(this, SIGNAL (connectWorkers()), controlWorker, SLOT (connectBackend()));
     connect(this, SIGNAL (connectWorkers()), crazyflieWorker, SLOT (connectBackend()));
 
-    connect(this, SIGNAL (disconnectWorkers()), trackerWorker, SLOT (disconnectBackend()));
+    //connect(this, SIGNAL (disconnectWorkers()), trackerWorker, SLOT (disconnectBackend()));
+    connect(this, SIGNAL (disconnectWorkers()), logWorker, SLOT (disconnectBackend()));
     connect(this, SIGNAL (disconnectWorkers()), controlWorker, SLOT (disconnectBackend()));
     connect(this, SIGNAL (disconnectWorkers()), crazyflieWorker, SLOT (disconnectBackend()));
 
@@ -146,8 +162,11 @@ MainWindow::MainWindow(QWidget *parent) :
     connect(controlWorker, SIGNAL (connected()), this, SLOT (workerConnected()));
     connect(controlWorker, SIGNAL (disconnected()), this, SLOT (workerDisconnected()));
 
-    connect(trackerWorker, SIGNAL (connected()), this, SLOT (workerConnected()));
-    connect(trackerWorker, SIGNAL (disconnected()), this, SLOT (workerDisconnected()));
+    //connect(trackerWorker, SIGNAL (connected()), this, SLOT (workerConnected()));
+    //connect(trackerWorker, SIGNAL (disconnected()), this, SLOT (workerDisconnected()));
+
+    connect(logWorker, SIGNAL (connected()), this, SLOT (workerConnected()));
+    connect(logWorker, SIGNAL (disconnected()), this, SLOT (workerDisconnected()));
 
     connect(crazyflieWorker, SIGNAL (connected()), this, SLOT (workerConnected()));
     connect(crazyflieWorker, SIGNAL (disconnected()), this, SLOT (workerDisconnected()));
@@ -156,7 +175,7 @@ MainWindow::MainWindow(QWidget *parent) :
     connect(trackerTimer, SIGNAL(timeout()), this, SLOT(updatePosAtt()));
     // connect(ui->pbRefresh, SIGNAL (clicked()), this, SLOT (updatePosAtt()));
 
-    connect(this, SIGNAL(getPosAttFromBackend()), trackerWorker, SLOT(process()));
+    //connect(this, SIGNAL(getPosAttFromBackend()), trackerWorker, SLOT(process()));
 
     /* Timer used for next setpoint */
     nextSpTimer->setSingleShot(true);
@@ -165,9 +184,10 @@ MainWindow::MainWindow(QWidget *parent) :
     /* Start the things */
     trackerTimer->start(100);
     crazyflieTimer->start(100);
-    workerThread->start();
+    //workerThread->start();
     cwThread->start();
     cfThread->start();
+    logThread->start();
     matlabProcess->start();
 
     /* Connect the setpointlist to the model */
@@ -352,6 +372,10 @@ void MainWindow::setParamGroups(QStringList groupNames) {
     ui->paramGroupComboBox_2->addItems(groupNames);
 }
 
+void MainWindow::setLoggingVariableBox(QStringList variableNames) {
+    ui->logVariableList->addItems(variableNames);
+}
+
 void MainWindow::getGroupBoxChange(int index) {
     // for(int i = 0; i < ui->paramGroupComboBox->count(); i++) {
     //     ui->paramEntriesComboBox->removeItem(i);
@@ -993,3 +1017,38 @@ void MainWindow::on_gamepadPitchScale_valueChanged(int arg1)
     ui->pitchVizBar->setMinimum(std::abs(arg1) * -1);
     ui->pitchVizBar->setMaximum(std::abs(arg1));
 }
+
+void MainWindow::graphLogs(QStringList logs)
+{   
+    ui->dataPlot->graph(0)->setPen(QPen(Qt::blue)); 
+    ui->dataPlot->graph(1)->setPen(QPen(Qt::red)); 
+    ui->dataPlot->graph(2)->setPen(QPen(Qt::green)); 
+    ui->dataPlot->graph(3)->setPen(QPen(Qt::black)); 
+    //ui->dataPlot->graph(4)->setPen(QPen(Qt::purple)); 
+
+
+    //parse data and put into vector
+    QVector<float> x0(logs.size()), y0(logs.size());
+    QVector<float> x1(logs.size()), y1(logs.size());
+    QVector<float> x2(logs.size()), y2(logs.size());
+    QVector<float> x3(logs.size()), y3(logs.size());
+    QVector<float> x4(logs.size()), y4(logs.size());
+
+    for(int i=0; i< logs.size(); i++) {
+        //use tokenizer here
+    }
+
+    //start graph and set data
+    ui->dataPlot->addGraph();
+   // ui->dataPlot->graph(0)->setData(x0, y0);
+
+    //set lables and boundaries
+    ui->dataPlot->xAxis->setLabel("time");
+    ui->dataPlot->yAxis->setLabel("angle");
+
+    ui->dataPlot->xAxis->setRange(0, 1);
+    ui->dataPlot->yAxis->setRange(0, 360);
+
+    ui->dataPlot->replot();
+}
+
diff --git a/groundStation/gui/MicroCART/mainwindow.h b/groundStation/gui/MicroCART/mainwindow.h
index becd6e3f5..a7bbcb446 100644
--- a/groundStation/gui/MicroCART/mainwindow.h
+++ b/groundStation/gui/MicroCART/mainwindow.h
@@ -8,6 +8,7 @@
 #include "quaditem.h"
 #include "gamepadmonitor.h"
 #include "crazyflieworker.h"
+#include "logworker.h"
 
 
 namespace Ui {
@@ -37,6 +38,9 @@ signals:
     void triggerAttSetpointSend();
     void triggerAttRateSetpointSend();
     void triggerMixedAttSetpointSend();
+    void getLogValues(QString log1, QString log2, QString log3, QString log4, QString log5);
+
+
 
 private slots:
     void on_pbStart_clicked();
@@ -113,6 +117,7 @@ private slots:
     void setParamGroups(QStringList groupNames);
     void getGroupBoxChange(int index);
     void getGroupBox2Change(int index);
+    void setLoggingVariableBox(QStringList variableNames);
     void displayParamValue(QString param, float value);
     void getEntryBoxChange(int index);
     void getEntryBoxChange2(int index);
@@ -146,6 +151,9 @@ private slots:
 
     void on_gamepadPitchScale_valueChanged(int arg1);
 
+    void graphLogs(QStringList logs);
+
+
 private:
     Ui::MainWindow *ui;
     QStandardItemModel * setpointList;
@@ -163,6 +171,7 @@ private:
     int connectedWorkers;
     GamepadMonitor *gamepadMonitor;
     CrazyflieWorker *crazyflieWorker;
+    LogWorker *logWorker;
 };
 
 #endif // MAINWINDOW_H
diff --git a/groundStation/gui/MicroCART/mainwindow.ui b/groundStation/gui/MicroCART/mainwindow.ui
index c202a2c5c..ca31bb05e 100644
--- a/groundStation/gui/MicroCART/mainwindow.ui
+++ b/groundStation/gui/MicroCART/mainwindow.ui
@@ -33,7 +33,7 @@
        </size>
       </property>
       <property name="currentIndex">
-       <number>2</number>
+       <number>3</number>
       </property>
       <widget class="QWidget" name="backend">
        <attribute name="title">
@@ -132,7 +132,7 @@
       </widget>
       <widget class="QWidget" name="param">
        <attribute name="title">
-        <string>Param</string>
+        <string>Parameters</string>
        </attribute>
        <widget class="QComboBox" name="paramGroupComboBox">
         <property name="geometry">
@@ -794,12 +794,128 @@
       </widget>
       <widget class="QWidget" name="plots">
        <attribute name="title">
-        <string>Plots</string>
+        <string>Log Blocks</string>
        </attribute>
+       <widget class="QComboBox" name="comboBox">
+        <property name="geometry">
+         <rect>
+          <x>410</x>
+          <y>680</y>
+          <width>201</width>
+          <height>25</height>
+         </rect>
+        </property>
+        <item>
+         <property name="text">
+          <string>Logging Block</string>
+         </property>
+        </item>
+       </widget>
+       <widget class="QPushButton" name="pushButton">
+        <property name="geometry">
+         <rect>
+          <x>330</x>
+          <y>750</y>
+          <width>80</width>
+          <height>25</height>
+         </rect>
+        </property>
+        <property name="text">
+         <string>Resume</string>
+        </property>
+       </widget>
+       <widget class="QPushButton" name="pushButton_2">
+        <property name="geometry">
+         <rect>
+          <x>470</x>
+          <y>750</y>
+          <width>80</width>
+          <height>25</height>
+         </rect>
+        </property>
+        <property name="text">
+         <string>Pause</string>
+        </property>
+       </widget>
+       <widget class="QPushButton" name="pushButton_3">
+        <property name="geometry">
+         <rect>
+          <x>600</x>
+          <y>750</y>
+          <width>80</width>
+          <height>25</height>
+         </rect>
+        </property>
+        <property name="text">
+         <string>Delete</string>
+        </property>
+       </widget>
+       <widget class="QPushButton" name="pushButton_4">
+        <property name="geometry">
+         <rect>
+          <x>330</x>
+          <y>520</y>
+          <width>401</width>
+          <height>25</height>
+         </rect>
+        </property>
+        <property name="text">
+         <string>Open Logging Block Setup File</string>
+        </property>
+       </widget>
+       <widget class="QPushButton" name="pushButton_5">
+        <property name="geometry">
+         <rect>
+          <x>330</x>
+          <y>610</y>
+          <width>141</width>
+          <height>25</height>
+         </rect>
+        </property>
+        <property name="text">
+         <string>Refresh Log Blocks</string>
+        </property>
+       </widget>
+       <widget class="QPushButton" name="pushButton_6">
+        <property name="geometry">
+         <rect>
+          <x>590</x>
+          <y>610</y>
+          <width>141</width>
+          <height>25</height>
+         </rect>
+        </property>
+        <property name="text">
+         <string>Stop All Log Blocks</string>
+        </property>
+       </widget>
+       <widget class="QLabel" name="label_2">
+        <property name="geometry">
+         <rect>
+          <x>460</x>
+          <y>450</y>
+          <width>141</width>
+          <height>20</height>
+         </rect>
+        </property>
+        <property name="text">
+         <string>Log Block Commands</string>
+        </property>
+       </widget>
+       <widget class="QListWidget" name="logVariableList">
+        <property name="geometry">
+         <rect>
+          <x>5</x>
+          <y>0</y>
+          <width>1101</width>
+          <height>441</height>
+         </rect>
+        </property>
+       </widget>
       </widget>
       <widget class="QWidget" name="navigation">
        <attribute name="title">
-        <string>Navigation</string>
+        <string>Control</string>
        </attribute>
        <layout class="QVBoxLayout" name="verticalLayout_3">
         <item>
@@ -1016,6 +1132,9 @@
               <property name="orientation">
                <enum>Qt::Vertical</enum>
               </property>
+              <property name="sizeType">
+               <enum>QSizePolicy::MinimumExpanding</enum>
+              </property>
               <property name="sizeHint" stdset="0">
                <size>
                 <width>20</width>
@@ -1024,6 +1143,84 @@
               </property>
              </spacer>
             </item>
+            <item>
+             <widget class="QLabel" name="logging_label">
+              <property name="font">
+               <font>
+                <weight>75</weight>
+                <bold>true</bold>
+               </font>
+              </property>
+              <property name="text">
+               <string>Logging Variables</string>
+              </property>
+              <property name="alignment">
+               <set>Qt::AlignCenter</set>
+              </property>
+             </widget>
+            </item>
+            <item>
+             <widget class="QComboBox" name="logEntriesComboBox1">
+              <property name="currentText">
+               <string>Logging Variable 1</string>
+              </property>
+              <item>
+               <property name="text">
+                <string>Logging Variable 1</string>
+               </property>
+              </item>
+             </widget>
+            </item>
+            <item>
+             <widget class="QComboBox" name="logEntriesComboBox2">
+              <item>
+               <property name="text">
+                <string>Logging Variable 2</string>
+               </property>
+              </item>
+             </widget>
+            </item>
+            <item>
+             <widget class="QComboBox" name="logEntriesComboBox3">
+              <item>
+               <property name="text">
+                <string>Logging Variable 3</string>
+               </property>
+              </item>
+             </widget>
+            </item>
+            <item>
+             <widget class="QComboBox" name="logEntriesComboBox4">
+              <item>
+               <property name="text">
+                <string>Logging Variable 4</string>
+               </property>
+              </item>
+             </widget>
+            </item>
+            <item>
+             <widget class="QComboBox" name="logEntriesComboBox5">
+              <item>
+               <property name="text">
+                <string>Logging Variable 5</string>
+               </property>
+              </item>
+             </widget>
+            </item>
+            <item>
+             <widget class="QPushButton" name="pb_startLog">
+              <property name="text">
+               <string>Start Logging</string>
+              </property>
+             </widget>
+            </item>
+            <item>
+             <widget class="QPushButton" name="pb_stopLog">
+              <property name="text">
+               <string>Stop Logging</string>
+              </property>
+             </widget>
+            </item>
             <item>
              <layout class="QFormLayout" name="formLayout">
               <property name="fieldGrowthPolicy">
@@ -1190,7 +1387,7 @@
  <resources/>
  <connections/>
  <buttongroups>
-  <buttongroup name="buttonGroup_2"/>
   <buttongroup name="AngleRateButtonGroup"/>
+  <buttongroup name="buttonGroup_2"/>
  </buttongroups>
 </ui>
-- 
GitLab