def create_output_file(self,
                           file_name,
                           mode,
                           dictTitles=None,
                           cat_name=''):

        sampling_subpath = get_sampling_subpath(self.nSamples,
                                                self.nSamplesRatio)

        prsStr = '%s,%s,%s' % (self.prs[0], self.prs[1], self.prs[2])
        nbinsStr = '%d,%d,%d' % (self.nbins[0], self.nbins[1], self.nbins[2])

        # e.g. triParams_l0l1a0_nbins10_10_10
        # This only pertains to histogram files. Triangle files don't need this,
        #   `.` triangle files save all 6 params, and histogram bin choice is
        #   irrelevant to triangle files.
        tri_nbins_subpath, _ = get_triparams_nbins_subpath(prsStr,
                                                           nbinsStr,
                                                           endSlash=False)

        if mode == self.HIST3D:
            datapath = tactile_config.config_paths(
                'custom',
                os.path.join('triangle_sampling/csv_hists/', sampling_subpath,
                             tri_nbins_subpath))
        elif mode == self.TRI:
            datapath = tactile_config.config_paths(
                'custom',
                os.path.join('triangle_sampling/csv_tri_lists/',
                             sampling_subpath))
        elif mode == self.HIST1D:
            datapath = tactile_config.config_paths(
                'custom',
                os.path.join('triangle_sampling/csv_hists_1d/',
                             sampling_subpath, tri_nbins_subpath))
        elif mode == self.KDE:
            datapath = tactile_config.config_paths(
                'custom',
                os.path.join('triangle_sampling/csv_kde/', sampling_subpath,
                             tri_nbins_subpath))
        #print ('Training data will be outputted to ' + datapath)

        # Create output file
        outfile_name = os.path.join(datapath, cat_name, file_name + '.csv')
        if not os.path.exists(os.path.dirname(outfile_name)):
            os.makedirs(os.path.dirname(outfile_name))
        outfile = open(outfile_name, 'wb')

        # If a field is non-existent, output '-1'
        if dictTitles:
            writer = csv.DictWriter(outfile, fieldnames=dictTitles)
            writer.writeheader()
        else:
            writer = csv.writer(outfile)

        #print ('Data will be outputted to %s' % outfile_name)

        return (outfile_name, outfile, writer, datapath)
    def __init__(self,
                 sampling_subpath,
                 testKDE=False,
                 csv_suffix='',
                 custom_hist_conf_name=''):

        self.decimeter = HistP.decimeter
        print ('%sMeter to DECIMETER conversion set to %s%s' % \
          (ansi_colors.OKCYAN, self.decimeter, ansi_colors.ENDC))

        # Triangles path

        self.tri_subpath = 'triangle_sampling/csv_' + csv_suffix + 'tri/'
        # Real-robot tactile data, collected by triangles_collect.py
        # config_paths() creates the folder if it doesn't exist. Since this file
        #   is a reader, not a writer, don't create it. Just get the root path
        #   using config_paths(), then join to it without creating the folder.
        self.tri_path = tactile_config.config_paths('custom', '')
        self.tri_path = os.path.join(self.tri_path, self.tri_subpath)

        # Histogram paths

        self.hist_path = tactile_config.config_paths(
            'custom', 'triangle_sampling/csv_' + csv_suffix + 'hists/' +
            sampling_subpath)

        # If you run on mixed data, then you should pass in custom_hist_conf_name,
        #   specifying which file to use. Otherwise it can be confusing whether
        #   to use the PCL hist_conf.csv in csv_tri/, or the Gazebo hist_conf.csv
        #   in csv_gz_tri/.
        if custom_hist_conf_name:
            self.hist_conf_name = custom_hist_conf_name

        # The one in csv_*_hists folder, i.e. csv_gz_hists.
        else:
            self.hist_conf_name = os.path.join(self.hist_path, 'hist_conf.csv')

        # KDE paths

        self.testKDE = testKDE
        # config_paths() creates the folder if it doesn't exist. So don't create it
        #   unless we're actually outputting kde files (testKDE==True).
        if self.testKDE:
            self.kde_path = tactile_config.config_paths(
                'custom', 'triangle_sampling/csv_' + csv_suffix + 'kde/' +
                sampling_subpath)

        # Book-keeping

        self.configured = False
Example #3
0
def read_training_poses(poses_bases, obj_cats):

    poses_path = tactile_config.config_paths('custom',
                                             'triangle_sampling/poses_bx/')

    # Create an empty array without knowing its size
    # http://stackoverflow.com/questions/19646726/unsuccessful-append-to-an-empty-numpy-array
    poses = np.array([])

    for i in range(len(poses_bases)):

        poses_name = os.path.join(poses_path, obj_cats[i], poses_bases[i])
        print('Loading poses from %s...' % (poses_name))

        with open(poses_name, 'rb') as f:
            curr_poses = pickle.load(f)

            # Init to the right number of columns
            if poses.size == 0:
                poses = poses.reshape(0, curr_poses.shape[1])
            # Concatenate to larger array
            poses = np.vstack((poses, curr_poses))

    print('Total %d poses loaded' % (poses.shape[0]))

    return poses
Example #4
0
def write_feasible_poses(poses, obj_cat, poses_base):

    poses_path = tactile_config.config_paths(
        'custom', 'triangle_sampling/poses_bx/' + obj_cat)
    poses_name = os.path.join(poses_path, poses_base)

    with open(poses_name, 'wb') as f:
        # HIGHEST_PROTOCOL is binary, good performance. 0 is text format,
        #   can use for debugging.
        pickle.dump(poses, f, pickle.HIGHEST_PROTOCOL)

    print('%d feasible poses written to %s' % (poses.shape[0], poses_name))
Example #5
0
  def __init__ (self, sampling_subpath, nSamples, nSamplesRatio):

    # Triangles path
    # Synthetic 3D model data, saved from running sample_pcl.cpp and
    #   sample_pcl_calc_hist.py
    self.tri_subpath = 'triangle_sampling/csv_tri_lists/'
    self.tri_path = tactile_config.config_paths ('custom',
      os.path.join (self.tri_subpath, sampling_subpath))

    self.tri_conf_name = os.path.join (self.tri_path, 'tri_conf.csv')

    self.tri_pub = rospy.Publisher ('/sample_pcl/triangle_params',
      TriangleParams, queue_size=5)

    # To determine what folder the data will be saved in, by an external node
    #   (sample_pcl_calc_hist.py) that subscribes to this node.
    self.nSamples_pub = rospy.Publisher ('/sample_pcl/nSamples',
      Int32, queue_size=1);
    self.nSamplesRatio_pub = rospy.Publisher ('/sample_pcl/nSamplesRatio',
      Float32, queue_size=1);
    self.nSamples_msg = Int32 ()
    self.nSamples_msg.data = nSamples
    self.nSamplesRatio_msg = Float32 ()
    self.nSamplesRatio_msg.data = nSamplesRatio

    self.tri_msg = None
    self.obj_idx = -1

    self.PR = []

    self.L0 = HistP.L0
    self.L1 = HistP.L1
    self.L2 = HistP.L2
    self.A0 = HistP.A0
    self.A1 = HistP.A1
    self.A2 = HistP.A2


    # Book-keeping

    self.configured = False
Example #6
0
def main():

    rospy.init_node('triangles_reader', anonymous=True)

    rospack = rospkg.RosPack()
    model_list_path = os.path.join(rospack.get_path('triangle_sampling'),
                                   'config')

    PCD_CLOUD_DATA = 0
    REAL_ROBOT_DATA = 1
    GAZEBO_HAND_DATA = 2
    MIXED_DATA = 3
    data_mode = -1

    wait_rate = rospy.Rate(10)

    #####
    # Parse cmd line args
    #####

    arg_parser = argparse.ArgumentParser()

    arg_parser.add_argument ('histSubdirParam1', type=str,
      help='Used to create directory name to read from.\n' + \
        'For point cloud, nSamples used when triangles were sampled.\n' + \
        'For real robot data, specify the sampling density you want to classify real objects with, e.g. 10, will be used to load histogram bin configs.\n' + \
        'For Gazebo, triangle params desired, with no spaces, e.g. l0,l1,a0\n' + \
        'For mixed, enter 2 point cloud params first, then 2 Gazebo params, 4 total')
    arg_parser.add_argument ('histSubdirParam2', type=str, nargs='+',
      help='Used to create directory name to read from.\n' + \
        'For point cloud, nSamplesRatio used when triangles were sampled.\n' + \
        'For real robot data, specify the sampling density you want to classify real objects with, e.g. 0.95, will be used to load histogram bin configs.\n' + \
        'For Gazebo, number of bins in 3D histogram, with no spaces, e.g. 10,10,10.\n' + \
        'For mixed, enter 2 point cloud params first, then 2 Gazebo params, 4 total.')

    # Ref: Boolean (Ctrl+F "flag") https://docs.python.org/2/howto/argparse.html
    arg_parser.add_argument(
        '--pcd',
        action='store_true',
        default=False,
        help=
        'Boolean flag, no args. Run on synthetic data in csv_tri_lists/ from point cloud'
    )
    arg_parser.add_argument(
        '--real',
        action='store_true',
        default=False,
        help='Boolean flag, no args. Run on real robot data in csv_bx_tri/')
    arg_parser.add_argument(
        '--gazebo',
        action='store_true',
        default=False,
        help=
        'Boolean flag, no args. Run on synthetic data in csv_gz_tri/ from Gazebo. nSamples and nSamplesRatio do not make sense currently, so just always enter same thing so data gets saved to same folder, like 0 0'
    )
    arg_parser.add_argument(
        '--mixed',
        action='store_true',
        default=False,
        help=
        'Boolean flag, no args. Run on a mix of PCL, Gazebo, etc data. This assumes you will specify long csv paths (--long_csv_path in some other scripts) in the meta file! We will read starting from train/ directory, everything after must be specified in meta file. If a line is for csv_gz_tri, histogram will be outputted to csv_gz_hists; csv_pcl_tri would get output in csv_pcl_hists.'
    )

    arg_parser.add_argument(
        '--pcdtest',
        action='store_true',
        default=False,
        help=
        'Boolean flag, no args. Use models_test.txt as opposed to models.txt.')
    arg_parser.add_argument(
        '--realtest',
        action='store_true',
        default=False,
        help=
        'Boolean flag, no args. Run Kernel Density Estimate 2D histogram slices plots.'
    )
    # No test mode for gazebo yet, don't need it yet

    arg_parser.add_argument(
        '--meta',
        type=str,
        default='',
        help=
        'String. Base name of meta list file in triangle_sampling/config directory'
    )

    args = arg_parser.parse_args()

    pcdTest = args.pcdtest
    realTest = args.realtest

    # More than one Boolean is True
    if args.pcd + args.real + args.gazebo + args.mixed > 1:
        print(
            'ERROR: More than one of --pcd, --real, --gazebo, and --mixed were specified. You must choose one. Terminating...'
        )
        return
    elif args.pcd + args.real + args.gazebo + args.mixed == 0:
        print ('%sERROR: Neither --pcd, --real, or --gazebo were specified. You must choose one. Terminating...%s' ( \
          ansi_colors.FAIL, ansi_colors.ENDC))
        return

    # Point clouds PCD data sampled by PCL
    if args.pcd or args.pcdtest:
        data_mode = PCD_CLOUD_DATA
    elif args.real or args.realtest:
        data_mode = REAL_ROBOT_DATA
    # Mesh model COLLADA data sampled by Gazebo physics
    elif args.gazebo:
        data_mode = GAZEBO_HAND_DATA
    elif args.mixed:
        data_mode = MIXED_DATA

    print('%sdata_mode set to %s %s' %
          (ansi_colors.OKCYAN, data_mode, ansi_colors.ENDC))
    print(
        '  (PCD_CLOUD_DATA 0, REAL_ROBOT_DATA 1, GAZEBO_HAND_DATA 2, MIXED_DATA 3)'
    )

    if args.pcd:
        if len(args.histSubdirParam2) != 1:
            print ('%sERROR: Expect histSubdirParam2 to only have one element, for --pcd mode. Check your args and retry.%s' % ( \
              ansi_colors.FAIL, ansi_colors.ENDC))
            return

        nSamples = int(args.histSubdirParam1)
        nSamplesRatio = float(args.histSubdirParam2[0])
        print ('%sAccessing directory with nSamples %d, nSamplesRatio %f%s' % \
          (ansi_colors.OKCYAN, nSamples, nSamplesRatio, ansi_colors.ENDC))

        # Sampling subpath to save different number of samples, for quick accessing
        #   without having to rerun sample_pcl.cpp.
        sampling_subpath = get_sampling_subpath(nSamples, nSamplesRatio)

    elif args.real:
        if len(args.histSubdirParam2) != 1:
            print ('%sERROR: Expect histSubdirParam2 to only have one element, for --gazebo mode. Check your args and retry.%s' % ( \
              ansi_colors.FAIL, ansi_colors.ENDC))
            return

        sampling_subpath, _ = get_triparams_nbins_subpath(
            args.histSubdirParam1, args.histSubdirParam2[0])

    elif args.gazebo:
        if len(args.histSubdirParam2) != 1:
            print ('%sERROR: Expect histSubdirParam2 to only have one element, for --gazebo mode. Check your args and retry.%s' % ( \
              ansi_colors.FAIL, ansi_colors.ENDC))
            return

        sampling_subpath, _ = get_triparams_nbins_subpath(
            args.histSubdirParam1, args.histSubdirParam2[0])

    # For mixed data, need to specify both kinds of histSubdirParams, a pair for
    #   PCL data's sampling_subpath, a pair for Gazebo data's sampling_subpath.
    elif args.mixed:
        if len(args.histSubdirParam2) != 3:
            print ('%sERROR: Expect histSubdirParam2 to have three elements, for --mixed mode. Check your args and retry.%s' % ( \
              ansi_colors.FAIL, ansi_colors.ENDC))
            return

        nSamples = int(args.histSubdirParam1)
        nSamplesRatio = float(args.histSubdirParam2[0])
        sampling_subpath = get_sampling_subpath(nSamples, nSamplesRatio)

        tri_nbins_subpath, _ = get_triparams_nbins_subpath(
            args.histSubdirParam2[1], args.histSubdirParam2[2])

    #####
    # Init the correct node for the task
    #####

    if (data_mode == PCD_CLOUD_DATA) or pcdTest:
        pcdNode = TrianglesListsSyntheticPublisher(sampling_subpath, nSamples,
                                                   nSamplesRatio)
        pcdNode.read_config()

        if args.meta:
            model_list_name = os.path.join(model_list_path, args.meta)
        else:
            if pcdTest:
                model_list_name = os.path.join(model_list_path,
                                               'models_test.txt')
            else:
                model_list_name = os.path.join(model_list_path, 'models.txt')

    # For real data, use the histogram bins saved in config when training on
    #   synthetic data. This is to make sure real data and synthetic data fall
    #   in the same bins, so that they can be used in recognition. Train
    #   synthetic test real.
    elif (data_mode == REAL_ROBOT_DATA) or realTest:
        realNode = TrianglesOnRobotToHists(sampling_subpath,
                                           testKDE=realTest,
                                           csv_suffix='bx_')
        realNode.read_config()

        if args.meta:
            model_list_name = os.path.join(model_list_path, args.meta)
        else:
            if realTest:
                model_list_name = os.path.join(model_list_path,
                                               'models_test.txt')
            else:
                model_list_name = os.path.join(model_list_path, 'real.txt')

    # For Gazebo hand data
    elif data_mode == GAZEBO_HAND_DATA:
        # Not setting testKDE to True, don't need it yet
        gazeboNode = TrianglesOnRobotToHists(sampling_subpath,
                                             csv_suffix='gz_')
        gazeboNode.read_config()

        if args.meta:
            model_list_name = os.path.join(model_list_path, args.meta)
        else:
            #model_list_name = os.path.join (model_list_path, 'models_active.txt')
            # Use models_test.txt, so don't need to uncomment and recomment btw
            #   running this file and sample_gazebo.py, because Gazebo can't handle
            #   more than one object file per run of sample_gazebo.py, because hand
            #   gets broken and can't be reloaded normally - -
            #model_list_name = os.path.join (model_list_path, 'models_active_test.txt')
            model_list_name = os.path.join(model_list_path,
                                           'models_gazebo_csv.txt')

    elif data_mode == MIXED_DATA:

        # Need to initialize both the TrianglesListsSyntheticPublisher AND
        #   the TrianglesOnRobotToHists nodes.

        # This should give the train/ directory. Used for prepending to long csv
        #   paths in meta file.
        train_path = tactile_config.config_paths('custom', '')

        # One node for PCL data
        pcdNode = TrianglesListsSyntheticPublisher(sampling_subpath, nSamples,
                                                   nSamplesRatio)
        pcdNode.read_config()

        # One node for Gazebo data
        #   Use the PCL data's hist_conf.csv, `.` training is done on PCL data,
        #     need Gazebo histograms to be in same range, for descriptors to make
        #     sense!
        hist_conf_path = os.path.join(train_path, 'triangle_sampling',
                                      'csv_hists', sampling_subpath,
                                      tri_nbins_subpath, 'hist_conf.csv')
        # Use the Gazebo histograms in gz_pclrange_ with PCL hist_conf.csv range,
        #   not the pure Gazebo data in csv_gz_hists using Gazebo hist_conf.csv
        #   range.
        gazeboNode = TrianglesOnRobotToHists(
            tri_nbins_subpath,
            csv_suffix='gz_pclrange_',
            custom_hist_conf_name=hist_conf_path)
        # This will read the custom hist_conf_name passed in to ctor above. That
        #   makes Gazebo histograms use same range as the PCL training data.
        gazeboNode.read_config(convert_to_decimeter=False)

        if args.meta:
            model_list_name = os.path.join(model_list_path, args.meta)
        else:
            # This meta file contains long csv paths starting from triangle_sampling
            model_list_name = os.path.join(model_list_path,
                                           'models_gazebo_pclrange_test.txt')
            # If you're lazy to uncomment everything in models_gazebo_csv.txt every
            #   time a new object is trained, just put one csv in a temp file and
            #   run this file on just one object...
            #model_list_name = os.path.join (model_list_path, 'tmp.txt')

    #####
    # Get triangle file names, pass to appropriate node
    #####

    model_list_file = open(model_list_name)

    nIters = 6

    nObjs = 0

    # Record number of triangles per object
    nTriangles = []

    # Seconds
    start_time = time.time()

    ended_immature = False

    # Read model list file line by line
    for line in model_list_file:

        if rospy.is_shutdown():
            break

        # Skip empty lines
        if not line:
            continue

        # Strip endline char
        # Some path, doesn't matter what. Nodes only need the base name. The data
        #   parent paths are defined within each node.
        line = line.rstrip()

        # Skip comment lines
        if line.startswith('#') or line == '':
            continue

        print('\n%s' % line)

        # Increment # objects seen
        nObjs += 1

        # Find base name
        base = os.path.basename(line)

        # Find category name
        #   Copied from triangle_sampling/parse_models_list.py
        # Drop base name
        cat_name, _ = os.path.split(line)
        # Grab last dir name. This won't contain a slash, guaranteed by split()
        _, cat_name = os.path.split(cat_name)

        if data_mode == PCD_CLOUD_DATA:
            # Synthetic model list files have .pcd extension. Chop it off, append .csv
            #   extension.
            csv_name = os.path.splitext(base)[0] + '.csv'

            # Get a ROS msg with triangles, publish them later
            nTriangles.append(pcdNode.read_triangles(csv_name, cat_name))

        elif data_mode == REAL_ROBOT_DATA:
            # Read the triangles, compute histogram, and save histogram file.
            #   This node doesn't care about full path being in real.txt, it
            #   just reads base name from csv_tri/ directory.
            # Real-robot collected list files already have the correct .csv extension
            nTriangles.append(realNode.read_triangles(base, cat_name))

        elif data_mode == GAZEBO_HAND_DATA:
            # Synthetic model list files have .dae extension. Chop it off, append .csv
            #   extension.
            #csv_name = os.path.splitext (base) [0] + '_hand.csv'
            # models_gazebo_csv.txt has long paths.
            csv_name = os.path.splitext(base)[0] + '.csv'

            # Read the triangles, compute histogram, and save histogram file.
            nTriangles.append(gazeboNode.read_triangles(csv_name, cat_name))

        elif data_mode == MIXED_DATA:

            # Use full path
            csv_name = os.path.join(train_path, line)

            # Find the csv directory in this line, to decide which instance to call
            if 'csv_tri' in line:
                # Get a ROS msg with triangles, publish them later
                nTriangles.append(
                    pcdNode.read_triangles(csv_name, full_path=True))

            elif 'csv_gz_tri' in line:
                # Read the triangles, compute histogram, and save histogram file.
                nTriangles.append(
                    gazeboNode.read_triangles(csv_name,
                                              cat_name,
                                              full_path=True))

            else:
                print ('%sDid not find csv_tri or csv_gz_tri keywords in this line of meta file. Did you specify the full path of csv files? Offending line: %s%s' % ( \
                  ansi_colors.WARNING, line, ansi_colors.ENDC))
                continue

        if data_mode == PCD_CLOUD_DATA or data_mode == MIXED_DATA:

            # If there are any triangles loaded
            if pcdNode.get_need_to_publish():

                seq = 0
                while not rospy.is_shutdown():

                    # Publish to sample_pcl_calc_hist.py, which will write
                    #   histograms to file.
                    pcdNode.publish()

                    seq += 1
                    if seq >= nIters:
                        break

                    try:
                        wait_rate.sleep()
                    except rospy.exceptions.ROSInterruptException, err:
                        ended_immature = True
                        break
