def __init__(self, **kwargs):
        """Read arguments (and change settings) and initialize modules."""
        # Default Data Inputs
        self.image = None
        self.plant_db = DB()

        # Default Parameter Inputs
        self.params = Parameters()
        self.params.add_missing_params('detect')

        # Load keyword argument inputs
        self._data_inputs(kwargs)
        self._parameter_inputs(kwargs)
        self.args = kwargs

        # Set remaining arguments to defaults
        self._set_defaults()

        # Changes based on inputs
        if self.args['calibration_img'] is not None:
            # self.coordinates = True
            self.args['coordinates'] = True
        if self.args['GUI']:
            self.args['save'] = False
            self.args['text_output'] = False
        if self.args['app']:
            self.args['verbose'] = False
            self.args['from_env_var'] = True
            self.plant_db.app = True

        # Remaining initialization
        self.p2c = None
        self.capture = Capture().capture
        self.final_marked_image = None
        self.plant_db.tmp_dir = None
예제 #2
0
 def setUp(self):
     self.outfile = open('db_text_output_test.txt', 'w')
     sys.stdout = self.outfile
     self.db = DB()
     self.db.plants['known'] = [{'x': 1000, 'y': 1000, 'radius': 100}]
     self.db.coordinate_locations = [[1000, 1000, 75],
                                     [1000, 825, 50],
                                     [800, 1000, 50],
                                     [1090, 1000, 75],
                                     [900, 900, 50],
                                     [1000, 1150, 50],
                                     [700, 700, 0],
                                     [600, 600, 1000],
                                     [-100, -100, 10],
                                     ]
     self.remove = [{'radius': 50.0, 'x': 1000.0, 'y': 825.0},
                    {'radius': 50.0, 'x': 800.0, 'y': 1000.0}]
     self.safe_remove = [{'radius': 50.0, 'x': 900.0, 'y': 900.0},
                         {'radius': 50.0, 'x': 1000.0, 'y': 1150.0}]
     self.save = [{'radius': 75.0, 'x': 1000.0, 'y': 1000.0},
                  {'radius': 75.0, 'x': 1090.0, 'y': 1000.0},
                  ]
     self.db.identify({
         'use_bounds': True,
         'min_radius': 1,
         'max_radius': 100,
     })
     self.add_point = [{'body': [{'kind': 'pair', 'args': {
         'value': 'plant-detection', 'label': 'created_by'}}],
         'kind': 'add_point', 'args': {'radius': 50.0, 'location': {
             'kind': 'coordinate', 'args': {'y': 825.0, 'x': 1000.0, 'z': 0}}}},
         {'body': [{'kind': 'pair', 'args': {
             'value': 'plant-detection', 'label': 'created_by'}}],
          'kind': 'add_point', 'args': {'radius': 50.0, 'location': {
              'kind': 'coordinate', 'args': {'y': 1000.0, 'x': 800.0, 'z': 0}}}}]
     self.point_data = {
         'pointer_type': 'Weed',
         'name': 'Weed',
         'x': '1000.0',
         'y': '825.0',
         'z': 0,
         'radius': '50.0',
         'plant_stage': 'pending',
         'meta': {
             'created_by': 'plant-detection',
             'color': 'red',
             'type': 'weed',
             'removal_method': 'automatic',
         }
     }
예제 #3
0
 def test_origin_location(self):
     p2c = Pixel2coord(DB())
     p2c.calibration_params['image_bot_origin_location'] = [0, 0]
     p2c.calibration_params['center_pixel_location'] = [100, 200]
     p2c._block_rotations(90)
     self.assertEqual(
         p2c.calibration_params['image_bot_origin_location'], [1, 0])
예제 #4
0
 def test_location_rotation(self):
     """Detect using different calibration object locations and rotations."""
     i = 0
     for flip in range(3):
         for angle in [-10, 10]:
             img = 'test_objects_{}.jpg'.format(i)
             i += 1
             calibration_img = cv2.imread(
                 'plant_detection/p2c_test_calibration.jpg', 1)
             if flip > 1:
                 cv2.circle(calibration_img,
                            (465, 290), int(1000),
                            (255, 255, 255), -1)
                 cv2.circle(calibration_img,
                            (172, 290), int(25),
                            (0, 0, 255), -1)
                 cv2.circle(calibration_img,
                            (755, 290), int(25),
                            (0, 0, 255), -1)
             elif flip:
                 calibration_img = cv2.flip(calibration_img, 0)
             calibration_img = rotate(calibration_img, angle)
             cv2.imwrite(img, calibration_img)
             p2c = Pixel2coord(DB(), calibration_image=img,
                               calibration_data=self.calibration_data)
             p2c.calibration()
             self.assertAlmostEqual(
                 p2c.calibration_params['total_rotation_angle'],
                 -angle, delta=1)
             self.assertAlmostEqual(
                 p2c.calibration_params['coord_scale'],
                 1.7, delta=0.1)
