예제 #1
0
def evaluate():
    with tf.Graph().as_default() as g:
        # A list used to save all hazed images
        psnr_list = []
        hazed_image_list = []
        clear_image_list = []
        hazed_image_placeholder_list = []
        height_list = []
        width_list = []
        # Read all hazed images and clear images from directory and save into memory
        di.image_input(df.FLAGS.clear_test_images_dir, _clear_test_file_names, _clear_test_img_list,
                       _clear_test_directory, clear_image=True)
        if len(_clear_test_img_list) == 0:
            raise RuntimeError("No image found! Please supply clear images for training or eval ")
        # Hazed training image pre-process
        di.image_input(df.FLAGS.haze_test_images_dir, _hazed_test_file_names, _hazed_test_img_list,
                       clear_dict=None, clear_image=False)
        if len(_hazed_test_img_list) == 0:
            raise RuntimeError("No image found! Please supply hazed images for training or eval ")
        for image in _hazed_test_img_list:
            # Read image from files and append them to the list
            hazed_image = im.open(image.path)
            shape = np.shape(hazed_image)
            height_list.append(shape[0])
            width_list.append(shape[1])
            hazed_image_placeholder = tf.placeholder(tf.float32,
                                         shape=[1, shape[0], shape[1], dn.RGB_CHANNEL])
            hazed_image_placeholder_list.append(hazed_image_placeholder)
            hazed_image_arr = np.array(hazed_image)
            float_hazed_image = hazed_image_arr.astype('float32') / 255
            hazed_image_list.append(float_hazed_image)
            clear_image = di.find_corres_clear_image(image, _clear_test_directory)
            clear_image_arr = np.array(clear_image)
            float_clear_image = clear_image_arr.astype('float32') / 255
            clear_image_list.append(float_clear_image)

        if len(clear_image_list) != len(hazed_image_list):
            raise RuntimeError("hazed images cannot correspond to clear images!")

        for index in range(len(clear_image_list)):
            # logist = dmgt.inference(hazed_image)
            logist = lz_net_eval(hazed_image_placeholder_list[index], height_list[index], width_list[index])
            variable_averages = tf.train.ExponentialMovingAverage(
                dn.MOVING_AVERAGE_DECAY)
            variables_to_restore = variable_averages.variables_to_restore()
            saver = tf.train.Saver(variables_to_restore)
            # TODO Zheng Liu please remove the comments of next two lines and add comment to upper five lines
            # logist = lz_net(hazed_image)
            # saver = tf.train.Saver()
            # Build the summary operation based on the TF collection of Summaries.
            summary_op = tf.summary.merge_all()
            # summary_writer = tf.summary.FileWriter(df.FLAGS.eval_dir, g)
            eval_once(saver, logist, summary_op, hazed_image_list, clear_image_list, _hazed_test_img_list, index, hazed_image_placeholder_list, psnr_list, height_list, width_list)

        sum = 0
        for psnr in psnr_list:
            sum += psnr
        psnr_avg = sum / len(psnr_list)
        print('Average PSNR: ')
        print(psnr_avg)
