codes = [item for item in items if 'code' in item.type.lower()] plants = [item for item in items if 'plant' in item.type.lower()] print '{} are codes and {} are plants.'.format(len(codes), len(plants)) # Now that plants are found calculate their field coordinates based on codes. calculate_field_positions_and_range(rows, codes, plants) # Shouldn't be necessary, but do it anyway. print 'Sorting items by number within field.' items = sorted(items, key=lambda item: item.number_within_field) plant_spacings = [] if plant_spacing > 0: # Run spacing verification on single plants to double check no codes were missed. all_segments = all_segments_from_rows(rows) single_segments = [segment for segment in all_segments if segment.start_code.type == 'SingleCode'] warn_about_missing_single_code_lengths(single_segments, plant_spacing) # Run spacing verification on regular plants. last_plant = None for item in items: if item.type not in ['Plant', 'CreatedPlant']: continue if last_plant and last_plant.row == item.row: spacing = position_difference(last_plant.position, item.position) if spacing > plant_spacing * 1.5: print "{} between {} and {}".format(spacing, last_plant.number_within_field, item.number_within_field) plant_spacings.append(spacing) last_plant = item
def stage3_extract_plant_parts(**args): ''' Extract out possible plant parts to be clustered in next stage. args should match the names and descriptions of command line parameters, but unlike command line, all arguments must be present. ''' # Copy args so we can archive them to a file when function is finished. args_copy = args.copy() # Convert arguments to local variables of the correct type. input_filepath = args.pop('input_filepath') out_directory = args.pop('output_directory') pad = float(args.pop('pad')) special_pad = float(args.pop('special_pad')) min_leaf_size = float(args.pop('min_leaf_size')) max_leaf_size = float(args.pop('max_leaf_size')) min_stick_part_size = float(args.pop('min_stick_part_size')) max_stick_part_size = float(args.pop('max_stick_part_size')) min_tag_size = float(args.pop('min_tag_size')) max_tag_size = float(args.pop('max_tag_size')) disable_sticks = args.pop('disable_sticks').lower() == 'true' disable_tags = args.pop('disable_tags').lower() == 'true' use_marked_image = args.pop('marked_image').lower() == 'true' debug_start = args.pop('debug_start') debug_stop = args.pop('debug_stop') if len(args) > 0: print "Unexpected arguments provided: {}".format(args) return ExitReason.bad_arguments rows, geo_images = unpickle_stage2_output(input_filepath) if len(rows) == 0 or len(geo_images) == 0: print "No rows or no geo images could be loaded from {}".format(input_filepath) return ExitReason.no_rows ImageWriter.level = ImageWriter.NORMAL # Write images out to subdirectory to keep separated from pickled results. image_out_directory = os.path.join(out_directory, 'images/') if not os.path.exists(image_out_directory): os.makedirs(image_out_directory) rows = sorted(rows, key=lambda r: r.number) # Sort geo images so they're processed by time. geo_images = sorted(geo_images, key=lambda img: img.image_time) # Look for start/stop filenames so user doesn't have to process all images. start_geo_index, stop_geo_index = get_subset_of_geo_images(geo_images, debug_start, debug_stop) print "Processing geo images {} through {}".format(start_geo_index, stop_geo_index) geo_images = geo_images[start_geo_index : stop_geo_index+1] num_images_not_in_segment = 0 num_images_without_path = 0 leaf_finder = LeafFinder(min_leaf_size, max_leaf_size) if disable_sticks: stick_finder = None else: stick_finder = BlueStickFinder(min_stick_part_size, max_stick_part_size) if disable_tags: tag_finder = None else: tag_finder = TagFinder(min_tag_size, max_tag_size) all_segments = all_segments_from_rows(rows) for segment in all_segments: if segment.is_special: segment.lrud = calculate_special_segment_lrud(segment, special_pad) else: segment.lrud = calculate_segment_lrud(segment, pad) num_matched = [] # keep track of how many segments each image maps to. num_leaves = [] # how many leaves are in each processed image num_sticks = [] # how many sticks are in each processed images num_tags = [] # how many tags are in each processed images for k, geo_image in enumerate(geo_images): if not geo_image.file_path: num_images_without_path += 1 continue # Check if image east/west/north/south (lrud) overlaps with any segments. image_lrud = calculate_image_lrud(geo_image) overlapping_segments = [seg for seg in all_segments if is_overlapping_segment(image_lrud, seg)] if len(overlapping_segments) == 0: num_images_not_in_segment += 1 continue print "{} [{} / {}]".format(geo_image.file_name, k, len(geo_images)) leaves, sticks, tags = process_geo_image_to_find_plant_parts(geo_image, leaf_finder, stick_finder, tag_finder, image_out_directory, use_marked_image) # Remove any false positive items that came from codes. geo_codes = geo_image.items['codes'] leaves = dont_overlap_with_items(geo_codes, leaves) sticks = dont_overlap_with_items(geo_codes, sticks) tags = dont_overlap_with_items(geo_codes, tags) geo_image.items['leaves'] = leaves geo_image.items['stick_parts'] = sticks geo_image.items['tags'] = tags print "Found {} leaves, {} stick parts and {} tags".format(len(leaves), len(sticks), len(tags)) for segment in overlapping_segments: segment.geo_images.append(geo_image) num_matched.append(len(overlapping_segments)) num_leaves.append(len(leaves)) num_sticks.append(len(sticks)) num_tags.append(len(tags)) print "\nProcessed {}".format(len(num_matched)) print "Not in segment {}".format(num_images_not_in_segment) print "Invalid path {}".format(num_images_without_path) print "Matched images were in average of {} segments".format(np.mean(num_matched)) print "Average of {} leaves, {} stick parts and {} tags per image".format(np.mean(num_leaves), np.mean(num_sticks), np.mean(num_tags)) if not os.path.exists(out_directory): os.makedirs(out_directory) # Pickle dump_filename = "stage3_output.s3" print "\nSerializing {} rows to {}".format(len(rows), dump_filename) pickle_results(dump_filename, out_directory, rows) # Write arguments out to file for archiving purposes. write_args_to_file("stage3_args.csv", out_directory, args_copy)
def stage4_locate_plants(**args): """ Cluster and filter plant parts into actual plants. args should match the names and descriptions of command line parameters, but unlike command line, all arguments must be present. """ # Copy args so we can archive them to a file when function is finished. args_copy = args.copy() # convert command line arguments input_filepath = args.pop("input_filepath") out_directory = args.pop("output_directory") max_plant_size = float(args.pop("max_plant_size")) / 100.0 # convert to meters max_plant_part_distance = float(args.pop("max_plant_part_distance")) / 100.0 # convert to meters plant_spacing = float(args.pop("plant_spacing")) / 100.0 # convert to meters start_code_spacing = float(args.pop("start_code_spacing")) / 100.0 # convert to meters end_code_spacing = float(args.pop("end_code_spacing")) / 100.0 # convert to meters single_max_dist = float(args.pop("single_max_dist")) / 100.0 # convert to meters stick_multiplier = float(args.pop("stick_multiplier")) leaf_multiplier = float(args.pop("leaf_multiplier")) tag_multiplier = float(args.pop("tag_multiplier")) lateral_penalty = float(args.pop("lateral_penalty")) projection_penalty = float(args.pop("projection_penalty")) closeness_penalty = float(args.pop("closeness_penalty")) spacing_filter_thresh = float(args.pop("spacing_filter_thresh")) extract_images = args.pop("extract_images").lower() == "true" debug_marked_image = args.pop("marked_image").lower() == "true" if len(args) > 0: print "Unexpected arguments provided: {}".format(args) return ExitReason.bad_arguments rows = unpickle_stage3_output(input_filepath) if len(rows) == 0: print "No rows could be loaded from {}".format(input_filepath) sys.exit(ExitReason.no_rows) if not os.path.exists(out_directory): os.makedirs(out_directory) all_segments = all_segments_from_rows(rows) # Use different filters for normal vs. single segments normal_plant_filter = RecursiveSplitPlantFilter( start_code_spacing, end_code_spacing, plant_spacing, lateral_penalty, projection_penalty, closeness_penalty, stick_multiplier, leaf_multiplier, tag_multiplier, ) closest_plant_filter = ClosestSinglePlantFilter(single_max_dist) # Use a spacing filter for detecting and fixing any mis-chosen plants. plant_spacing_filter = PlantSpacingFilter(spacing_filter_thresh) if extract_images: ImageWriter.level = ImageWriter.NORMAL image_out_directory = os.path.join(out_directory, "images/") else: image_out_directory = None for seg_num, segment in enumerate(all_segments): # if segment.start_code.name != 'TBJ': # continue print "Processing segment {} [{}/{}] with {} images".format( segment.start_code.name, seg_num + 1, len(all_segments), len(segment.geo_images) ) if segment.row_number > 6: for geo_image in segment.geo_images: try: del geo_image.items["stick_parts"] del geo_image.items["leaves"] except KeyError: pass try: if segment.start_code.is_gap_item: print "Skipping segment since its start code is listed as a gap." continue except AttributeError: pass # This used to not be supported so it's not a big deal if segment is missing property # Cluster together leaves, stick parts and tags into possible plants possible_plants = [] for geo_image in segment.geo_images: if "possible_plants" in geo_image.items: # Already clustered this image. possible_plants += geo_image.items["possible_plants"] else: possible_plants += cluster_geo_image_items(geo_image, segment, max_plant_size, max_plant_part_distance) if len(possible_plants) == 0: print "Warning: segment {} has no possible plants.".format(segment.start_code.name) continue # Remove small parts that didn't get clustered. possible_plants = filter_out_noise(possible_plants) print "{} possible plants found between all images".format(len(possible_plants)) print "clustered down to {} possible plants".format(len(possible_plants)) # Find UTM positions of possible plants so that they can be easily compared between different images. last_plant = None for plant in possible_plants: stick_parts = [part for part in plant["items"] if part["item_type"] == "stick_part"] plant_tags = [tag for tag in plant["items"] if part["item_type"] == "tag"] if len(plant_tags) > 0: # Use position of tag for plant position. positioning_rect = merge_corner_rectangles([tag["rect"] for tag in plant_tags]) elif len(stick_parts) > 0: # Use blue stick parts for position positioning_rect = merge_corner_rectangles([part["rect"] for part in stick_parts]) else: # No tags or blue sticks so just use entire plant positioning_rect = plant["rect"] if "image_altitude" in plant: altitude = plant["image_altitude"] elif last_plant is not None: altitude = last_plant["position"][2] else: altitude = segment.start_code.position[2] px, py = corner_rect_center(positioning_rect) plant["position"] = (px, py, altitude) last_plant = plant if segment.start_code.type == "RowCode" and segment.end_code.type == "SingleCode": # Special case... don't want to process this segment since there shouldn't be a plant associated with it. continue if segment.is_special: selected_plant = closest_plant_filter.find_actual_plant(possible_plants, segment) actual_plants = [selected_plant] else: actual_plants = normal_plant_filter.locate_actual_plants_in_segment(possible_plants, segment) plant_spacing_filter.filter(actual_plants) print "{} actual plants found".format(len(actual_plants)) # Now that plant filter has run make sure all created plants have a bounding rectangle so they show up in output images. for plant in actual_plants: if plant.type == "CreatedPlant": px, py, pz = plant.position po = 0.12 # plant offset in meters plant.bounding_rect = [(px - po, py - po), (px - po, py + po), (px + po, py - po), (px + po, py + po)] extract_global_plants_from_images(actual_plants, segment.geo_images, image_out_directory) for plant in actual_plants: plant.row = segment.row_number segment.add_item(plant) if debug_marked_image: if len(actual_plants) > 0: debug_draw_plants_in_images(segment.geo_images, possible_plants, actual_plants, out_directory) print "\n---------Normal Groups----------" print "Successfully found {} total plants".format(normal_plant_filter.num_successfully_found_plants) print "Created {} plants".format(normal_plant_filter.num_created_plants) print "\n---------Single Groups----------" print "Successfully found {} total plants".format(closest_plant_filter.num_successfully_found_plants) print "Created {} plants due to no valid plants".format(closest_plant_filter.num_created_because_no_plants) print "\n-----Spacing Filter Results-----" print "Relocated {} plants due to bad spacing.".format(plant_spacing_filter.num_plants_moved) # Pickle dump_filename = "stage4_output.s4" print "\nSerializing {} rows to {}".format(len(rows), dump_filename) pickle_results(dump_filename, out_directory, rows) # Write arguments out to file for archiving purposes. write_args_to_file("stage4_args.csv", out_directory, args_copy)