Example #7
0
    # Write number of triangles to file
    #   Best place for this code to be is really
    #     sample_pcl_calc_hist.py, but since I'm not running
    #     sampling any time soon, that file will not have access to this.
    #     So putting it here for now, move there the next time I run
    #     sample_pcl.cpp
    #####

    # Don't record number of triangles if ended midway! You could overwrite
    #   existing good ones.
    if not ended_immature and not pcdTest and not realTest:

        if data_mode == PCD_CLOUD_DATA:
            # Split off the sampling_subpath directory
            stats_dir = tactile_config.config_paths(
                'custom',
                os.path.split(pcdNode.tri_subpath)[0] + '/_stats/')
            write_num_triangles(stats_dir, sampling_subpath, nTriangles)

        elif data_mode == REAL_ROBOT_DATA:
            # No sampling_subpath dir to split off
            stats_dir = tactile_config.config_paths(
                'custom', realNode.tri_subpath + '/_stats/')
            write_num_triangles(stats_dir, sampling_subpath, nTriangles)

        elif data_mode == GAZEBO_HAND_DATA:
            stats_dir = tactile_config.config_paths(
                'custom', gazeboNode.tri_subpath + '/_stats/')
            # No sampling_subpath necessary here. Number of triangles doesn't differ
            #   btw different triangle parameter choices or hist bin choices. Those
            #   are for histogram only and are further down the pipeline.