예제 #5
0
 def test_one_object(self):
     """Detect one object during calibration"""
     db = DB()
     p2c = Pixel2coord(
         db, calibration_image=image_file('one.jpg', self.one_object))
     exit_flag = p2c.calibration()
     self.assertEqual(db.object_count, 1)
     self.assertTrue(exit_flag)
예제 #6
0
 def test_zero_objects(self):
     """Detect zero objects during calibration"""
     db = DB()
     p2c = Pixel2coord(
         db, calibration_image=image_file('zero.jpg', self.zero_objects))
     exit_flag = p2c.calibration()
     self.assertEqual(db.object_count, 0)
     self.assertTrue(exit_flag)
예제 #7
0
 def test_three_objects(self):
     """Detect three objects during calibration"""
     db = DB()
     p2c = Pixel2coord(
         db, calibration_image=image_file('three.jpg', self.three_objects))
     exit_flag = p2c.calibration()
     self.assertEqual(db.object_count, 3)
     self.assertFalse(exit_flag)
예제 #8
0
    def test_orientation(self):
        """Detect calibration objects based on image origin.

                  |  top (0)  | bottom (1) |
                    ---------   ----------
        left  (0) |    00     |     01     |
                    ---------   ----------
        right (1) |    10     |     11     |
                    ---------   ----------
        """
        orientations = [[0, 0], [0, 1], [1, 0], [1, 1]]
        expectations = [{
            "x": 1300,
            "y": 800
        }, {
            "x": 1300,
            "y": 200
        }, {
            "x": 300,
            "y": 800
        }, {
            "x": 300,
            "y": 200
        }]
        for orientation, expectation in zip(orientations, expectations):
            image_origin = '{} {}'.format(['top', 'bottom'][orientation[1]],
                                          ['left', 'right'][orientation[0]])
            convert_to_env_var = {
                '[0, 0]': 'TOP_LEFT',
                '[1, 0]': 'TOP_RIGHT',
                '[0, 1]': 'BOTTOM_LEFT',
                '[1, 1]': 'BOTTOM_RIGHT'
            }
            os.environ[
                'CAMERA_CALIBRATION_image_bot_origin_location'] = json.dumps(
                    convert_to_env_var[str(orientation)])
            os.environ[
                'CAMERA_CALIBRATION_calibration_object_separation'] = '1000'
            os.environ['CAMERA_CALIBRATION_camera_offset_x'] = '200'
            os.environ['CAMERA_CALIBRATION_camera_offset_y'] = '100'
            os.environ['CAMERA_CALIBRATION_calibration_along_axis'] = 'X'
            p2c = Pixel2coord(
                DB(),
                calibration_image='plant_detection/p2c_test_calibration.jpg',
                load_data_from='env_var')
            p2c.calibration()
            p2c.image.load('single_object.jpg')
            coordinates = p2c.determine_coordinates()
            for axis in ['x', 'y']:
                self.assertAlmostEqual(
                    coordinates[0][axis],
                    expectation[axis],
                    delta=5,
                    msg="object {} coordinate {} != {} within 5 delta for {}"
                    " image origin".format(axis, coordinates[0][axis],
                                           expectation[axis], image_origin))
예제 #9
0
 def setUp(self):
     self.outfile = open('p2c_text_output_test.txt', 'w')
     sys.stdout = self.outfile
     self.db = DB()
     self.two_objects = cv2.imread(
         'plant_detection/p2c_test_calibration.jpg', 1)
     self.three_objects = self.two_objects.copy()
     self.one_object = self.two_objects.copy()
     self.zero_objects = self.two_objects.copy()
     cv2.circle(self.zero_objects, (600, 300), int(1000), (255, 255, 255),
                -1)
     cv2.circle(self.one_object, (175, 475), int(50), (255, 255, 255), -1)
     cv2.circle(self.three_objects, (600, 300), int(25), (0, 0, 255), -1)
