def load_masks(images): """ Return a list of boolean land masks. Images must all be from the same station. Arguments: images (iterable): Image objects """ # All images must be from the same station (for now) station = parse_image_path(images[0].path)['station'] pattern = re.compile(station + r'_[0-9]{8}_[0-9]{6}[^\/]*$') is_station = [pattern.search(img.path) is not None for img in images[1:]] assert all(is_station) # Find all station svg with 'land' markup imgsz = images[0].cam.imgsz svg_paths = glob.glob(os.path.join(CG_PATH, 'svg', station + '_*.svg')) markups = [glimpse.svg.parse_svg(path, imgsz=imgsz) for path in svg_paths] land_index = np.where(['land' in markup for markup in markups])[0] if len(land_index) == 0: raise ValueError('No land masks found for station') svg_paths = np.array(svg_paths)[land_index] land_markups = np.array(markups)[land_index] # Select svg files nearest to images, with preference within breaks svg_datetimes = paths_to_datetimes(svg_paths) svg_break_indices = np.array( [_station_break_index(path) for path in svg_paths]) img_datetimes = [img.datetime for img in images] distances = glimpse.helpers.pairwise_distance_datetimes( img_datetimes, svg_datetimes) nearest_index = [] for i, img in enumerate(images): break_index = _station_break_index(img.path) same_break = np.where(break_index == svg_break_indices)[0] if same_break.size > 0: i = same_break[np.argmin(distances[i][same_break])] else: raise ValueError('No mask found within motion breaks for image', i) i = np.argmin(distances[i]) nearest_index.append(i) nearest = np.unique(nearest_index) # Make masks and expand per image without copying masks = [None] * len(images) image_sizes = np.array([img.cam.imgsz for img in images]) sizes = np.unique(image_sizes, axis=0) for i in nearest: polygons = land_markups[i]['land'].values() is_nearest = nearest_index == i for size in sizes: scale = size / imgsz rpolygons = [polygon * scale for polygon in polygons] mask = glimpse.helpers.polygons_to_mask(rpolygons, size=size).astype(np.uint8) mask = sharedmem.copy(mask) for j in np.where(is_nearest & np.all(image_sizes == size, axis=1))[0]: masks[j] = mask return masks
def load_model(camera, svgs=None, keys=None, step=None, group_params=dict(), station_calib=False, camera_calib=False, fixed=None): # Gather motion control motion_images, motion_controls, motion_cam_params = cg.camera_motion_matches( camera, station_calib=station_calib, camera_calib=camera_calib) # Gather svg control svg_images, svg_controls, svg_cam_params = cg.camera_svg_controls( camera, keys=keys, svgs=svgs, correction=True, station_calib=station_calib, camera_calib=camera_calib, step=step) # Standardize image sizes imgszs = np.unique([img.cam.imgsz for img in (motion_images + svg_images)], axis=0) if len(imgszs) > 1: i_max = np.argmax(imgszs[:, 0]) print('Resizing images and controls to', imgszs[i_max]) for control in motion_controls + svg_controls: control.resize(size=imgszs[i_max], force=True) # Set new imgsz as original camera imgsz for img in motion_images + svg_images: img.cam.original_vector[6:8] = imgszs[i_max] # Determine whether xyz can be optimized stations = [cg.parse_image_path(img.path)['station'] for img in svg_images] if fixed is None: if len(stations) > 0 and (np.array(stations) == stations[0]).all(): fixed = cg.Stations()[stations[0]]['properties']['fixed'] else: fixed = True station = None if fixed else stations[0] if station: group_params = glimpse.helpers.merge_dicts(group_params, dict(xyz=True)) model = glimpse.optimize.Cameras( cams=[img.cam for img in motion_images + svg_images], controls=motion_controls + svg_controls, cam_params=motion_cam_params + svg_cam_params, group_params=group_params) return motion_images, svg_images, model, station
# not_small = np.diff(tranges, axis=1) >= min_dt # tranges = tranges[not_small.ravel()] # ranges = ranges[not_small.ravel()] # Save results station_iranges[station] = ranges station_ranges[station] = tranges station_images[station] = images station_datetimes[station] = datetimes # ---- Count dropped images ---- print('--- Image loss (after station filtering) ----') for station in stations: n = len(station_images[station]) x = np.arange(n) nf = np.unique(np.concatenate([x[i:(j + 1)] for i, j in station_iranges[station]])).size dropped = n - nf print(station, dropped, '(' + str(round(100 * dropped / n, 1)) + '%)') # ---- Combine station ranges ---- # coverage, station_coverage # Cut all ranges at all range endpoints ranges = np.vstack([ranges for ranges in station_ranges.values()]) cuts = np.unique(ranges.ravel()) cut_ranges = glimpse.helpers.cut_ranges(ranges, cuts) # Flatten to coverage unique_cut_ranges = np.array(list({tuple(r) for r in cut_ranges})) order = np.lexsort((unique_cut_ranges[:, 1], unique_cut_ranges[:, 0])) coverage = unique_cut_ranges[order] # Append stubs to coverage
print(station, services) images = cg.load_images(station=station, services=services, snap=SNAP, use_exif=False, service_exif=True, anchors=True, viewdir=True, viewdir_as_anchor=True) # Keep only oriented images images = np.array([img for img in images if img.anchor]) # Write animation(s) # HACK: Split at camera changes to use observer.animate() # HACK: Use k1 to quickly differentiate between cameras ks = np.array([img.cam.k[0] for img in images]) for k in np.unique(ks): idx = np.where(ks == k)[0] sizes = np.row_stack([img.cam.imgsz for img in images[idx]]) unique_sizes = np.unique(sizes, axis=0) if len(unique_sizes) > 1: # Standardize image sizes size = unique_sizes.min(axis=0) not_size = np.any(sizes != size, axis=1) f = images[~not_size][0].cam.f for img in images[not_size]: img.cam.resize(size, force=True) # HACK: Fix focal length rounding errors if any(img.cam.f - f > 0.1): raise ValueError('Focal lengths cannot be reconciled') img.cam.f = f observer = glimpse.Observer(images[idx], cache=False).subset(snap=snap)
diagonal_neighbors = False # Whether to include diagonal neighbors min_observers = 2 # Median ignores neighbors with observers below min if any above min fill_missing = False # Whether to fill missing points with neighborhood median nan_median = True # Whether to ignore missing values in computing median exact_median_variance = False # Whether to use "exact" variance of median calculation (when averaging nearest neighbors) # ---- Load medatada ---- # basenames: <date>.<interval id> # template: # ids: Unique point ids (flat index of points in template raster) # Load tracks basenames paths = glob.glob(os.path.join(tracks_path, '*.pkl')) basepaths = [glimpse.helpers.strip_path(path) for path in paths] basenames = np.unique([ re.sub(r"-[^-]+$", '', path) for path in basepaths if re.search(r"[0-9]+-.+$", path) is not None ]) track_ids = [int(re.sub(r"^[0-9]+-", '', basename)) for basename in basenames] # Load template template = glimpse.Raster.read(os.path.join(points_path, 'template.tif')) # Load ids of all tracked points ids = [] for basename in basenames: points = glimpse.helpers.read_pickle( os.path.join(points_path, basename + '.pkl')) ids = np.union1d(ids, points['ids']).astype(int) # ---- Build arrays ---- # xyi: [ x | y | point_id ]
def camera_svg_controls(camera, size=1, force_size=False, keys=None, svgs=None, correction=True, step=None, station_calib=False, camera_calib=False, synth=True): """ Return all SVG control objects available for a camera. Arguments: camera (str): Camera identifer size: Image scale factor (number) or image size in pixels (nx, ny) force_size (bool): Whether to force `size` even if different aspect ratio than original size. keys (iterable): SVG layers to include svgs (iterable): SVG basenames to include correction: Whether control objects should use elevation correction (bool) or arguments to `glimpse.helpers.elevation_corrections()` station_calib (bool): Whether to load station calibration. If `False`, falls back to the station estimate. camera_calib (bool): Whether to load camera calibrations. If `False`, falls back to the EXIF estimate. Returns: list: Image objects list: Control objects (Points, Lines) list: Per-camera calibration parameters [{'viewdir': True}, ...] """ paths = glob.glob(os.path.join(CG_PATH, 'svg', '*.svg')) if synth: paths += glob.glob(os.path.join(CG_PATH, 'svg-synth', '*.pkl')) paths += glob.glob(os.path.join(CG_PATH, 'svg-synth', '*.svg')) basenames = np.unique([glimpse.helpers.strip_path(path) for path in paths]) images, controls, cam_params = [], [], [] for basename in basenames: ids = parse_image_path(basename, sequence=True) if ids['camera'] == camera: calibration = load_calibrations(basename, camera=camera_calib, station=station_calib, station_estimate=not station_calib, merge=True) img_path = find_image(basename) img = glimpse.Image(img_path, cam=calibration) control = [] if svgs is None or basename in svgs: control += svg_controls(img, keys=keys, correction=correction, step=step) if synth: control += synth_controls(img, step=None, directions=False) if control: for x in control: x.resize(size, force=force_size) images.append(img) controls.extend(control) cam_params.append(dict(viewdir=True)) return images, controls, cam_params
def load_images(station, services, use_exif=False, service_exif=False, anchors=False, viewdir=True, viewdir_as_anchor=False, file_errors=True, **kwargs): """ Return list of calibrated Image objects. Any available station, camera, image, and viewdir calibrations are loaded and images with image calibrations are marked as anchors. Arguments: station (str): Station identifier services (iterable): Service identifiers use_exif (bool): Whether to parse image datetimes from EXIF (slower) rather than parsed from paths (faster) service_exif (bool): Whether to extract EXIF from first image (faster) or all images (slower) in service. If `True`, `Image.datetime` is parsed from path. Always `False` if `use_exif=True`. anchors (bool): Whether to include anchor images even if filtered out by `kwargs['snap']` **kwargs: Arguments to `glimpse.helpers.select_datetimes()` """ if use_exif: service_exif = False # Sort services in time if isinstance(services, str): services = services, services = np.sort(services) # Parse datetimes of all candidate images paths_service = [ glob.glob( os.path.join(IMAGE_PATH, station, station + '_' + service, '*.JPG')) for service in services ] paths = np.hstack(paths_service) basenames = [glimpse.helpers.strip_path(path) for path in paths] if use_exif: exifs = [glimpse.Exif(path) for path in paths] datetimes = np.array([exif.datetime for exif in exifs]) else: datetimes = paths_to_datetimes(basenames) # Select images based on datetimes indices = glimpse.helpers.select_datetimes(datetimes, **kwargs) if anchors: # Add anchors # HACK: Ignore any <image>-<suffix>.json files anchor_paths = glob.glob( os.path.join(CG_PATH, 'images', station + '_*[0-9].json')) anchor_basenames = [ glimpse.helpers.strip_path(path) for path in anchor_paths ] if 'start' in kwargs or 'end' in kwargs: # Filter by start, end anchor_datetimes = np.asarray(paths_to_datetimes(anchor_basenames)) inrange = glimpse.helpers.select_datetimes( anchor_datetimes, **glimpse.helpers.merge_dicts(kwargs, dict(snap=None))) anchor_basenames = np.asarray(anchor_basenames)[inrange] anchor_indices = np.where(np.isin(basenames, anchor_basenames))[0] indices = np.unique(np.hstack((indices, anchor_indices))) service_breaks = np.hstack((0, np.cumsum([len(x) for x in paths_service]))) station_calibration = load_calibrations(station_estimate=station, station=station, merge=True, file_errors=False) images = [] for i, service in enumerate(services): index = indices[(indices >= service_breaks[i]) & (indices < service_breaks[i + 1])] if not index.size: continue service_calibration = glimpse.helpers.merge_dicts( station_calibration, load_calibrations(path=paths[index[0]], camera=True, merge=True, file_errors=file_errors)) if service_exif: exif = glimpse.Exif(paths[index[0]]) for j in index: basename = basenames[j] calibrations = load_calibrations( image=basename, viewdir=basename if viewdir else False, station_estimate=station, merge=False, file_errors=False) if calibrations['image']: calibration = glimpse.helpers.merge_dicts( service_calibration, calibrations['image']) anchor = True else: calibration = glimpse.helpers.merge_dicts( service_calibration, dict(viewdir=calibrations['station_estimate']['viewdir'])) anchor = False if viewdir and calibrations['viewdir']: calibration = glimpse.helpers.merge_dicts( calibration, calibrations['viewdir']) if viewdir_as_anchor: anchor = True if use_exif: exif = exifs[j] elif not service_exif: exif = None if KEYPOINT_PATH: keypoint_path = os.path.join(KEYPOINT_PATH, basename + '.pkl') else: keypoint_path = None image = glimpse.Image(path=paths[j], cam=calibration, anchor=anchor, exif=exif, datetime=None if use_exif else datetimes[j], keypoints_path=keypoint_path) images.append(image) return images