Example #8
0
def main():

    #####
    # User adjust parameter
    #####

    # Set to a high number when don't want to prune anything
    prune_thresh_l = 0.5

    # Bin configurations to test and get stats on
    # For quick testing
    #nbins_list = [10]  # ICRA configuration
    #nbins_list = [10, 12]
    #nbins_list = [8, 10, 12, 14]
    #nbins_list = [8, 10]
    #nbins_list = [6, 8, 10, 12, 14, 16]
    #nbins_list = [18, 20, 22, 24, 26]
    # IROS
    nbins_list = [6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26]

    # Pick triangle parameters
    # For quick testing
    #prs_list = [(HistP.L0, HistP.L1, HistP.A0)]  # ICRA configuration
    # IROS
    prs_list = [ \
      (HistP.A0, HistP.A1, HistP.L0),
      (HistP.A1, HistP.A2, HistP.L2),
      (HistP.L0, HistP.L1, HistP.A0),
      (HistP.L1, HistP.L2, HistP.A2),
      (HistP.L0, HistP.L1, HistP.L2)
    ]

    # Thorough test, all possibilities
    # 5 possible combinations for solving a triangle:
    #   1. Given 2 angles, and a side that's NOT btw the 2 angles
    #   2. Given 2 angles, and the side that IS btw the 2 angles
    #   3. Given 2 sides, and the angle btw the 2 sides
    #   4. Given 2 sides, and an angle NOT in btw the 2 sides
    #   5. Given 3 sides
    # 1 and 2 can be combined to this case: a# a# l#, because we don't record
    #   which side is btw which angles in training. That requires extra
    #   book-keeping, you'd have to say, always fix on the largest angle, side
    #   CCW is side 1, angle CCW is angle 2, etc. But in 3D, CW and CCW are
    #   arbitrary, it's not easy to define.
    #   We can alternate the parameters, e.g. a0 a1 l0, a0 a2 l0... that's a lot.
    # 3 and 4 can be combined to this case: l# l# a#.
    # 5 is its own case, l0 l1 l2.
    # So we have 3 general cases. For first two, rotate parameters.
    #   Order doesn't matter, because which one you use on which histogram
    #   dimension is arbitrary, and SVM doesn't care. So no permutation needed,
    #   only iterate on combination.
    # Ref https://www.mathsisfun.com/algebra/trig-solving-triangles.html
    '''
  prs_list = [ \
    # Case 1: a# a# l#
    (HistP.A0, HistP.A1, HistP.L0),
    (HistP.A0, HistP.A2, HistP.L0),
    (HistP.A1, HistP.A2, HistP.L0),
    #
    (HistP.A0, HistP.A1, HistP.L1),
    (HistP.A0, HistP.A2, HistP.L1),
    (HistP.A1, HistP.A2, HistP.L1),
    #
    (HistP.A0, HistP.A1, HistP.L2),
    (HistP.A0, HistP.A2, HistP.L2),
    (HistP.A1, HistP.A2, HistP.L2),
    # Case 2: l# l# a#
    (HistP.L0, HistP.L1, HistP.A0),  # ICRA paper case
    (HistP.L0, HistP.L2, HistP.A0),
    (HistP.L1, HistP.L2, HistP.A0),
    #
    (HistP.L0, HistP.L1, HistP.A1),
    (HistP.L0, HistP.L2, HistP.A1),
    (HistP.L1, HistP.L2, HistP.A1),
    #
    (HistP.L0, HistP.L1, HistP.A2),
    (HistP.L0, HistP.L2, HistP.A2),
    (HistP.L1, HistP.L2, HistP.A2),
    # Case 3: l0 l1 l2
    (HistP.L0, HistP.L1, HistP.L2)
  ]
  '''

    #####
    # Parse command line args
    #   Ref: Tutorial https://docs.python.org/2/howto/argparse.html
    #        Full API https://docs.python.org/dev/library/argparse.html
    #####

    arg_parser = argparse.ArgumentParser()

    arg_parser.add_argument('--pcd',
                            action='store_true',
                            default=False,
                            help='Run on PCL files')
    arg_parser.add_argument('--gazebo',
                            action='store_true',
                            default=False,
                            help='Run on Gazebo files')

    arg_parser.add_argument ('histSubdirParam1', type=str,
      help='Used to create directory name to read from.\n' + \
        'Only used for point cloud (--pcd mode). nSamples used when triangles were sampled.\n')
    arg_parser.add_argument ('histSubdirParam2', type=str,
      help='Used to create directory name to read from.\n' + \
        'Only used for point cloud (--pcd mode). nSamplesRatio used when triangles were sampled.\n')

    arg_parser.add_argument(
        '--meta',
        type=str,
        default='models_gazebo_csv.txt',
        help=
        'String. Base name of meta list file in triangle_sampling/config directory'
    )

    arg_parser.add_argument(
        '--use_existing_hists',
        action='store_true',
        default=False,
        help=
        'Specify this to skip generating files, useful if you already generated all pruned triangles and histograms in a previous run, and just want to test SVM on different files that are already on disk.'
    )
    arg_parser.add_argument(
        '--plot_existing_accs',
        action='store_true',
        default=False,
        help=
        'Specify this to plot directly the accuracies already stored in nbins_vs_acc_<triParams>.csv files. Nothing will be run, just plotting from a chart.'
    )
    arg_parser.add_argument(
        '--append_to_existing_accs',
        action='store_true',
        default=False,
        help=
        'Specify this to append accuracy results from SVM to existing nbins_vs_acc_*.csv files, instead of overwriting them. Choose this so that you can experiment with different parameters individually, then simply concatenate them together, instead of having to rerun every single one again.'
    )

    # Set to True to upload to ICRA. (You can't view the plot in OS X Preview)
    # Set to False if want to see the plot for debugging.
    arg_parser.add_argument(
        '--truetype',
        action='store_true',
        default=False,
        help=
        'Tell matplotlib to generate TrueType 42 font, instead of rasterized Type 3 font. Specify this flag for uploading to ICRA.'
    )
    arg_parser.add_argument(
        '--notitle',
        action='store_true',
        default=False,
        help=
        'Do not plot titles, for paper figures, description should all be in caption.'
    )

    arg_parser.add_argument(
        '--black_bg',
        action='store_true',
        default=False,
        help=
        'Boolean flag. Plot with black background, useful for black presentation slides.'
    )

    args = arg_parser.parse_args()

    if args.gazebo:
        hist_subpath = 'csv_gz_hists'

        sampling_subpath = ''

        hist_path = tactile_config.config_paths(
            'custom', os.path.join('triangle_sampling', hist_subpath))

        mode_suff = '_gz'

    elif args.pcd:

        hist_subpath = 'csv_hists'

        if not args.histSubdirParam1 or not args.histSubdirParam2:
            print ('%sIn --pcd mode, must specify histSubdirParam1 and histSubdirParam2. Check your args and retry. Terminating...%s' % ( \
            ansi_colors.FAIL, ansi_colors.ENDC))
            return

        nSamples = int(args.histSubdirParam1)
        nSamplesRatio = float(args.histSubdirParam2)
        sampling_subpath = get_sampling_subpath(nSamples,
                                                nSamplesRatio,
                                                endSlash=False)

        hist_path = tactile_config.config_paths(
            'custom',
            os.path.join('triangle_sampling', hist_subpath, sampling_subpath))

        mode_suff = '_pcd'

    use_existing_hists = args.use_existing_hists
    if args.use_existing_hists:
        print ('%suse_existing_hists flag set to %s, will skip generating files and just run SVM.%s' % ( \
          ansi_colors.OKCYAN, use_existing_hists, ansi_colors.ENDC))

    # For ICRA PDF font compliance. No Type 3 font (rasterized) allowed
    #   Ref: http://phyletica.org/matplotlib-fonts/
    # You can do this in code, or edit matplotlibrc. But problem with matplotlibrc
    #   is that it's permanent. When you export EPS using TrueType (42), Mac OS X
    #   cannot convert to PDF. So you won't be able to view the file you
    #   outputted! Better to do it in code therefore.
    #   >>> import matplotlib
    #   >>> print matplotlib.matplotlib_fname()
    #   Ref: http://matplotlib.1069221.n5.nabble.com/Location-matplotlibrc-file-on-my-Mac-td24960.html
    if args.truetype:
        matplotlib.rcParams['pdf.fonttype'] = 42
        matplotlib.rcParams['ps.fonttype'] = 42

    draw_title = not args.notitle

    #####
    # Run SVM for different histogram bin sizes
    #####

    if not args.plot_existing_accs:

        # Overwrite file in first iter, append to file in subsequent iters
        write_new_file = ''
        if not args.append_to_existing_accs:
            write_new_file = '--overwrite_stats'

        # Loop through each triangle parameters choice
        for p_i in range(0, len(prs_list)):

            prs_str = '%s,%s,%s' % prs_list[p_i]

            # Loop through each bin size choice
            for b_i in range(0, len(nbins_list)):

                nbins_str = '%d,%d,%d' % (nbins_list[b_i], nbins_list[b_i],
                                          nbins_list[b_i])

                if not use_existing_hists:

                    # subprocess.call() prints stuff to rospy.loginfo, AND returns.
                    # subprocess.call() waits till process finishes.
                    # subprocess.Popen() doesn't wait.
                    # Ref: https://docs.python.org/2/library/subprocess.html

                    #####
                    # rosrun triangle_sampling hist_conf_writer.py 0 0 --nbins #
                    #   This prunes triangles above a length threshold, saves the triangle
                    #     data after pruning, and saves hist_conf.csv file with min/max
                    #     ranges of triangles data for calculating histograms later.
                    #####

                    # Don't need to run for PCL data, all triangles should already be
                    #   generated - I currently do that manually by running
                    #   sample_pcl_calc_hist.py.
                    if args.gazebo:
                        #   Pass in --nbins, to write to hist_conf.csv file
                        prune_args = [
                            'rosrun', 'triangle_sampling',
                            'hist_conf_writer.py', '0', '0', '--no_prune',
                            '--long_csv_path', '--meta', args.meta, '--nbins',
                            nbins_str, '--prs', prs_str
                        ]

                        if args.gazebo:
                            prune_args.append('--gazebo')

                        # Not using this for prune.py right now. Not sure how to best
                        #   implement this feature. Prune would need to check every
                        #   triangle file exists, which is okay, but what about histogram
                        #   ranges? It'd have to take current hist_conf.csv's range, and
                        #   compare to any new triangle files that it does load! That's a
                        #   a mess! I rather just run the program! It only takes a few
                        #   seconds.
                        #if use_existing_hists:
                        #  prune_args.append ('--use_existing_hists')

                        p = subprocess.call(prune_args)

                    #####
                    # rosrun triangle_sampling triangles_reader.py 0 0 --gazebo
                    #   This reads pruned triangles data, calculates histograms, and saves
                    #     the histograms to file.
                    #####

                    # Don't need to run for PCL data, all triangles should already be
                    #   generated - I currently do that manually by running
                    #   sample_pcl_calc_hist.py.
                    if args.gazebo:
                        tri_reader_args = [
                            'rosrun', 'triangle_sampling',
                            'triangles_reader.py', prs_str, nbins_str
                        ]
                        tri_reader_args.append('--gazebo')

                        # Generate histograms from triangle files
                        p = subprocess.call(tri_reader_args)

                else:
                    print(
                        '%sSkipping prune_triangle_outliers.py and triangles_reader.py, because --use_existing_hists was specified. Make sure this is what you want! Any errors of missing files can be due to this flag, if you do not have existing files yet! Currently we do not automatically check for that, as this is a convenience feature up to user to decide to use.%s'
                        % (ansi_colors.OKCYAN, ansi_colors.ENDC))

                # end if not use_existing_hists

                #####
                # rosrun triangle_sampling triangles_svm.py 0 0 --rand_splits --meta
                #   models_active_test.txt --write_stats [--overwrite_stats] --gazebo
                #
                #   This loads the histograms data, which are object descriptors, and runs
                #     SVM classification. It saves nbins vs. accuracy data to stats.
                #####

                svm_args = [
                    'rosrun', 'triangle_sampling', 'triangles_svm.py',
                    '--rand_splits', '--truetype', '--notitle', '--meta',
                    args.meta, '--write_stats'
                ]

                # After first call to triangles_svm.py, which wrote a new file, next
                #   ones should append to the same file.
                if b_i == 0:
                    if write_new_file:
                        svm_args.append(write_new_file)

                if args.pcd:
                    svm_args.append('--pcd')
                    # These 4 args need to be together in this order, to be parsed
                    #   correctly
                    svm_args.append(args.histSubdirParam1)
                    svm_args.append(args.histSubdirParam2)
                    svm_args.append(prs_str)
                    svm_args.append(nbins_str)
                elif args.gazebo:
                    svm_args.append('--gazebo')
                    svm_args.append(prs_str)
                    svm_args.append(nbins_str)

                # Run SVM, write stats to a csv file
                p = subprocess.call(svm_args)

    # end if not args.plot_existing_accs

    #####
    # Load ALL accuracy vs nbins csv data, for all triangle parameter choices,
    #   plot each triangle parameter choice as a curve.
    #####

    # Init plotting colors

    # Can set higher numbers when have more things to plot!
    n_colors = len(prs_list)

    # colormap_name: string from
    #   http://matplotlib.org/examples/color/colormaps_reference.html
    colormap_name = 'jet'
    # This colormap pops out more brilliantly on black background
    if args.black_bg:
        #colormap_name = 'Set1'
        colormap_name = custom_colormap_neon()

    colormap = get_cmap(colormap_name, n_colors)

    # Generate image name. Only need one image, plot all
    #   triangle parameter choices in one graph, in different colors.
    stats_plot_path = get_img_path('nbins_triparams_acc')
    # Discard directory name, just want the base name from this
    stats_plot_base = get_nbins_acc_stats_name('', mode_suff, '')
    # Get base name, replace .csv extension with .eps
    stats_plot_base = os.path.splitext(
        os.path.basename(stats_plot_base))[0] + '.eps'
    stats_plot_name = os.path.join(stats_plot_path, stats_plot_base)

    #hdls = []

    # Loop through each triangle param
    for p_i in range(0, len(prs_list)):

        # Make sure arg after % is a tuple, not list. Can use tuple() to cast.
        prs_str = '%s,%s,%s' % prs_list[p_i]

        # Argument here needs to be in range [0, luc], where luc is the 2nd arg
        #   specified to get_cmap().
        color = colormap(p_i)

        #####
        # Load ONE accuracy vs nbins csv data, plot it.
        #####

        # file name e.g. <hist_path>/_stats/nbins_vs_acc_l0l1a0.csv
        b_stats_name = get_nbins_acc_stats_name(hist_path, mode_suff, prs_str)

        if not os.path.exists(b_stats_name):
            print('%sFile not found: %s. Has it been created?%s' %
                  (ansi_colors.FAIL, b_stats_name, ansi_colors.ENDC))
            return

        # Dictionary of (nbins, accuracy) key-value pair
        nbins_accs = {}

        print ('Loading average accuracy from one of the files generated: %s' % ( \
          b_stats_name))

        # Read csv stats
        with open(b_stats_name, 'rb') as b_stats_file:
            b_stats_reader = csv.DictReader(b_stats_file)

            for row in b_stats_reader:

                nbins0 = float(row[HistP.BINS3D_TITLES[0]])
                nbins1 = float(row[HistP.BINS3D_TITLES[1]])
                nbins2 = float(row[HistP.BINS3D_TITLES[2]])

                # Assumption: Use same number of bins for all dimensions, for generality
                assert ((nbins0 == nbins1) and (nbins1 == nbins2)
                        and (nbins2 == nbins0))

                if nbins0 not in nbins_accs.keys():
                    nbins_accs[nbins0] = np.zeros((0, ))
                nbins_accs[nbins0] = np.append(nbins_accs[nbins0],
                                               float(row['acc']))

        # List of floats. Average accuracies
        avg_accs = []
        stdevs = []

        # Sort bin counts, so can use them as x-axis values, in order
        nbins_loaded = np.sort(nbins_accs.keys())
        print('nbins  avg_acc  stdev')
        # Append accuracies, in the order of sorted bin counts
        for nbins in nbins_loaded:
            # Calc avg accuracy
            avg_accs.append(np.mean(nbins_accs[nbins]))
            # Calc stdev
            stdevs.append(np.std(nbins_accs[nbins]))

            print(
                ' %3d   %.4f   %.4f' %
                (nbins, avg_accs[len(avg_accs) - 1], stdevs[len(stdevs) - 1]))

        print('Average standard deviation across all data: %.4f' %
              (np.mean(stdevs)))

        # Plot
        title = ''
        if draw_title:
            title = 'Accuracy vs. Number of Histogram Bins',
        plot_line(nbins_loaded,
                  avg_accs,
                  title,
                  'Number of bins per 3D histogram dimension',
                  'Average accuracy over 100 random train-test splits',
                  stats_plot_name,
                  color,
                  prs_str,
                  stdev=stdevs,
                  do_save=False,
                  black_bg=args.black_bg)
        #hdls.append (hdl)

    # Ref: http://matplotlib.org/users/legend_guide.html
    legend = plt.legend(loc=4)
    # Set color to black bg, white text
    if args.black_bg:
        black_legend(legend)

    # Ref savefig() black background: https://stackoverflow.com/questions/4804005/matplotlib-figure-facecolor-background-color
    fig = plt.gcf()
    plt.savefig(stats_plot_name,
                bbox_inches='tight',
                facecolor=fig.get_facecolor(),
                edgecolor='none',
                transparent=True)
    print('Plot saved to %s' % stats_plot_name)
    plt.show()