예제 #10
0
    def __init__(self, farmwarename):
        self.farmwarename = farmwarename
        prefix = self.farmwarename.lower().replace('-', '_')
        self.input_default_speed = int(os.environ.get(prefix + "_default_speed", 800))
        self.x_photo_pos = 400
        self.y_photo_pos = 235
        self.z_photo_pos = 0
        self.image = None
        self.plant_db = DB()
        self.params = Parameters()
        self.plant_detection = None
        self.dir = os.path.dirname(os.path.realpath(__file__)) + os.sep

        """"self.api = API(self)
예제 #11
0
class LocationTest(unittest.TestCase):
    """Get the bot's location"""

    def setUp(self):
        self.coordinates = [300, 500, -100]
        self.test_coordinates = [600, 400, 0]
        self.r = fakeredis.FakeStrictRedis()
        self.db = DB()

    def test_get_coordinates(self):
        """Get location from redis"""
        self.r.set('BOT_STATUS.location_data.position.x', self.coordinates[0])
        self.r.set('BOT_STATUS.location_data.position.y', self.coordinates[1])
        self.r.set('BOT_STATUS.location_data.position.z', self.coordinates[2])
        self.db.getcoordinates(redis=self.r)
        self.assertEqual(self.db.coordinates, self.coordinates)

    def test_partial_coordinates(self):
        """Coordinates aren't complete"""
        self.r.set('BOT_STATUS.location_data.position.x', self.coordinates[0])
        self.r.set('BOT_STATUS.location_data.position.y', self.coordinates[1])
        self.db.getcoordinates(redis=self.r)
        self.assertEqual(self.db.coordinates, self.test_coordinates)

    def test_no_coordinates(self):
        """Coordinates don't exist"""
        self.db.getcoordinates(redis=self.r)
        self.assertEqual(self.db.coordinates, self.test_coordinates)

    def test_not_coordinates(self):
        """Coordinates aren't numbers"""
        self.r.set('BOT_STATUS.location_data.position.x', 'text')
        self.r.set('BOT_STATUS.location_data.position.y', 'text')
        self.r.set('BOT_STATUS.location_data.position.z', 'text')
        self.db.getcoordinates(redis=self.r)
        self.assertEqual(self.db.coordinates, self.test_coordinates)

    def tearDown(self):
        self.r.flushall()
예제 #12
0
 def setUp(self):
     self.parameters = Parameters()
     self.db = DB()
     self.image = Image(self.parameters, self.db)
예제 #13
0
        self.image.initial_processing()
        self.image.find(calibration=True)
        self.plant_db.print_count(calibration=True)  # print detected obj count
        self.p2c(self.plant_db)
        self.plant_db.print_coordinates()
        if self.viewoutputimage:
            self.image.grid(self)
            self.image.images['current'] = self.image.images['marked']
            self.image.show()
        return self.plant_db.get_json_coordinates()


if __name__ == "__main__":
    DIR = os.path.dirname(os.path.realpath(__file__)) + os.sep
    print("Calibration image load...")
    P2C = Pixel2coord(DB(), calibration_image=DIR +
                      "p2c_test_calibration.jpg")
    P2C.viewoutputimage = True
    # Calibration
    P2C.image.rotate_main_images(P2C.test_rotation)
    EXIT = P2C.calibration()
    if EXIT:
        sys.exit(0)
    P2C.plant_db.print_count(calibration=True)  # print detected object count
    if P2C.calibration_params['total_rotation_angle'] != 0:
        print(" Note: required rotation executed = {:.2f} degrees".format(
            P2C.calibration_params['total_rotation_angle']))
    # Tests
    # Object detection
    print("Calibration object test...")
    P2C.image.load(DIR + "p2c_test_objects.jpg")
