def aux(rootdir, level=0): pad = ' ' * level dirpaths, filepaths = collect_dirs_and_files(rootdir) filepaths_by_ext = sort_files_by_ext(filepaths) if filepaths_by_ext: for ext, filepaths in sorted(filepaths_by_ext.items()): seq = FileSequence.load(filepaths) if seq: range_str = ', '.join( ['(%d-%d)' % (lo, hi) for lo, hi in seq.ranges]) print('%s %s | %4d | %s' % (pad, ext.ljust(8), len(filepaths), range_str)) else: print('%s %s | %4d ' % (pad, ext.ljust(8), len(filepaths))) elif not dirpaths: print('%s (empty)' % pad) for dirpath in dirpaths: print('%s %s:' % (pad, os.path.basename(dirpath))) aux(dirpath, level + 1)
def run(cls, args): print('Collection: %s' % os.path.basename(args.collection)) print(' Subdir: %s' % (args.subdir or '<ROOT>')) print('') images_dir = os.path.normpath( os.path.join(args.collection, args.subdir)) _, filepaths = collect_dirs_and_files(images_dir) filepaths_by_ext = sort_files_by_ext(filepaths) cr2_files = filepaths_by_ext.get('.cr2', []) if cr2_files: seq = FileSequence.load(cr2_files) if seq: range_str = ', '.join( ['(%d-%d)' % (lo, hi) for lo, hi in seq.ranges]) print('%d image files found: %s' % (len(cr2_files), range_str)) else: print('%d image files found.' % len(cr2_files)) else: raise RuntimeError('No .cr2 image files found') print('Setting exposure delta to %+0.2f EV...' % args.ev_delta) print('') changed_count = 0 unchanged_count = 0 for image_filepath in sorted(cr2_files): old_ev_delta = read_param(image_filepath, 'ev_delta', 0.0) if old_ev_delta == args.ev_delta: unchanged_count += 1 else: print('%s: %+0.2f EV --> %+0.2f EV' % (os.path.basename(image_filepath), old_ev_delta, args.ev_delta)) write_param(image_filepath, 'ev_delta', args.ev_delta) changed_count += 1 print('') print('Updated params for %d images; left %d images unchanged.' % (changed_count, unchanged_count)) if changed_count: print('Run fma dt-apply to apply changes to .xmp files.')
def run(cls, args): print('Collection: %s' % os.path.basename(args.collection)) print(' Subdir: %s' % (args.subdir or '<ROOT>')) print('') images_dir = os.path.normpath(os.path.join(args.collection, args.subdir)) _, filepaths = collect_dirs_and_files(images_dir) xmp_files = sort_files_by_ext(filepaths).get('.cr2.xmp', []) if xmp_files: seq = FileSequence.load(xmp_files) if seq: range_str = ', '.join(['(%d-%d)' % (lo, hi) for lo, hi in seq.ranges]) print('%d sidecar files found: %s' % (len(xmp_files), range_str)) else: print('%d sidecar files found.' % len(xmp_files)) if not args.force: print('') print('These existing sidecar files contain edits made in darktable.') print('Clearing these files will DELETE these previously-made edits.') print('If you\'re sure you want to do that, re-run with -f.') return else: print('No .xmp sidecar files found.') return print('') print('Deleting %d sidecar files...' % len(xmp_files)) num_deleted = 0 for filepath in xmp_files: print('DELETE %s' % os.path.basename(filepath)) os.remove(filepath) num_deleted += 1 print('') print('Deleted %d files.' % num_deleted) print('Regenerating .xmp sidecar files...') regenerate_xmps(images_dir)
def run(cls, args): print('Collection: %s' % os.path.basename(args.collection)) print(' Subdir: %s' % (args.subdir or '<ROOT>')) print('') images_dir = os.path.normpath(os.path.join(args.collection, args.subdir)) _, filepaths = collect_dirs_and_files(images_dir) filepaths_by_ext = sort_files_by_ext(filepaths) cr2_files = filepaths_by_ext.get('.cr2', []) if cr2_files: seq = FileSequence.load(cr2_files) if seq: range_str = ', '.join(['(%d-%d)' % (lo, hi) for lo, hi in seq.ranges]) print('%d image files found: %s' % (len(cr2_files), range_str)) else: print('%d image files found.' % len(cr2_files)) else: raise RuntimeError('No .cr2 image files found') print('') print('Launching darktable...') run_darktable([images_dir])
def run(cls, args): print('Collection: %s' % os.path.basename(args.collection)) print(' Subdir: %s' % (args.subdir or '<ROOT>')) print('') images_dir = os.path.normpath( os.path.join(args.collection, args.subdir)) _, filepaths = collect_dirs_and_files(images_dir) cr2_files = sort_files_by_ext(filepaths).get('.cr2', []) seq = FileSequence.load(cr2_files) if cr2_files else None if not seq: raise RuntimeError('No .cr2 image sequence found') print('Generating image cache...') generate_cache(images_dir) tmp_dirpath = os.path.join(images_dir, '.temp-sides') with temporary_directory(tmp_dirpath): os.startfile(tmp_dirpath) backs_dirpath = os.path.join(tmp_dirpath, 'backs') os.makedirs(backs_dirpath) cr2_filepath_lookup = {} for image_filepath, cached_filepath in image_iterator(images_dir): short_filename = os.path.basename(cached_filepath) cr2_filepath_lookup[short_filename] = image_filepath img = cv2.imread(cached_filepath) img = cv2.resize(img, (img.shape[1] // 4, img.shape[0] // 4)) is_back = None image_params = read_params(image_filepath) if image_params.get('side') in ('front', 'back'): is_back = image_params.get('side') == 'back' print('%s -- loaded -- %s' % (short_filename, 'back' if is_back else 'FRONT')) if is_back is None: corner_size_factor = image_params.get( 'crop_corner_size_factor', CORNER_SIZE_FACTOR) key_range_h = image_params.get('crop_key_range_h', KEY_RANGE_HSV[0]) key_range_s = image_params.get('crop_key_range_S', KEY_RANGE_HSV[1]) key_range_v = image_params.get('crop_key_range_v', KEY_RANGE_HSV[2]) erosion_size = image_params.get('crop_erosion_size', EROSION_SIZE) dilation_size = image_params.get('crop_dilation_size', DILATION_SIZE) mask = get_background_mask( img, corner_size_factor, [key_range_h, key_range_s, key_range_v], erosion_size, dilation_size) alpha = cv2.erode(~mask, np.ones((5, 5), np.uint8), iterations=5) gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) res = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY_INV, 7, 5.0) res = cv2.bitwise_and(res, res, mask=alpha) percentage = np.count_nonzero(res) / np.count_nonzero( alpha) is_back = percentage < args.threshold print('%s -- % 5.1f%% -- %s' % (short_filename, percentage * 100.0, 'back' if is_back else 'FRONT')) dst_dirpath = backs_dirpath if is_back else tmp_dirpath dst_filepath = os.path.join(dst_dirpath, short_filename) cv2.imwrite(dst_filepath, img) print('') print( 'Please check the .temp-sides directory and move any back images to backs folder.' ) print( 'Then check the backs folder and make sure it doesn\'t contain any front images.' ) input('Once all images are sorted, press enter to proceed.') for filename in os.listdir(tmp_dirpath): cr2_filepath = cr2_filepath_lookup.get(filename) if cr2_filepath: print('%s - front' % filename) write_param(cr2_filepath, 'side', 'front') for filename in os.listdir(backs_dirpath): cr2_filepath = cr2_filepath_lookup.get(filename) if cr2_filepath: print('%s - back' % filename) write_param(cr2_filepath, 'side', 'back')
def run(cls, args): print('Collection: %s' % os.path.basename(args.collection)) print(' Subdir: %s' % (args.subdir or '<ROOT>')) print('') images_dir = os.path.normpath(os.path.join(args.collection, args.subdir)) _, filepaths = collect_dirs_and_files(images_dir) filepaths_by_ext = sort_files_by_ext(filepaths) cr2_files = filepaths_by_ext.get('.cr2', []) if cr2_files: seq = FileSequence.load(cr2_files) if seq: range_str = ', '.join(['(%d-%d)' % (lo, hi) for lo, hi in seq.ranges]) print('%d image files found: %s' % (len(cr2_files), range_str)) else: print('%d image files found.' % len(cr2_files)) else: raise RuntimeError('No .cr2 image files found') xmp_files = filepaths_by_ext.get('.cr2.xmp', []) if xmp_files: seq = FileSequence.load(xmp_files) if seq: range_str = ', '.join(['(%d-%d)' % (lo, hi) for lo, hi in seq.ranges]) print('%d sidecar files found: %s' % (len(xmp_files), range_str)) else: print('%d sidecar files found.' % len(xmp_files)) if not args.force: print('') print('These existing sidecar files contain edits made in darktable.') print('Applying these edits will DELETE these previously-made edits.') print('If you\'re sure you want to do that, re-run with -f.') return print('') print('Generating image cache...') generate_cache(images_dir) print('Regenerating .xmp sidecar files...') regenerate_xmps(images_dir) for image_filepath, cached_filepath in image_iterator(images_dir): iops = [] image_params = read_params(image_filepath) try: crop_params = compute_crop_params(cached_filepath, image_params) print ('%s -> %r' % (os.path.basename(image_filepath), crop_params)) iop_clipping = dt_iop_clipping_params_t() iop_clipping.crop_auto = 0 iop_clipping.angle = crop_params['angle'] iop_clipping.cx = crop_params['cx'] iop_clipping.cy = crop_params['cy'] iop_clipping.cw = crop_params['cw'] iop_clipping.ch = crop_params['ch'] iops.append(iop_clipping) except Exception as exc: print('WARNING: Failed to crop %s: %s' % (os.path.basename(image_filepath), exc)) ev_delta = image_params.get('ev_delta') if ev_delta: iop_exposure = dt_iop_exposure_params_t() iop_exposure.exposure = ev_delta iops.append(iop_exposure) xmp_filepath = image_filepath + '.xmp' if iops: edit_xmp(xmp_filepath, iops) print('%s: %s' % (os.path.basename(xmp_filepath), ', '.join([iop.operation for iop in iops]))) else: print('%s: <skipped>' % (os.path.basename(xmp_filepath))) print('Launching darktable. Reimport all changed .xmp files when prompted.') run_darktable([images_dir])
def run(cls, args): print('Collection: %s' % os.path.basename(args.collection)) print(' Subdir: %s' % (args.subdir or '<ROOT>')) print('') images_dir = os.path.normpath(os.path.join(args.collection, args.subdir)) _, filepaths = collect_dirs_and_files(images_dir) cr2_files = sort_files_by_ext(filepaths).get('.cr2', []) seq = FileSequence.load(cr2_files) if cr2_files else None if not seq: raise RuntimeError('No .cr2 image sequence found') cr2_filepath = seq.pattern % (args.image or seq.ranges[0][0]) if not os.path.isfile(cr2_filepath): raise RuntimeError('No such file: %s' % cr2_filepath) cached_filepath = get_cached_image_filepath(cr2_filepath) if not os.path.isfile(cached_filepath): print('Generating cached image...') regenerate_cached_image(cr2_filepath) img = cv2.imread(cached_filepath) img = cv2.resize(img, (img.shape[1] // 4, img.shape[0] // 4)) modes = ['original', 'color key', 'corners'] mode_index = len(modes) - 1 corner_size_factor = read_param(cr2_filepath, 'crop_corner_size_factor', 0.05) key_range_h = read_param(cr2_filepath, 'crop_key_range_h', 4.0) key_range_s = read_param(cr2_filepath, 'crop_key_range_s', 25.0) key_range_v = read_param(cr2_filepath, 'crop_key_range_v', 25.0) erosion_size = read_param(cr2_filepath, 'crop_erosion_size', 5) dilation_size = read_param(cr2_filepath, 'crop_dilation_size', 0) min_line_length_factor = read_param(cr2_filepath, 'crop_min_line_length_factor', 0.005) max_line_gap_factor = read_param(cr2_filepath, 'crop_max_line_gap_factor', 0.001) max_inclination_deg = read_param(cr2_filepath, 'crop_max_inclination_deg', 10.0) line_exclusion_size_factor = read_param(cr2_filepath, 'crop_line_exclusion_size_factor', 0.1) num_clusters = read_param(cr2_filepath, 'crop_num_clusters', 8) cluster_merge_threshold_size_factor = read_param(cr2_filepath, 'crop_cluster_merge_threshold_size_factor', 0.025) inset_interval = read_param(cr2_filepath, 'crop_inset_interval', 1.0) inset_white_threshold = read_param(cr2_filepath, 'crop_inset_white_threshold', 0.0025) extra_inset = read_param(cr2_filepath, 'crop_extra_inset', 8.0) save_prompted = False while True: mode = modes[mode_index] if mode_index >= modes.index('color key'): mask = get_background_mask(img, corner_size_factor, [key_range_h, key_range_s, key_range_v], erosion_size, dilation_size) if mode_index >= modes.index('corners'): rect_corners = find_rectilinear_corners(mask, min_line_length_factor, max_line_gap_factor, max_inclination_deg, line_exclusion_size_factor, num_clusters, cluster_merge_threshold_size_factor) corners = shrink_inside_mask(mask, rect_corners, inset_interval, inset_white_threshold, extra_inset * 0.25) if rect_corners else None base = None if mode == 'original': base = img.copy() elif mode == 'color key': base = cv2.cvtColor(mask, cv2.COLOR_GRAY2RGB) elif mode == 'corners': base = img.copy() if corners: top_left = int(corners[Corner.top_left][0]), int(corners[Corner.top_left][1]) top_right = int(corners[Corner.top_right][0]), int(corners[Corner.top_right][1]) bottom_left = int(corners[Corner.bottom_left][0]), int(corners[Corner.bottom_left][1]) bottom_right = int(corners[Corner.bottom_right][0]), int(corners[Corner.bottom_right][1]) cv2.line(base, top_left, top_right, (255, 192, 192), 2) cv2.line(base, top_right, bottom_right, (255, 192, 192), 2) cv2.line(base, bottom_right, bottom_left, (255, 192, 192), 2) cv2.line(base, bottom_left, top_left, (255, 192, 192), 2) else: raise RuntimeError('Unsupported mode: %s' % mode) height, width = base.shape[0], base.shape[1] long_side = height if height > width else width short_side = width if height > width else height size = min(short_side // 2, max(1, int(long_side * corner_size_factor))) cv2.rectangle(base, (0, 0), (size, size), (255, 192, 128), 1) cv2.rectangle(base, (base.shape[1] - size, 0), (base.shape[1], size), (255, 192, 128), 1) cv2.rectangle(base, (0, base.shape[0] - size), (size, base.shape[0]), (255, 192, 128), 1) cv2.rectangle(base, (base.shape[1] - size, base.shape[0] - size), (base.shape[1], base.shape[0]), (255, 192, 128), 1) mode_labels = [('[%s]' % m) if m == mode else m for m in modes] controls_label(0, 'MODE (,|.): %s' % ' | '.join(mode_labels), base) controls_label(2, 'COLOR KEY:', base) float_control(3, corner_size_factor, 'corner_size', base, 'C') float_control(4, key_range_h, 'key_range_h', base, 'H') float_control(5, key_range_s, 'key_range_s', base, 'S') float_control(6, key_range_v, 'key_range_v', base, 'V') int_control(7, erosion_size, 'erosion_size', base, 'E') int_control(8, dilation_size, 'dilation_size', base, 'D') controls_label(10, 'CORNERS:', base) float_control(11, min_line_length_factor, 'min_line_length_factor', base, 'L') float_control(12, max_line_gap_factor, 'max_line_gap_factor', base, 'G') float_control(13, max_inclination_deg, 'max_inclination_deg', base, 'I') float_control(14, line_exclusion_size_factor, 'line_exclusion_size_factor', base, 'X') int_control(15, num_clusters, 'num_clusters', base, 'N') float_control(16, cluster_merge_threshold_size_factor, 'cluster_merge_threshold', base, 'M') float_control(17, inset_interval, 'inset_interval', base, 'T') float_control(18, inset_white_threshold, 'inset_white_threshold', base, 'W') float_control(19, extra_inset, 'extra_inset', base, 'Z') if save_prompted: draw_text(base, (400, 400), 'Press ENTER to save settings for all images in collection.') draw_text(base, (500, 420), '(Press any other key to cancel.)') cv2.imshow('image', base) key = cv2.waitKeyEx(0) if key in (27, ord('q'), ord('Q')): break if key == 13: if save_prompted: num_saved = 0 for num in seq.numbers: filepath = seq.pattern % num params = read_params(filepath) params['crop_corner_size_factor'] = corner_size_factor params['crop_key_range_h'] = key_range_h params['crop_key_range_s'] = key_range_s params['crop_key_range_v'] = key_range_v params['crop_erosion_size'] = erosion_size params['crop_dilation_size'] = dilation_size params['crop_min_line_length_factor'] = min_line_length_factor params['crop_max_line_gap_factor'] = max_line_gap_factor params['crop_max_inclination_deg'] = max_inclination_deg params['crop_line_exclusion_size_factor'] = line_exclusion_size_factor params['crop_num_clusters'] = num_clusters params['crop_cluster_merge_threshold_size_factor'] = cluster_merge_threshold_size_factor params['crop_inset_interval'] = inset_interval params['crop_inset_white_threshold'] = inset_white_threshold params['crop_extra_inset'] = extra_inset write_params(filepath, params) num_saved += 1 print('Wrote crop params for %d images.' % num_saved) save_prompted = False else: save_prompted = True else: save_prompted = False if key == ord(','): mode_index = (mode_index - 1) if mode_index > 0 else len(modes) - 1 elif key == ord('.'): mode_index = (mode_index + 1) % len(modes) elif is_key(key, 'C'): incr = 0.01 if key == ord('c') else -0.01 corner_size_factor += incr elif is_key(key, 'H'): incr = 0.1 if key == ord('h') else -0.1 key_range_h += incr elif is_key(key, 'S'): incr = 0.1 if key == ord('s') else -0.1 key_range_s += incr elif is_key(key, 'V'): incr = 0.1 if key == ord('v') else -0.1 key_range_v += incr elif is_key(key, 'E'): incr = 1 if key == ord('e') else -1 erosion_size += incr elif is_key(key, 'D'): incr = 1 if key == ord('d') else -1 dilation_size += incr elif is_key(key, 'L'): incr = (1.0 if key == ord('l') else -1.0) * 0.001 min_line_length_factor += incr elif is_key(key, 'G'): incr = (1.0 if key == ord('g') else -1.0) * 0.0001 max_line_gap_factor += incr elif is_key(key, 'I'): incr = (1.0 if key == ord('i') else -1.0) * 1.0 max_inclination_deg += incr elif is_key(key, 'X'): incr = (1.0 if key == ord('x') else -1.0) * 0.1 line_exclusion_size_factor += incr elif is_key(key, 'N'): incr = (1 if key == ord('n') else -1) num_clusters += incr elif is_key(key, 'M'): incr = (1.0 if key == ord('m') else -1.0) * 0.025 cluster_merge_threshold_size_factor += incr elif is_key(key, 'T'): incr = (1.0 if key == ord('t') else -1.0) * 0.1 inset_interval += incr elif is_key(key, 'W'): incr = (1.0 if key == ord('w') else -1.0) * 0.001 inset_white_threshold += incr elif is_key(key, 'Z'): incr = (1.0 if key == ord('z') else -1.0) extra_inset += incr cv2.destroyAllWindows()
def run(cls, args): print('Collection: %s' % os.path.basename(args.collection)) print(' Subdir: %s' % (args.subdir or '<ROOT>')) print('') output_dir = os.path.normpath( os.path.join(args.collection, args.subdir)) start_num = 1 num_prefix = args.subdir[ 0] if args.subdir and args.subdir != 'images' else '' pattern = os.path.join( output_dir, '%s_%s' % (os.path.basename(args.collection), num_prefix)) + '%04d.CR2' print("Writing to: %s" % output_dir) _, filepaths = collect_dirs_and_files(output_dir) existing_cr2_files = sort_files_by_ext(filepaths).get('.cr2') if existing_cr2_files: print('%d existing images:' % len(existing_cr2_files)) seq = FileSequence.load(existing_cr2_files) for range in seq.ranges: print(' - ranging from %d to %d' % range) start_num = range[1] + 1 pattern = seq.pattern else: print('No existing images.') print('') prefix = os.path.basename(pattern[:pattern.rfind(r'%')]) print('Capture will start from:') print(' - %s' % (pattern % start_num)) print('') print('Pre-capture checklist:') print('') print(' 1. Arrange lights.') print(' 2. Disable OIS in lens.') print(' 3. Ensure that lens zoom is not extreme.') print(' 4. Ensure that camera height exceeds minimum macro distance.') print(' 5. Set shooting mode to full manual.') print(' 6. Find appropriate exposure around f/5.6.') print(' 7. Take full-frame photo of 18% gray card.') print(' 8. Set that photo as source for custom white balance.') print(' 9. Set white balance mode to Custom.') print(' 10. Ensure that color-keyable background fills frame.') print('') if not args.force: print('Proceed?') choice = input('> ') if choice.lower() not in ('y', 'yes'): print("Abort.") return wait_for_device() if not os.path.isdir(output_dir): os.makedirs(output_dir) print("Created: %s" % output_dir) close_eos_windows() update_eos_config(output_dir, prefix, start_num) activate_liveview() print( "escape: exit | up/down/left/right: capture | backspace: delete last" ) print("Waiting for input...") image_num = start_num delete_prompted = False for command in interactive_process(__shoot_keys__): if delete_prompted: if command == 'delete': raw_filepath = pattern % (image_num - 1) os.remove(raw_filepath) print('Deleted %s' % raw_filepath) xmp_filepath = raw_filepath + '.xmp' if os.path.isfile(xmp_filepath): os.remove(xmp_filepath) print('Deleted %s' % xmp_filepath) image_num -= 1 close_eos_windows() update_eos_config(output_dir, prefix, image_num) activate_liveview() else: print('Cancelled delete.') delete_prompted = False elif command == 'delete': raw_filepath = pattern % (image_num - 1) if os.path.isfile(raw_filepath): print('Press backspace again to delete %s...' % os.path.basename(raw_filepath)) delete_prompted = True else: print('No such file: %s' % raw_filepath) elif command.startswith('capture'): raw_filepath = pattern % image_num top_edge = command.replace('capture_', '') write_param(raw_filepath, 'top_edge', top_edge) print('%s --> %s' % (raw_filepath, top_edge)) capture_liveview_photo() image_num += 1 print('Finished.') close_eos_windows() print('Turn camera power off to conserve battery.')