def main ():

  #####
  # User adjust param
  #####

  # Index at which real robot data start. All data including and after this
  #   row will be held as test data. 0 based.
  idx_start_real_robot = 192
  #idx_start_real_robot = 7
 

  #####
  # Parse command line args
  #   Ref: Tutorial https://docs.python.org/2/howto/argparse.html
  #        Full API https://docs.python.org/dev/library/argparse.html
  #####

  arg_parser = argparse.ArgumentParser ()

  #arg_parser.add_argument ('nSamples', type=int,
  #  help='nSamples used when triangles were sampled, used to create directory name to read from.')
  #arg_parser.add_argument ('nSamplesRatio', type=float,
  #  help='nSamplesRatio used when triangles were sampled, used to create directory name to read from.')

  arg_parser.add_argument ('-k', type=int, default=5,
    help='Integer. k number of neighbors in kNN')

  # store_true is for boolean flags with no arguments
  arg_parser.add_argument ('--exhaustive', action='store_true', default=False,
    help='Boolean flag. Do kNN on ALL samples, so you get (n-1) x (n-1) distances printout. If True, overrides -k.')
  arg_parser.add_argument ('--exhaustive_ticks', action='store_true', default=False,
    help='Boolean flag. Draw confusion matrix with class names. Do not use this if you have many objects! Suitable for debugging small number of objects.')

  # I moved iros 2016 files into their own subdirectory, to keep them apart
  #   and safe from later work and wiping!
  arg_parser.add_argument ('--iros2016', action='store_true', default=False,
    help='Boolean flag. Load files in iros2016 subdirectory, to generate Fig 8 in IROS 2016 paper.')
  arg_parser.add_argument ('--black_bg', action='store_true', default=False,
    help='Boolean flag. Plot with black background, useful for black presentation slides.')

  args, valid = parse_args_for_svm (arg_parser)
  if not valid:
    return


  #####
  # Parse args for each mode
  #####

  sampling_subpath = ''

  if args.pcd:

    sampling_subpath, tri_nbins_subpath, hist_parent_path, hist_path, \
      img_mode_suffix, tri_suffix, _ = \
      config_hist_paths_from_args (args)

  elif args.real:

    _, tri_nbins_subpath, hist_parent_path, hist_path, \
      img_mode_suffix, tri_suffix, _ = \
        config_hist_paths_from_args (args)


  metafile_name = args.meta

  exhaustive = args.exhaustive
  if exhaustive:
    # Fill in later
    knn_k = -1
  else:
    knn_k = args.k

  exhaustive_ticks = args.exhaustive_ticks

  doKDE = args.kde
  img_kde_suffix = ''
  if doKDE:
    img_kde_suffix = '_kde'

  isSynthetic = not args.real

  rand_splits = args.rand_splits

  # For ICRA PDF font compliance. No Type 3 font (rasterized) allowed
  #   Ref: http://phyletica.org/matplotlib-fonts/
  # You can do this in code, or edit matplotlibrc. But problem with matplotlibrc
  #   is that it's permanent. When you export EPS using TrueType (42), Mac OS X
  #   cannot convert to PDF. So you won't be able to view the file you
  #   outputted! Better to do it in code therefore.
  #   >>> import matplotlib
  #   >>> print matplotlib.matplotlib_fname()
  #   Ref: http://matplotlib.1069221.n5.nabble.com/Location-matplotlibrc-file-on-my-Mac-td24960.html
  if args.truetype:
    matplotlib.rcParams['pdf.fonttype'] = 42
    matplotlib.rcParams['ps.fonttype'] = 42

  draw_title = not args.notitle

  if args.black_bg:
    bg_color = 'black'
  else:
    bg_color = 'white'


  print ('%sSettings: knn_k = %d (-1 if exhaustive, will be set later). Exhaustive = %s. Meta file = %s. Do KDE = %s. isSynthetic = %s. rand_splits = %s. TrueType font = %s. Plot titles = %s %s' % \
    (ansi_colors.OKCYAN, knn_k, exhaustive, metafile_name, doKDE, isSynthetic,
     rand_splits, args.truetype, draw_title, ansi_colors.ENDC))


  #####
  # Init params
  #####

  # These are same as sample_pcl.cpp
  rospack = rospkg.RosPack ()
  pkg_path = rospack.get_path ('triangle_sampling')
  model_metafile_path = os.path.join (pkg_path, 'config/', metafile_name)

  if doKDE:
    hist_subpath = 'csv_kde'
  else:
    if args.pcd:
      hist_subpath = 'csv_hists'
    elif args.real:
      hist_subpath = 'csv_bx_hists'
    # Haven't tested any other cases
    else:
      return

  img_path = tactile_config.config_paths ('custom',
    'triangle_sampling/imgs/conf_mats/')



  #####
  # Read inputs
  #####

  print ('Loading descriptor data from %s' % hist_path)
  [samples, lbls, catnames, _, catids, _] = load_hists ( \
    model_metafile_path, hist_path, tri_suffix=tri_suffix,
    mixed_paths=False, sampling_subpath=sampling_subpath,
    tri_nbins_subpath=tri_nbins_subpath)

  print ('Dimensions of data (d, in n x d):')
  print (np.shape (samples))

  nSamples = np.shape (samples) [0]
  nBins = np.shape (samples) [1]

  print ('%d samples, %d bins' % (nSamples, nBins))
  print ('Truths in all data (train and test)')

  # Print out the class name for each object, for seeing what the category of
  #   a closest neighor is.
  for i in range (0, len (lbls)):
    # Make sure you don't have custom_catids, if you do, then thsi will need
    #   to change. The index will not simply be lbls[i],
    #   need to map from custom_catids back to the 0:n non-custom catids
    #   somehow..
    print ('%d: %s' % (i, catnames [lbls [i]]))

  if isSynthetic:
    # For synthetic data, no data is held out from splitting
    idx_start_real_robot = nSamples
  # As of Jul 2016, we don't do train-PCL test-real-robot anymore, so no 
  #   holding out data. Run on real.txt, not models_and_real.txt.
  else:
    idx_start_real_robot = 0


  #####
  # Run classification
  #####

  img_suff = img_mode_suffix + img_kde_suffix
  if sampling_subpath:
    img_suff += ('_' + sampling_subpath)
  if tri_nbins_subpath: 
    img_suff += ('_' + tri_nbins_subpath)


  calc_knn_dists (samples, lbls, catnames, knn_k, img_path, img_suff, draw_title,
    metric=chisqr_dist, #inner_prod_dist, #l2_dist, #hist_inter_dist, #None,
    print_nn=True, exhaustive=exhaustive, exhaustive_ticks=exhaustive_ticks,
    bg_color=bg_color)


  # Classify using knn. This is good for nClasses x nClasses confusion matrix
  #   plots. Temperature color indicates number of samples classified correct.
  #   Hot = more correct, cold = fewer correct.

  # Run once, no random
  if not rand_splits:
    n_random_splits = 1
    # Specify a fixed number, so no randomness
    random_state = 0

  # Run 10 times, random splits, get average accuracy
  else:
    n_random_splits = 100
    # Specify None, so random each call
    # Ref: http://stackoverflow.com/questions/28064634/random-state-pseudo-random-numberin-scikit-learn
    random_state = None

  accs = []


  # Don't run classification with exhaustive... might kill my computer
  if not exhaustive:

    for i in range (0, n_random_splits):

      idx_test, lbls_test, lbls_pred = knn_classify (samples, lbls, knn_k,
        idx_start_real_robot, random_state=random_state)
    
      print ('Truths and Predictions for test data:')
      for i in range (0, len (idx_test)):
        print ('%d (truth %s): predicted %s' % (idx_test [i],
          catnames [lbls_test [i]], catnames [lbls_pred [i]]))
  
      # Calculate accuracy
      accs.append (calc_accuracy (lbls_test, lbls_pred, len(catids)))
  
    # Draw confusion matrix (if there are multiple random splits, this is for
    #   the very last run)
    img_name_cl = os.path.join (img_path, 'nn_per_class' + img_kde_suffix + '_' + \
      sampling_subpath + '.eps')
    draw_confusion_matrix (lbls_test, lbls_pred, catnames, img_name=img_name_cl,
      title_prefix='NN ', draw_title=draw_title, bg_color=bg_color)

    if rand_splits:
      print ('%sAverage accuracy over %d runs of random splits: %f %s' % \
        (ansi_colors.OKCYAN, n_random_splits, np.mean (np.asarray (accs)),
        ansi_colors.ENDC))
def main():

    #####
    # Parse command line args
    #   Ref: Tutorial https://docs.python.org/2/howto/argparse.html
    #        Full API https://docs.python.org/dev/library/argparse.html
    #####

    arg_parser = argparse.ArgumentParser()

    arg_parser.add_argument(
        '--meta',
        type=str,
        default='models_test.txt',
        help=
        'String. Base name of meta list file in triangle_sampling/config directory. For this script, we suggest putting only one file in models.txt (or whatever meta file you supply with --meta), because of video size, and you probably only need one video.'
    )

    arg_parser.add_argument(
        'nSamples',
        type=int,
        help=
        'nSamples used when triangles were sampled, used to create directory name to read from.'
    )
    arg_parser.add_argument(
        'nSamplesRatio',
        type=float,
        help=
        'nSamplesRatio used when triangles were sampled, used to create directory name to read from.'
    )

    args = arg_parser.parse_args()

    metafile_name = args.meta

    #####
    # Load histogram
    #####

    # Copied from triangles_nn.py
    rospack = rospkg.RosPack()
    pkg_path = rospack.get_path('triangle_sampling')
    model_metafile_path = os.path.join(pkg_path, 'config/', metafile_name)

    print ('%sAccessing directory with nSamples %d, nSamplesRatio %f%s' % \
      (ansi_colors.OKCYAN, args.nSamples, args.nSamplesRatio, ansi_colors.ENDC))
    sampling_subpath = get_sampling_subpath(args.nSamples,
                                            args.nSamplesRatio,
                                            endSlash=False)

    # Input and output path
    # This is same as sample_pcl_calc_hist.py
    hist_subpath = 'csv_hists'
    hist_path = tactile_config.config_paths(
        'custom',
        os.path.join('triangle_sampling', hist_subpath, sampling_subpath) +
        '/')

    hist_conf_name = os.path.join(hist_path, 'hist_conf.csv')

    # Read histogram config file
    (param_names, nbins, bin_range) = read_hist_config(hist_conf_name)

    # Load all histograms in meta list
    print('Loading descriptor data from %s' % hist_path)
    [hists, lbls, catnames, _, catids, sample_names] = load_hists ( \
      model_metafile_path, hist_path)

    sample_bases = [os.path.basename(n) for n in sample_names]

    #####
    # Plot 2D slices
    #####

    slices_path = tactile_config.config_paths(
        'custom',
        os.path.join('triangle_sampling/imgs/slices', sampling_subpath) + '/')

    # Pick a dimension, 0, 1, or 2
    pick_dim = 2

    nSamples = np.shape(hists)[0]

    # Turn off interaction
    plt.ioff()

    # Ref no border: http://stackoverflow.com/questions/8218608/scipy-savefig-without-frames-axes-only-content/8218887#8218887
    fig = plt.figure(frameon=False)
    #ax = plt.Axes (fig, [0, 0, 1, 1])

    videoNode = MakeMovie()

    # For each histogram
    for i in range(0, nSamples):

        # Convert linear histogram to 3D
        hist_3d = flat_to_3d_hist(hists[i, :], nbins)

        # Loop through all slices of the picked dimension
        for s in range(0, np.shape(hist_3d)[pick_dim]):

            print('Slice %d' % s)

            # Copied from write_hist_3d.py

            if pick_dim == 0:
                curr_slice = hist_3d[s, :, :]
                other_dim1 = 1
                other_dim2 = 2
            elif pick_dim == 1:
                curr_slice = hist_3d[:, s, :]
                other_dim1 = 0
                other_dim2 = 2
            elif pick_dim == 2:
                curr_slice = hist_3d[:, :, s]
                other_dim1 = 0
                other_dim2 = 1

            ax = plt.gca()
            ax.set_xlabel(param_names[other_dim1])
            ax.set_ylabel(param_names[other_dim2])

            # Set ticks to bin centers

            ax.set_xticklabels ([format ('%.1f' % bi) for bi in \
              np.linspace (bin_range [other_dim1] [0],
                bin_range [other_dim1] [1], nbins [other_dim1])], rotation='vertical')
            ax.set_yticklabels ([format ('%.1f' % bi) for bi in \
              np.linspace (bin_range [other_dim2] [0],
                bin_range [other_dim2] [1], nbins [other_dim2])])

            plt.xticks(np.arange(0, np.shape(curr_slice)[0], 1.0))
            plt.yticks(np.arange(0, np.shape(curr_slice)[1], 1.0))

            #ax.set_axis_off ()
            #fig.add_axes (ax)

            #ax.get_xaxis ().set_visible (False)
            #ax.get_yaxis ().set_visible (False)

            im = plt.imshow(curr_slice, interpolation='nearest')

            fig.savefig(os.path.join(slices_path, 'tmp_slice.eps'),
                        bbox_inches='tight',
                        pad_inches=0)

            plt.clf()

            # Load the saved image, create one frame of video out of the image
            _ = videoNode.load_eps_to_numpy(
                os.path.join(slices_path, 'tmp_slice.eps'))

        # Save the image sequence to video file
        create_video_from_numpy_list (videoNode.imgs, videoNode.max_h, videoNode.max_w,
          os.path.join (slices_path, os.path.splitext (sample_bases[i]) [0] + '_' + \
          param_names[pick_dim] + '.mp4'), frame_duration=0.5)

        videoNode.clear_sequence()
Example #11
0
      # Convert density vector to 3D first, `.` currently in a flattened 1D
      #   array, becaues that's what score_samples() returns.
      #   score_samples() only deals with (n x d) data, where d is dimension of
      #   histogram, n is the total number of counts in all bins. Data it takes
      #   are bin positions in histogram. It does not deal with 3D histogram
      #   directly.

      density_3d = flat_to_3d_hist (density, [xbins * factor_granular,
        ybins * factor_granular, zbins * factor_granular])


      # ['l0', 'l1', 'a0']
      param_names = prs

      out_path = tactile_config.config_paths ('custom',
        'triangle_sampling/imgs/kde/')


      #####
      # Plot 2D slice of 3D histogram, as heat map
      #####

      # Turn off interactive mode, so you don't have to close every single
      #   window. It's faster. Just look at the images once saved.
      plt.ioff ()

      fig = plt.figure ()

      # These correspond to indices 0 1 2, of selected dimension
      other_dim = [[1,2], [0,2], [0,1]]
def main():

    #####
    # Parse command line arguments
    #####

    arg_parser = argparse.ArgumentParser()

    arg_parser.add_argument ('histSubdirParam1', type=str,
      help='Used to create directory name to read from.\n' + \
        'For point cloud, nSamples used when triangles were sampled.\n' + \
        'For real robot data, specify the sampling density you want to classify real objects with, e.g. 10, will be used to load histogram bin configs.\n' + \
        'For Gazebo, triangle params desired, with no spaces, e.g. l0,l1,a0\n' + \
        'For mixed, enter 2 point cloud params first, then 2 Gazebo params, 4 total')
    arg_parser.add_argument ('histSubdirParam2', type=str, nargs='+',
      help='Used to create directory name to read from.\n' + \
        'For point cloud, nSamplesRatio used when triangles were sampled.\n' + \
        'For real robot data, specify the sampling density you want to classify real objects with, e.g. 0.95, will be used to load histogram bin configs.\n' + \
        'For Gazebo, number of bins in 3D histogram, with no spaces, e.g. 10,10,10\n' + \
        'For mixed, enter 2 point cloud params first, then 2 Gazebo params, 4 total.')

    # Ref: Boolean (Ctrl+F "flag") https://docs.python.org/2/howto/argparse.html
    arg_parser.add_argument(
        '--pcd',
        action='store_true',
        default=False,
        help='Boolean flag, no args. Run on point cloud data in csv_tri_lists/'
    )
    arg_parser.add_argument(
        '--real',
        action='store_true',
        default=False,
        help='Boolean flag, no args. Run on real robot data in csv_tri/')
    arg_parser.add_argument(
        '--gazebo',
        action='store_true',
        default=False,
        help='Boolean flag, no args. Run on Gazebo data in csv_gz_tri/')

    arg_parser.add_argument(
        '--multi',
        action='store_true',
        default=False,
        help=
        'Plot overlapped number of moves vs number of contacts per move, for many sampling densities (adjust these in code). nSamples and nSamplesRatio are ignored if this flag is specified.'
    )

    arg_parser.add_argument(
        '--truetype',
        action='store_true',
        default=False,
        help=
        'Tell matplotlib to generate TrueType 42 font, instead of rasterized Type 3 font. Specify this flag for uploading to ICRA.'
    )
    arg_parser.add_argument(
        '--notitle',
        action='store_true',
        default=False,
        help=
        'Do not plot titles, for paper figures, description should all be in caption.'
    )

    args = arg_parser.parse_args()

    if args.pcd & args.real & args.gazebo == 1 or \
      args.pcd | args.real | args.gazebo == 0:
        print ('%sYou must choose ONE of --pcd, --real, and --gazebo. Check your args and try again. Terminating...%s' % ( \
          ansi_colors.FAIL, ansi_colors.ENDC))
        return

    print ('%sPlot multi set to %s %s' % \
      (ansi_colors.OKCYAN, args.multi, ansi_colors.ENDC))

    # Set to True to upload to ICRA. (You can't view the plot in OS X)
    # Set to False if want to see the plot for debugging.

    # For ICRA PDF font compliance. No Type 3 font (rasterized) allowed
    #   Ref: http://phyletica.org/matplotlib-fonts/
    # You can do this in code, or edit matplotlibrc. But problem with matplotlibrc
    #   is that it's permanent. When you export EPS using TrueType (42), Mac OS X
    #   cannot convert to PDF. So you won't be able to view the file you
    #   outputted! Better to do it in code therefore.
    #   >>> import matplotlib
    #   >>> print matplotlib.matplotlib_fname()
    #   Ref: http://matplotlib.1069221.n5.nabble.com/Location-matplotlibrc-file-on-my-Mac-td24960.html
    if args.truetype:
        matplotlib.rcParams['pdf.fonttype'] = 42
        matplotlib.rcParams['ps.fonttype'] = 42

    draw_title = not args.notitle

    #####
    # Set input path
    #####

    if args.pcd:
        # Synthetic 3D model data, saved from running sample_pcl.cpp and
        #   sample_pcl_calc_hist.py
        tri_path = tactile_config.config_paths(
            'custom', 'triangle_sampling/csv_tri_lists/_stats/')
    elif args.real:
        # Real-robot tactile data, collected by triangles_collect.py
        tri_path = tactile_config.config_paths(
            'custom', 'triangle_sampling/csv_tri/_stats/')
    elif args.gazebo:
        tri_path = tactile_config.config_paths(
            'custom', 'triangle_sampling/csv_gz_tri/_stats/')

    thisNode = TrianglesStats(tri_path, draw_title)

    if args.multi:
        # Gazebo doesn't have this mode, I never needed it.
        if args.pcd or args.real:

            nSamples = [10, 25, 50, 100, 150, 200, 250, 300]
            #nSamples = [10, 25]#, 50, 100, 150, 200, 250, 300]
            nSamplesRatio = [0.95]

            tri_suffixes = []
            for s in range(0, len(nSamples)):
                tri_suffixes.append([])
                for r in range(0, len(nSamplesRatio)):
                    tri_suffixes[s].append('_' + get_sampling_subpath(
                        nSamples, nSamplesRatio, endSlash=False))

            # Ref: matplotlib.org/examples/lines_bars_and_markers/line_styles_reference.html
            #   matplotlib.org/1.3.1/examples/pylab_examples/line_styles.html
            #linestyles = ['-', '--', '-.', ':', '-', '--', '-.', ':']
            linestyles = None

            # Ref: matplotlib.org/api/markers_api.html MarkerStyle()
            #   matplotlib.org/api/markers_api.html
            markerstyles = ['o', 'd', '^', 'D', 's', '*', '<', 'v']
            #markerstyles = MarkerStyle.filled_markers

            thisNode.plot_multi(nSamples, nSamplesRatio, tri_suffixes,
                                linestyles, markerstyles)

    else:
        if args.pcd or args.real:
            tri_suffix = '_' + get_sampling_subpath(
                nSamples, nSamplesRatio, endSlash=False)
            thisNode.plot_single(tri_suffix, plot_nMoves=True)

        elif args.gazebo:
            # No sampling rate in Gazebo. Triangle params and nbins don't affect
            #   number of triangles either, they are more downstream parameters,
            #   after triangles have been computed. They're for the histograms.
            thisNode.plot_single('', plot_nMoves=False)