class PlantDetection(object):
    """Detect plants in image and output an image with plants marked.

    Kwargs:
       image (str): filename of image to process (default = None)
           None -> take photo instead
       coordinates (boolean): use coordinate conversion (default = False)
       calibration_img (filename): calibration image filename used to
           output coordinates instead of pixel locations (default = None)
       calibration_data (dict): calibration data inputs,
           overwrites other calibration data inputs (default = None)
       known_plants (list): {'x': x, 'y': y, 'radius': radius}
                            of known (intentional) plants
                            (default = None)
       debug (boolean): output debug images (default = False)
       blur (int): blur kernel size (must be odd, default = 5)
       morph (int): amount of filtering (default = 5)
       iterations (int): number of morphological iterations (default = 1)
       array (list): list of morphs to run
           [morph kernel size, morph kernel type, morph type, iterations]
           example:
           [{"size": 5, "kernel": 'ellipse', "type": 'erode',  "iters": 2},
            {"size": 3, "kernel": 'ellipse', "type": 'dilate', "iters": 8}]
                  (default = None)
       save (boolean): save images (default = True)
       clump_buster (boolean): attempt to break
                               plant clusters (default = False)
       HSV_min (list): green lower bound Hue(0-179), Saturation(0-255),
                       and Value(0-255) (default = [30, 20, 20])
       HSV_max (list): green upper bound Hue(0-179), Saturation(0-255),
                       and Value(0-255) (default = [90, 255, 255])
       from_file (boolean): load data from file
            plant-detection_inputs.json
            plant-detection_p2c_calibration_parameters.json
            plant-detection_plants.json
            (default = False)
       from_env_var (boolean): load data from environment variable,
            overriding other parameter inputs
            (default = False)
       text_output (boolean): print text to STDOUT (default = True)
       verbose (boolean): print verbose text to STDOUT.
            otherwise, print condensed text output (default = True)
       print_all_json (boolean): print all JSON data used to STDOUT
            (default = False)
       grey_out (boolean): grey out regions in image that have
            not been selected (default = False)
       draw_contours (boolean): draw an outline around the boundary of
            detected plants (default = True)
       circle_plants (boolean): draw an enclosing circle around
            detected plants (default = True)
       GUI (boolean): settings for the local GUI (default = False)
       app (boolean): connect to the FarmBot web app (default = False)
       app_image_id (string): use image from the FarmBot API (default = None)

    Examples:
       PD = PlantDetection()
       PD.detect_plants()

       PD = PlantDetection(
          image='plant_detection/soil_image.jpg', morph=3, iterations=10,
          debug=True)
       PD.detect_plants()

       PD = PlantDetection(
          image='plant_detection/soil_image.jpg',
          blur=9, morph=7, iterations=4,
          calibration_img="plant_detection/p2c_test_calibration.jpg")
       PD.calibrate()
       PD.detect_plants()

       PD = PlantDetection(
         image='plant_detection/soil_image.jpg', blur=15, grey_out=True,
         array=[
            {"size": 5, "kernel": 'ellipse', "type": 'erode',  "iters": 2},
            {"size": 3, "kernel": 'ellipse', "type": 'dilate', "iters": 8}],
         debug=True, clump_buster=False,
         HSV_min=[30, 15, 15], HSV_max=[85, 245, 245])
       PD.detect_plants()
    """
    def __init__(self, **kwargs):
        """Read arguments (and change settings) and initialize modules."""
        # Default Data Inputs
        self.image = None
        self.plant_db = DB()

        # Default Parameter Inputs
        self.params = Parameters()
        self.params.add_missing_params('detect')

        # Load keyword argument inputs
        self._data_inputs(kwargs)
        self._parameter_inputs(kwargs)
        self.args = kwargs

        # Set remaining arguments to defaults
        self._set_defaults()

        # Changes based on inputs
        if self.args['calibration_img'] is not None:
            # self.coordinates = True
            self.args['coordinates'] = True
        if self.args['GUI']:
            self.args['save'] = False
            self.args['text_output'] = False
        if self.args['app']:
            self.args['verbose'] = False
            self.args['from_env_var'] = True
            self.plant_db.app = True

        # Remaining initialization
        self.p2c = None
        self.capture = Capture().capture
        self.final_marked_image = None
        self.plant_db.tmp_dir = None

    def _set_defaults(self):
        default_args = {
            # Default Data Inputs
            'image':
            None,
            'calibration_img':
            None,
            'known_plants':
            None,
            'app_image_id':
            None,
            'calibration_data':
            None,
            # Default Program Options
            'coordinates':
            False,
            'from_file':
            False,
            'from_env_var':
            False,
            'clump_buster':
            False,
            'GUI':
            False,
            'app':
            False,
            # Default Output Options
            'debug':
            False,
            'save':
            True,
            'text_output':
            True,
            'verbose':
            True,
            'print_all_json':
            False,
            'output_celeryscript_points':
            False,
            # Default Graphic Options
            'grey_out':
            False,
            'draw_contours':
            True,
            'circle_plants':
            True,
            # Default processing options
            'array':
            None,
            'blur':
            self.params.parameters['blur'],
            'morph':
            self.params.parameters['morph'],
            'iterations':
            self.params.parameters['iterations'],
            'HSV_min': [
                self.params.parameters['H'][0], self.params.parameters['S'][0],
                self.params.parameters['V'][0]
            ],
            'HSV_max': [
                self.params.parameters['H'][1], self.params.parameters['S'][1],
                self.params.parameters['V'][1]
            ],
        }
        for key, value in default_args.items():
            if key not in self.args:
                self.args[key] = value

    def _data_inputs(self, kwargs):
        """Load data inputs from keyword arguments."""
        for key in kwargs:
            if key == 'known_plants':
                self.plant_db.plants['known'] = kwargs[key]

    def _parameter_inputs(self, kwargs):
        """Load parameter inputs from keyword arguments."""
        for key in kwargs:
            if key == 'blur':
                self.params.parameters['blur'] = kwargs[key]
            if key == 'morph':
                self.params.parameters['morph'] = kwargs[key]
            if key == 'iterations':
                self.params.parameters['iterations'] = kwargs[key]
            if key == 'array':
                self.params.array = kwargs[key]
            if key == 'HSV_min':
                hsv_min = kwargs[key]
                self.params.parameters['H'][0] = hsv_min[0]
                self.params.parameters['S'][0] = hsv_min[1]
                self.params.parameters['V'][0] = hsv_min[2]
            if key == 'HSV_max':
                hsv_max = kwargs[key]
                self.params.parameters['H'][1] = hsv_max[0]
                self.params.parameters['S'][1] = hsv_max[1]
                self.params.parameters['V'][1] = hsv_max[2]

    def _calibration_input(self):  # provide inputs to calibration
        if self.args['app_image_id'] is not None:
            self.args['calibration_img'] = int(self.args['app_image_id'])
        if self.args['calibration_img'] is None and self.args['coordinates']:
            # Calibration requested, but no image provided.
            # Take a calibration image.
            self.args['calibration_img'] = self.capture()

        # Set calibration input parameters
        if self.args['from_env_var']:
            calibration_input = 'env_var'
        elif self.args['from_file']:  # try to load from file
            calibration_input = 'file'
        else:  # Use default calibration inputs
            calibration_input = None

        # Call coordinate conversion module
        self.p2c = Pixel2coord(self.plant_db,
                               calibration_image=self.args['calibration_img'],
                               calibration_data=self.args['calibration_data'],
                               load_data_from=calibration_input)
        self.p2c.debug = self.args['debug']

    def calibrate(self):
        """Calibrate the camera for plant detection.

        Initialize the coordinate conversion module using a calibration image,
        perform calibration, and save calibration data.
        """
        self._calibration_input()  # initialize coordinate conversion module
        exit_flag = self.p2c.calibration()  # perform calibration
        if exit_flag:
            sys.exit(0)
        self._calibration_output()  # save calibration data

    def _calibration_output(self):  # save calibration data
        if self.args['save'] or self.args['debug']:
            self.p2c.image.images['current'] = self.p2c.image.images['marked']
            self.p2c.image.save('calibration_result')

        # Print verbose results
        if self.args['verbose'] and self.args['text_output']:
            if self.p2c.calibration_params['total_rotation_angle'] != 0:
                print(" Note: required rotation of "
                      "{:.2f} degrees executed.".format(
                          self.p2c.calibration_params['total_rotation_angle']))
            if self.args['debug']:
                # print number of objects detected
                self.plant_db.print_count(calibration=True)
                # print coordinate locations of calibration objects
                self.p2c.p2c(self.plant_db)
                self.plant_db.print_coordinates()
                print('')

        # Print condensed output if verbose output is not chosen
        if self.args['text_output'] and not self.args['verbose']:
            print("Calibration complete. (rotation:{}, scale:{})".format(
                self.p2c.calibration_params['total_rotation_angle'],
                self.p2c.calibration_params['coord_scale']))

        # Send calibration result log toast
        if self.args['app']:
            log(
                'Camera calibration complete; setting pixel coordinate scale'
                ' to {} and camera rotation to {} degrees.'.format(
                    self.p2c.calibration_params['coord_scale'],
                    self.p2c.calibration_params['total_rotation_angle']),
                'success', 'Success', ['toast'], True)

        # Save calibration data
        if self.args['from_env_var']:
            # to environment variable
            self.p2c.save_calibration_data_to_env()
        elif self.args['from_file']:  # to file
            self.p2c.save_calibration_parameters()
        else:  # to Parameters() instance
            self.params.calibration_data = self.p2c.calibration_params

    def _detection_input(self):  # provide input to detect_plants
        # Load input parameters
        if self.args['from_file']:
            # Requested to load detection parameters from file
            try:
                self.params.load('detect')
            except IOError:
                print("Warning: Input parameter file load failed. "
                      "Using defaults.")
            self.plant_db.load_plants_from_file()
        if self.args['app']:
            self.plant_db.load_plants_from_web_app()
        if self.args['from_env_var']:
            # Requested to load detection parameters from json ENV variable
            self.params.load_env_var('detect')

        # Print input parameters and filename of image to process
        if self.args['verbose'] and self.args['text_output']:
            self.params.print_input()
            print("\nProcessing image: {}".format(self.args['image']))

    def _detection_image(self):  # get image to process
        self.image = Image(self.params, self.plant_db)
        # Get image to process
        try:  # check for API image ID
            image_id = self.args['app_image_id']
        except KeyError:
            image_id = None
        if image_id is not None:  # download image
            try:
                self.image.download(image_id)
            except IOError:
                print("Image download failed for image ID {}.".format(
                    str(image_id)))
                sys.exit(0)
        elif self.args['image'] is None:  # No image provided. Capture one.
            self.image.capture()
            if self.args['debug']:
                self.image.save('photo')
        else:  # Image provided. Load it.
            filename = self.args['image']
            self.image.load(filename)
        self.image.debug = self.args['debug']

    def _coordinate_conversion(self):  # determine detected object coordinates
        # Load calibration data
        load_data_from = None
        calibration_data = None
        if self.args['from_env_var']:
            load_data_from = 'env_var'
        elif self.args['from_file']:
            load_data_from = 'file'
        else:  # use data saved in self.params
            calibration_data = self.params.calibration_data
        # Initialize coordinate conversion module
        self.p2c = Pixel2coord(self.plant_db,
                               load_data_from=load_data_from,
                               calibration_data=calibration_data)
        self.p2c.debug = self.args['debug']
        # Check for coordinate conversion calibration results
        present = {
            'coord_scale': False,
            'camera_z': False,
            'center_pixel_location': False,
            'total_rotation_angle': False
        }
        try:
            for key in present:
                present[key] = self.p2c.calibration_params[key]
        except KeyError:
            log(
                "ERROR: Coordinate conversion calibration values "
                "not found. Run calibration first.",
                message_type='error',
                title='plant-detection')
            sys.exit(0)
        # Validate coordinate conversion calibration data for image
        calibration_data_valid = self.p2c.validate_calibration_data(
            self.image.images['current'])
        if not calibration_data_valid:
            log(
                "ERROR: Coordinate conversion calibration values "
                "invalid for provided image.",
                message_type='error',
                title='plant-detection')
            sys.exit(0)
        # Determine object coordinates
        self.image.coordinates(self.p2c,
                               draw_contours=self.args['draw_contours'])
        # Organize objects into plants and weeds
        self.plant_db.identify(self.params.parameters)
        if self.plant_db.plants['safe_remove']:
            self.image.safe_remove(self.p2c)

    def _coordinate_conversion_output(self):  # output detected object data
        # Print and output results
        if self.args['text_output']:
            self.plant_db.print_count()  # print number of objects detected
        if self.args['verbose'] and self.args['text_output']:
            self.plant_db.print_identified()  # print organized plant data
        if self.args['output_celeryscript_points']:
            self.plant_db.output_celery_script()  # print points JSON to stdout
        if self.args['app']:
            save_detected_plants = self.params.parameters[
                'save_detected_plants']
            # add detected weeds and points to FarmBot Web App
            self.plant_db.upload_plants(save_detected_plants)
        if self.args['debug']:
            self.image.save_annotated('contours')
            self.image.images['current'] = self.image.images['marked']
            self.image.save_annotated('coordinates_found')
        if self.args['circle_plants']:
            self.image.label(self.p2c)  # mark objects with colored circles
        self.image.grid(self.p2c)  # add coordinate grid and features

    def detect_plants(self):
        """Detect the green objects in the image."""
        # Gather inputs
        self._detection_input()
        self._detection_image()

        # Process image in preparation for detecting plants (blur, mask, morph)
        self.image.initial_processing()

        # Optionally break up masses by splitting them into quarters
        if self.args['clump_buster']:
            self.image.clump_buster()

        # Optionally grey out regions not detected as objects
        if self.args['grey_out']:
            self.image.grey()

        # Return coordinates if requested
        if self.args['coordinates']:  # Convert pixel locations to coordinates
            self._coordinate_conversion()
            self._coordinate_conversion_output()
        else:  # No coordinate conversion
            # get pixel locations of objects
            self.image.find(draw_contours=self.args['draw_contours'])
            if self.args['circle_plants']:
                self.image.label()  # Mark plants with red circle
            if self.args['debug']:
                self.image.save_annotated('contours')
            if self.args['text_output']:
                self.plant_db.print_count()  # print number of objects detected
            if self.args['verbose'] and self.args['text_output']:
                self.plant_db.print_pixel()  # print object pixel location text
            self.image.images['current'] = self.image.images['marked']

        self._show_detection_output()  # show output data
        self._save_detection_output()  # save output data

    def _show_detection_output(self):  # show detect_plants output
        # Print raw JSON to STDOUT
        if self.args['print_all_json']:
            print("\nJSON:")
            print(self.params.parameters)
            print(self.plant_db.plants)
            if self.p2c is not None:
                print(self.p2c.calibration_params)

        # Print condensed inputs if verbose output is not chosen
        if self.args['text_output'] and not self.args['verbose']:
            print('{}: {}'.format('known plants input',
                                  self.plant_db.plants['known']))
            print('{}: {}'.format('parameters input', self.params.parameters))
            print('{}: {}'.format('coordinates input',
                                  self.plant_db.coordinates))

    def _save_detection_output(self):  # save detect_plants output
        # Final marked image
        if self.args['save'] or self.args['debug']:
            self.image.save('marked')
        elif self.args['GUI']:
            self.final_marked_image = self.image.images['marked']

        # Save input parameters
        if self.args['from_env_var']:
            # to environment variable
            self.params.save_to_env_var('detect')
        elif self.args['save']:
            # to file
            self.params.save()
        elif self.args['GUI']:
            # to file for GUI
            self.params.save()

        # Save plants
        if self.args['save']:
            self.plant_db.save_plants()
