def main():
    """Main function for the program.  Everything starts here.

    :return: None
    """

    global quit_flag, pause_flag, reset_flag, total_paused_time, number_of_inferences

    if (handle_args() != True):
        print_usage()
        exit()

    print_arg_vals()

    labels_list = read_labels(classes_filename)

    num_ncs_devs = number_of_devices

    # adjust number of inferences to evenly work out for number of images
    inferences_per_thread = int(number_of_inferences / ((threads_per_dev * num_ncs_devs)))
    inferences_per_thread = int(inferences_per_thread / simultaneous_infer_per_thread) * simultaneous_infer_per_thread
    total_number_threads = num_ncs_devs * threads_per_dev

    # get list of all the image files in the image directory
    image_filename_list = os.listdir(image_dir)

    image_filename_list = [image_dir + sep + i for i in image_filename_list if (i.endswith('.jpg') or i.endswith(".png"))]
    if (len(image_filename_list) < 1):
        # no images to show
        print('No image files found (.jpg or .png)')
        return 1

    print("Found " + str(len(image_filename_list)) + " images.")

    if (len(image_filename_list) > images_across * images_down):
        print("More images than grid can hold.  Only using " + str(images_across*images_down) + " images.")
        image_filename_list = image_filename_list[0:images_across * images_down]


    total_image_count = len(image_filename_list)

    print("total_image_count is: " + str(total_image_count))

    infer_result_queue = queue.Queue(INFER_RES_QUEUE_SIZE)

    result_times_list = [None] * (num_ncs_devs * threads_per_dev)
    thread_list = [None] * (num_ncs_devs * threads_per_dev)

    start_barrier = threading.Barrier(num_ncs_devs*threads_per_dev+1)
    end_barrier = threading.Barrier(num_ncs_devs*threads_per_dev+1)

    display_canvas = canvas.Canvas(canvas_width, canvas_height, images_across=images_across, images_down=images_down)

    cv2.namedWindow(cv_window_name, cv2.WINDOW_NORMAL)
    cv2.resizeWindow(cv_window_name,  (canvas_width, canvas_height))

    cv2.moveWindow(cv_window_name, 0,0)
    cv2.waitKey(1)
    display_canvas.show_loading()
    cv2.imshow(cv_window_name, display_canvas.get_canvas_image())
    cv2.waitKey(1)

    # load a single plugin for the application
    ie = IECore()
    net = IENetwork(model=model_xml_fullpath, weights=model_bin_fullpath)
    input_blob = next(iter(net.inputs))
    out_blob = next(iter(net.outputs))

    n, c, h, w = net.inputs[input_blob].shape

    display_image_width = 224
    display_image_height = 224

    display_image_list = [None]*len(image_filename_list)
    preprocessed_image_list = [None]*len(image_filename_list)
    preprocessed_image_index = 0
    for one_image_filename in image_filename_list:
        display_image_list[preprocessed_image_index] = cv2.imread(one_image_filename)
        display_image_list[preprocessed_image_index] = cv2.resize(display_image_list[preprocessed_image_index], (display_image_width, display_image_height))
        one_preprocessed_image = preprocess_image(n,c,h,w,one_image_filename)
        preprocessed_image_list[preprocessed_image_index] = one_preprocessed_image
        preprocessed_image_index += 1
        cv2.waitKey(1)

    display_canvas.load_images(display_image_list)

    base_images_per_thread = int(len(preprocessed_image_list) / total_number_threads)

    # in case it doesn't divide evenly there will be some threads that have one more
    # image than the base.  the extra_images is the number of threads that need
    # an extra image.
    extra_images = len(preprocessed_image_list) % base_images_per_thread

    exec_net_list = [None] * num_ncs_devs

    last_image_index = -1

    for dev_index in range(0, num_ncs_devs):
        # create one executable network for each device in the system
        # create 4 requests for each executable network, two for each NCE
        exec_net_list[dev_index] = ie.load_network(network=net, num_requests=threads_per_dev*simultaneous_infer_per_thread, device_name = inference_device)

        # create threads for each executable network (one executable network per device)
        for dev_thread_index in range(0,threads_per_dev):
            total_thread_index = dev_thread_index + (threads_per_dev*dev_index)
            first_image_index = last_image_index + 1 #int(total_thread_index*base_images_per_thread)
            last_image_index = int(first_image_index + base_images_per_thread - 1)
            if (total_thread_index < extra_images):
                last_image_index += 1

            print("first image index: " + str(first_image_index) + "  last_image_index: " + str(last_image_index))
            if (run_async):
                thread_list[total_thread_index] = threading.Thread(target=infer_async_thread_proc,
                                                                              args=[exec_net_list[dev_index], dev_thread_index*simultaneous_infer_per_thread,
                                                                                    preprocessed_image_list, image_filename_list, display_image_list,
                                                                                    first_image_index, last_image_index,
                                                                                    inferences_per_thread,
                                                                                    result_times_list, total_thread_index,
                                                                                    start_barrier, end_barrier, simultaneous_infer_per_thread,
                                                                                    infer_result_queue])
            else:
                pass


    del net

    #start the threads
    for one_thread in thread_list:
        one_thread.start()

    while (True):
        display_canvas.show_device(inference_device)
        display_canvas.clear_loading()
        display_canvas.pause_start()
        display_canvas.press_any_key()
        cv2.imshow(cv_window_name, display_canvas.get_canvas_image())
        cv2.waitKey(-1)    
        display_canvas.pause_stop()
        display_canvas.clear_press_any_key()

        for result_time_index in range(0, len(result_times_list)):
            result_times_list[result_time_index] = 0.0

        reset_flag = False
        quit_flag = False
        pause_flag = False
        start_barrier.wait()

        # resetting the start barrier in case we are resetting rather than quiting
        start_barrier.reset()

        total_paused_time = 0.0

        # save the main starting time
        main_start_time = time.time()
        interval_start_time = time.time()

        print("Inferences started.")

        cur_fps = 0.0
        result_counter = 0
        accum_fps = 0.0
        frames_since_last_report = 0

        while (result_counter < total_image_count):
            infer_res = infer_result_queue.get(True, QUEUE_WAIT_SECONDS)
            if (infer_result_queue.qsize() > (frames_to_skip)):
                for extra_res_counter in range(0, frames_to_skip):
                    infer_res_display_image_index = infer_res[4]

                    display_canvas.mark_image_done(infer_res_display_image_index)
                    infer_result_queue.task_done()
                    result_counter += 1
                    frames_since_last_report += 1
                    infer_res = infer_result_queue.get(True, QUEUE_WAIT_SECONDS)
            display_canvas.press_quit_key()
            
            infer_res_filename = infer_res[0]
            infer_res_index = infer_res[1]
            infer_res_probability = infer_res[2]
            infer_res_display_image = infer_res[3]
            infer_res_display_image_index = infer_res[4]

            fps_to_use = accum_fps
            if (use_interval_fps):
                fps_to_use = cur_fps
            if (show_fps):
                display_canvas.show_fps(fps_to_use)
            else :
                display_canvas.hide_fps()

            if (show_timer):
                display_canvas.show_timer(time.time()-main_start_time)
            else:
                display_canvas.hide_timer()
            
            display_canvas.mark_image_done(infer_res_display_image_index, labels_list[infer_res_index])
            cv2.imshow(cv_window_name, display_canvas.get_canvas_image())

            raw_key = cv2.waitKey(1)
            if (raw_key != -1 or quit_flag):
                if (handle_keys(raw_key) == False or quit_flag or reset_flag):
                    print("Quitting application")
                    quit_flag = True
                    pause_flag = False
                    exception_count = 0
                    max_exceptions = 50000
                    while(end_barrier.n_waiting < total_number_threads) :
                        try:
                            infer_res = infer_result_queue.get_nowait()
                        except:
                            exception_count += 1
                        if (exception_count >= max_exceptions):
                            break
                    print("Exception Count: " + str(exception_count))
                    if (exception_count >= max_exceptions):
                        print("Gave up waiting for threads to finish, exiting!")
                        cv2.destroyAllWindows()
                        exit(-1)
                    break
                if (pause_flag):
                    pause_start_time = time.time()
                    frames_since_last_report = 0
                    interval_start_time = time.time()
                    while(pause_flag and not quit_flag):
                        raw_key = cv2.waitKey(1)
                        if (raw_key != -1):
                            if (handle_keys(raw_key) == False):
                                quit_flag = True
                    pause_end_time = time.time()
                    total_paused_time += (pause_end_time - pause_start_time)

            result_counter += 1
            frames_since_last_report += 1
            if (report_interval > 0):
                if ((frames_since_last_report > report_interval)):
                    cur_time = time.time()
                    accum_duration = cur_time - main_start_time
                    cur_duration = cur_time - interval_start_time
                    cur_fps = frames_since_last_report / cur_duration
                    accum_fps = result_counter / (accum_duration - total_paused_time)

                    frames_since_last_report = 0
                    interval_start_time = time.time()

            infer_result_queue.task_done()
        display_canvas.clear_press_any_key()
        display_canvas.press_any_key()
        cv2.imshow(cv_window_name, display_canvas.get_canvas_image())
        # wait for all the inference threads to reach end barrier
        print("main end barrier reached")
        
        end_barrier.wait()

        # save main end time
        main_end_time = time.time()
        

        if (not quit_flag):
            # user didn't hit quit, so reset
            reset_flag=True
            cv2.waitKey(-1)

        end_barrier.reset()

        if (not reset_flag):
            print("main not resetting, breaking")
            break

        display_canvas.reset_canvas()
        print("main thread looping back.")


    print("Inferences finished.")

    for one_thread in thread_list:
        one_thread.join()

    total_thread_fps = 0.0
    total_thread_time = 0.0

    for thread_index in range(0, (num_ncs_devs*threads_per_dev)):
        total_thread_time += (result_times_list[thread_index] - total_paused_time)
        inferences_per_thread = base_images_per_thread
        if (thread_index < extra_images):
            inferences_per_thread += 1
        total_thread_fps += (inferences_per_thread / (result_times_list[thread_index] - total_paused_time))

    if (quit_flag):
        # adjust the thread fps since didn't do every inference.
        total_thread_fps = result_counter / (total_thread_time / total_number_threads)


    devices_count = str(number_of_devices)


    if (time_threads):
        print("\n------------------- Thread timing -----------------------")
        print("--- Device: " + str(inference_device))
        print("--- Model:  " + model_xml_fullpath)
        print("--- Total FPS: " + '{0:.1f}'.format(total_thread_fps))
        print("--- FPS per device: " + '{0:.1f}'.format(total_thread_fps / num_ncs_devs))
        print("---------------------------------------------------------")


    main_time = (main_end_time - main_start_time) - total_paused_time

    if (time_main):
        main_fps = result_counter / main_time
        print ("\n------------------ Main timing -------------------------")
        print ("--- FPS: " + str(main_fps))
        print ("--- FPS per device: " + str(main_fps/num_ncs_devs))
        print ("--------------------------------------------------------")

    # clean up
    for one_exec_net in exec_net_list:
        del one_exec_net
Main function for the program.  Everything starts here.

    :return: None
    
apps/classifier_grid/classifier_grid.py