예제 #2
0
def evaluate_cartesian_product():
    with tf.Graph().as_default() as g:
        # A list used to save all hazed images
        psnr_list = []
        hazed_image_list = []
        clear_image_list = []
        # Read all hazed images and clear images from directory and save into memory
        di.image_input(df.FLAGS.clear_test_images_dir, _clear_test_file_names, _clear_test_img_list,
                       _clear_test_directory, clear_image=True)
        if len(_clear_test_img_list) == 0:
            raise RuntimeError("No image found! Please supply clear images for training or eval ")
        # Hazed training image pre-process
        eval_hazed_input(df.FLAGS.haze_test_images_dir, _hazed_test_file_names, _hazed_test_img_list, _hazed_test_A_dict, _hazed_test_beta_dict)

        if len(_hazed_test_img_list) == 0:
            raise RuntimeError("No image found! Please supply hazed images for training or eval ")
        haze_image_obj_list = []
        for k, v in _hazed_test_A_dict.items():
            for image in v:
                hazed_image = im.open(image.path)
                haze_image_obj_list.append(image)
                shape = np.shape(hazed_image)
                # left, upper, right, lower
                if df.FLAGS.input_image_width % 2 != 0:
                    left = df.FLAGS.input_image_width // 2
                    right = left + 1
                else:
                    left = df.FLAGS.input_image_width / 2
                    right = left
                if df.FLAGS.input_image_height % 2 != 0:
                    up = df.FLAGS.input_image_height // 2
                    low = up + 1
                else:
                    up = df.FLAGS.input_image_height / 2
                    low = up
                reshape_hazed_image = hazed_image.crop(
                    (shape[1] // 2 - left, shape[0] // 2 - up, shape[1] // 2 + right, shape[0] // 2 + low))
                # reshape_hazed_image = hazed_image.resize((df.FLAGS.input_image_height, df.FLAGS.input_image_width),
                #                                          resample=im.BICUBIC)
                # reshape_hazed_image = tf.image.resize_image_with_crop_or_pad(hazed_image,
                #                                                              target_height=df.FLAGS.input_image_height,
                #                                                              target_width=df.FLAGS.input_image_width)
                reshape_hazed_image_arr = np.array(reshape_hazed_image)
                float_hazed_image = reshape_hazed_image_arr.astype('float32') / 255
                hazed_image_list.append(float_hazed_image)

                # arr = np.resize(arr, [224, 224])
                clear_image = di.find_corres_clear_image(image, _clear_test_directory)
                # reshape_clear_image = clear_image.resize((df.FLAGS.input_image_height, df.FLAGS.input_image_width),
                #                                          resample=im.BICUBIC)
                # reshape_clear_image = tf.image.resize_image_with_crop_or_pad(clear_image,
                #                                                              target_height=df.FLAGS.input_image_height,
                #                                                              target_width=df.FLAGS.input_image_width)
                shape = np.shape(clear_image)
                # left, upper, right, lower
                if df.FLAGS.input_image_width % 2 != 0:
                    left = df.FLAGS.input_image_width // 2
                    right = left + 1
                else:
                    left = df.FLAGS.input_image_width / 2
                    right = left
                if df.FLAGS.input_image_height % 2 != 0:
                    up = df.FLAGS.input_image_height // 2
                    low = up + 1
                else:
                    up = df.FLAGS.input_image_height / 2
                    low = up
                reshape_clear_image = clear_image.crop(
                    (shape[1] // 2 - left, shape[0] // 2 - up, shape[1] // 2 + right, shape[0] // 2 + low))
                reshape_clear_image_arr = np.array(reshape_clear_image)
                float_clear_image = reshape_clear_image_arr.astype('float32') / 255
                clear_image_list.append(float_clear_image)
        for k, v in _hazed_test_beta_dict.items():
            for image in v:
                hazed_image = im.open(image.path)
                haze_image_obj_list.append(image)
                shape = np.shape(hazed_image)
                # left, upper, right, lower
                if df.FLAGS.input_image_width % 2 != 0:
                    left = df.FLAGS.input_image_width // 2
                    right = left + 1
                else:
                    left = df.FLAGS.input_image_width / 2
                    right = left
                if df.FLAGS.input_image_height % 2 != 0:
                    up = df.FLAGS.input_image_height // 2
                    low = up + 1
                else:
                    up = df.FLAGS.input_image_height / 2
                    low = up
                reshape_hazed_image = hazed_image.crop(
                    (shape[1] // 2 - left, shape[0] // 2 - up, shape[1] // 2 + right, shape[0] // 2 + low))
                # reshape_hazed_image = hazed_image.resize((df.FLAGS.input_image_height, df.FLAGS.input_image_width),
                #                                          resample=im.BICUBIC)
                # reshape_hazed_image = tf.image.resize_image_with_crop_or_pad(hazed_image,
                #                                                              target_height=df.FLAGS.input_image_height,
                #                                                              target_width=df.FLAGS.input_image_width)
                reshape_hazed_image_arr = np.array(reshape_hazed_image)
                float_hazed_image = reshape_hazed_image_arr.astype('float32') / 255
                hazed_image_list.append(float_hazed_image)

                # arr = np.resize(arr, [224, 224])
                clear_image = di.find_corres_clear_image(image, _clear_test_directory)
                # reshape_clear_image = clear_image.resize((df.FLAGS.input_image_height, df.FLAGS.input_image_width),
                #                                          resample=im.BICUBIC)
                # reshape_clear_image = tf.image.resize_image_with_crop_or_pad(clear_image,
                #                                                              target_height=df.FLAGS.input_image_height,
                #                                                              target_width=df.FLAGS.input_image_width)
                shape = np.shape(clear_image)
                # left, upper, right, lower
                if df.FLAGS.input_image_width % 2 != 0:
                    left = df.FLAGS.input_image_width // 2
                    right = left + 1
                else:
                    left = df.FLAGS.input_image_width / 2
                    right = left
                if df.FLAGS.input_image_height % 2 != 0:
                    up = df.FLAGS.input_image_height // 2
                    low = up + 1
                else:
                    up = df.FLAGS.input_image_height / 2
                    low = up
                reshape_clear_image = clear_image.crop(
                    (shape[1] // 2 - left, shape[0] // 2 - up, shape[1] // 2 + right, shape[0] // 2 + low))
                reshape_clear_image_arr = np.array(reshape_clear_image)
                float_clear_image = reshape_clear_image_arr.astype('float32') / 255
                clear_image_list.append(float_clear_image)

        if len(clear_image_list) != len(hazed_image_list):
            raise RuntimeError("hazed images cannot correspond to clear images!")

        hazed_image = tf.placeholder(tf.float32,
                                     shape=[1, df.FLAGS.input_image_height, df.FLAGS.input_image_width,
                                            dn.RGB_CHANNEL])

        # logist = dmgt.inference(hazed_image)
        logist = lz_net_eval(hazed_image,1,1)
        variable_averages = tf.train.ExponentialMovingAverage(
            dn.MOVING_AVERAGE_DECAY)
        variables_to_restore = variable_averages.variables_to_restore()
        saver = tf.train.Saver(variables_to_restore)
        # TODO Zheng Liu please remove the comments of next two lines and add comment to upper five lines
        # logist = lz_net(hazed_image)
        # saver = tf.train.Saver()
        # Build the summary operation based on the TF collection of Summaries.
        summary_op = tf.summary.merge_all()
        summary_writer = tf.summary.FileWriter(df.FLAGS.eval_dir, g)
        for index in range(len(clear_image_list)):
            eval_once(saver, summary_writer, logist, summary_op, hazed_image_list, clear_image_list, haze_image_obj_list, index, hazed_image, psnr_list)

        sum = 0
        for psnr in psnr_list:
            sum += psnr
        psnr_avg = sum / len(psnr_list)
        print('Average PSNR: ')
        print(psnr_avg)
예제 #3
0
def evaluate():
    with tf.Graph().as_default() as g:
        # A list used to save all hazed images
        psnr_list = []
        ssim_list = []
        hazed_image_list = []
        clear_image_list = []
        hazed_image_placeholder_list = []
        height_list = []
        width_list = []
        # Read all hazed images and clear images from directory and save into memory
        if not df.FLAGS.eval_only_haze:
            di.image_input(df.FLAGS.clear_test_images_dir, _clear_test_file_names, _clear_test_img_list,
                           _clear_test_directory, clear_image=True)
            if len(_clear_test_img_list) == 0:
                raise RuntimeError("No image found! Please supply clear images for training or eval ")
        # Hazed training image pre-process
        di.image_input(df.FLAGS.haze_test_images_dir, _hazed_test_file_names, _hazed_test_img_list,
                       clear_dict=None, clear_image=False)
        if len(_hazed_test_img_list) == 0:
            raise RuntimeError("No image found! Please supply hazed images for training or eval ")

        for image in _hazed_test_img_list:
            # Read image from files and append them to the list
            # hazed_image = im.open(image.path)
            hazed_image = im.open(image.path)
            hazed_image = hazed_image.convert('RGB')
            shape = np.shape(hazed_image)
            height_list.append(shape[0])
            width_list.append(shape[1])
            hazed_image_placeholder = tf.placeholder(tf.float32, shape=[constant.SINGLE_IMAGE_NUMBER, shape[0], shape[1], constant.RGB_CHANNEL])
            hazed_image_placeholder_list.append(hazed_image_placeholder)
            hazed_image_arr = np.array(hazed_image)
            float_hazed_image = hazed_image_arr.astype('float32') / 255
            hazed_image_list.append(float_hazed_image)
            if not df.FLAGS.eval_only_haze:
                clear_image = di.find_corres_clear_image(image, _clear_test_directory)
                clear_image_arr = np.array(clear_image)
                float_clear_image = clear_image_arr.astype('float32') / 255
                clear_image_list.append(float_clear_image)

        if not df.FLAGS.eval_only_haze:
            if len(clear_image_list) != len(hazed_image_list):
                raise RuntimeError("hazed images cannot correspond to clear images!")
        for index in range(len(hazed_image_list)):
            logist = lz_net_eval(hazed_image_placeholder_list[index], height_list[index], width_list[index])
            variable_averages = tf.train.ExponentialMovingAverage(
                constant.MOVING_AVERAGE_DECAY)
            variables_to_restore = variable_averages.variables_to_restore()
            saver = tf.train.Saver(variables_to_restore)
            if not df.FLAGS.eval_only_haze:
                eval_once(saver, logist, hazed_image_list, clear_image_list, _hazed_test_img_list, index,
                          hazed_image_placeholder_list, psnr_list, ssim_list, height_list, width_list)
            else:
                eval_once(saver, logist, hazed_image_list, None, _hazed_test_img_list, index,
                          hazed_image_placeholder_list, psnr_list, ssim_list, height_list, width_list)

        if not df.FLAGS.eval_only_haze:
            psnr_avg = cal_average(psnr_list)
            format_str = '%s: Average PSNR: %5f'
            print(format_str % (datetime.now(), psnr_avg))
            ssim_avg = cal_average(ssim_list)
            format_str = '%s: Average SSIM: %5f'
            print(format_str % (datetime.now(), ssim_avg))
예제 #4
0
def train():
    print(PROGRAM_START)
    # Create all dehazenet information in /cpu:0
    with tf.Graph().as_default(), tf.device('/cpu:0'):
        # Create a variable to count the number of train() calls. This equals the
        # number of batches processed * FLAGS.num_gpus.
        global_step = tf.get_variable('global_step', [], initializer=tf.constant_initializer(0), trainable=False)
        # Calculate the learning rate schedule.
        num_batches_per_epoch = (di.NUM_EXAMPLES_PER_EPOCH_FOR_TRAIN /
                                 df.FLAGS.batch_size)
        decay_steps = int(num_batches_per_epoch * dn.NUM_EPOCHS_PER_DECAY)

        lr = tf.train.exponential_decay(dn.INITIAL_LEARNING_RATE,
                                        global_step,
                                        decay_steps,
                                        dn.LEARNING_RATE_DECAY_FACTOR,
                                        staircase=True)

        # Create an optimizer that performs gradient descent.
        opt = tf.train.GradientDescentOptimizer(lr)

        # Image pre-process
        # Clear training image pre-process
        di.image_input(df.FLAGS.clear_train_images_dir, _clear_train_file_names, _clear_train_img_list,
                       _clear_train_directory, clear_image=True)
        if len(_clear_train_img_list) == 0:
            raise RuntimeError("No image found! Please supply clear images for training or eval ")
        # Hazed training image pre-process
        di.image_input(df.FLAGS.haze_train_images_dir, _hazed_train_file_names, _hazed_train_img_list,
                       clear_dict=None, clear_image=False)
        if len(_hazed_train_img_list) == 0:
            raise RuntimeError("No image found! Please supply hazed images for training or eval ")
        # Get queues for training image and ground truth, which is internally multi-thread safe
        hazed_image_queue, clear_image_queue = di.get_distorted_image(_hazed_train_img_list, df.FLAGS.input_image_height,
                                                                      df.FLAGS.input_image_width, _clear_train_directory,
                                                                      file_names=_hazed_train_file_names)
        batch_queue = tf.contrib.slim.prefetch_queue.prefetch_queue(
            [hazed_image_queue, clear_image_queue], capacity=2 * df.FLAGS.num_gpus)
        # Calculate the gradients for each model tower.
        tower_grads = []
        with tf.variable_scope(tf.get_variable_scope()):
            for i in range(df.FLAGS.num_gpus):
                with tf.device('/gpu:%d' % i):
                    with tf.name_scope('%s_%d' % (dn.TOWER_NAME, i)) as scope:
                        # Dequeues one batch for the GPU
                        hazed_image_batch, clear_image_batch = batch_queue.dequeue()
                        # Calculate the loss for one tower of the dehazenet model. This function
                        # constructs the entire dehazenet model but shares the variables across
                        # all towers.
                        loss, _ = tower_loss(scope, hazed_image_batch, clear_image_batch)

                        # Reuse variables for the next tower.
                        tf.get_variable_scope().reuse_variables()

                        # Retain the summaries from the final tower.
                        summaries = tf.get_collection(tf.GraphKeys.SUMMARIES, scope)

                        # Calculate the gradients for the batch of data on this CIFAR tower.
                        grads = opt.compute_gradients(loss)

                        # Keep track of the gradients across all towers.
                        tower_grads.append(grads)

        # We must calculate the mean of each gradient. Note that this is the
        # synchronization point across all towers.
        grads = average_gradients(tower_grads)
        # Add a summary to track the learning rate.
        summaries.append(tf.summary.scalar('learning_rate', lr))

        # Apply the gradients to adjust the shared variables.
        apply_gradient_op = opt.apply_gradients(grads, global_step=global_step)

        # Add histograms for gradients.
        for grad, var in grads:
            if grad is not None:
                summaries.append(tf.summary.histogram(var.op.name + '/gradients', grad))

        # Track the moving averages of all trainable variables.
        variable_averages = tf.train.ExponentialMovingAverage(
            dn.MOVING_AVERAGE_DECAY, global_step)
        variables_averages_op = variable_averages.apply(tf.trainable_variables())

        # Group all updates to into a single train op.
        train_op = tf.group(apply_gradient_op, variables_averages_op)

        # Create a saver.
        saver = tf.train.Saver(tf.global_variables())

        # Build the summary operation from the last tower summaries.
        summary_op = tf.summary.merge(summaries)

        # Build an initialization operation to run below.
        init = tf.global_variables_initializer()

        # Start running operations on the Graph. allow_soft_placement must be set to
        # True to build towers on GPU, as some of the ops do not have GPU
        # implementations.
        sess = tf.Session(config=tf.ConfigProto(
            allow_soft_placement=True,
            log_device_placement=df.FLAGS.log_device_placement))
        sess.run(init)

        # Start the queue runners.
        tf.train.start_queue_runners(sess=sess)

        summary_writer = tf.summary.FileWriter(df.FLAGS.train_dir, sess.graph)

        for step in range(df.FLAGS.max_steps):
            start_time = time.time()
            _, loss_value = sess.run([train_op, loss])
            duration = time.time() - start_time

            assert not np.isnan(loss_value), 'Model diverged with loss = NaN'

            if step % 10 == 0:
                num_examples_per_step = df.FLAGS.batch_size * df.FLAGS.num_gpus
                examples_per_sec = num_examples_per_step / duration
                sec_per_batch = duration / df.FLAGS.num_gpus

                format_str = ('%s: step %d, loss = %.2f (%.1f examples/sec; %.3f '
                              'sec/batch)')
                print(format_str % (datetime.now(), step, loss_value,
                                    examples_per_sec, sec_per_batch))

            if step % 100 == 0:
                summary_str = sess.run(summary_op)
                summary_writer.add_summary(summary_str, step)

            # Save the model checkpoint periodically.
            if step % 1000 == 0 or (step + 1) == df.FLAGS.max_steps:
                checkpoint_path = os.path.join(df.FLAGS.train_dir, 'model.ckpt')
                saver.save(sess, checkpoint_path, global_step=step)
    print(PROGRAM_END)
예제 #5
0
def evaluate():
    # TODO Mengzhen Wang please re-write this function to achieve a better performance
    # This function read the model from ./DeHazeNetModel/model.ckpt
    # file and dehaze the hazed test images. This final result graph will
    # be put into ./ClearImages/ResultImages

    # TODO STEP1:Get a test hazed and clear image batch from queue, which is not shuffled
    with tf.Graph().as_default() as g, tf.device('/cpu:0'):
        # Create a variable to count the number of evaluate() calls. This equals the
        # number of batches processed * FLAGS.num_gpus.
        global_step = tf.get_variable('global_step', [], initializer=tf.constant_initializer(0), trainable=False)
        # Read test data and pre-process
        # Clear training image pre-process
        di.image_input(df.FLAGS.clear_test_images_dir, _clear_test_file_names, _clear_test_img_list,
                       _clear_test_directory, clear_image=True)
        if len(_clear_test_img_list) == 0:
            raise RuntimeError("No image found! Please supply clear images for training or eval ")
        # Hazed training image pre-process
        di.image_input(df.FLAGS.haze_test_images_dir, _hazed_test_file_names, _hazed_test_img_list,
                       clear_dict=None, clear_image=False)
        if len(_hazed_test_img_list) == 0:
            raise RuntimeError("No image found! Please supply hazed images for training or eval ")

        #Get image queues
        hazed_image_queue, clear_image_queue = di.get_distorted_image(_hazed_test_img_list,
                                                                      df.FLAGS.eval_input_image_height,
                                                                      df. FLAGS.eval_input_image_width,
                                                                      _clear_test_directory, Train=False)
        batch_queue = tf.contrib.slim.prefetch_queue.prefetch_queue(
            [hazed_image_queue, clear_image_queue], capacity=2 * df.FLAGS.eval_num_gpus)

        global_loss = []
        # Calculate the gradients for each model tower.
        with tf.variable_scope(tf.get_variable_scope()):
            for i in range(df.FLAGS.eval_num_gpus):
                with tf.device('/gpu:%d' % i):
                    with tf.name_scope('%s_%d' % (EVAL_TOWER_NAME, i)) as scope:
                        # Dequeues one batch for the GPU
                        hazed_image_batch, clear_image_batch = batch_queue.dequeue()

                        # Build a Graph that computes the logits predictions from the
                        # inference model.
                        # TODO STEP2:Use inference to create a test operation
                        result_image_batch = dmgt.inference(hazed_image_batch)

                        losses = dmgt.loss(result_image_batch, clear_image_batch)

                        # Write the clear image into specific path
                        _save_clear_image(df.FLAGS.clear_result_images_dir, result_image_batch)

                        # Retain the summaries from the final tower.
                        summaries = tf.get_collection(tf.GraphKeys.SUMMARIES, scope)

                        # Reuse variables for the next tower.
                        tf.get_variable_scope().reuse_variables()

        # Track the moving averages of all trainable variables.
        variable_averages = tf.train.ExponentialMovingAverage(
            EVAL_MOVING_AVERAGE_DECAY, global_step)

        variables_to_restore = variable_averages.variables_to_restore()
        saver = tf.train.Saver(variables_to_restore)

        # Build the summary operation based on the TF collection of Summaries.
        summary_op = tf.summary.merge_all()

        summary_writer = tf.summary.FileWriter(df.FLAGS.eval_dir, g)

        # Start running operations on the Graph. allow_soft_placement must be set to
        # True to build towers on GPU, as some of the ops do not have GPU
        # implementations.
        sess = tf.Session(config=tf.ConfigProto(
            allow_soft_placement=True,
            # TODO Need to define a directory to save log
            log_device_placement=df.FLAGS.eval_log_device_placement))

        ckpt = tf.train.get_checkpoint_state(df.FLAGS.checkpoint_dir)
        if ckpt and ckpt.model_checkpoint_path:
            # Restores from checkpoint
            saver.restore(sess, ckpt.model_checkpoint_path)
            # Assuming model_checkpoint_path looks something like:
            #   /my-favorite-path/cifar10_train/model.ckpt-0,
            # extract global_step from it.
            global_step = ckpt.model_checkpoint_path.split('/')[-1].split('-')[-1]
        else:
            print('No checkpoint file found')
            return

        # Start the queue runners.
        tf.train.start_queue_runners(sess=sess)
        for step in range(df.FLAGS.eval_max_steps):
            start_time = time.time()
            _, loss_value = sess.run([losses])
            duration = time.time() - start_time

        assert not np.isnan(loss_value), 'Model diverged with loss = NaN'

        if step % 10 == 0:
            num_examples_per_step = df.FLAGS.batch_size * df.FLAGS.num_gpus
            examples_per_sec = num_examples_per_step / duration
            sec_per_batch = duration / df.FLAGS.num_gpus

            format_str = ('%s: step %d, loss = %.2f (%.1f examples/sec; %.3f '
                          'sec/batch)')
            print(format_str % (datetime.now(), step, loss_value,
                                examples_per_sec, sec_per_batch))

        if step % 100 == 0:
            summary_str = sess.run(summary_op)
            summary_writer.add_summary(summary_str, step)

    # TODO STEP3:Create a program used for printing test result
    pass