예제 #15
0
class DBTest(unittest.TestCase):
    """Check plant identification"""
    def setUp(self):
        self.outfile = open('db_text_output_test.txt', 'w')
        sys.stdout = self.outfile
        self.db = DB()
        self.db.plants['known'] = [{'x': 1000, 'y': 1000, 'radius': 100}]
        self.db.coordinate_locations = [[1000, 1000, 75], [1000, 825, 50],
                                        [800, 1000, 50], [1090, 1000, 75],
                                        [900, 900, 50], [1000, 1150, 50]]
        self.remove = [{
            'radius': 50.0,
            'x': 1000.0,
            'y': 825.0
        }, {
            'radius': 50.0,
            'x': 800.0,
            'y': 1000.0
        }]
        self.safe_remove = [{
            'radius': 50.0,
            'x': 900.0,
            'y': 900.0
        }, {
            'radius': 50.0,
            'x': 1000.0,
            'y': 1150.0
        }]
        self.save = [
            {
                'radius': 75.0,
                'x': 1000.0,
                'y': 1000.0
            },
            {
                'radius': 75.0,
                'x': 1090.0,
                'y': 1000.0
            },
        ]
        self.db.identify()
        self.add_point = [{
            'body': [{
                'kind': 'pair',
                'args': {
                    'value': 'plant-detection',
                    'label': 'created_by'
                }
            }],
            'kind':
            'add_point',
            'args': {
                'radius': 50.0,
                'location': {
                    'kind': 'coordinate',
                    'args': {
                        'y': 825.0,
                        'x': 1000.0,
                        'z': 0
                    }
                }
            }
        }, {
            'body': [{
                'kind': 'pair',
                'args': {
                    'value': 'plant-detection',
                    'label': 'created_by'
                }
            }],
            'kind':
            'add_point',
            'args': {
                'radius': 50.0,
                'location': {
                    'kind': 'coordinate',
                    'args': {
                        'y': 1000.0,
                        'x': 800.0,
                        'z': 0
                    }
                }
            }
        }]
        self.point_data = {
            'pointer_type': 'Weed',
            'name': 'Weed',
            'x': '1000.0',
            'y': '825.0',
            'z': 0,
            'radius': '50.0',
            'plant_stage': 'pending',
            'meta': {
                'created_by': 'plant-detection',
                'color': 'red',
                'type': 'weed',
                'removal_method': 'automatic',
            }
        }

    def test_plant_id_remove(self):
        """Check plants to be removed"""
        self.assertEqual(self.remove, self.db.plants['remove'])

    def test_plant_id_save(self):
        """Check plants to be saved"""
        self.assertEqual(self.save, self.db.plants['save'])

    def test_plant_id_safe_remove(self):
        """Check plants to be safely removed"""
        self.assertEqual(self.safe_remove, self.db.plants['safe_remove'])

    def test_point_data_preparation(self):
        """Verify point data content and format."""
        self.assertEqual(self.point_data,
                         self.db.prepare_point_data(self.remove[0], 'Weed'))

    def test_api_download(self):
        """Run (failing) plant download assuming no API_TOKEN ENV"""
        self.db.load_plants_from_web_app()
        self.assertEqual(self.db.errors, {} if USING_FT else {'401': 1})

    def test_api_upload(self):
        """Run (failing) plant upload assuming no API_TOKEN ENV"""
        self.db.upload_plants()
        self.assertEqual(self.db.errors, {} if USING_FT else {'401': 1})

    def test_print_coordinates(self):
        """Print unidentified plant coordinate data"""
        self.db.print_coordinates()
        self.outfile.close()
        self.outfile = open('db_text_output_test.txt', 'r')
        self.assertEqual(sum(1 for line in self.outfile), 7)

    def test_cs_add_point(self):
        """Output Celery Script add_point"""
        add_point = self.db.output_celery_script()
        self.assertEqual(add_point, self.add_point)

    def test_save_to_tmp(self):
        """Save plants to file in tmp directory"""
        self.db.tmp_dir = "/tmp/"
        self.db.save_plants()

    def tearDown(self):
        self.outfile.close()
        sys.stdout = sys.__stdout__
        os.remove('db_text_output_test.txt')
