0.14: added gps field to Node table in database (#226). Tango: saving gps if enabled, added Rename/Remove/Share on long click in Open dialog (fixed #233)

This commit is contained in:
matlabbe
2017-09-21 20:59:45 -04:00
parent 114490f01e
commit bfc393a090
22 changed files with 516 additions and 103 deletions
+2 -2
View File
@@ -20,8 +20,8 @@ SET(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake_modules")
# VERSION
#######################
SET(RTABMAP_MAJOR_VERSION 0)
SET(RTABMAP_MINOR_VERSION 13)
SET(RTABMAP_PATCH_VERSION 3)
SET(RTABMAP_MINOR_VERSION 14)
SET(RTABMAP_PATCH_VERSION 0)
SET(RTABMAP_VERSION
${RTABMAP_MAJOR_VERSION}.${RTABMAP_MINOR_VERSION}.${RTABMAP_PATCH_VERSION})
+2
View File
@@ -12,6 +12,8 @@
<uses-permission android:name="android.permission.ACCESS_SURFACE_FLINGER" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-feature android:name="android.hardware.location.gps" />
<uses-feature android:glEsVersion="0x00020000" />
<!-- This is the platform API where NativeActivity was introduced. -->
+28 -1
View File
@@ -116,7 +116,8 @@ CameraTango::CameraTango(bool colorCamera, int decimation, bool publishRawScan,
cloudStamp_(0),
tangoColorType_(0),
tangoColorStamp_(0),
colorCameraToDisplayRotation_(ROTATION_0)
colorCameraToDisplayRotation_(ROTATION_0),
lastKnownGPS_(std::vector<double>(6,0))
{
UASSERT(decimation >= 1);
}
@@ -189,6 +190,8 @@ bool CameraTango::init(const std::string & calibrationFolder, const std::string
{
close();
lastKnownGPS_ = std::vector<double>(6,0);
TangoSupport_initialize(TangoService_getPoseAtTime, TangoService_getCameraIntrinsics);
// Connect to Tango
@@ -513,6 +516,21 @@ std::string CameraTango::getSerial() const
return "Tango";
}
void CameraTango::setGPS(double stamp,
double longitude,
double latitude,
double altitude,
double accuracy,
double bearing)
{
lastKnownGPS_[0] = stamp;
lastKnownGPS_[1] = longitude;
lastKnownGPS_[2] = latitude;
lastKnownGPS_[3] = altitude;
lastKnownGPS_[4] = accuracy;
lastKnownGPS_[5] = bearing;
}
rtabmap::Transform CameraTango::tangoPoseToTransform(const TangoPoseData * tangoPose) const
{
UASSERT(tangoPose);
@@ -837,6 +855,15 @@ SensorData CameraTango::captureImage(CameraInfo * info)
data = SensorData(rgb, depth, model, this->getNextSeqID(), rgbStamp);
}
data.setGroundTruth(odom);
if(lastKnownGPS_[0] > 0.0 && rgbStamp-lastKnownGPS_[0]<2.0)
{
data.setGPS(lastKnownGPS_[0], lastKnownGPS_[1], lastKnownGPS_[2], lastKnownGPS_[3], lastKnownGPS_[4], lastKnownGPS_[5]);
}
else if(lastKnownGPS_[0]>0.0)
{
LOGD("GPS too old (current time=%f, gps time = %f)", rgbStamp, lastKnownGPS_[0]);
}
}
else
{
+7
View File
@@ -89,6 +89,12 @@ public:
void setSmoothing(bool enabled) {smoothing_ = enabled;}
void setRawScanPublished(bool enabled) {rawScanPublished_ = enabled;}
void setScreenRotation(TangoSupportRotation colorCameraToDisplayRotation) {colorCameraToDisplayRotation_ = colorCameraToDisplayRotation;}
void setGPS(double stamp,
double longitude,
double latitude,
double altitude,
double accuracy,
double bearing);
void cloudReceived(const cv::Mat & cloud, double timestamp);
void rgbReceived(const cv::Mat & tangoImage, int type, double timestamp);
@@ -126,6 +132,7 @@ private:
TangoSupportRotation colorCameraToDisplayRotation_;
cv::Mat fisheyeRectifyMapX_;
cv::Mat fisheyeRectifyMapY_;
std::vector<double> lastKnownGPS_;
};
} /* namespace rtabmap */
+13
View File
@@ -1975,6 +1975,19 @@ int RTABMapApp::setMappingParameter(const std::string & key, const std::string &
}
}
void RTABMapApp::setGPS(double stamp,
double longitude,
double latitude,
double altitude,
double accuracy,
double bearing)
{
if(camera_)
{
camera_->setGPS(stamp, longitude, latitude, altitude, accuracy, bearing);
}
}
void RTABMapApp::resetMapping()
{
LOGW("Reset!");
+6
View File
@@ -147,6 +147,12 @@ class RTABMapApp : public UEventsHandler {
void setRenderingTextureDecimation(int value);
void setBackgroundColor(float gray);
int setMappingParameter(const std::string & key, const std::string & value);
void setGPS(double stamp,
double longitude,
double latitude,
double altitude,
double accuracy,
double bearing);
void resetMapping();
void save(const std::string & databasePath);
+18
View File
@@ -339,6 +339,24 @@ Java_com_introlab_rtabmap_RTABMapLib_setMappingParameter(
return app.setMappingParameter(keyC, valueC);
}
JNIEXPORT void JNICALL
Java_com_introlab_rtabmap_RTABMapLib_setGPS(
JNIEnv*, jobject,
double stamp,
double longitude,
double latitude,
double altitude,
double accuracy,
double bearing)
{
return app.setGPS(stamp,
longitude,
latitude,
altitude,
accuracy,
bearing);
}
JNIEXPORT void JNICALL
Java_com_introlab_rtabmap_RTABMapLib_resetMapping(
JNIEnv*, jobject)
@@ -197,6 +197,11 @@
android:title="@string/pref_title_raw_scan_saved"
android:summary="@string/pref_summary_raw_scan_saved"
android:defaultValue="@string/pref_default_raw_scan_saved"/>
<SwitchPreference
android:key="@string/pref_key_gps_saved"
android:title="@string/pref_title_gps_saved"
android:summary="@string/pref_summary_gps_saved"
android:defaultValue="@string/pref_default_gps_saved"/>
<SwitchPreference
android:key="@string/pref_key_db_in_memory"
android:title="@string/pref_title_db_in_memory"
+6
View File
@@ -34,6 +34,8 @@
<string name="memory">"Used Memory (MB): "</string>
<string name="hypothesis">"Hypothesis (%): "</string>
<string name="fps">"FPS (rendering): "</string>
<string name="gps">"GPS (long,lat,alt,bearing,acc): "</string>
<string name="time">"Time: "</string>
<!-- Preference keys: BEGIN -->
<string name="pref_key_tags">pref_key_tags</string>
@@ -101,6 +103,8 @@
<string name="pref_default_keep_all_db">true</string>
<string name="pref_key_raw_scan_saved">pref_key_raw_scan_saved</string>
<string name="pref_default_raw_scan_saved">false</string>
<string name="pref_key_gps_saved">pref_key_gps_saved</string>
<string name="pref_default_gps_saved">false</string>
<string name="pref_key_db_in_memory">pref_key_db_in_memory</string>
<string name="pref_default_db_in_memory">true</string>
@@ -339,6 +343,8 @@
<string name="pref_summary_keep_all_db">Discarded frames while not moving are still saved in database. Useful to replay exactly the scanning on RTAB-Map Desktop.</string>
<string name="pref_title_raw_scan_saved">Save Raw Scan</string>
<string name="pref_summary_raw_scan_saved">Save raw point clouds in database.</string>
<string name="pref_title_gps_saved">Save GPS</string>
<string name="pref_summary_gps_saved">Save GPS in database.</string>
<string name="pref_title_db_in_memory">Database In Memory</string>
<string name="pref_summary_db_in_memory">The database is kept in RAM for fast access. Set to false to reduce RAM used at the cost of slower access. This parameter is applied on reset or when a database is opened.</string>
@@ -33,6 +33,7 @@ import android.app.ProgressDialog;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnShowListener;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
@@ -50,8 +51,15 @@ import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.hardware.Camera;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.graphics.Point;
import android.hardware.display.DisplayManager;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
@@ -74,15 +82,19 @@ import android.text.method.LinkMovementMethod;
import android.text.util.Linkify;
import android.util.Log;
import android.util.TypedValue;
import android.view.ContextMenu;
import android.view.Display;
import android.view.GestureDetector;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MenuItem.OnMenuItemClickListener;
import android.view.MenuInflater;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.View;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.View.OnClickListener;
import android.view.View.OnCreateContextMenuListener;
import android.view.View.OnTouchListener;
import android.view.Window;
import android.view.WindowManager;
@@ -90,11 +102,13 @@ import android.view.inputmethod.EditorInfo;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemLongClickListener;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.NumberPicker;
import android.widget.RelativeLayout;
import android.widget.SeekBar;
@@ -108,7 +122,7 @@ import com.google.atap.tangoservice.Tango;
// The main activity of the application. This activity shows debug information
// and a glSurfaceView that renders graphic content.
public class RTABMapActivity extends Activity implements OnClickListener, OnItemSelectedListener {
public class RTABMapActivity extends Activity implements OnClickListener, OnItemSelectedListener, SensorEventListener {
// Tag for debug logging.
public static final String TAG = RTABMapActivity.class.getSimpleName();
@@ -206,6 +220,12 @@ public class RTABMapActivity extends Activity implements OnClickListener, OnItem
private String mLoopThr;
private String mMinInliers;
private String mMaxOptimizationError;
private LocationManager mLocationManager;
private LocationListener mLocationListener;
private Location mLastKnownLocation;
private SensorManager mSensorManager;
private float mCompassDeg = 0.0f;
private int mTotalLoopClosures = 0;
private boolean mMapIsEmpty = false;
@@ -454,30 +474,69 @@ public class RTABMapActivity extends Activity implements OnClickListener, OnItem
RTABMapLib.openDatabase(tmpDatabase, databaseInMemory, false);
DisplayManager displayManager = (DisplayManager) getSystemService(DISPLAY_SERVICE);
if (displayManager != null) {
displayManager.registerDisplayListener(new DisplayManager.DisplayListener() {
@Override
public void onDisplayAdded(int displayId) {
if (displayManager != null) {
displayManager.registerDisplayListener(new DisplayManager.DisplayListener() {
@Override
public void onDisplayAdded(int displayId) {
}
}
@Override
public void onDisplayChanged(int displayId) {
synchronized (this) {
setAndroidOrientation();
Display display = getWindowManager().getDefaultDisplay();
display.getSize(mScreenSize);
}
}
@Override
public void onDisplayChanged(int displayId) {
synchronized (this) {
setAndroidOrientation();
Display display = getWindowManager().getDefaultDisplay();
display.getSize(mScreenSize);
}
}
@Override
public void onDisplayRemoved(int displayId) {}
}, null);
}
DISABLE_LOG = !( 0 != ( getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE ) );
@Override
public void onDisplayRemoved(int displayId) {}
}, null);
}
// Acquire a reference to the system Location Manager
mLocationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE);
// Define a listener that responds to location updates
mLocationListener = new LocationListener() {
public void onLocationChanged(Location location) {
mLastKnownLocation = location;
double stamp = location.getTime()/1000.0;
if(!DISABLE_LOG) Log.d(TAG, String.format("GPS received at %f (%d)", stamp, location.getTime()));
RTABMapLib.setGPS(
stamp,
(double)location.getLongitude(),
(double)location.getLatitude(),
(double)location.getAltitude(),
(double)location.getAccuracy(),
(double)mCompassDeg);
}
public void onStatusChanged(String provider, int status, Bundle extras) {}
public void onProviderEnabled(String provider) {}
public void onProviderDisabled(String provider) {}
};
mSensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
DISABLE_LOG = !( 0 != ( getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE ) );
}
@Override
public void onSensorChanged(SensorEvent event) {
// get the angle around the z-axis rotated
mCompassDeg = event.values[0];
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
// not in use
}
public int getStatusBarHeight() {
int result = 0;
int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
@@ -580,6 +639,10 @@ public class RTABMapActivity extends Activity implements OnClickListener, OnItem
if(!DISABLE_LOG) Log.i(TAG, "onPause()");
mOnPause = true;
mLocationManager.removeUpdates(mLocationListener);
mSensorManager.unregisterListener(this);
// This deletes OpenGL context!
mGLView.onPause();
@@ -619,9 +682,8 @@ public class RTABMapActivity extends Activity implements OnClickListener, OnItem
}
mProgressDialog.show();
mOnPause = false;
setAndroidOrientation();
// update preferences
try
{
@@ -640,6 +702,12 @@ public class RTABMapActivity extends Activity implements OnClickListener, OnItem
boolean keepAllDb = sharedPref.getBoolean(getString(R.string.pref_key_keep_all_db), Boolean.parseBoolean(getString(R.string.pref_default_keep_all_db)));
boolean optimizeFromGraphEnd = sharedPref.getBoolean(getString(R.string.pref_key_optimize_end), Boolean.parseBoolean(getString(R.string.pref_default_optimize_end)));
String optimizer = sharedPref.getString(getString(R.string.pref_key_optimizer), getString(R.string.pref_default_optimizer));
boolean gpsSaved = sharedPref.getBoolean(getString(R.string.pref_key_gps_saved), Boolean.parseBoolean(getString(R.string.pref_default_gps_saved)));
if(gpsSaved)
{
mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, mLocationListener);
mSensorManager.registerListener(this, mSensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION), SensorManager.SENSOR_DELAY_GAME);
}
if(!DISABLE_LOG) Log.d(TAG, "set mapping parameters");
RTABMapLib.setOnlineBlending(sharedPref.getBoolean(getString(R.string.pref_key_blending), Boolean.parseBoolean(getString(R.string.pref_default_blending))));
@@ -1065,7 +1133,7 @@ public class RTABMapActivity extends Activity implements OnClickListener, OnItem
{
if(!DISABLE_LOG) Log.i(TAG, String.format("updateStatsCallback()"));
final String[] statusTexts = new String[16];
final String[] statusTexts = new String[17];
if(mButtonPause!=null && !mButtonPause.isChecked())
{
String updateValue = mUpdateRate.compareTo("0")==0?"Max":mUpdateRate;
@@ -1101,8 +1169,7 @@ public class RTABMapActivity extends Activity implements OnClickListener, OnItem
statusTexts[1] = mStatusTexts[1];
}
statusTexts[2] = getString(R.string.free_memory)+getFreeMemory();
if(loopClosureId > 0)
{
++mTotalLoopClosures;
@@ -1110,7 +1177,29 @@ public class RTABMapActivity extends Activity implements OnClickListener, OnItem
mMapNodes = nodes;
int index = 4;
if(mLastKnownLocation != null)
{
long millisec = System.currentTimeMillis() - mLastKnownLocation.getTime();
if(millisec > 2000)
{
statusTexts[3] = getString(R.string.gps)+String.format("[too old, %d ms]", millisec);
}
else
{
statusTexts[3] = getString(R.string.gps)+
String.format("%.2f %.2f %.2fm %.0fdeg %.0fm",
mLastKnownLocation.getLongitude(),
mLastKnownLocation.getLatitude(),
mLastKnownLocation.getAltitude(),
mCompassDeg,
mLastKnownLocation.getAccuracy());
}
}
String formattedDate = new SimpleDateFormat("HH:mm:ss.SSS").format(new Date());
statusTexts[4] = getString(R.string.time)+formattedDate;
int index = 5;
statusTexts[index++] = getString(R.string.nodes)+nodes+" (" + nodesDrawn + " shown)";
statusTexts[index++] = getString(R.string.words)+words;
statusTexts[index++] = getString(R.string.database_size)+databaseMemoryUsed;
@@ -1123,7 +1212,7 @@ public class RTABMapActivity extends Activity implements OnClickListener, OnItem
statusTexts[index++] = getString(R.string.inliers)+inliers;
statusTexts[index++] = getString(R.string.hypothesis)+(int)(hypothesis*100.0f) +" / " + (int)(Float.parseFloat(mLoopThr)*100.0f) + " (" + (loopClosureId>0?loopClosureId:highestHypId)+")";
statusTexts[index++] = getString(R.string.fps)+(int)fps+" Hz";
runOnUiThread(new Runnable() {
public void run() {
updateStatsUI(adjustedMemoryUsed, loopClosureId, inliers, matches, rejected, optimizationMaxError, statusTexts);
@@ -1489,7 +1578,7 @@ public class RTABMapActivity extends Activity implements OnClickListener, OnItem
mMapIsEmpty = false;
mDateOnPause = new Date();
long memoryFree = getFreeMemory();
if(!mOnPause && !mItemLocalizationMode.isChecked() && !mItemDataRecorderMode.isChecked() && memoryFree >= 100 && mMapNodes>2)
{
@@ -1943,54 +2032,7 @@ public class RTABMapActivity extends Activity implements OnClickListener, OnItem
}
else if(itemId == R.id.open)
{
final String[] files = Util.loadFileList(mWorkingDirectory, true);
if(files.length > 0)
{
String[] filesWithSize = new String[files.length];
for(int i = 0; i<filesWithSize.length; ++i)
{
File filePath = new File(mWorkingDirectory+files[i]);
long mb = filePath.length()/(1024*1024);
filesWithSize[i] = files[i] + " ("+mb+" MB)";
}
ArrayList<HashMap<String, String> > arrayList = new ArrayList<HashMap<String, String> >();
for (int i = 0; i < filesWithSize.length; i++) {
HashMap<String, String> hashMap = new HashMap<String, String>();//create a hashmap to store the data in key value pair
hashMap.put("name", filesWithSize[i]);
hashMap.put("path", mWorkingDirectory + files[i]);
arrayList.add(hashMap);//add the hashmap into arrayList
}
String[] from = {"name", "path"};//string array
int[] to = {R.id.textView, R.id.imageView};//int array of views id's
DatabaseListArrayAdapter simpleAdapter = new DatabaseListArrayAdapter(this, arrayList, R.layout.database_list, from, to);//Create object and set the parameters for simpleAdapter
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("Choose Your File (*.db)");
builder.setAdapter(simpleAdapter, new DialogInterface.OnClickListener() {
//builder.setItems(filesWithSize, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, final int which) {
// Adjust color now?
new AlertDialog.Builder(getActivity())
.setTitle("Opening database...")
.setMessage("Do you want to adjust colors now?\nThis can be done later under Optimize menu.")
.setPositiveButton("Yes", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichIn) {
openDatabase(files[which], true);
}
})
.setNeutralButton("No", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichIn) {
openDatabase(files[which], false);
}
})
.show();
return;
}
});
builder.show();
}
openDatabase();
}
else if(itemId == R.id.settings)
{
@@ -2008,6 +2050,170 @@ public class RTABMapActivity extends Activity implements OnClickListener, OnItem
return true;
}
private void openDatabase()
{
final String[] files = Util.loadFileList(mWorkingDirectory, true);
if(files.length > 0)
{
String[] filesWithSize = new String[files.length];
for(int i = 0; i<filesWithSize.length; ++i)
{
File filePath = new File(mWorkingDirectory+files[i]);
long mb = filePath.length()/(1024*1024);
filesWithSize[i] = files[i] + " ("+mb+" MB)";
}
ArrayList<HashMap<String, String> > arrayList = new ArrayList<HashMap<String, String> >();
for (int i = 0; i < filesWithSize.length; i++) {
HashMap<String, String> hashMap = new HashMap<String, String>();//create a hashmap to store the data in key value pair
hashMap.put("name", filesWithSize[i]);
hashMap.put("path", mWorkingDirectory + files[i]);
arrayList.add(hashMap);//add the hashmap into arrayList
}
String[] from = {"name", "path"};//string array
int[] to = {R.id.textView, R.id.imageView};//int array of views id's
DatabaseListArrayAdapter simpleAdapter = new DatabaseListArrayAdapter(this, arrayList, R.layout.database_list, from, to);//Create object and set the parameters for simpleAdapter
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("Choose Your File (*.db)");
builder.setAdapter(simpleAdapter, new DialogInterface.OnClickListener() {
//builder.setItems(filesWithSize, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, final int which) {
// Adjust color now?
new AlertDialog.Builder(getActivity())
.setTitle("Opening database...")
.setMessage("Do you want to adjust colors now?\nThis can be done later under Optimize menu.")
.setPositiveButton("Yes", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichIn) {
openDatabase(files[which], true);
}
})
.setNeutralButton("No", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichIn) {
openDatabase(files[which], false);
}
})
.show();
return;
}
});
final AlertDialog ad = builder.create(); //don't show dialog yet
ad.setOnShowListener(new OnShowListener()
{
@Override
public void onShow(DialogInterface dialog)
{
ListView lv = ad.getListView();
ad.registerForContextMenu(lv);
lv.setOnCreateContextMenuListener(new OnCreateContextMenuListener() {
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
if (v.getId()==ad.getListView().getId()) {
AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo)menuInfo;
final int position = info.position;
menu.setHeaderTitle(files[position]);
menu.add(Menu.NONE, 0, 0, "Rename").setOnMenuItemClickListener(new OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
AlertDialog.Builder builderRename = new AlertDialog.Builder(getActivity());
builderRename.setTitle("RTAB-Map Database Name (*.db):");
final EditText input = new EditText(getActivity());
input.setInputType(InputType.TYPE_CLASS_TEXT);
input.setText("");
input.setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI);
input.setSelectAllOnFocus(true);
input.selectAll();
builderRename.setView(input);
builderRename.setPositiveButton("OK", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which)
{
final String fileName = input.getText().toString();
dialog.dismiss();
if(!fileName.isEmpty())
{
File newFile = new File(mWorkingDirectory + fileName + ".db");
if(newFile.exists())
{
new AlertDialog.Builder(getActivity())
.setTitle("File Already Exists")
.setMessage(String.format("Name %s already used, choose another name.", fileName))
.show();
}
else
{
File from = new File(mWorkingDirectory, files[position]);
File to = new File(mWorkingDirectory, fileName + ".db");
from.renameTo(to);
ad.dismiss();
resetNoTouchTimer(true);
}
}
}
});
AlertDialog alertToShow = builderRename.create();
alertToShow.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
alertToShow.show();
return true;
}
});
menu.add(Menu.NONE, 1, 1, "Delete").setOnMenuItemClickListener(new OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
DialogInterface.OnClickListener dialogClickListener = new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
switch (which){
case DialogInterface.BUTTON_POSITIVE:
Log.e(TAG, String.format("Yes delete %s!", files[position]));
(new File(mWorkingDirectory+files[position])).delete();
ad.dismiss();
resetNoTouchTimer(true);
break;
case DialogInterface.BUTTON_NEGATIVE:
//No button clicked
break;
}
}
};
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setTitle(String.format("Delete %s", files[position]))
.setMessage("Are you sure?")
.setPositiveButton("Yes", dialogClickListener)
.setNegativeButton("No", dialogClickListener).show();
return true;
}
});
menu.add(Menu.NONE, 2, 2, "Share").setOnMenuItemClickListener(new OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
// Send to...
File f = new File(mWorkingDirectory+files[position]);
final int fileSizeMB = (int)f.length()/(1024 * 1024);
Intent shareIntent = new Intent();
shareIntent.setAction(Intent.ACTION_SEND);
shareIntent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(f));
shareIntent.setType("application/octet-stream");
startActivity(Intent.createChooser(shareIntent, String.format("Sharing database \"%s\" (%d MB)...", files[position], fileSizeMB)));
ad.dismiss();
resetNoTouchTimer(true);
return true;
}
});
}
}
});
}
});
ad.show();
}
}
private void export(final boolean isOBJ, final boolean meshing, final boolean regenerateCloud, final boolean optimized, final int optimizedMaxPolygons)
{
final String extension = isOBJ? ".obj" : ".ply";
@@ -2211,8 +2417,6 @@ public class RTABMapActivity extends Activity implements OnClickListener, OnItem
msg = String.format("Database saved to \"%s\".", newDatabasePathHuman);
}
mToast.makeText(getActivity(), msg, mToast.LENGTH_LONG).show();
// build notification
Intent intent = new Intent(getActivity(), RTABMapActivity.class);
// use System.currentTimeMillis() to have a unique ID for the pending intent
@@ -2232,6 +2436,15 @@ public class RTABMapActivity extends Activity implements OnClickListener, OnItem
notificationManager.notify(0, n);
// Send to...
File f = new File(newDatabasePath);
final int fileSizeMB = (int)f.length()/(1024 * 1024);
Intent shareIntent = new Intent();
shareIntent.setAction(Intent.ACTION_SEND);
shareIntent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(f));
shareIntent.setType("application/octet-stream");
startActivity(Intent.createChooser(shareIntent, String.format("Database \"%s\" (%d MB) successfully saved on the SD-CARD! Share it?", newDatabasePathHuman, fileSizeMB)));
resetNoTouchTimer(true);
if(!mItemDataRecorderMode.isChecked())
{
@@ -2385,11 +2598,13 @@ public class RTABMapActivity extends Activity implements OnClickListener, OnItem
mProgressDialog.dismiss();
// Send to...
File f = new File(zipOutput);
final int fileSizeMB = (int)f.length()/(1024 * 1024);
Intent shareIntent = new Intent();
shareIntent.setAction(Intent.ACTION_SEND);
shareIntent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(new File(zipOutput)));
shareIntent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(f));
shareIntent.setType("application/zip");
startActivity(Intent.createChooser(shareIntent, String.format("Mesh \"%s\" successfully exported! Share it?", pathHuman)));
startActivity(Intent.createChooser(shareIntent, String.format("Mesh \"%s\" (%d MB) successfully exported on the SD-CARD! Share it?", pathHuman, fileSizeMB)));
resetNoTouchTimer(true);
}
@@ -92,6 +92,13 @@ public class RTABMapLib
public static native void setRenderingTextureDecimation(int value);
public static native void setBackgroundColor(float gray);
public static native int setMappingParameter(String key, String value);
public static native void setGPS(
double stamp,
double longitude,
double latitude,
double altitude,
double accuracy,
double bearing);
public static native void resetMapping();
public static native void save(String outputDatabasePath);
+2 -2
View File
@@ -159,7 +159,7 @@ public:
void getNodeData(int signatureId, SensorData & data, bool images = true, bool scan = true, bool userData = true, bool occupancyGrid = true) const;
bool getCalibration(int signatureId, std::vector<CameraModel> & models, StereoCameraModel & stereoModel) const;
bool getLaserScanInfo(int signatureId, LaserScanInfo & info) const;
bool getNodeInfo(int signatureId, Transform & pose, int & mapId, int & weight, std::string & label, double & stamp, Transform & groundTruthPose, std::vector<float> & velocity) const;
bool getNodeInfo(int signatureId, Transform & pose, int & mapId, int & weight, std::string & label, double & stamp, Transform & groundTruthPose, std::vector<float> & velocity, std::vector<double> & gps) const;
void loadLinks(int signatureId, std::map<int, Link> & links, Link::Type type = Link::kUndef) const;
void getWeight(int signatureId, int & weight) const;
void getAllNodeIds(std::set<int> & ids, bool ignoreChildren = false, bool ignoreBadSignatures = false) const;
@@ -253,7 +253,7 @@ private:
virtual void loadNodeDataQuery(std::list<Signature *> & signatures, bool images=true, bool scan=true, bool userData=true, bool occupancyGrid=true) const = 0;
virtual bool getCalibrationQuery(int signatureId, std::vector<CameraModel> & models, StereoCameraModel & stereoModel) const = 0;
virtual bool getLaserScanInfoQuery(int signatureId, LaserScanInfo & info) const = 0;
virtual bool getNodeInfoQuery(int signatureId, Transform & pose, int & mapId, int & weight, std::string & label, double & stamp, Transform & groundTruthPose, std::vector<float> & velocity) const = 0;
virtual bool getNodeInfoQuery(int signatureId, Transform & pose, int & mapId, int & weight, std::string & label, double & stamp, Transform & groundTruthPose, std::vector<float> & velocity, std::vector<double> & gps) const = 0;
virtual void getAllNodeIdsQuery(std::set<int> & ids, bool ignoreChildren, bool ignoreBadSignatures) const = 0;
virtual void getAllLinksQuery(std::multimap<int, Link> & links, bool ignoreNullLinks) const = 0;
virtual void getLastIdQuery(const std::string & tableName, int & id) const = 0;
+1
View File
@@ -177,6 +177,7 @@ public:
double & stamp,
Transform & groundTruth,
std::vector<float> & velocity,
std::vector<double> & gps,
bool lookInDatabase = false) const;
cv::Mat getImageCompressed(int signatureId) const;
SensorData getNodeData(int nodeId, bool uncompressedData = false) const;
+14
View File
@@ -225,6 +225,18 @@ public:
const Transform & globalPose() const {return globalPose_;}
const cv::Mat & globalPoseCovariance() const {return globalPoseCovariance_;}
void setGPS(double stamp, double longitude, double latitude, double altitude, double accuracy, double bearing)
{
gps_ = std::vector<double>(6,0.0);
gps_[0]=stamp;
gps_[1]=longitude;
gps_[2]=latitude;
gps_[3]=altitude;
gps_[4]=accuracy;
gps_[5]=bearing;
}
const std::vector<double> & gps() const {return gps_;}
long getMemoryUsed() const; // Return memory usage in Bytes
private:
@@ -265,6 +277,8 @@ private:
Transform globalPose_;
cv::Mat globalPoseCovariance_; // 6x6 double
std::vector<double> gps_;
};
}
+4 -2
View File
@@ -712,7 +712,8 @@ bool DBDriver::getNodeInfo(
std::string & label,
double & stamp,
Transform & groundTruthPose,
std::vector<float> & velocity) const
std::vector<float> & velocity,
std::vector<double> & gps) const
{
bool found = false;
// look in the trash
@@ -725,6 +726,7 @@ bool DBDriver::getNodeInfo(
label = _trashSignatures.at(signatureId)->getLabel();
stamp = _trashSignatures.at(signatureId)->getStamp();
groundTruthPose = _trashSignatures.at(signatureId)->getGroundTruthPose();
gps = _trashSignatures.at(signatureId)->sensorData().gps();
found = true;
}
_trashesMutex.unlock();
@@ -732,7 +734,7 @@ bool DBDriver::getNodeInfo(
if(!found)
{
_dbSafeAccessMutex.lock();
found = this->getNodeInfoQuery(signatureId, pose, mapId, weight, label, stamp, groundTruthPose, velocity);
found = this->getNodeInfoQuery(signatureId, pose, mapId, weight, label, stamp, groundTruthPose, velocity, gps);
_dbSafeAccessMutex.unlock();
}
return found;
+68 -5
View File
@@ -497,7 +497,11 @@ long DBDriverSqlite3::getNodesMemoryUsedQuery() const
if(_ppDb)
{
std::string query;
if(uStrNumCmp(_version, "0.13.0") >= 0)
if(uStrNumCmp(_version, "0.14.0") >= 0)
{
query = "SELECT sum(length(id) + length(map_id) + length(weight) + length(pose) + length(stamp) + ifnull(length(label),0) + length(ground_truth_pose) + ifnull(length(velocity),0) + ifnull(length(gps),0) + length(time_enter)) from Node;";
}
else if(uStrNumCmp(_version, "0.13.0") >= 0)
{
query = "SELECT sum(length(id) + length(map_id) + length(weight) + length(pose) + length(stamp) + ifnull(length(label),0) + length(ground_truth_pose) + ifnull(length(velocity),0) + length(time_enter)) from Node;";
}
@@ -1751,7 +1755,8 @@ bool DBDriverSqlite3::getNodeInfoQuery(int signatureId,
std::string & label,
double & stamp,
Transform & groundTruthPose,
std::vector<float> & velocity) const
std::vector<float> & velocity,
std::vector<double> & gps) const
{
bool found = false;
if(_ppDb && signatureId)
@@ -1760,7 +1765,14 @@ bool DBDriverSqlite3::getNodeInfoQuery(int signatureId,
sqlite3_stmt * ppStmt = 0;
std::stringstream query;
if(uStrNumCmp(_version, "0.13.0") >= 0)
if(uStrNumCmp(_version, "0.14.0") >= 0)
{
query << "SELECT pose, map_id, weight, label, stamp, ground_truth_pose, velocity, gps "
"FROM Node "
"WHERE id = " << signatureId <<
";";
}
else if(uStrNumCmp(_version, "0.13.0") >= 0)
{
query << "SELECT pose, map_id, weight, label, stamp, ground_truth_pose, velocity "
"FROM Node "
@@ -1839,6 +1851,17 @@ bool DBDriverSqlite3::getNodeInfoQuery(int signatureId,
memcpy(velocity.data(), data, dataSize);
}
}
if(uStrNumCmp(_version, "0.14.0") >= 0)
{
gps.resize(6,0);
data = sqlite3_column_blob(ppStmt, index); // velocity
dataSize = sqlite3_column_bytes(ppStmt, index++);
if((unsigned int)dataSize == gps.size()*sizeof(double) && data)
{
memcpy(gps.data(), data, dataSize);
}
}
}
}
@@ -2239,7 +2262,13 @@ void DBDriverSqlite3::loadSignaturesQuery(const std::list<int> & ids, std::list<
unsigned int loaded = 0;
// Load nodes information
if(uStrNumCmp(_version, "0.13.0") >= 0)
if(uStrNumCmp(_version, "0.14.0") >= 0)
{
query << "SELECT id, map_id, weight, pose, stamp, label, ground_truth_pose, velocity, gps "
<< "FROM Node "
<< "WHERE id=?;";
}
else if(uStrNumCmp(_version, "0.13.0") >= 0)
{
query << "SELECT id, map_id, weight, pose, stamp, label, ground_truth_pose, velocity "
<< "FROM Node "
@@ -2281,6 +2310,7 @@ void DBDriverSqlite3::loadSignaturesQuery(const std::list<int> & ids, std::list<
Transform pose;
Transform groundTruthPose;
std::vector<float> velocity;
std::vector<double> gps;
const void * data = 0;
int dataSize = 0;
std::string label;
@@ -2329,6 +2359,17 @@ void DBDriverSqlite3::loadSignaturesQuery(const std::list<int> & ids, std::list<
memcpy(velocity.data(), data, dataSize);
}
}
if(uStrNumCmp(_version, "0.14.0") >= 0)
{
gps.resize(6,0);
data = sqlite3_column_blob(ppStmt, index); // gps
dataSize = sqlite3_column_bytes(ppStmt, index++);
if((unsigned int)dataSize == gps.size()*sizeof(double) && data)
{
memcpy(gps.data(), data, dataSize);
}
}
}
}
@@ -2352,6 +2393,10 @@ void DBDriverSqlite3::loadSignaturesQuery(const std::list<int> & ids, std::list<
{
s->setVelocity(velocity[0], velocity[1], velocity[2], velocity[3], velocity[4], velocity[5]);
}
if(gps.size() == 6)
{
s->sensorData().setGPS(gps[0], gps[1], gps[2], gps[3], gps[4], gps[5]);
}
s->setSaved(true);
nodes.push_back(s);
++loaded;
@@ -4213,7 +4258,11 @@ cv::Mat DBDriverSqlite3::loadOptimizedMeshQuery(
std::string DBDriverSqlite3::queryStepNode() const
{
if(uStrNumCmp(_version, "0.13.0") >= 0)
if(uStrNumCmp(_version, "0.14.0") >= 0)
{
return "INSERT INTO Node(id, map_id, weight, pose, stamp, label, ground_truth_pose, velocity, gps) VALUES(?,?,?,?,?,?,?,?,?);";
}
else if(uStrNumCmp(_version, "0.13.0") >= 0)
{
return "INSERT INTO Node(id, map_id, weight, pose, stamp, label, ground_truth_pose, velocity) VALUES(?,?,?,?,?,?,?,?);";
}
@@ -4293,6 +4342,20 @@ void DBDriverSqlite3::stepNode(sqlite3_stmt * ppStmt, const Signature * s) const
UASSERT_MSG(rc == SQLITE_OK, uFormat("DB error (%s): %s", _version.c_str(), sqlite3_errmsg(_ppDb)).c_str());
}
}
if(uStrNumCmp(_version, "0.14.0") >= 0)
{
if(s->sensorData().gps().empty())
{
rc = sqlite3_bind_null(ppStmt, index++);
UASSERT_MSG(rc == SQLITE_OK, uFormat("DB error (%s): %s", _version.c_str(), sqlite3_errmsg(_ppDb)).c_str());
}
else
{
rc = sqlite3_bind_blob(ppStmt, index++, s->sensorData().gps().data(), s->sensorData().gps().size()*sizeof(double), SQLITE_STATIC);
UASSERT_MSG(rc == SQLITE_OK, uFormat("DB error (%s): %s", _version.c_str(), sqlite3_errmsg(_ppDb)).c_str());
}
}
}
}
else if(uStrNumCmp(_version, "0.8.8") >= 0)
+1 -1
View File
@@ -127,7 +127,7 @@ private:
virtual void loadNodeDataQuery(std::list<Signature *> & signatures, bool images=true, bool scan=true, bool userData=true, bool occupancyGrid=true) const;
virtual bool getCalibrationQuery(int signatureId, std::vector<CameraModel> & models, StereoCameraModel & stereoModel) const;
virtual bool getLaserScanInfoQuery(int signatureId, LaserScanInfo & info) const;
virtual bool getNodeInfoQuery(int signatureId, Transform & pose, int & mapId, int & weight, std::string & label, double & stamp, Transform & groundTruthPose, std::vector<float> & velocity) const;
virtual bool getNodeInfoQuery(int signatureId, Transform & pose, int & mapId, int & weight, std::string & label, double & stamp, Transform & groundTruthPose, std::vector<float> & velocity, std::vector<double> & gps) const;
virtual void getAllNodeIdsQuery(std::set<int> & ids, bool ignoreChildren, bool ignoreBadSignatures) const;
virtual void getAllLinksQuery(std::multimap<int, Link> & links, bool ignoreNullLinks) const;
virtual void getLastIdQuery(const std::string & tableName, int & id) const;
+4 -2
View File
@@ -268,7 +268,8 @@ SensorData DBReader::captureImage(CameraInfo * info)
int mapId;
Transform localTransform, pose, groundTruth;
std::vector<float> velocity;
_dbDriver->getNodeInfo(*_currentId, pose, mapId, weight, label, stamp, groundTruth, velocity);
std::vector<double> gps;
_dbDriver->getNodeInfo(*_currentId, pose, mapId, weight, label, stamp, groundTruth, velocity, gps);
if(previousStamp && stamp && stamp > previousStamp)
{
delay = stamp - previousStamp;
@@ -322,7 +323,8 @@ SensorData DBReader::getNextData(CameraInfo * info)
double stamp;
Transform groundTruth;
std::vector<float> velocity;
_dbDriver->getNodeInfo(*_currentId, pose, mapId, weight, label, stamp, groundTruth, velocity);
std::vector<double> gps;
_dbDriver->getNodeInfo(*_currentId, pose, mapId, weight, label, stamp, groundTruth, velocity, gps);
cv::Mat infMatrix = cv::Mat::eye(6,6,CV_64FC1);
if(!_odometryIgnored)
+11 -3
View File
@@ -3051,7 +3051,8 @@ Transform Memory::getOdomPose(int signatureId, bool lookInDatabase) const
std::string label;
double stamp;
std::vector<float> velocity;
getNodeInfo(signatureId, pose, mapId, weight, label, stamp, groundTruth, velocity, lookInDatabase);
std::vector<double> gps;
getNodeInfo(signatureId, pose, mapId, weight, label, stamp, groundTruth, velocity, gps, lookInDatabase);
return pose;
}
@@ -3062,7 +3063,8 @@ Transform Memory::getGroundTruthPose(int signatureId, bool lookInDatabase) const
std::string label;
double stamp;
std::vector<float> velocity;
getNodeInfo(signatureId, pose, mapId, weight, label, stamp, groundTruth, velocity, lookInDatabase);
std::vector<double> gps;
getNodeInfo(signatureId, pose, mapId, weight, label, stamp, groundTruth, velocity, gps, lookInDatabase);
return groundTruth;
}
@@ -3074,6 +3076,7 @@ bool Memory::getNodeInfo(int signatureId,
double & stamp,
Transform & groundTruth,
std::vector<float> & velocity,
std::vector<double> & gps,
bool lookInDatabase) const
{
const Signature * s = this->getSignature(signatureId);
@@ -3086,11 +3089,12 @@ bool Memory::getNodeInfo(int signatureId,
stamp = s->getStamp();
groundTruth = s->getGroundTruthPose();
velocity = s->getVelocity();
gps = s->sensorData().gps();
return true;
}
else if(lookInDatabase && _dbDriver)
{
return _dbDriver->getNodeInfo(signatureId, odomPose, mapId, weight, label, stamp, groundTruth, velocity);
return _dbDriver->getNodeInfo(signatureId, odomPose, mapId, weight, label, stamp, groundTruth, velocity, gps);
}
return false;
}
@@ -3963,6 +3967,10 @@ Signature * Memory::createSignature(const SensorData & data, const Transform & p
s->sensorData().setUserDataRaw(data.userDataRaw());
s->sensorData().setGroundTruth(data.groundTruth());
if(!data.gps().empty())
{
s->sensorData().setGPS(data.gps()[0], data.gps()[1], data.gps()[2], data.gps()[3], data.gps()[4], data.gps()[5]);
}
t = timer.ticks();
if(stats) stats->addStatistic(Statistics::kTimingMemCompressing_data(), t*1000.0f);
+20 -4
View File
@@ -761,7 +761,8 @@ void Rtabmap::exportPoses(const std::string & path, bool optimized, bool global,
std::string l;
double stamp = 0.0;
std::vector<float> v;
_memory->getNodeInfo(iter->first, o, m, w, l, stamp, g, v, true);
std::vector<double> gps;
_memory->getNodeInfo(iter->first, o, m, w, l, stamp, g, v, gps, true);
stamps.insert(std::make_pair(iter->first, stamp));
}
}
@@ -2650,7 +2651,8 @@ bool Rtabmap::process(
double stamp = 0;
Transform groundTruth;
std::vector<float> velocity;
_memory->getNodeInfo(iter->first, odomPoseLocal, mapId, weight, label, stamp, groundTruth, velocity, false);
std::vector<double> gps;
_memory->getNodeInfo(iter->first, odomPoseLocal, mapId, weight, label, stamp, groundTruth, velocity, gps, false);
signatures.insert(std::make_pair(iter->first,
Signature(iter->first,
mapId,
@@ -2663,6 +2665,10 @@ bool Rtabmap::process(
{
signatures.at(iter->first).setVelocity(velocity[0], velocity[1], velocity[2], velocity[3], velocity[4], velocity[5]);
}
if(!gps.empty())
{
signatures.at(iter->first).sensorData().setGPS(gps[0], gps[1], gps[2], gps[3], gps[4], gps[5]);
}
}
localGraphSize = (int)poses.size();
if(!lastSignatureLocalizedPose.isNull())
@@ -3340,7 +3346,8 @@ void Rtabmap::get3DMap(
double stamp = 0;
Transform groundTruth;
std::vector<float> velocity;
_memory->getNodeInfo(*iter, odomPoseLocal, mapId, weight, label, stamp, groundTruth, velocity, true);
std::vector<double> gps;
_memory->getNodeInfo(*iter, odomPoseLocal, mapId, weight, label, stamp, groundTruth, velocity, gps, true);
SensorData data = _memory->getNodeData(*iter);
data.setId(*iter);
std::multimap<int, cv::KeyPoint> words;
@@ -3363,6 +3370,10 @@ void Rtabmap::get3DMap(
{
signatures.at(*iter).setVelocity(velocity[0], velocity[1], velocity[2], velocity[3], velocity[4], velocity[5]);
}
if(!gps.empty())
{
signatures.at(*iter).sensorData().setGPS(gps[0], gps[1], gps[2], gps[3], gps[4], gps[5]);
}
}
}
else if(_memory && (_memory->getStMem().size() || _memory->getWorkingMem().size() > 1))
@@ -3415,7 +3426,8 @@ void Rtabmap::getGraph(
double stamp = 0;
Transform groundTruth;
std::vector<float> velocity;
_memory->getNodeInfo(iter->first, odomPoseLocal, mapId, weight, label, stamp, groundTruth, velocity, global);
std::vector<double> gps;
_memory->getNodeInfo(iter->first, odomPoseLocal, mapId, weight, label, stamp, groundTruth, velocity, gps, global);
signatures->insert(std::make_pair(iter->first,
Signature(iter->first,
mapId,
@@ -3443,6 +3455,10 @@ void Rtabmap::getGraph(
{
signatures->at(iter->first).setVelocity(velocity[0], velocity[1], velocity[2], velocity[3], velocity[4], velocity[5]);
}
if(!gps.empty())
{
signatures->at(iter->first).sensorData().setGPS(gps[0], gps[1], gps[2], gps[3], gps[4], gps[5]);
}
}
}
}
@@ -22,6 +22,7 @@ CREATE TABLE Node (
ground_truth_pose BLOB, -- 3x4 float
velocity BLOB, -- 6 float (vx,vy,vz,vroll,vpitch,vyaw) m/s and rad/s
label TEXT,
gps BLOB, -- 1x6 double: stamp, longitude (DD), latitude (DD), altitude (m), accuracy (m), bearing (North 0->360 deg clockwise)
time_enter DATE,
PRIMARY KEY (id)
+1 -1
View File
@@ -1,7 +1,7 @@
<?xml version="1.0"?>
<package>
<name>rtabmap</name>
<version>0.13.3</version>
<version>0.14.0</version>
<description>RTAB-Map's standalone library. RTAB-Map is a RGB-D SLAM approach with real-time constraints.</description>
<maintainer email="[email protected]">Mathieu Labbe</maintainer>
<author>Mathieu Labbe</author>