00001 package com.introlab.rtabmap;
00002
00003 import java.io.File;
00004 import java.io.FilenameFilter;
00005
00006 import android.app.Activity;
00007 import android.app.AlertDialog;
00008 import android.app.Notification;
00009 import android.app.NotificationManager;
00010 import android.app.PendingIntent;
00011 import android.app.ProgressDialog;
00012 import android.content.ComponentName;
00013 import android.content.DialogInterface;
00014 import android.content.Intent;
00015 import android.content.ServiceConnection;
00016 import android.content.pm.PackageInfo;
00017 import android.content.pm.PackageManager;
00018 import android.content.pm.PackageManager.NameNotFoundException;
00019 import android.graphics.Point;
00020 import android.opengl.GLSurfaceView;
00021 import android.os.Bundle;
00022 import android.os.Environment;
00023 import android.os.Handler;
00024 import android.os.Debug;
00025 import android.os.IBinder;
00026 import android.text.Editable;
00027 import android.text.InputType;
00028 import android.util.Log;
00029 import android.view.Display;
00030 import android.view.Menu;
00031 import android.view.MenuItem;
00032 import android.view.MenuInflater;
00033 import android.view.MotionEvent;
00034 import android.view.View;
00035 import android.view.View.OnClickListener;
00036 import android.view.WindowManager;
00037 import android.widget.EditText;
00038 import android.widget.LinearLayout;
00039 import android.widget.TextView;
00040 import android.widget.Toast;
00041
00042 import com.google.atap.tangoservice.Tango;
00043
00044
00045
00046 public class RTABMapActivity extends Activity implements OnClickListener {
00047
00048
00049 private static final String TAG = RTABMapActivity.class.getSimpleName();
00050
00051
00052 private static final int MIN_TANGO_CORE_VERSION = 6804;
00053
00054
00055 private static final String TANGO_PACKAGE_NAME = "com.projecttango.tango";
00056
00057 public static final String EXTRA_KEY_PERMISSIONTYPE = "PERMISSIONTYPE";
00058 public static final String EXTRA_VALUE_ADF = "ADF_LOAD_SAVE_PERMISSION";
00059
00060
00061
00062 private Renderer mRenderer;
00063 private GLSurfaceView mGLView;
00064
00065 ProgressDialog mProgressDialog;
00066
00067
00068 private Point mScreenSize = new Point();
00069
00070 private MenuItem mItemPause;
00071 private MenuItem mItemSave;
00072 private MenuItem mItemOpen;
00073 private MenuItem mItemPostProcessing;
00074 private MenuItem mItemExport;
00075 private MenuItem mItemLocalizationMode;
00076 private MenuItem mItemTrajectoryMode;
00077
00078 private String mOpenedDatabasePath = "";
00079 private String mTempDatabasePath = "";
00080 private String mNewDatabasePath = "";
00081 private String mWorkingDirectory = "";
00082
00083 private int mMaxDepthIndex = 5;
00084 private int mMeshAngleToleranceIndex = 1;
00085 private int mMeshTriangleSizeIndex = 0;
00086
00087 private int mParamUpdateRateHzIndex = 1;
00088 private int mParamTimeThrMsIndex = 4;
00089 private int mParamMaxFeaturesIndex = 4;
00090 private int mParamLoopThrMsIndex = 1;
00091 private int mParamOptimizeErrorIndex = 3;
00092
00093 final String[] mUpdateRateValues = {"0.5", "1", "2", "Max"};
00094 final String[] mTimeThrValues = {"400", "500", "600", "700", "800", "900", "1000", "1100", "1200", "1300", "1400", "1500", "No Limit"};
00095 final String[] mMaxFeaturesValues = {"Disabled", "100", "200", "300", "400", "500", "600", "700", "800", "900", "1000", "No Limit"};
00096 final String[] mLoopThrValues = {"Disabled", "0.11", "0.20", "0.30", "0.40", "0.50", "0.60", "0.70", "0.80", "0.90"};
00097 final String[] mOptimizeErrorValues = {"Disabled", "0.01", "0.025", "0.05", "0.1", "0.2", "0.35", "0.5", "1"};
00098
00099 private LinearLayout mLayoutDebug;
00100
00101 private int mTotalLoopClosures = 0;
00102
00103 private Toast mToast = null;
00104
00105
00106 ServiceConnection mTangoServiceConnection = new ServiceConnection() {
00107 public void onServiceConnected(ComponentName name, IBinder service) {
00108 if(!RTABMapLib.onTangoServiceConnected(service))
00109 {
00110 mToast.makeText(getApplicationContext(),
00111 String.format("Failed to intialize Tango!"), mToast.LENGTH_SHORT).show();
00112 }
00113 }
00114
00115 public void onServiceDisconnected(ComponentName name) {
00116
00117
00118 mToast.makeText(getApplicationContext(),
00119 String.format("Tango disconnected!"), mToast.LENGTH_SHORT).show();
00120 }
00121 };
00122
00123 @Override
00124 protected void onCreate(Bundle savedInstanceState) {
00125 super.onCreate(savedInstanceState);
00126 setTitle(R.string.menu_name);
00127
00128
00129
00130 Display display = getWindowManager().getDefaultDisplay();
00131 display.getSize(mScreenSize);
00132
00133 getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
00134
00135
00136 setContentView(R.layout.activity_rtabmap);
00137
00138
00139 findViewById(R.id.first_person_button).setOnClickListener(this);
00140 findViewById(R.id.third_person_button).setOnClickListener(this);
00141 findViewById(R.id.top_down_button).setOnClickListener(this);
00142
00143 mToast = Toast.makeText(getApplicationContext(), "", Toast.LENGTH_SHORT);
00144
00145
00146 mGLView = (GLSurfaceView) findViewById(R.id.gl_surface_view);
00147
00148
00149 mGLView.setEGLContextClientVersion(2);
00150
00151
00152 mRenderer = new Renderer();
00153 mGLView.setRenderer(mRenderer);
00154
00155 mLayoutDebug = (LinearLayout) findViewById(R.id.debug_layout);
00156 mLayoutDebug.setVisibility(LinearLayout.GONE);
00157
00158 mProgressDialog = new ProgressDialog(this);
00159 mRenderer.setProgressDialog(mProgressDialog);
00160
00161
00162 if (!CheckTangoCoreVersion(MIN_TANGO_CORE_VERSION)) {
00163 mToast.makeText(this, "Tango Core out dated, please update in Play Store", mToast.LENGTH_LONG).show();
00164 finish();
00165 return;
00166 }
00167
00168 mOpenedDatabasePath = "";
00169 mTempDatabasePath = "";
00170 mNewDatabasePath = "";
00171 mWorkingDirectory = "";
00172 mTotalLoopClosures = 0;
00173
00174 if(Environment.getExternalStorageState().compareTo(Environment.MEDIA_MOUNTED)==0)
00175 {
00176 File extStore = Environment.getExternalStorageDirectory();
00177 mWorkingDirectory = extStore.getAbsolutePath() + "/" + getString(R.string.app_name) + "/";
00178 extStore = new File(mWorkingDirectory);
00179 extStore.mkdirs();
00180 mTempDatabasePath = mWorkingDirectory + "rtabmap.tmp.db";
00181 extStore = new File(mTempDatabasePath);
00182
00183 if(extStore.exists())
00184 {
00185 extStore.delete();
00186 }
00187 }
00188 else
00189 {
00190
00191 mToast.makeText(getApplicationContext(),
00192 String.format("Failed to get external storage path (SD-CARD, state=%s). Saving disabled.",
00193 Environment.getExternalStorageState()), mToast.LENGTH_LONG).show();
00194 }
00195
00196 RTABMapLib.onCreate(this);
00197 RTABMapLib.openDatabase(mTempDatabasePath);
00198 }
00199
00200 @Override
00201 protected void onActivityResult(int requestCode, int resultCode, Intent data) {
00202
00203 if (requestCode == Tango.TANGO_INTENT_ACTIVITYCODE) {
00204
00205 if (resultCode == RESULT_CANCELED) {
00206 mToast.makeText(this, "Motion Tracking Permissions Required!",
00207 mToast.LENGTH_SHORT).show();
00208 finish();
00209 }
00210 }
00211 }
00212
00213 @Override
00214 protected void onResume() {
00215 super.onResume();
00216
00217 TangoInitializationHelper.bindTangoService(this, mTangoServiceConnection);
00218
00219 Log.i(TAG, String.format("onResume()"));
00220
00221 if (Tango.hasPermission(this, Tango.PERMISSIONTYPE_MOTION_TRACKING)) {
00222
00223 mGLView.onResume();
00224
00225 mTotalLoopClosures = 0;
00226 if(mItemOpen != null)
00227 {
00228 mItemOpen.setEnabled(false);
00229 mItemPause.setChecked(false);
00230 mItemSave.setEnabled(false);
00231 mItemExport.setEnabled(false);
00232 mItemPostProcessing.setEnabled(false);
00233 }
00234
00235 } else {
00236 Log.i(TAG, String.format("Asking for motion tracking permission"));
00237 startActivityForResult(
00238 Tango.getRequestPermissionIntent(Tango.PERMISSIONTYPE_MOTION_TRACKING),
00239 Tango.TANGO_INTENT_ACTIVITYCODE);
00240 }
00241 }
00242
00243 @Override
00244 protected void onPause() {
00245 super.onPause();
00246 mGLView.onPause();
00247
00248
00249 RTABMapLib.onPause();
00250 mOpenedDatabasePath = "";
00251 RTABMapLib.openDatabase(mTempDatabasePath);
00252
00253 unbindService(mTangoServiceConnection);
00254 }
00255
00256 @Override
00257 public void onClick(View v) {
00258
00259 switch (v.getId()) {
00260 case R.id.first_person_button:
00261 RTABMapLib.setCamera(0);
00262 break;
00263 case R.id.third_person_button:
00264 RTABMapLib.setCamera(1);
00265 break;
00266 case R.id.top_down_button:
00267 RTABMapLib.setCamera(2);
00268 break;
00269 default:
00270 return;
00271 }
00272 }
00273
00274 @Override
00275 public boolean onTouchEvent(MotionEvent event) {
00276
00277
00278
00279 int pointCount = event.getPointerCount();
00280 if (pointCount == 1) {
00281 float normalizedX = event.getX(0) / mScreenSize.x;
00282 float normalizedY = event.getY(0) / mScreenSize.y;
00283 RTABMapLib.onTouchEvent(1,
00284 event.getActionMasked(), normalizedX, normalizedY, 0.0f, 0.0f);
00285 }
00286 if (pointCount == 2) {
00287 if (event.getActionMasked() == MotionEvent.ACTION_POINTER_UP) {
00288 int index = event.getActionIndex() == 0 ? 1 : 0;
00289 float normalizedX = event.getX(index) / mScreenSize.x;
00290 float normalizedY = event.getY(index) / mScreenSize.y;
00291 RTABMapLib.onTouchEvent(1,
00292 MotionEvent.ACTION_DOWN, normalizedX, normalizedY, 0.0f, 0.0f);
00293 } else {
00294 float normalizedX0 = event.getX(0) / mScreenSize.x;
00295 float normalizedY0 = event.getY(0) / mScreenSize.y;
00296 float normalizedX1 = event.getX(1) / mScreenSize.x;
00297 float normalizedY1 = event.getY(1) / mScreenSize.y;
00298 RTABMapLib.onTouchEvent(2, event.getActionMasked(),
00299 normalizedX0, normalizedY0, normalizedX1, normalizedY1);
00300 }
00301 }
00302 return true;
00303 }
00304
00305 @Override
00306 public boolean onCreateOptionsMenu(Menu menu) {
00307 Log.i(TAG, "called onCreateOptionsMenu;");
00308
00309 MenuInflater inflater = getMenuInflater();
00310 inflater.inflate(R.menu.optionmenu, menu);
00311
00312 mItemPause = menu.findItem(R.id.pause);
00313 mItemSave = menu.findItem(R.id.save);
00314 mItemOpen = menu.findItem(R.id.open);
00315 mItemPostProcessing = menu.findItem(R.id.post_processing);
00316 mItemExport = menu.findItem(R.id.export);
00317 mItemLocalizationMode = menu.findItem(R.id.localization_mode);
00318 mItemTrajectoryMode = menu.findItem(R.id.trajectory_mode);
00319 mItemSave.setEnabled(false);
00320 mItemExport.setEnabled(false);
00321 mItemOpen.setEnabled(false);
00322 mItemPostProcessing.setEnabled(false);
00323
00324 return true;
00325 }
00326
00327 private void updateStatsUI(
00328 int nodes,
00329 int words,
00330 int points,
00331 int polygons,
00332 float updateTime,
00333 int loopClosureId,
00334 int highestHypId,
00335 int databaseMemoryUsed,
00336 int inliers,
00337 int featuresExtracted,
00338 float hypothesis,
00339 int nodesDrawn,
00340 float fps,
00341 int rejected)
00342 {
00343 if(mItemPause!=null)
00344 {
00345 ((TextView)findViewById(R.id.status)).setText(mItemPause.isChecked()?"Paused":mItemLocalizationMode.isChecked()?String.format("Localization (%s Hz)", mUpdateRateValues[mParamUpdateRateHzIndex]):String.format("Mapping (%s Hz)", mUpdateRateValues[mParamUpdateRateHzIndex]));
00346 }
00347
00348 ((TextView)findViewById(R.id.points)).setText(String.valueOf(points));
00349 ((TextView)findViewById(R.id.polygons)).setText(String.valueOf(polygons));
00350 ((TextView)findViewById(R.id.nodes)).setText(String.format("%d (%d shown)", nodes, nodesDrawn));
00351 ((TextView)findViewById(R.id.words)).setText(String.valueOf(words));
00352 ((TextView)findViewById(R.id.memory)).setText(String.valueOf(Debug.getNativeHeapAllocatedSize()/(1024*1024)));
00353 ((TextView)findViewById(R.id.db_size)).setText(String.valueOf(databaseMemoryUsed));
00354 ((TextView)findViewById(R.id.inliers)).setText(String.valueOf(inliers));
00355 ((TextView)findViewById(R.id.features)).setText(String.format("%d / %s", featuresExtracted, mMaxFeaturesValues[mParamMaxFeaturesIndex]));
00356 ((TextView)findViewById(R.id.update_time)).setText(String.format("%.3f / %s", updateTime, mTimeThrValues[mParamTimeThrMsIndex]));
00357 ((TextView)findViewById(R.id.hypothesis)).setText(String.format("%.3f / %s (%d)", hypothesis, mLoopThrValues[mParamLoopThrMsIndex], loopClosureId>0?loopClosureId:highestHypId));
00358 ((TextView)findViewById(R.id.fps)).setText(String.format("%.3f Hz", fps));
00359 if(mItemPause!=null && !mItemPause.isChecked())
00360 {
00361 if(loopClosureId > 0)
00362 {
00363 ++mTotalLoopClosures;
00364
00365 mToast.setText("Loop closure detected!");
00366 mToast.show();
00367 }
00368 else if(rejected > 0 && inliers >= 15)
00369 {
00370 mToast.setText("Loop closure rejected after graph optimization.");
00371 mToast.show();
00372 }
00373 }
00374 ((TextView)findViewById(R.id.total_loop)).setText(String.valueOf(mTotalLoopClosures));
00375 }
00376
00377
00378 public void updateStatsCallback(
00379 final int nodes,
00380 final int words,
00381 final int points,
00382 final int polygons,
00383 final float updateTime,
00384 final int loopClosureId,
00385 final int highestHypId,
00386 final int databaseMemoryUsed,
00387 final int inliers,
00388 final int features,
00389 final float hypothesis,
00390 final int nodesDrawn,
00391 final float fps,
00392 final int rejected)
00393 {
00394 Log.i(TAG, String.format("updateStatsCallback()"));
00395
00396 runOnUiThread(new Runnable() {
00397 public void run() {
00398 updateStatsUI(nodes, words, points, polygons, updateTime, loopClosureId, highestHypId, databaseMemoryUsed, inliers, features, hypothesis, nodesDrawn, fps, rejected);
00399 }
00400 });
00401 }
00402
00403 private void rtabmapInitEventUI(
00404 int status,
00405 String msg)
00406 {
00407 Log.i(TAG, String.format("rtabmapInitEventsUI() status=%d msg=%s", status, msg));
00408
00409 ((TextView)findViewById(R.id.status)).setText(
00410 status == 1 && msg.isEmpty()?mItemPause!=null&&mItemPause.isChecked()?"Paused":mItemLocalizationMode!=null&&mItemLocalizationMode.isChecked()?"Localization":"Mapping":msg);
00411
00412
00413
00414
00415
00416
00417
00418
00419 if(status == 3)
00420 {
00421 msg = "";
00422 if(!mNewDatabasePath.isEmpty())
00423 {
00424 boolean removed = true;
00425 File outputFile = new File(mNewDatabasePath);
00426 if(outputFile.exists())
00427 {
00428 removed = outputFile.delete();
00429 }
00430 if(removed)
00431 {
00432 File tempFile = new File(mTempDatabasePath);
00433 if(tempFile.renameTo(outputFile))
00434 {
00435 msg = String.format("Database saved to \"%s\".", mNewDatabasePath);
00436
00437 Intent intent = new Intent(this, RTABMapActivity.class);
00438
00439 PendingIntent pIntent = PendingIntent.getActivity(this, (int) System.currentTimeMillis(), intent, 0);
00440
00441
00442
00443 Notification n = new Notification.Builder(this)
00444 .setContentTitle(getString(R.string.app_name))
00445 .setContentText(mNewDatabasePath + " saved!")
00446 .setSmallIcon(R.drawable.ic_launcher)
00447 .setContentIntent(pIntent)
00448 .setAutoCancel(true).build();
00449
00450
00451 NotificationManager notificationManager =
00452 (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
00453
00454 notificationManager.notify(0, n);
00455 }
00456 else
00457 {
00458 msg = String.format("Failed to rename temporary database from \"%s\" to \"%s\".",
00459 mTempDatabasePath, mNewDatabasePath);
00460 }
00461 }
00462 else
00463 {
00464 msg = String.format("Failed to overwrite the database \"%s\". The temporary database is still correctly saved at \"%s\".",
00465 mNewDatabasePath, mTempDatabasePath);
00466 }
00467 }
00468 else if(!mOpenedDatabasePath.isEmpty())
00469 {
00470 msg = String.format("Database \"%s\" updated.", mOpenedDatabasePath);
00471 }
00472
00473 if(!msg.isEmpty())
00474 {
00475 mToast.makeText(this, msg, mToast.LENGTH_LONG).show();
00476 }
00477
00478 mOpenedDatabasePath = "";
00479 mNewDatabasePath = "";
00480
00481
00482 RTABMapLib.openDatabase(mTempDatabasePath);
00483
00484 ((TextView)findViewById(R.id.points)).setText(String.valueOf(0));
00485 ((TextView)findViewById(R.id.polygons)).setText(String.valueOf(0));
00486 ((TextView)findViewById(R.id.nodes)).setText(String.valueOf(0));
00487 ((TextView)findViewById(R.id.words)).setText(String.valueOf(0));
00488 ((TextView)findViewById(R.id.memory)).setText(String.valueOf(Debug.getNativeHeapAllocatedSize()/(1024*1024)));
00489 ((TextView)findViewById(R.id.db_size)).setText(String.valueOf(0));
00490 ((TextView)findViewById(R.id.inliers)).setText(String.valueOf(0));
00491 ((TextView)findViewById(R.id.features)).setText(String.valueOf(0));
00492 ((TextView)findViewById(R.id.update_time)).setText(String.valueOf(0));
00493 ((TextView)findViewById(R.id.hypothesis)).setText(String.valueOf(0));
00494 ((TextView)findViewById(R.id.fps)).setText(String.valueOf(0));
00495 mTotalLoopClosures = 0;
00496 ((TextView)findViewById(R.id.total_loop)).setText(String.valueOf(mTotalLoopClosures));
00497
00498 if(mItemSave!=null)
00499 {
00500 if(mItemPause.isChecked())
00501 {
00502 mItemPause.setChecked(false);
00503 mItemOpen.setEnabled(false);
00504 mItemPostProcessing.setEnabled(false);
00505 mItemSave.setEnabled(false);
00506 mItemExport.setEnabled(false);
00507 RTABMapLib.setPausedMapping(false);
00508 }
00509 }
00510 mProgressDialog.dismiss();
00511 }
00512 }
00513
00514
00515 public void rtabmapInitEventCallback(
00516 final int status,
00517 final String msg)
00518 {
00519 Log.i(TAG, String.format("rtabmapInitEventCallback()"));
00520
00521 runOnUiThread(new Runnable() {
00522 public void run() {
00523 rtabmapInitEventUI(status, msg);
00524 }
00525 });
00526 }
00527
00528 private void tangoEventUI(
00529 int type,
00530 String key,
00531 String value)
00532 {
00544 String str = null;
00545 if(key.equals("TangoServiceException"))
00546 str = String.format("Tango service exception: %s", value);
00547 else if(key.equals("FisheyeOverExposed"))
00548 ;
00549 else if(key.equals("FisheyeUnderExposed"))
00550 ;
00551 else if(key.equals("ColorOverExposed"))
00552 ;
00553 else if(key.equals("ColorUnderExposed"))
00554 ;
00555 else if(key.equals("CameraTango"))
00556 str = value;
00557 else if(key.equals("TooFewFeaturesTracked"))
00558 {
00559 if(!value.equals("0"))
00560 {
00561 str = String.format("Too few features (%s) were tracked in the fisheye image. This may result in poor odometry!", value);
00562 }
00563 }
00564 else
00565 {
00566 str = String.format("Unknown Tango event detected!? (type=%d)", type);
00567 }
00568 if(str!=null)
00569 {
00570 mToast.setText(str);
00571 mToast.show();
00572 }
00573 }
00574
00575
00576 public void tangoEventCallback(
00577 final int type,
00578 final String key,
00579 final String value)
00580 {
00581 if(mItemPause != null && !mItemPause.isChecked())
00582 {
00583 runOnUiThread(new Runnable() {
00584 public void run() {
00585 tangoEventUI(type, key, value);
00586 }
00587 });
00588 }
00589 }
00590
00591 private boolean CheckTangoCoreVersion(int minVersion) {
00592 int versionNumber = 0;
00593 String packageName = TANGO_PACKAGE_NAME;
00594 try {
00595 PackageInfo pi = getApplicationContext().getPackageManager().getPackageInfo(packageName,
00596 PackageManager.GET_META_DATA);
00597 versionNumber = pi.versionCode;
00598 } catch (NameNotFoundException e) {
00599 e.printStackTrace();
00600 }
00601 return (minVersion <= versionNumber);
00602 }
00603
00604 private RTABMapActivity getActivity() {return this;}
00605
00606 private String[] loadFileList(String directory) {
00607 File path = new File(directory);
00608 String fileList[];
00609 try {
00610 path.mkdirs();
00611 }
00612 catch(SecurityException e) {
00613 Log.e(TAG, "unable to write on the sd card " + e.toString());
00614 }
00615 if(path.exists()) {
00616 FilenameFilter filter = new FilenameFilter() {
00617
00618 @Override
00619 public boolean accept(File dir, String filename) {
00620 File sel = new File(dir, filename);
00621 return filename.endsWith(".db");
00622 }
00623
00624 };
00625 fileList = path.list(filter);
00626 }
00627 else {
00628 fileList = new String[0];
00629 }
00630 return fileList;
00631 }
00632
00633 public boolean onOptionsItemSelected(MenuItem item) {
00634 Log.i(TAG, "called onOptionsItemSelected; selected item: " + item);
00635 int itemId = item.getItemId();
00636 if (itemId == R.id.pause)
00637 {
00638 item.setChecked(!item.isChecked());
00639 mItemSave.setEnabled(item.isChecked());
00640 mItemExport.setEnabled(item.isChecked());
00641 mItemOpen.setEnabled(item.isChecked());
00642 mItemPostProcessing.setEnabled(item.isChecked());
00643
00644 if(item.isChecked())
00645 {
00646 RTABMapLib.setPausedMapping(true);
00647 ((TextView)findViewById(R.id.status)).setText("Paused");
00648 }
00649 else
00650 {
00651 RTABMapLib.setPausedMapping(false);
00652 ((TextView)findViewById(R.id.status)).setText(mItemLocalizationMode.isChecked()?"Localization":"Mapping");
00653 }
00654 }
00655 else if (itemId == R.id.detect_more_loop_closures)
00656 {
00657 mProgressDialog.setTitle("Post-Processing");
00658 mProgressDialog.setMessage(String.format("Please wait while detecting more loop closures..."));
00659 mProgressDialog.show();
00660
00661 Thread workingThread = new Thread(new Runnable() {
00662 public void run() {
00663 final int loopDetected = RTABMapLib.postProcessing(2);
00664 runOnUiThread(new Runnable() {
00665 public void run() {
00666 mProgressDialog.dismiss();
00667 if(loopDetected >= 0)
00668 {
00669 mTotalLoopClosures+=loopDetected;
00670 mToast.makeText(getActivity(), String.format("Detection done! %d new loop closure(s) added.", loopDetected), mToast.LENGTH_SHORT).show();
00671 }
00672 else if(loopDetected < 0)
00673 {
00674 mToast.makeText(getActivity(), String.format("Detection failed!"), mToast.LENGTH_SHORT).show();
00675 }
00676 }
00677 });
00678 }
00679 });
00680 workingThread.start();
00681 }
00682 else if (itemId == R.id.global_graph_optimization)
00683 {
00684 mProgressDialog.setTitle("Post-Processing");
00685 mProgressDialog.setMessage(String.format("Global graph optimization..."));
00686 mProgressDialog.show();
00687
00688 Thread workingThread = new Thread(new Runnable() {
00689 public void run() {
00690 final int value = RTABMapLib.postProcessing(0);
00691 runOnUiThread(new Runnable() {
00692 public void run() {
00693 mProgressDialog.dismiss();
00694 if(value >= 0)
00695 {
00696 mToast.makeText(getActivity(), String.format("Optimization done!"), mToast.LENGTH_SHORT).show();
00697 }
00698 else if(value < 0)
00699 {
00700 mToast.makeText(getActivity(), String.format("Optimization failed!"), mToast.LENGTH_SHORT).show();
00701 }
00702 }
00703 });
00704 }
00705 });
00706 workingThread.start();
00707 }
00708 else if (itemId == R.id.sba)
00709 {
00710 mProgressDialog.setTitle("Post-Processing");
00711 mProgressDialog.setMessage(String.format("Bundle adjustement..."));
00712 mProgressDialog.show();
00713
00714 Thread workingThread = new Thread(new Runnable() {
00715 public void run() {
00716 final int value = RTABMapLib.postProcessing(1);
00717 runOnUiThread(new Runnable() {
00718 public void run() {
00719 mProgressDialog.dismiss();
00720 if(value >= 0)
00721 {
00722 mToast.makeText(getActivity(), String.format("Optimization done!"), mToast.LENGTH_SHORT).show();
00723 }
00724 else if(value < 0)
00725 {
00726 mToast.makeText(getActivity(), String.format("Optimization failed!"), mToast.LENGTH_SHORT).show();
00727 }
00728 }
00729 });
00730 }
00731 });
00732 workingThread.start();
00733 }
00734 else if(itemId == R.id.debug)
00735 {
00736 item.setChecked(!item.isChecked());
00737 if(!item.isChecked())
00738 {
00739 mLayoutDebug.setVisibility(LinearLayout.GONE);
00740 }
00741 else
00742 {
00743 mLayoutDebug.setVisibility(LinearLayout.VISIBLE);
00744 }
00745 }
00746 else if(itemId == R.id.mesh_rendering)
00747 {
00748 item.setChecked(!item.isChecked());
00749 RTABMapLib.setMeshRendering(item.isChecked());
00750 }
00751 else if(itemId == R.id.map_shown)
00752 {
00753 item.setChecked(!item.isChecked());
00754 RTABMapLib.setMapCloudShown(item.isChecked());
00755 }
00756 else if(itemId == R.id.odom_shown)
00757 {
00758 item.setChecked(!item.isChecked());
00759 RTABMapLib.setOdomCloudShown(item.isChecked());
00760 }
00761 else if(itemId == R.id.localization_mode)
00762 {
00763 item.setChecked(!item.isChecked());
00764 RTABMapLib.setLocalizationMode(item.isChecked());
00765 }
00766 else if(itemId == R.id.trajectory_mode)
00767 {
00768 item.setChecked(!item.isChecked());
00769 RTABMapLib.setTrajectoryMode(item.isChecked());
00770 }
00771 else if(itemId == R.id.graph_optimization)
00772 {
00773 item.setChecked(!item.isChecked());
00774 RTABMapLib.setGraphOptimization(item.isChecked());
00775 }
00776 else if(itemId == R.id.nodes_filtering)
00777 {
00778 item.setChecked(!item.isChecked());
00779 RTABMapLib.setNodesFiltering(item.isChecked());
00780 }
00781 else if(itemId == R.id.graph_visible)
00782 {
00783 item.setChecked(!item.isChecked());
00784 RTABMapLib.setGraphVisible(item.isChecked());
00785 }
00786 else if(itemId == R.id.auto_exposure)
00787 {
00788 item.setChecked(!item.isChecked());
00789 RTABMapLib.setAutoExposure(item.isChecked());
00790
00791
00792 onPause();
00793 onResume();
00794 }
00795 else if(itemId == R.id.resolution)
00796 {
00797 item.setChecked(!item.isChecked());
00798 RTABMapLib.setFullResolution(item.isChecked());
00799 }
00800 else if(itemId == R.id.max_depth)
00801 {
00802
00803 AlertDialog.Builder builder = new AlertDialog.Builder(this);
00804 builder.setTitle("Max Depth (m)");
00805 final String[] values = {"1", "2", "3", "4", "5", "No Limit"};
00806 builder.setSingleChoiceItems(values, mMaxDepthIndex, new DialogInterface.OnClickListener() {
00807 @Override
00808 public void onClick(DialogInterface dialog, int which) {
00809 dialog.dismiss();
00810 if(which >=0 && which < 6)
00811 {
00812 mMaxDepthIndex = which;
00813 RTABMapLib.setMaxCloudDepth(which < 5?Float.parseFloat(values[which]):0);
00814 }
00815 }
00816 });
00817 builder.show();
00818 }
00819 else if(itemId == R.id.mesh_angle_tolerance)
00820 {
00821
00822 AlertDialog.Builder builder = new AlertDialog.Builder(this);
00823 builder.setTitle("Mesh Angle Tolerance (deg)");
00824 final String[] values = {"5", "10", "15", "20", "25", "30"};
00825 builder.setSingleChoiceItems(values, mMeshAngleToleranceIndex, new DialogInterface.OnClickListener() {
00826 @Override
00827 public void onClick(DialogInterface dialog, int which) {
00828 dialog.dismiss();
00829 if(which >=0 && which < 6)
00830 {
00831 mMeshAngleToleranceIndex = which;
00832 RTABMapLib.setMeshAngleTolerance(Float.parseFloat(values[which]));
00833 }
00834 }
00835 });
00836 builder.show();
00837 }
00838 else if(itemId == R.id.mesh_triangle_size)
00839 {
00840
00841 AlertDialog.Builder builder = new AlertDialog.Builder(this);
00842 builder.setTitle("Mesh Triangle Size (pixels)");
00843 final String[] values = {"2", "3", "4", "5", "6"};
00844 builder.setSingleChoiceItems(values, mMeshTriangleSizeIndex, new DialogInterface.OnClickListener() {
00845 @Override
00846 public void onClick(DialogInterface dialog, int which) {
00847 dialog.dismiss();
00848 if(which >=0 && which < 5)
00849 {
00850 mMeshTriangleSizeIndex = which;
00851 RTABMapLib.setMeshTriangleSize(Integer.parseInt(values[which]));
00852 }
00853 }
00854 });
00855 builder.show();
00856 }
00857 else if(itemId == R.id.update_rate)
00858 {
00859
00860 AlertDialog.Builder builder = new AlertDialog.Builder(this);
00861 builder.setTitle("Update Rate (Hz)");
00862 builder.setSingleChoiceItems(mUpdateRateValues, mParamUpdateRateHzIndex, new DialogInterface.OnClickListener() {
00863 @Override
00864 public void onClick(DialogInterface dialog, int which) {
00865 dialog.dismiss();
00866 if(which >=0 && which < mUpdateRateValues.length)
00867 {
00868 mParamUpdateRateHzIndex = which;
00869 if(RTABMapLib.setMappingParameter("Rtabmap/DetectionRate", mUpdateRateValues[which]) != 0)
00870 {
00871 mToast.makeText(getActivity(), "Failed to set parameter \"Rtabmap/DetectionRate\"!", mToast.LENGTH_LONG).show();
00872 }
00873 }
00874 }
00875 });
00876 builder.show();
00877 }
00878 else if(itemId == R.id.time_threshold)
00879 {
00880
00881 AlertDialog.Builder builder = new AlertDialog.Builder(this);
00882 builder.setTitle("Time Threshold (ms)");
00883 builder.setSingleChoiceItems(mTimeThrValues, mParamTimeThrMsIndex, new DialogInterface.OnClickListener() {
00884 @Override
00885 public void onClick(DialogInterface dialog, int which) {
00886 dialog.dismiss();
00887 if(which >=0 && which < mTimeThrValues.length)
00888 {
00889 mParamTimeThrMsIndex = which;
00890 if(RTABMapLib.setMappingParameter("Rtabmap/TimeThr", which==mTimeThrValues.length-1?"0":mTimeThrValues[which]) != 0)
00891 {
00892 mToast.makeText(getActivity(), "Failed to set parameter \"Rtabmap/TimeThr\"!", mToast.LENGTH_LONG).show();
00893 }
00894 }
00895 }
00896 });
00897 builder.show();
00898 }
00899 else if(itemId == R.id.features)
00900 {
00901
00902 AlertDialog.Builder builder = new AlertDialog.Builder(this);
00903 builder.setTitle("Max Features");
00904 builder.setSingleChoiceItems(mMaxFeaturesValues, mParamMaxFeaturesIndex, new DialogInterface.OnClickListener() {
00905 @Override
00906 public void onClick(DialogInterface dialog, int which) {
00907 dialog.dismiss();
00908 if(which >=0 && which < mMaxFeaturesValues.length)
00909 {
00910 mParamMaxFeaturesIndex = which;
00911 if(RTABMapLib.setMappingParameter("Kp/MaxFeatures", which==0?"-1":which==mMaxFeaturesValues.length-1?"0":mMaxFeaturesValues[which]) != 0)
00912 {
00913 mToast.makeText(getActivity(),"Failed to set parameter \"Kp/MaxFeatures\"!", mToast.LENGTH_LONG).show();
00914 }
00915 }
00916 }
00917 });
00918 builder.show();
00919 }
00920 else if(itemId == R.id.loop_threshold)
00921 {
00922
00923 AlertDialog.Builder builder = new AlertDialog.Builder(this);
00924 builder.setTitle("Loop Closure Threshold");
00925 builder.setSingleChoiceItems(mLoopThrValues, mParamLoopThrMsIndex, new DialogInterface.OnClickListener() {
00926 @Override
00927 public void onClick(DialogInterface dialog, int which) {
00928 dialog.dismiss();
00929 if(which >=0 && which < mLoopThrValues.length)
00930 {
00931 mParamLoopThrMsIndex = which;
00932 if(RTABMapLib.setMappingParameter("Rtabmap/LoopThr", which==0?"1":mLoopThrValues[which]) != 0)
00933 {
00934 mToast.makeText(getActivity(), "Failed to set parameter \"Rtabmap/LoopThr\"!", mToast.LENGTH_LONG).show();
00935 }
00936 }
00937 }
00938 });
00939 builder.show();
00940 }
00941 else if(itemId == R.id.optimize_error)
00942 {
00943
00944 AlertDialog.Builder builder = new AlertDialog.Builder(this);
00945 builder.setTitle("Max Optimization Error (m)");
00946 builder.setSingleChoiceItems(mOptimizeErrorValues, mParamOptimizeErrorIndex, new DialogInterface.OnClickListener() {
00947 @Override
00948 public void onClick(DialogInterface dialog, int which) {
00949 dialog.dismiss();
00950 if(which >=0 && which < mOptimizeErrorValues.length)
00951 {
00952 mParamOptimizeErrorIndex = which;
00953 if(RTABMapLib.setMappingParameter("RGBD/OptimizeMaxError", which==0?"0":mOptimizeErrorValues[which]) != 0)
00954 {
00955 mToast.makeText(getActivity(), "Failed to set parameter \"RGBD/OptimizeMaxError\"!", mToast.LENGTH_LONG).show();
00956 }
00957 }
00958 }
00959 });
00960 builder.show();
00961 }
00962 else if (itemId == R.id.save)
00963 {
00964 if(mOpenedDatabasePath.isEmpty())
00965 {
00966 AlertDialog.Builder builder = new AlertDialog.Builder(this);
00967 builder.setTitle("RTAB-Map Database Name (*.db):");
00968 final EditText input = new EditText(this);
00969 input.setInputType(InputType.TYPE_CLASS_TEXT);
00970 builder.setView(input);
00971 builder.setPositiveButton("OK", new DialogInterface.OnClickListener() {
00972 @Override
00973 public void onClick(DialogInterface dialog, int which)
00974 {
00975 final String fileName = input.getText().toString();
00976 dialog.dismiss();
00977 if(!fileName.isEmpty())
00978 {
00979 File newFile = new File(mWorkingDirectory + fileName + ".db");
00980 if(newFile.exists())
00981 {
00982 new AlertDialog.Builder(getActivity())
00983 .setTitle("File Already Exists")
00984 .setMessage("Do you want to overwrite the existing file?")
00985 .setPositiveButton("Yes", new DialogInterface.OnClickListener() {
00986 public void onClick(DialogInterface dialog, int which) {
00987 mNewDatabasePath = mWorkingDirectory + fileName + ".db";
00988
00989 mProgressDialog.setTitle("Saving");
00990 mProgressDialog.setMessage(String.format("Please wait while saving \"%s\"...", mNewDatabasePath));
00991 mProgressDialog.show();
00992
00993 RTABMapLib.save();
00994
00995 mItemSave.setEnabled(false);
00996 mItemOpen.setEnabled(false);
00997 mItemPostProcessing.setEnabled(false);
00998 mItemExport.setEnabled(false);
00999 }
01000 })
01001 .setNegativeButton("No", new DialogInterface.OnClickListener() {
01002 public void onClick(DialogInterface dialog, int which) {
01003 dialog.dismiss();
01004 }
01005 })
01006 .show();
01007 }
01008 else
01009 {
01010 mNewDatabasePath = mWorkingDirectory + fileName + ".db";
01011
01012 mProgressDialog.setTitle("Saving");
01013 mProgressDialog.setMessage(String.format("Please wait while saving \"%s\"...", mNewDatabasePath));
01014 mProgressDialog.show();
01015
01016 RTABMapLib.save();
01017
01018 mItemSave.setEnabled(false);
01019 mItemOpen.setEnabled(false);
01020 mItemPostProcessing.setEnabled(false);
01021 mItemExport.setEnabled(false);
01022 }
01023 }
01024 }
01025 });
01026 builder.show();
01027 }
01028 else
01029 {
01030 mProgressDialog.setTitle("Saving");
01031 mProgressDialog.setMessage(String.format("Please wait while updating \"%s\"...", mOpenedDatabasePath));
01032 mProgressDialog.show();
01033
01034 RTABMapLib.save();
01035
01036 mItemSave.setEnabled(false);
01037 mItemOpen.setEnabled(false);
01038 mItemPostProcessing.setEnabled(false);
01039 mItemExport.setEnabled(false);
01040 }
01041 }
01042 else if(itemId == R.id.reset)
01043 {
01044 ((TextView)findViewById(R.id.points)).setText(String.valueOf(0));
01045 ((TextView)findViewById(R.id.polygons)).setText(String.valueOf(0));
01046 ((TextView)findViewById(R.id.nodes)).setText(String.valueOf(0));
01047 ((TextView)findViewById(R.id.words)).setText(String.valueOf(0));
01048 ((TextView)findViewById(R.id.memory)).setText(String.valueOf(Debug.getNativeHeapAllocatedSize()/(1024*1024)));
01049 ((TextView)findViewById(R.id.db_size)).setText(String.valueOf(0));
01050 ((TextView)findViewById(R.id.inliers)).setText(String.valueOf(0));
01051 ((TextView)findViewById(R.id.features)).setText(String.valueOf(0));
01052 ((TextView)findViewById(R.id.update_time)).setText(String.valueOf(0));
01053 ((TextView)findViewById(R.id.hypothesis)).setText(String.valueOf(0));
01054 ((TextView)findViewById(R.id.fps)).setText(String.valueOf(0));
01055 mTotalLoopClosures = 0;
01056 ((TextView)findViewById(R.id.total_loop)).setText(String.valueOf(mTotalLoopClosures));
01057
01058 if(mOpenedDatabasePath.isEmpty())
01059 {
01060 RTABMapLib.resetMapping();
01061 }
01062 else
01063 {
01064 mOpenedDatabasePath = "";
01065 RTABMapLib.openDatabase(mTempDatabasePath);
01066 }
01067 }
01068 else if(itemId == R.id.export_obj || itemId == R.id.export_ply)
01069 {
01070 final String extension = itemId == R.id.export_ply ? ".ply" : ".obj";
01071 final boolean isOBJ = itemId == R.id.export_obj;
01072
01073 final int polygons = Integer.parseInt(((TextView)findViewById(R.id.polygons)).getText().toString());
01074
01075 AlertDialog.Builder builder = new AlertDialog.Builder(this);
01076 builder.setTitle(String.format("File Name (*%s):", extension));
01077 final EditText input = new EditText(this);
01078 input.setInputType(InputType.TYPE_CLASS_TEXT);
01079 builder.setView(input);
01080 builder.setPositiveButton("OK", new DialogInterface.OnClickListener() {
01081 @Override
01082 public void onClick(DialogInterface dialog, int which)
01083 {
01084 final String fileName = input.getText().toString();
01085 dialog.dismiss();
01086 if(!fileName.isEmpty())
01087 {
01088 File newFile = new File(mWorkingDirectory + fileName + extension);
01089 if(newFile.exists())
01090 {
01091 new AlertDialog.Builder(getActivity())
01092 .setTitle("File Already Exists")
01093 .setMessage("Do you want to overwrite the existing file?")
01094 .setPositiveButton("Yes", new DialogInterface.OnClickListener() {
01095 public void onClick(DialogInterface dialog, int which) {
01096 final String path = mWorkingDirectory + fileName + extension;
01097
01098 mItemExport.setEnabled(false);
01099
01100 mProgressDialog.setTitle("Exporting");
01101 if(polygons > 1000000)
01102 {
01103 mProgressDialog.setMessage(String.format(
01104 "Please wait while exporting \"%s\"...\n"
01105 + "Tip: With more than 1M polygons, to reduce exporting time and file size, consider:\n"
01106 + " a) increasing triangle size (Rendering options)\n"
01107 + " b) decreasing maximum camera depth (Rendering options)\n"
01108 + " c) activate Nodes Filtering (Mapping options)\n"
01109 + "then save/open to refresh the meshes.", fileName+extension));
01110 }
01111 else
01112 {
01113 mProgressDialog.setMessage(String.format("Please wait while exporting \"%s\"...", fileName+extension));
01114 }
01115
01116 mProgressDialog.show();
01117
01118 Thread exportThread = new Thread(new Runnable() {
01119 public void run() {
01120 final boolean success = RTABMapLib.exportMesh(path);
01121 runOnUiThread(new Runnable() {
01122 public void run() {
01123 if(success)
01124 {
01125 if(isOBJ)
01126 {
01127 mToast.makeText(getActivity(), String.format("Mesh \"%s\" (with textures \"%s/\" and \"%s\") successfully exported!", path, fileName, fileName + ".mtl"), mToast.LENGTH_LONG).show();
01128 }
01129 else
01130 {
01131 mToast.makeText(getActivity(), String.format("Mesh \"%s\" successfully exported!", path), mToast.LENGTH_LONG).show();
01132 }
01133 }
01134 else
01135 {
01136 mToast.makeText(getActivity(), String.format("Exporting mesh to \"%s\" failed!", path), mToast.LENGTH_LONG).show();
01137 }
01138 mItemExport.setEnabled(true);
01139 mProgressDialog.dismiss();
01140 }
01141 });
01142 }
01143 });
01144 exportThread.start();
01145 }
01146 })
01147 .setNegativeButton("No", new DialogInterface.OnClickListener() {
01148 public void onClick(DialogInterface dialog, int which) {
01149 dialog.dismiss();
01150 }
01151 })
01152 .show();
01153 }
01154 else
01155 {
01156 final String path = mWorkingDirectory + fileName + extension;
01157 mItemExport.setEnabled(false);
01158 mProgressDialog.setTitle("Exporting");
01159 mProgressDialog.setMessage(String.format("Please wait while exporting \"%s\"...", fileName+extension));
01160 mProgressDialog.show();
01161 Thread exportThread = new Thread(new Runnable() {
01162 public void run() {
01163 final boolean success = RTABMapLib.exportMesh(path);
01164 runOnUiThread(new Runnable() {
01165 public void run() {
01166 if(success)
01167 {
01168 if(isOBJ)
01169 {
01170 mToast.makeText(getActivity(), String.format("Mesh \"%s\" (with textures \"%s/\" and \"%s\") successfully exported!", path, fileName, fileName + ".mtl"), mToast.LENGTH_LONG).show();
01171 }
01172 else
01173 {
01174 mToast.makeText(getActivity(), String.format("Mesh \"%s\" successfully exported!", path), mToast.LENGTH_LONG).show();
01175 }
01176 }
01177 else
01178 {
01179 mToast.makeText(getActivity(), String.format("Exporting mesh to \"%s\" failed!", path), mToast.LENGTH_LONG).show();
01180 }
01181 mItemExport.setEnabled(true);
01182 mProgressDialog.dismiss();
01183 }
01184 });
01185 }
01186 });
01187 exportThread.start();
01188 }
01189 }
01190 }
01191 });
01192 builder.show();
01193 }
01194 else if(itemId == R.id.open)
01195 {
01196 final String[] files = loadFileList(mWorkingDirectory);
01197 if(files.length > 0)
01198 {
01199 AlertDialog.Builder builder = new AlertDialog.Builder(this);
01200 builder.setTitle("Choose your file");
01201 builder.setItems(files, new DialogInterface.OnClickListener() {
01202 public void onClick(DialogInterface dialog, int which) {
01203 mOpenedDatabasePath = mWorkingDirectory + files[which];
01204
01205 if(!mItemTrajectoryMode.isChecked())
01206 {
01207 mProgressDialog.setTitle("Loading");
01208 mProgressDialog.setMessage(String.format("Database \"%s\" loaded. Please wait while creating point clouds and meshes...", files[which]));
01209 mProgressDialog.show();
01210 }
01211
01212 RTABMapLib.openDatabase(mOpenedDatabasePath);
01213 RTABMapLib.setCamera(1);
01214
01215 File extStore = new File(mTempDatabasePath);
01216 if(extStore.exists())
01217 {
01218 extStore.delete();
01219 }
01220 }
01221 });
01222 builder.show();
01223 }
01224 }
01225 else if(itemId == R.id.about)
01226 {
01227 AboutDialog about = new AboutDialog(this);
01228 about.setTitle("About RTAB-Map");
01229 about.show();
01230 }
01231
01232 return true;
01233 }
01234 }