예제 #16
0
 def setUp(self):
     self.coordinates = [300, 500, -100]
     self.test_coordinates = [600, 400, 0]
     self.r = fakeredis.FakeStrictRedis()
     self.db = DB()
예제 #17
0
        self.image.initial_processing()
        self.image.find(calibration=True)
        self.plant_db.print_count(calibration=True)  # print detected obj count
        self.p2c(self.plant_db)
        self.plant_db.print_coordinates()
        if self.viewoutputimage:
            self.image.grid(self)
            self.image.images['current'] = self.image.images['marked']
            self.image.show()
        return self.plant_db.get_json_coordinates()


if __name__ == "__main__":
    DIR = os.path.dirname(os.path.realpath(__file__)) + os.sep
    print("Calibration image load...")
    P2C = Pixel2coord(DB(), calibration_image=DIR + "p2c_test_calibration.jpg")
    P2C.viewoutputimage = True
    # Calibration
    P2C.image.rotate_main_images(P2C.test_rotation)
    EXIT = P2C.calibration()
    if EXIT:
        sys.exit(0)
    P2C.plant_db.print_count(calibration=True)  # print detected object count
    if P2C.calibration_params['total_rotation_angle'] != 0:
        print(" Note: required rotation executed = {:.2f} degrees".format(
            P2C.calibration_params['total_rotation_angle']))
    # Tests
    # Object detection
    print("Calibration object test...")
    P2C.image.load(DIR + "p2c_test_objects.jpg")
    P2C.image.rotate_main_images(P2C.test_rotation)