def concat(args):

    # If no args specified, use defaults
    if not args:
        metafile_name = 'models.txt'
        out_name = 'hists_all.csv'

    # If at least one arg specified, use first arg as custom meta file name
    else:
        # Take the first string in list ONLY
        metafile_name = args[0]

        # Use the model file name, swap extension to .csv, for big csv file
        out_name = os.path.splitext(metafile_name)[0] + '.csv'

    # If more than 1 args are specified, take the remaining as custom cateogry
    #   IDs.
    custom_catids = False
    catids = list()
    if len(args) > 1:

        custom_catids = True

        # Take the remaining args as custom class IDs. Convert strings to ints
        catids = [int(i) for i in args[1:]]

    print('User specified %d custom category IDs:' % len(catids))
    print(catids)
    print('')

    # Input and output path
    # This is same as sample_pcl_calc_hist.py
    hist_path = tactile_config.config_paths('custom',
                                            'triangle_sampling/csv_hists/')
    #'triangle_sampling/csv_hists_1d/')

    # These are same as sample_pcl.cpp
    rospack = rospkg.RosPack()
    pkg_path = rospack.get_path('triangle_sampling')
    model_metafile_path = os.path.join(pkg_path, 'config/', metafile_name)

    # To hold names of all categories.
    # Indices to this list are category IDs to be put in new big .csv file
    catlist = list()
    catcounts = list()

    # Open output big .csv file
    out_path = os.path.join(hist_path, out_name)
    out_file = open(out_path, 'wb')
    out_writer = csv.writer(out_file)

    print ('Inputting individual histogram object names and class strings ' + \
      'from %s' % model_metafile_path)
    print('')
    print ('Outputting all histograms, with class labels prepended, to %s' %\
      out_path)
    print('')

    # Read meta list file line by line
    with open(model_metafile_path, 'rb') as metafile:

        # http://stackoverflow.com/questions/15599639/whats-perfect-counterpart-in-python-for-while-not-eof
        #while True:
        #  line = metafile.readline ()
        #  if not line:
        #    break

        for line in metafile:

            # Parse line in file, for base name and category info
            parse_result = parse_meta_one_line(line, catlist, catcounts,
                                               catids, custom_catids)
            if not parse_result:
                continue

            basename = parse_result[0]
            cat_idx = parse_result[1]

            # Sanity check
            if len(catids) < len(catlist):
                print(
                    'concat_hists_to_train.py ERROR: Not enough custom category IDs specified! Saw more category names (%d) than custom category IDs (%d). Specify more category IDs and rerun.'
                    % (len(catlist), len(catids)))
                break

            # Open individual object's histogram file
            with open(os.path.join(hist_path, basename), 'rb') as hist_file:

                # Read csv file
                hist_reader = csv.reader(hist_file)

                # There's only 1 row per file, the whole histogram flattened
                row = hist_reader.next()

                # Prepend object class ID to front of list
                row.insert(0, catids[cat_idx])

                # Write to big .csv file
                out_writer.writerow(row)

    out_file.close()

    for i in range(0, len(catlist)):
        print('Object class %d: %15s (%d)' %
              (catids[i], catlist[i], catcounts[i]))
    print('Total %d objects' % sum(catcounts))
def main ():

  arg_parser = argparse.ArgumentParser ()

  # Set to True to upload to ICRA. (You can't view the plot in OS X)
  # Set to False if want to see the plot for debugging.
  arg_parser.add_argument ('--truetype', action='store_true', default=False,
    help='Tell matplotlib to generate TrueType 42 font, instead of rasterized Type 3 font. Specify this flag for uploading to ICRA.')
  arg_parser.add_argument ('--notitle', action='store_true', default=False,
    help='Do not plot titles, for paper figures, description should all be in caption.')

  args = arg_parser.parse_args ()


  # For ICRA PDF font compliance. No Type 3 font (rasterized) allowed
  #   Ref: http://phyletica.org/matplotlib-fonts/
  # You can do this in code, or edit matplotlibrc. But problem with matplotlibrc
  #   is that it's permanent. When you export EPS using TrueType (42), Mac OS X
  #   cannot convert to PDF. So you won't be able to view the file you
  #   outputted! Better to do it in code therefore.
  #   >>> import matplotlib
  #   >>> print matplotlib.matplotlib_fname()
  #   Ref: http://matplotlib.1069221.n5.nabble.com/Location-matplotlibrc-file-on-my-Mac-td24960.html
  if args.truetype:
    matplotlib.rcParams['pdf.fonttype'] = 42
    matplotlib.rcParams['ps.fonttype'] = 42

  draw_title = not args.notitle


  #####
  # Define values. Get these from Google Spreadsheet
  #####

  nSamples = [300, 250, 200, 150, 100, 50, 25, 10]
  nSamplesRatio = 0.95

  '''
  out_of = 96

  nn_correct = np.array ([78, 75, 74, 77, 74, 71, 71, 67])
  nn_acc = nn_correct / float (out_of)

  svm_correct = np.array ([80, 82, 82, 83, 81, 73, 78, 68])
  svm_acc = svm_correct / float (out_of)
  '''

  # 100 random split average
  nn_acc = np.array ([0.781667, 0.777396, 0.764583, 0.774583, 0.739896, 0.728333, 0.719375, 0.635625])

  svm_acc = np.array ([0.836354, 0.841458, 0.850938, 0.847812, 0.841458, 0.822604, 0.796771, 0.700625])

  # For plot title
  bins = [10, 10, 10]

  acc_path = tactile_config.config_paths ('custom',
    'triangle_sampling/imgs/acc/')


  #####
  # Init plotting colors
  #####

  # Can set higher numbers when have more things to plot!
  n_colors = 2

  # colormap_name: string from
  #   http://matplotlib.org/examples/color/colormaps_reference.html
  colormap_name = 'PiYG'

  # 11 for max # sensors on any segment of hand (palm has 11)
  colormap = get_cmap (colormap_name, n_colors)

  # Argument here needs to be in range [0, luc], where luc is the 2nd arg
  #   specified to get_cmap().
  #color = colormap (color_idx)



  #####
  # Plot
  #####

  plt.plot (nSamples, nn_acc, 'o', markersize=5,
    markeredgewidth=0, color=colormap(0))
  nn_hdl, = plt.plot (nSamples, nn_acc, '--', color=colormap(0), linewidth=2,
    label='Euclidean 5NN')

  plt.plot (nSamples, svm_acc, 'o', markersize=5,
    markeredgewidth=0, color=colormap(1))
  svm_hdl, = plt.plot (nSamples, svm_acc, color=colormap(1), linewidth=2,
    label='Linear SVM')


  # Ref: http://matplotlib.org/users/legend_guide.html
  plt.legend (handles=[nn_hdl, svm_hdl], loc=4)

  plt.grid (True)

  if draw_title:
    plt.title ('Accuracy vs. Samples per 20 cm Local Neighborhood, \nfor 3D Histograms with Bins %d %d %d' % (bins[0], bins[1], bins[2]))

  # 20 cm is the sampling sphere diameter. Radius is defined in
  #   sample_pcl.cpp. Mult 2 to get diameter.
  plt.xlabel ('Samples per 20 cm Local Neighborhood')
  plt.ylabel ('Accuracy')


  # Save to file
  out_name = os.path.join (acc_path, 'acc.eps')
  plt.savefig (out_name, bbox_inches='tight')
  print ('Plot saved to %s' % out_name)

  plt.show ()