예제 #18
0
 def setUp(self):
     self.outfile = open('db_text_output_test.txt', 'w')
     sys.stdout = self.outfile
     self.db = DB()
     self.db.plants['known'] = [{'x': 1000, 'y': 1000, 'radius': 100}]
     self.db.coordinate_locations = [[1000, 1000, 75], [1000, 825, 50],
                                     [800, 1000, 50], [1090, 1000, 75],
                                     [900, 900, 50], [1000, 1150, 50]]
     self.remove = [{
         'radius': 50.0,
         'x': 1000.0,
         'y': 825.0
     }, {
         'radius': 50.0,
         'x': 800.0,
         'y': 1000.0
     }]
     self.safe_remove = [{
         'radius': 50.0,
         'x': 900.0,
         'y': 900.0
     }, {
         'radius': 50.0,
         'x': 1000.0,
         'y': 1150.0
     }]
     self.save = [
         {
             'radius': 75.0,
             'x': 1000.0,
             'y': 1000.0
         },
         {
             'radius': 75.0,
             'x': 1090.0,
             'y': 1000.0
         },
     ]
     self.db.identify()
     self.add_point = [{
         'body': [{
             'kind': 'pair',
             'args': {
                 'value': 'plant-detection',
                 'label': 'created_by'
             }
         }],
         'kind':
         'add_point',
         'args': {
             'radius': 50.0,
             'location': {
                 'kind': 'coordinate',
                 'args': {
                     'y': 825.0,
                     'x': 1000.0,
                     'z': 0
                 }
             }
         }
     }, {
         'body': [{
             'kind': 'pair',
             'args': {
                 'value': 'plant-detection',
                 'label': 'created_by'
             }
         }],
         'kind':
         'add_point',
         'args': {
             'radius': 50.0,
             'location': {
                 'kind': 'coordinate',
                 'args': {
                     'y': 1000.0,
                     'x': 800.0,
                     'z': 0
                 }
             }
         }
     }]
     self.point_data = {
         "pointer_type": "GenericPointer",
         "name": "Weed",
         "x": "1000.0",
         "y": "825.0",
         "z": 0,
         "radius": "50.0",
         "meta": {
             "created_by": "plant-detection",
             "color": "red"
         }
     }