Example #15
0
def main():

    #####
    # User adjust param
    #####

    # Used for train_sim_test_real=True mode only
    #   Otherwise, this is reset to the number of objects in meta file.
    # Index at which real robot data start. All data including and after this
    #   row will be held as test data. 0 based.
    idx_start_real_robot = 192

    # Show confusion matrix at the end. Disable this if you are running prune.py
    #   to generate accuracy plots from many different histogram bin and
    #   triangle param choices.
    show_plot_at_end = True  #False

    # Debug tools flags

    # Show confusion matrix after every random split, for each of the 100 splits!
    #   Useful for detailed debugging.
    show_plot_every_split = False

    # Enable to enter test sample index to see 1d histogram intersectin plots
    interactive_debug = False

    experimental_1dhists = True
    debug_print_all_labels = False

    # Print all histogram intersection distances
    debug_draw_nn_dists = False

    # To find the index of a specific object you are interested in seeing the
    #   histogram of. If it is a test object, it will print in cyan. Then you
    #   can use the interactive_debug=True mode to enter the index and see the
    #   3 flattened 1D histograms - they are saved to file too.
    debug_find_specific_obj = False
    specific_obj_startswith = '6ed884'

    # Set true_cat and pred_cat below, to find objects of a specific
    #   category that are predicted as another specific category. It will print
    #   in cyan.
    debug_find_specific_misclass = False
    specific_lbl = 'hammer'
    specific_wrong_lbl = 'bottle'

    # Only used for active sensing monte carlo tree search, in active_touch
    #   package. Only set to True when you are working with active_touch and are
    #   sure you want to retrain the model.
    # Set this flag to False at other times, so you don't mistakenly overwrite
    #   model file!! Model file is not committed to repo!
    # This will train SVM with probability=True, in order to output probs at
    #   test time.
    save_svm_to_file = True

    #####
    # Parse command line args
    #####

    arg_parser = argparse.ArgumentParser()

    arg_parser.add_argument(
        '--black_bg',
        action='store_true',
        default=False,
        help=
        'Boolean flag. Plot with black background, useful for black presentation slides.'
    )

    args, valid = parse_args_for_svm(arg_parser)
    if not valid:
        return

    if args.black_bg:
        bg_color = 'black'
    else:
        bg_color = 'white'

    #####
    # Parse args for each mode
    #####

    img_mode_suffix = ''

    doKDE = args.kde
    img_kde_suffix = ''
    if doKDE:
        img_kde_suffix = '_kde'

    # Sampling density for PCL
    sampling_subpath = ''
    # Triangle parameters choice, number of histogram bins choice
    tri_nbins_subpath = ''

    meta_train_test_base = ''
    meta_train_base = ''
    meta_test_base = ''

    if args.pcd:

        # Sanity check
        if len(args.histSubdirParam2) != 3:
            print ('%sERROR: Expect histSubdirParam2 to have three elements, for --pcd mode. Check your args and retry.%s' % ( \
              ansi_colors.FAIL, ansi_colors.ENDC))
            return

        sampling_subpath, tri_nbins_subpath, hist_parent_path, hist_path, \
          img_mode_suffix, tri_suffix, _ = \
            config_hist_paths_from_args (args)

        mixed_paths = False

        # One meta file. Let scikit-learn split into train and test randomly
        one_meta_train_test = True
        meta_train_test_base = args.meta

    elif args.gazebo or args.real:

        # Sanity check
        if len(args.histSubdirParam2) != 1:
            print ('%sERROR: Expect histSubdirParam2 to only have one element, for --gazebo mode. Check your args and retry.%s' % ( \
              ansi_colors.FAIL, ansi_colors.ENDC))
            return

        _, tri_nbins_subpath, hist_parent_path, hist_path, \
          img_mode_suffix, tri_suffix, _ = \
            config_hist_paths_from_args (args)

        mixed_paths = False

        # One meta file. Let scikit-learn split into train and test randomly
        one_meta_train_test = True
        meta_train_test_base = args.meta

    # For mixed data, need to specify both kinds of histSubdirParams, a pair for
    #   PCL data's sampling_subpath, a pair for Gazebo data's tri_nbins_subpath.
    elif args.mixed:

        # Sanity check
        if len(args.histSubdirParam2) != 3:
            print ('%sERROR: Expect histSubdirParam2 to have three elements, for --mixed mode. Check your args and retry.%s' % ( \
              ansi_colors.FAIL, ansi_colors.ENDC))
            return

        sampling_subpath, tri_nbins_subpath, hist_parent_path, hist_path, \
          img_mode_suffix, tri_suffix, _ = \
            config_hist_paths_from_args (args)
        #print ('hist_path: %s' % hist_path)
        #print ('  sampling_subpath: %s' % sampling_subpath)

        mixed_paths = True
        train_path = tactile_config.config_paths('custom', '')

        # Two meta files, for fixed train and test sets. Don't let scikit-learn
        #   split the sets.
        one_meta_train_test = False
        meta_train_base = args.meta_train
        meta_test_base = args.meta_test

    # end if args.pcd or args.real

    train_sim_test_real = False

    # In mixed mode, train on PCL data, test on Gazebo data
    if args.mixed:
        rand_splits = False
        print ('%sIn mixed mode. rand_splits automatically set to FALSE.%s' % (\
          ansi_colors.OKCYAN, ansi_colors.ENDC))
    else:
        rand_splits = args.rand_splits

    # For ICRA PDF font compliance. No Type 3 font (rasterized) allowed
    #   Ref: http://phyletica.org/matplotlib-fonts/
    # You can do this in code, or edit matplotlibrc. But problem with matplotlibrc
    #   is that it's permanent. When you export EPS using TrueType (42), Mac OS X
    #   cannot convert to PDF. So you won't be able to view the file you
    #   outputted! Better to do it in code therefore.
    #   >>> import matplotlib
    #   >>> print matplotlib.matplotlib_fname()
    #   Ref: http://matplotlib.1069221.n5.nabble.com/Location-matplotlibrc-file-on-my-Mac-td24960.html
    if args.truetype:
        matplotlib.rcParams['pdf.fonttype'] = 42
        matplotlib.rcParams['ps.fonttype'] = 42

    draw_title = not args.notitle

    # For writing average accuracy at the end to a stats file
    #   Used by stats_hist_num_bins.py
    write_stats = args.write_stats
    overwrite_stats = args.overwrite_stats
    # If not even writing stats, overwrite doesn't make sense. Don't overwrite
    if overwrite_stats and not write_stats:
        print ('%s--overwrite_stats was specified, without specifying write_stats. That does not make sense, automatically setting overwrite_stats to False.%s' % (\
        ansi_colors.WARNING, ansi_colors.ENDC))

        overwrite_stats = False

    print ('%sSettings: Do KDE = %s. train_sim_test_real = %s. rand_splits = %s. TrueType font = %s. Plot titles = %s %s' % \
      (ansi_colors.OKCYAN, doKDE, train_sim_test_real, rand_splits,
       args.truetype, draw_title, ansi_colors.ENDC))
    if one_meta_train_test:
        print('One meta file %s, to split into train and test sets' %
              meta_train_test_base)
    else:
        print ('%sTwo meta files, train on %s, test on %s%s' % ( \
          ansi_colors.OKCYAN, meta_train_base, meta_test_base, ansi_colors.ENDC))

    # Path to save confusion matrix image to
    img_path = get_recog_confmat_path()

    img_suff = img_mode_suffix + img_kde_suffix
    if sampling_subpath:
        img_suff += ('_' + sampling_subpath)
    if tri_nbins_subpath:
        img_suff += ('_' + tri_nbins_subpath)

    img_name = os.path.join(img_path, 'svm' + img_suff + '.eps')

    #####
    # Load objects data
    #####

    print('Loading descriptor data from %s' % hist_path)

    # One meta file
    if one_meta_train_test:

        meta_train_test_name = os.path.join(get_recog_meta_path(),
                                            meta_train_test_base)

        # Each row of samples is the histogram for one objects
        # lbls is numSamples size list with category integer label for each sample
        [samples, lbls, catnames, catcounts, catids, sample_names] = load_hists ( \
          meta_train_test_name, hist_path, tri_suffix=tri_suffix,
          mixed_paths=False, sampling_subpath=sampling_subpath,
          tri_nbins_subpath=tri_nbins_subpath)

        numSamples = np.shape(samples)[0]
        nBins = np.shape(samples)[1]
        print('%d samples, %d bins' % (numSamples, nBins))
        print('%d labels' % len(lbls))

        if not train_sim_test_real:
            # For training and testing on same domain data, all data go into fair
            #   train-test split, no data is held out.
            idx_start_real_robot = numSamples

    # Load train and test sets from separate files
    # Output: samples_tr, samples_te, lbls_tr, lbls_te, catnames, catids
    else:

        meta_train_name = os.path.join(get_recog_meta_path(), meta_train_base)

        # Each row of samples is the histogram for one objects
        # lbls is numSamples size list with category integer label for each sample
        # For now, just assuming this is PCL data only, so pass in
        #   mixed_paths=False, to do the default replace pcd with csv thing.
        [samples_tr, lbls_tr, catnames, catcounts_tr, catids, sample_names_tr] = \
          load_hists ( \
            meta_train_name, train_path, tri_suffix=tri_suffix,
            mixed_paths=mixed_paths, sampling_subpath=sampling_subpath,
            tri_nbins_subpath=tri_nbins_subpath)

        meta_test_name = os.path.join(get_recog_meta_path(), meta_test_base)

        # Each row of samples is the histogram for one objects
        # lbls is numSamples size list with category integer label for each sample
        [samples_te, lbls_te_tmp, catnames_te_tmp, catcounts_te_tmp, \
          catids_te_tmp, sample_names_te] = load_hists ( \
            meta_test_name, train_path, tri_suffix=tri_suffix,
            mixed_paths=mixed_paths, sampling_subpath=sampling_subpath,
            tri_nbins_subpath=tri_nbins_subpath)

        print ('%d train samples, %d test samples, %d bins' % ( \
          np.shape (samples_tr) [0], np.shape (samples_te) [0],
          np.shape (samples_tr) [1]))

        # Renumber test set's catids, so they match training set's catids, in case
        #   categories in the two meta files don't come in same order!

        fix_test_catids = False
        for i in range(0, len(catnames_te_tmp)):
            # If catname from test meta doesn't match catname from train meta (i.e.
            #   they're ordered differently, then catids in test meta won't match
            #   catids in train meta, need to rematch)
            if catnames_te_tmp[i] != catnames[i]:
                fix_test_catids = True
                break

        # Don't need to renumber
        if not fix_test_catids:
            lbls_te = lbls_te_tmp

        # Renumber test catids to training set's, by matching catname strings
        # NOT TESTED! Because this never happened in my meta files. Can
        #   deliberately swap the order of categories in a meta file to test
        #   this, when I have more time.
        else:
            print ('%sOrder of categories in test meta file is different from order in train meta file. Re-numbering labels in test samples. This code is NOT TESTED, WATCH CLOSELY for mistakes.%s' % ( \
              ansi_colors.WARNING, ansi_colors.ENDC))

            lbls_te = np.zeros(len(lbls_te_tmp), dtype=int)

            # array [test_idx] = train_idx. Note the value is the INDEX in catnames_*
            #   and catids_*, not the actualy category ID value!
            test_to_train_idx = []

            # Find the index of the categories in training catnames
            for test_idx in range(0, len(catnames_te_tmp)):
                # Index of this test category in training catnames list
                train_idx = catnames.index(catnames_te_tmp[test_idx])
                # array [test_idx] = train_idx
                test_to_train_idx.append(train_idx)

            # Renumber old test labels with the new indices
            # Loop through each old category index
            for test_idx in range(0, len(catids_te_tmp)):

                train_idx = test_to_train_idx[test_idx]

                # Find old test labels that match this old category ID (value, not idx)
                samples_idx = np.where (np.array (lbls_te_tmp) == \
                  catids_te_tmp [test_idx])

                # Renumber all these old test labels with the new label ID in training
                lbls_te[samples_idx] = catids[train_idx]

            # Convert NumPy array to list
            lbls_te = lbls_te.tolist()

        # end if not fix_test_catids

        samples = np.append(samples_tr, samples_te, axis=0)
        lbls = lbls_tr + lbls_te
        sample_names = sample_names_tr + sample_names_te

    #####
    # Testing if 1d hists concatenated work better for --mixed mode, because I
    #   can't figure out why 1d hists look good and classification of train-PCL
    #   test-Gazebo predicts everything as a skinny object - banana, hammer, tool.
    #####

    # EXPERIMENTAL
    if args.mixed and experimental_1dhists:

        print(
            '%sEXPERIMENTAL run: converting all 3d histograms to concatenation of 3 1d histograms, to see what happens. Disable this for real run!!!%s'
            % (ansi_colors.OKCYAN, ansi_colors.ENDC))

        samples = convert_samples_to_1dhists ( \
          np.append (samples_tr, samples_te, axis=0), hist_path)

        # First len(lbls_tr) are training samples
        samples_tr = samples[0:len(lbls_tr), :]
        # Last ones are test samples
        samples_te = samples[len(lbls_tr):np.shape(samples)[0], :]

        print('%d training samples, dimension %d' %
              (np.shape(samples_tr)[0], np.shape(samples_tr)[1]))
        print('%d test samples, dimension %d' %
              (np.shape(samples_te)[0], np.shape(samples_te)[1]))
        print('')

    #####
    # Draw big confusion matrix of pairwise distances
    #####

    if debug_draw_nn_dists:

        # Sometimes regrouping helps you see it better, sometimes it doesn't.
        # Set it based on your use case
        REGROUP = False

        print('')
        print('Plotting confusion matrix of all samples')

        if args.mixed:
            samples = np.append(samples_tr, samples_te, axis=0)
            lbls = lbls_tr + lbls_te

        if REGROUP:
            # Group samples of the same classes together, so conf mat clear diagonal
            #   means good.
            # Preallocate to save time.
            samples_grouped = np.zeros(samples.shape)
            lbls_grouped = np.zeros((samples.shape[0], ))

            lbls_np = np.asarray(lbls)
            nSamples_grouped = 0

            nSamples_perCat = []

            print('Regrouped indices just for confusion matrix plot:')
            for i in range(0, len(catids)):

                # Find labels that match current catid
                samples_idx = np.where(lbls_np == catids[i])
                nSamples_currCat = samples_idx[0].size

                # Print BEFORE updating nSamples_grouped!
                print('%s: %d to %d' %
                      (catnames[i], nSamples_grouped,
                       nSamples_grouped + nSamples_currCat - 1))

                # Append to the end of filled rows in lbls_grouped and samples_grouped
                lbls_grouped [nSamples_grouped : \
                  nSamples_grouped + nSamples_currCat] = catids [i]
                samples_grouped [nSamples_grouped : \
                  nSamples_grouped + nSamples_currCat, :] = samples [samples_idx, :]

                nSamples_grouped += nSamples_currCat
                nSamples_perCat.append(nSamples_currCat)

            lbls_grouped = lbls_grouped.tolist()

        else:
            samples_grouped = samples
            lbls_grouped = lbls

        calc_knn_dists(
            samples_grouped,
            lbls_grouped,
            catnames,
            len(lbls),
            img_path,
            img_suff,
            draw_title=draw_title,
            metric=None,
            #metric=hist_inter_dist,
            #metric=hist_minus_hist_inter_dist,
            #metric=kl_divergence_dist,
            print_nn=False,
            exhaustive=True,
            exhaustive_ticks=False)

    #####
    # Main loop
    #####

    # Run once, no random
    if not rand_splits:
        n_random_splits = 1
        # Specify a fixed number, so no randomness
        random_state = 0

    # Run 10 times, random splits, get average accuracy
    else:
        n_random_splits = 100
        # Specify None, so random each call
        # Ref: http://stackoverflow.com/questions/28064634/random-state-pseudo-random-numberin-scikit-learn
        random_state = None

    accs = []

    # Print out all the input path and labels, to manually inspect they're
    #   correct
    if debug_print_all_labels:
        lbls = lbls_tr + lbls_te
        sample_names = sample_names_tr + sample_names_te
        for i in range(0, len(lbls)):

            last_dir = os.path.split(os.path.dirname(sample_names[i]))[1]
            base = os.path.basename(sample_names[i])

            print('%s: %s' % (catnames[lbls[i]], last_dir + '/' + base))

    # Seconds
    start_time = time.time()

    for sp_i in range(0, n_random_splits):

        print('%sRandom split %d%s' %
              (ansi_colors.OKCYAN, sp_i, ansi_colors.ENDC))

        if one_meta_train_test:

            #####
            # Split into train and test sets
            #   Ref: http://scikit-learn.org/stable/auto_examples/plot_confusion_matrix.html
            #####

            # Default test ratio is 0.25
            # lbls are Python lists
            # Use my_train_test_split(), to split satisfsying INDIVIDUAL-CATEGORY
            #   ratio. Split such that 0.5 per category is used for train, 0.5 per
            #   category is used for test. This is better if you have very few data,
            #   when sklearn's train_test_split() can end up giving you 0 items for
            #   test data, or 1 for train and 4 for test!
            # Otherwise, you can also call sklearn's train_test_split() many times,
            #   once for every category, and then sort out the indices yourself...
            samples_tr, samples_te, lbls_tr, lbls_te = my_train_test_split(
                # Use sklearn's train_test_split(), to split satisfying OVERALL ratio,
                #   no regard to how many are train and test in individual categories.
                #samples_tr, samples_te, lbls_tr, lbls_te = train_test_split (
                samples[0:idx_start_real_robot, :],
                lbls[0:idx_start_real_robot],
                test_size=0.5,
                random_state=random_state)

            # Append the held out data rows at the end of test split
            #   Use NumPy arrays for easier manipulation
            samples_te = np.append(samples_te,
                                   samples[idx_start_real_robot:, :],
                                   axis=0)
            lbls_te = np.append(lbls_te, lbls[idx_start_real_robot:], axis=0)
            lbls_te = lbls_te.astype(int)

            # Find the original indices of test samples, so caller can see what
            #   objects are misclassified, by tracing back to the input object.
            idx_te = []
            for l_i in range(0, len(lbls_te)):
                # http://stackoverflow.com/questions/25823608/find-matching-rows-in-2-dimensional-numpy-array
                # axis=1 specifies look for rows that are same
                idx_te.append (np.where ((samples == samples_te [l_i, :]).all (axis=1)) \
                  [0] [0])

            #print ('lbls_tr:')
            #print (lbls_tr)
            #print (type (lbls_tr))
            #print ('lbls_te:')
            #print (lbls_te)
            #print (type (lbls_te))

            # Debug output
            print ('Total train %d instances, test %d instances' % ( \
              np.size (lbls_tr), np.size (lbls_te)))
            for c_i in range(0, len(catnames)):
                #print (np.where (np.array (lbls_tr) == catids [c_i]))
                #print (np.where (np.array (lbls_te) == catids [c_i]))
                n_tr = np.size(np.where(np.array(lbls_tr) == catids[c_i]))
                n_te = np.size(np.where(np.array(lbls_te) == catids[c_i]))

                print('%s: train %d instances, test %d instances' %
                      (catnames[c_i], n_tr, n_te))

                if n_te == 0:
                    print ('Test set for category %s has size 0' % ( \
                      catnames [c_i]))

        # samples_tr, samples_te, lbls_tr, lbls_te already loaded before the loop.
        else:
            # Treat samples as (samples_tr, samples_te). Then test indices are just
            #   (0 : num test samples), plus offset of (num train samples).
            #idx_te = range (0, len (lbls_te))
            idx_te = range(len(lbls_tr), len(lbls_tr) + len(lbls_te))

        # end if one_meta_train_test

        #####
        # Run classification and draw confusion matrix
        #   Ref: http://scikit-learn.org/stable/auto_examples/plot_confusion_matrix.html
        #####

        # Run classifier
        # The confusion matrix example had kernel='linear'. Keeping the default
        #   ('rbf') didn't work, all classes get classified as class 5! For some
        #   reason. So will use linear.
        # 'ovr': One-vs-rest, this is needed to get correct probabilities at test
        #   time. Default option is 'ovo' one-vs-one, which is deprecated, and
        #   outputs (n_samples, n_classes * (n_classes-1) / 2), which requires
        #   voting to predict result. 'ovr' does what SVM base its results on, no
        #   voting.
        # Ref: http://scikit-learn.org/stable/modules/generated/sklearn.svm.SVC.html
        classifier = svm.SVC(kernel='linear', decision_function_shape='ovr')

        # This works fast and similar to 'linear' kernel, but only tested on a
        #   sphere and a cube.
        # 20 Jun 2016. For active touch, inner product kernel is better, `.`
        #   distance is [0, 1], makes more sense for matching an incremental
        #   histogram to a known one. For 3cm-radius cube and sphere, inner prod
        #   distance NN produced better results than SVM with 'linear' kernel.
        #classifier = svm.SVC (kernel=inner_prod_kernel)

        # This works too
        # My custom kernel from metrics.py. Only gets 80/96, when 'linear' gets
        #   81/96. So might as well use linear.
        #classifier = svm.SVC (kernel=hist_inter_kernel)

        # This doesn't work at all. Terrible results
        # My custom kernel. This is slow because I can't figure out how to do it
        #   without nested for-loops, because ai and bi need to be different for
        #   each of bj and aj, to take care of division by 0!
        #classifier = svm.SVC (kernel=kl_divergence_kernel)

        # Failed kernels:
        # All classes get classified as class 5 too!
        #classifier = svm.SVC (kernel=chi2_kernel)
        # All classes get classified as class 5 too!
        #classifier = svm.SVC (kernel=neg_chi2_kernel)

        # Only used for active sensing monte carlo tree search, in active_touch
        #   package. At other times, set this flag to False so you don't mistakenly
        #   overwrite model file!! Model file is not committed to repo!
        if save_svm_to_file:

            # Train SVM with probabilities
            #   http://scikit-learn.org/stable/modules/generated/sklearn.svm.SVC.html
            classifier.probability = True
            # Refit with probabilities
            classifier_probs_model = classifier.fit(samples_tr, lbls_tr)
            # nSamples x nClasses
            probs_pred = classifier_probs_model.predict_proba(samples_te)

            svm_name, svm_lbls_name = get_svm_model_path(
                hist_path, img_mode_suffix)

            print('%sOutputting SVM model to %s%s' %
                  (ansi_colors.OKCYAN, svm_name, ansi_colors.ENDC))
            print('%sOutputting SVM labels to %s%s' %
                  (ansi_colors.OKCYAN, svm_lbls_name, ansi_colors.ENDC))

            # Save trained model to file
            # Ref joblib: http://stackoverflow.com/questions/10592605/save-classifier-to-disk-in-scikit-learn
            joblib.dump(classifier_probs_model, svm_name, compress=9)

            # Save correspondence of {integer_label: class_name} to file, so
            #   reader of SVM model can interpret what classes do the predicted
            #   labels represent!
            svm_lbls = {
                catids[c_i]: catnames[c_i]
                for c_i in range(0, len(catids))
            }
            with open(svm_lbls_name, 'wb') as f:
                # HIGHEST_PROTOCOL is binary, good performance. 0 is text format,
                #   can use for debugging.
                pickle.dump(svm_lbls, f, pickle.HIGHEST_PROTOCOL)

            #print ('Probabilities predicted by SVM:')
            #print ('class1 class2 ...')
            #print (probs_pred)

        # end if save_svm_to_file

        classifier_model = classifier.fit(samples_tr, lbls_tr)
        lbls_pred = classifier_model.predict(samples_te)
        # I think this works too, predicting from the model trained with
        #   probabilities
        #lbls_pred = classifier_probs_model.predict (samples_te)

        print('Truths and Predictions for test data:')
        print(' id name     (truth cat): predicted cat')
        for i_i in range(0, len(idx_te)):

            sample_base = os.path.basename(sample_names[idx_te[i_i]])
            short_sample_name = sample_base[0:min(len(sample_base), 8)]

            true_cat = catnames[lbls_te[i_i]]
            pred_cat = catnames[lbls_pred[i_i]]

            # To help me find a specific object to plot for paper figure
            if debug_find_specific_obj and specific_obj_startswith:
                if sample_base.startswith(specific_obj_startswith):
                    #or sample_base.startswith ('109d55'):
                    print('%s%s is idx %d%s' %
                          (ansi_colors.OKCYAN, sample_base, idx_te[i_i],
                           ansi_colors.ENDC))

            if debug_find_specific_misclass:
                if true_cat == specific_lbl and pred_cat == specific_wrong_lbl:
                    print ('%sObject idx %d is %s predicted as %s%s' % ( \
                      ansi_colors.OKCYAN, idx_te [i_i], true_cat, pred_cat, ansi_colors.ENDC))

            # If want to print NN distance
            dist_str = ''

            # Most helpful debug info. TEMPORARILY commented out to generate
            #   nbins vs acc plots. Faster if don't print to screen!
            #print ('%3d %s (truth %s): predicted %s%s' % (idx_te [i_i],
            #  short_sample_name, true_cat, pred_cat, dist_str))

        # Calculate accuracy
        accs.append(calc_accuracy(lbls_te, lbls_pred, len(catids)))

        # Do this for every random split, for debugging, user can see conf
        #   mats for every test, not just last one.
        # Draw confusion matrix and save to file
        if show_plot_every_split:
            draw_confusion_matrix(lbls_te,
                                  lbls_pred,
                                  catnames,
                                  img_name=img_name,
                                  title_prefix='SVM ',
                                  draw_title=draw_title,
                                  bg_color=bg_color)

        # Show 1d histogram for the sample that user chooses
        if interactive_debug:
            if args.mixed:
                interact_plot_hist(hist_path,
                                   sample_names_tr + sample_names_te,
                                   lbls_tr + lbls_te,
                                   catnames,
                                   plot_opt='1d')
            else:
                interact_plot_hist(hist_path,
                                   sample_names,
                                   lbls,
                                   catnames,
                                   plot_opt='1d')

        print('')

    # end for sp_i = 0 : n_random_splits

    # Copied from triangles_reader.py
    # Print out running time
    # Seconds
    end_time = time.time()
    print ('Total time for %d random splits: %f seconds.' % \
      (n_random_splits, end_time - start_time))
    if n_random_splits != 0:
        print ('Average %f seconds per split.\n' % ( \
          (end_time - start_time) / n_random_splits))

    # Draw confusion matrix and save to file (if there are multiple random
    #   splits, this is for the very last run)
    if show_plot_at_end:
        draw_confusion_matrix(lbls_te,
                              lbls_pred,
                              catnames,
                              img_name=img_name,
                              title_prefix='SVM ',
                              draw_title=draw_title,
                              bg_color=bg_color)

    avg_acc = np.mean(np.asarray(accs))

    if rand_splits:
        print ('%sAverage accuracy over %d runs of random splits: %f %s' % \
          (ansi_colors.OKCYAN, n_random_splits, avg_acc, ansi_colors.ENDC))

    # Write to stats file, for plotting graph for paper
    if write_stats:
        write_stat(
            hist_parent_path,
            hist_path,
            overwrite_stats,
            accs,  #avg_acc,
            img_mode_suffix)
def main():

    #####
    # Parse cmd line args
    #####

    arg_parser = argparse.ArgumentParser()

    arg_parser.add_argument ('histSubdirParam1', type=str,
      help='Used to create directory name to read from.\n' + \
        'For point cloud, nSamples used when triangles were sampled.\n' + \
        'For real robot data, specify the sampling density you want to classify real objects with, e.g. 10, will be used to load histogram bin configs.\n' + \
        'For Gazebo, omit this (pass in like 0) and specify --prs instead. Triangle params to use for 3D histogram, with no spaces, e.g. l0,l1,a0')
    arg_parser.add_argument ('histSubdirParam2', type=str,
      help='Used to create directory name to read from.\n' + \
        'For point cloud, nSamplesRatio used when triangles were sampled.\n' + \
        'For real robot data, specify the sampling density you want to classify real objects with, e.g. 0.95, will be used to load histogram bin configs.\n' + \
        'For Gazebo, omit this (pass in like 0) and specify --nbins instead. Number of bins in 3D histogram, with no spaces, e.g. 10,10,10. This will be outputted to hist_conf.csv for all subsequent files in classification to use.')

    arg_parser.add_argument(
        '--pcd',
        action='store_true',
        default=False,
        help=
        'Boolean flag, no args. Run on synthetic data in csv_tri_lists/ from point cloud'
    )
    arg_parser.add_argument(
        '--gazebo',
        action='store_true',
        default=False,
        help=
        'Boolean flag, no args. Run on synthetic data in csv_tri/ from Gazebo. nSamples and nSamplesRatio do not make sense currently, so just always enter same thing so data gets saved to same folder, like 0 0'
    )
    arg_parser.add_argument(
        '--real',
        action='store_true',
        default=False,
        help=format('Boolean flag. Run on real-robot data.'))

    # Custom subdir under csv_tri, that doesn't have nSamples_nSamplesRatio
    #   style, nor triparams_nbins style.
    # Used for plotting 1D hist intersection plots for simple known shapes.
    arg_parser.add_argument(
        '--out_tri_subdir',
        type=str,
        default='',
        help=
        'String. Subdirectory name of output directory under csv_tri/. This overwrites --gazebo flag. Do not specify both.'
    )
    arg_parser.add_argument(
        '--long_csv_path',
        action='store_true',
        default=False,
        help=
        'Boolean flag, no args. Specify it to read the full path in config file, as opposed to just using the base name in config file to read csv file from csv_tri or csv_gz_tri. Useful for comparing pcl and Gazebo data, which are outputted to different csv_*_tri paths.'
    )

    # Meters. Though histograms use decimeters, raw triangles recorded from
    #   PCL, real robot, and Gazebo are all in meters
    arg_parser.add_argument(
        '--thresh_l',
        type=float,
        default=0.5,
        help='Length threshold in meters, above which to throw away a triangle.'
    )
    arg_parser.add_argument(
        '--no_prune',
        action='store_true',
        default=False,
        help=
        'Specify this if you want no pruning to occur, i.e. keep all triangles.'
    )

    # Number of histogram bins. Used to systematically test a range of different
    #   number of bins, to plot a graph of how number of bins affect SVM
    #   classification accuracy. For paper.
    arg_parser.add_argument(
        '--nbins',
        type=str,
        default='%d,%d,%d' %
        (HistP.bins3D[0], HistP.bins3D[1], HistP.bins3D[2]),
        help=
        'Number of histogram bins. Same number for all 3 triangle parameter dimensions. This will be outputted to hist_conf.csv for all subsequent files in classification to use.'
    )
    arg_parser.add_argument(
        '--prs',
        type=str,
        default='%s,%s,%s' % (HistP.PR1, HistP.PR2, HistP.PR3),
        help=
        'Triangle parameters to use for 3D histogram, e.g. l0,l1,a0, no spaces.'
    )

    arg_parser.add_argument(
        '--plot_hist_inter',
        action='store_true',
        default=False,
        help=
        'Plot confusion matrix style histogram minus histogram intersection (only enable if have very few objects in meta list!!!! Plot is nObjs x nObjs big!)'
    )
    # This looks ugly. I'm not using this. Would need to look up how to resize
    #   subplots to like 4 x 3 or something. Now it's all stretched out. Also
    #   not as useful as plot_hist_inter to spot differences.
    arg_parser.add_argument(
        '--plot_hists',
        action='store_true',
        default=False,
        help=
        'Plot histograms of all objects side by side, in one row. Only enable if you have very few objects in meta list! Else plots will be very small to fit on screen.'
    )
    arg_parser.add_argument(
        '--save_ind_subplots',
        action='store_true',
        default=False,
        help=
        'Save each histogram intersection subplot to an individual file. Only in effect if --plot_hist_inter or --plot_hists is specified.'
    )

    #arg_parser.add_argument ('--meta', type=str, default='models_active_test.txt',
    arg_parser.add_argument(
        '--meta',
        type=str,
        default='models_gazebo_csv.txt',
        help=
        'String. Base name of meta list file in triangle_sampling/config directory'
    )

    # Set to True to upload to ICRA. (You can't view the plot in OS X Preview)
    # Set to False if want to see the plot for debugging.
    arg_parser.add_argument(
        '--truetype',
        action='store_true',
        default=False,
        help=
        'Tell matplotlib to generate TrueType 42 font, instead of rasterized Type 3 font. Specify this flag for uploading to ICRA.'
    )

    arg_parser.add_argument(
        '--black_bg',
        action='store_true',
        default=False,
        help=
        'Boolean flag. Plot with black background, useful for black presentation slides.'
    )

    args = arg_parser.parse_args()

    if args.out_tri_subdir and args.long_csv_path:
        print(
            'Both --out_tri_subdir and --long_csv_path are specified. These are used to construct subdirectory name under csv_tri/<out_tri_subdir> or an explicit path in config file. Cannot have both paths as input. Pick only one, and try again.'
        )
        return

    # Construct input dir name
    out_tri_subdir = args.out_tri_subdir
    long_csv_path = args.long_csv_path

    csv_suffix = ''
    if args.gazebo:
        sampling_subpath, bins3D = get_triparams_nbins_subpath(
            args.prs, args.nbins)
        csv_suffix = 'gz_'

    elif args.real:
        sampling_subpath, bins3D = get_triparams_nbins_subpath(
            args.prs, args.nbins)
        csv_suffix = 'bx_'

    # default to pcd mode
    # though I haven't tried this since added the other modes...
    else:
        # Sampling subpath to save different number of samples, for quick accessing
        #   without having to rerun sample_pcl.cpp.
        nSamples = int(args.histSubdirParam1)
        nSamplesRatio = float(args.histSubdirParam2)
        sampling_subpath = get_sampling_subpath(nSamples, nSamplesRatio)
        bins3D = get_ints_from_comma_string(args.nbins)

    if args.no_prune:
        print('%sNo pruning%s' % (ansi_colors.OKCYAN, ansi_colors.ENDC))
    else:
        thresh_l = args.thresh_l
        print('Length threshold (meters) above which to throw away: %g' %
              thresh_l)
        # No decimeters when pruning! All files should be saved in meters, so that
        #   when run triangles_reader(), which calls read_triangles(), the decimeters
        #   isn't double counted!!! All data file on disk should be in meters!!
        #if HistP.decimeter:
        #  thresh_l *= 10

    # Parse the chosen parameters, to get a list of strings
    #   e.g. ['l0', 'l1', 'a0']
    prs = args.prs.split(',')
    # Figure out the index
    prs_idx = []
    for i in range(0, len(prs)):
        if prs[i] == HistP.A0:
            prs_idx.append(HistP.A0_IDX)
        elif prs[i] == HistP.A1:
            prs_idx.append(HistP.A1_IDX)
        elif prs[i] == HistP.A2:
            prs_idx.append(HistP.A2_IDX)
        elif prs[i] == HistP.L0:
            prs_idx.append(HistP.L0_IDX)
        elif prs[i] == HistP.L1:
            prs_idx.append(HistP.L1_IDX)
        elif prs[i] == HistP.L2:
            prs_idx.append(HistP.L2_IDX)

    plot_hist_inter = args.plot_hist_inter
    plot_hists = args.plot_hists
    save_ind_subplots = False
    if plot_hist_inter:
        print ('%splot_hist_inter is set to true. Make sure your meta file has no more than 6 objects uncommented!!! Else you may run out of memory, trying to plot nObjs x nObjs plots at the end.%s' % ( \
          ansi_colors.OKCYAN, ansi_colors.ENDC))

    if plot_hist_inter or plot_hists:
        # Only in effect if --plot_hist_inter or --plot_hists is specified, else
        #  ignore it
        save_ind_subplots = args.save_ind_subplots

    # Background color of bar graph. If black, text will be set to white via
    #    matplotlib_util.py black_background in call from plot_hist_dd.py.
    if args.black_bg:
        bg_color = 'black'
        fg_color = (0.0, 1.0, 1.0)
    else:
        bg_color = 'white'
        fg_color = 'g'

    # Init node to read triangle csv file
    triReaderNode = TrianglesOnRobotToHists(sampling_subpath,
                                            csv_suffix=csv_suffix)
    triReaderNode.default_config()

    # Read meta list file
    rospack = rospkg.RosPack()
    meta_name = os.path.join(get_recog_meta_path(), args.meta)
    meta_list = read_meta_file(meta_name)
    print ('%sReading meta file from %s%s' % ( \
      ansi_colors.OKCYAN, meta_name, ansi_colors.ENDC))

    # Init output dir
    if not args.no_prune:
        pruned_dir = triReaderNode.tri_path.replace('_tri', '_pruned_tri')
        print('Pruned files will be outputted to %s' % pruned_dir)
        if not os.path.exists(pruned_dir):
            os.makedirs(pruned_dir)

    if long_csv_path:
        # This should give the train/ directory
        train_path = tactile_config.config_paths('custom', '')

    # For ICRA PDF font compliance. No Type 3 font (rasterized) allowed
    #   Ref: http://phyletica.org/matplotlib-fonts/
    # You can do this in code, or edit matplotlibrc. But problem with matplotlibrc
    #   is that it's permanent. When you export EPS using TrueType (42), Mac OS X
    #   cannot convert to PDF. So you won't be able to view the file you
    #   outputted! Better to do it in code therefore.
    #   >>> import matplotlib
    #   >>> print matplotlib.matplotlib_fname()
    #   Ref: http://matplotlib.1069221.n5.nabble.com/Location-matplotlibrc-file-on-my-Mac-td24960.html
    if args.truetype:
        matplotlib.rcParams['pdf.fonttype'] = 42
        matplotlib.rcParams['ps.fonttype'] = 42

    nDims = -1
    min_vals = None
    max_vals = None

    # Copied from sample_pcl_calc_hist.py
    # tri_params is a Python list of NumPy 2D arrays.
    #   tri_params [i] [:, dim] gives data for object i, dimension dim.
    tri_params = []
    obj_names = []

    #####
    # Prune outlier triangles (NOT IN USE ANYMORE. Do NOT prune! It was a
    #   wrong solution for a bug I later found the real solution to (duplicate
    #   threshold in triangles_collect.py was too big).)
    #####

    for line in meta_list:

        #####
        # Read triangle .csv file
        #####

        line = line.strip()

        # Full path to triangle csv file
        #   Construct csv file name from object model base name in meta file
        base = os.path.basename(line)
        if out_tri_subdir:
            base_tri = os.path.splitext(base)[0] + '_robo.csv'
            tri_name = os.path.join(triReaderNode.tri_path, out_tri_subdir,
                                    base_tri)
        elif long_csv_path:
            # Read path from csv file, instead of just the base name
            base_tri = base
            tri_name = os.path.join(train_path, line)
            if not tri_name.endswith('.csv'):
                print(
                    '%sLine in meta file does not end with .csv. Fix it and try again: %s%s'
                    % (ansi_colors.FAIL, line, ansi_colors.ENDC))
                return
        else:
            base_tri = os.path.splitext(base)[0] + '_robo.csv'
            tri_name = os.path.join(triReaderNode.tri_path, base_tri)

        print(tri_name)

        # Ret val is a Python list of 3 n-item lists, each list storing one
        #   parameter for all n triangles in the file.
        tris, param_names = triReaderNode.read_tri_csv(tri_name,
                                                       read_all_params=True)
        if tris is None:
            print ('%sread_tri_csv() encountered error. Terminating...%s' % (\
              ansi_colors.FAIL, ansi_colors.ENDC))
            return

        # Only true in first iteration. Initialize nDims and arrays
        if nDims < 0:
            nDims = len(param_names)
            min_vals = [1000] * nDims
            max_vals = [-1000] * nDims

        #####
        # Prune triangles
        #####

        # Ret val is a list of 6 lists of nTriangles floats, now converted to a
        #   NumPy array of 6 x nTriangles.
        if not args.no_prune:
            pruned = prune(tris, param_names, thresh_l)
        else:
            pruned = tris

        # Reshape ret val from prune() to a format accepted by
        #   plot_conf_mat_hist_inter(), for later plotting.
        # Do a sanity check, for if there are any triangles at all
        if (plot_hist_inter or plot_hists) and np.shape(pruned)[0] > 0:

            nTriangles = np.shape(pruned)[1]

            # tri_params is a Python list of 6 NumPy 2D arrays.
            #   tri_params [i] [:, dim] gives data for object i, dimension dim.
            tri_params.append(np.zeros([nTriangles, nDims]))

            if HistP.decimeter:
                pruned_deci = scale_tris_to_decimeters(pruned, True)
            else:
                pruned_deci = pruned

            for dim in range(0, nDims):
                tri_params[len(tri_params) - 1][:, dim] = pruned_deci[dim, :]

            obj_names.append(os.path.splitext(base)[0])

        #####
        # Write triangles to CSV file
        #   Code copied from triangles_collect.py. Decided not to refactor that
        #     file, `.` that file needs to run fast.
        #####

        if not args.no_prune:

            pruned_name = os.path.join(pruned_dir, base_tri)
            print('Pruned triangle data will be outputted to %s' %
                  (pruned_name))

            pruned_file = open(pruned_name, 'wb')

            # Column names (triangle parameters) are defned in param_names, a list of 6
            #   strings.
            pruned_writer = csv.DictWriter(pruned_file,
                                           fieldnames=param_names,
                                           restval='-1')
            pruned_writer.writeheader()

            # Write each row. Each row is a triangle, represented by 6 floats
            for t_idx in range(0, np.shape(pruned)[1]):

                # Each row is a dictionary. Keys are column titles (triangle parameters in
                #   strings), values are floats.
                row = dict()

                for p_idx in range(0, len(param_names)):
                    row[param_names[p_idx]] = pruned[p_idx, t_idx]

                pruned_writer.writerow(row)

        #####
        # Find min and max in triangle data, for histogram min max edges
        #   Like find_data_range_for_hist() in find_data_range_for_hist.py, but we
        #   don't want to store all objects' data, so we don't call that function,
        #   just call each of the two funcitons separately.
        #####

        min_vals, max_vals = find_data_range_in_one_obj(
            pruned.T, min_vals, max_vals)

    # end for each line in meta list file

    print('')

    # If meta file was empty (or all commented out)
    if (plot_hist_inter or plot_hists) and not obj_names:
        print ('%sNothing was loaded from meta file. Did you specify the correct one? Did you uncomment at least one line? Terminating...%s' % ( \
          ansi_colors.FAIL, ansi_colors.ENDC))
        return

    # Debug
    #print ('min and max vals:')
    #print (min_vals)
    #print (max_vals)

    #####
    # Save hist_conf.csv using min max from all objects
    #   Copied from sample_pcl_calc_hist.py.
    #####

    # Pass in decimeter=False, `.` above, when read_tri_csv, already read the
    #   triangles data in as decimeters! Don't need to do another multiplication
    #   by 10 here, it'd be doing it twice!! Then it'd become centimeter scale!
    # Pass in bins3D to pick bin sizes to write to hist_conf.csv.
    # Pass in prs_idx to pick the 3 triangle parameters to write to header string
    #   of hist_conf.csv, and pick the appropriate bin ranges out of 6.
    bin_range, bin_range3D, header, row = make_bin_ranges_from_min_max ( \
      min_vals, max_vals, decimeter=False,
      # Parameters set at top
      bins3D=bins3D, prs_idx=prs_idx)

    if not args.no_prune:
        conf_path = tactile_config.config_paths(
            'custom',
            os.path.join('triangle_sampling',
                         'csv_' + csv_suffix + 'pruned_hists/',
                         sampling_subpath))
    else:
        # eh... just use csv_gz_pruned_hists, I don't want it to mess up my good
        #   files in csv_gz_hists!!!
        conf_path = tactile_config.config_paths(
            'custom',
            os.path.join('triangle_sampling', 'csv_' + csv_suffix + 'hists/',
                         sampling_subpath))

    # Create output file
    conf_outfile_name = os.path.join(conf_path, 'hist_conf.csv')
    conf_outfile = open(conf_outfile_name, 'wb')

    conf_writer = csv.DictWriter(conf_outfile, fieldnames=header)
    conf_writer.writeheader()

    conf_writer.writerow(dict(zip(header, row)))
    conf_outfile.close()

    print('Outputted histogram configs to ' + conf_outfile_name)

    #####
    # Plot confusion matrix style histogram minus histogram intersection,
    #   for debugging.
    #####

    tick_rot = 0

    if plot_hist_inter or plot_hists:

        nObjs = len(tri_params)

        #print ('nDims %d, nObjs %d' % (nDims, nObjs))

        xlbl_suffix = ' (Meters)'
        # When plotting, do mind decimeter mode, because classification will take
        #   this mode into account. Runtime = heed decimeter; files on disk = no
        #   decimeters.
        if HistP.decimeter:

            xlbl_suffix = ' (Decimeters)'

            # Scale bin ranges to decimeters too
            bin_range_deci, _ = scale_bin_range_to_decimeter(
                bin_range, bin_range3D)
        else:
            bin_range_deci = deepcopy(bin_range)

        if plot_hist_inter:

            tick_rot = 45

            # Copied from sample_pcl_calc_hist.py
            # This gives you a detailed look
            #bins = [30, 30, 30, 30, 30, 30]
            # These are bins that are actually passed to classifier
            bins = [10, 10, 10, 10, 10, 10]
            plotMinus = True

            suptitles = deepcopy(HistP.TRI_PARAMS)
            file_suff = deepcopy(HistP.TRI_PARAMS)
            if plotMinus:
                suptitles.extend(suptitles)
                file_suff.extend([i + '_minusHist' for i in file_suff])

            # Make histogram intersection confusion matrix plots
            figs, success = plot_conf_mat_hist_inter(tri_params,
                                                     bins,
                                                     bin_range_deci,
                                                     nDims,
                                                     nObjs,
                                                     plotMinus,
                                                     suptitles,
                                                     obj_names,
                                                     xlbl_suffix=xlbl_suffix,
                                                     bg_color=bg_color,
                                                     fg_color=fg_color,
                                                     tick_rot=tick_rot)

            nRows = nObjs
            nCols = nObjs

        elif plot_hists:

            tick_rot = 45

            bins = [10, 10, 10]

            suptitles = (HistP.TRI_PARAMS[prs_idx[0]],
                         HistP.TRI_PARAMS[prs_idx[1]],
                         HistP.TRI_PARAMS[prs_idx[2]])
            file_suff = deepcopy(suptitles)

            tri_params_3only = []
            for i in range(0, len(tri_params)):
                # Append columns of the 3 chosen parameters
                tri_params_3only.append(tri_params[i][:,
                                                      (prs_idx[0], prs_idx[1],
                                                       prs_idx[2])])

            # For each object, make 3 titles
            # List of nHists (nObjs) lists of nDims (3) strings
            xlbls = []
            #for i in range (0, len (obj_names)):
            # Include triangle parameter name
            #xlbls.append ([])
            #xlbls [len (xlbls) - 1].append (suptitles[0] + ' in ' + obj_names [i])
            #xlbls [len (xlbls) - 1].append (suptitles[1] + ' in ' + obj_names [i])
            #xlbls [len (xlbls) - 1].append (suptitles[2] + ' in ' + obj_names [i])

            # Just object name
            #xlbls.append ([obj_names [i] [0 : min(20, len(obj_names[i]))]] * 3)

            # Pass in explicit edges, so all 4 hists have same edges! Else
            #   np.histogramdd() finds a different range for each object!
            #   bin[i]+1, the +1 is `.` there are n+1 edges for n bins.
            edges = []
            edges.append(
                np.linspace(bin_range3D[0][0], bin_range3D[0][1], bins[0] + 1))
            edges.append(
                np.linspace(bin_range3D[1][0], bin_range3D[1][1], bins[1] + 1))
            edges.append(
                np.linspace(bin_range3D[2][0], bin_range3D[2][1], bins[2] + 1))

            figs, success = plot_hists_side_by_side(tri_params_3only,
                                                    edges,
                                                    suptitles,
                                                    xlbls,
                                                    bg_color=bg_color,
                                                    fg_color=fg_color,
                                                    tick_rot=tick_rot)

            nCols = nObjs
            nRows = 1

        if success:
            # Show the plot
            print('Saving figures...')
            show_plot(figs, file_suff, 1, show=False, bg_color=bg_color)

            if save_ind_subplots:

                if plot_hist_inter:
                    #save_individual_subplots (figs, file_suff, 'l0_minusHist',
                    save_individual_subplots(figs,
                                             file_suff,
                                             'l2_minusHist',
                                             nRows,
                                             nCols,
                                             scale_xmin=1.15,
                                             scale_x=1.3,
                                             scale_y=1.3,
                                             ndims=1,
                                             show=False,
                                             bg_color=bg_color,
                                             tick_rot=tick_rot)

                elif plot_hists:
                    desired_plot = 'l0'
                    save_individual_subplots(figs,
                                             file_suff,
                                             desired_plot,
                                             nRows,
                                             nCols,
                                             scale_xmin=1.1,
                                             scale_x=1.2,
                                             scale_y=1.15,
                                             ndims=1,
                                             show=False,
                                             bg_color=bg_color)