def get_modified_coordinates1d(self, coords, dim): """Returns the expanded coordinates for the requested dimension, depending on the expansion parameter for the given dimension. Parameters ---------- dim : str Dimension to expand Returns ------- expanded : Coordinates1d Expanded coordinates """ coords1d = coords[dim] expansion = getattr(self, dim) if not expansion: # i.e. if list is empty # no expansion in this dimension return coords1d if len(expansion) == 2: # use available native coordinates dstart = make_coord_delta(expansion[0]) dstop = make_coord_delta(expansion[1]) available_coordinates = self.coordinates_source.find_coordinates() if len(available_coordinates) != 1: raise ValueError( "Cannot implicity expand coordinates; too many available coordinates" ) acoords = available_coordinates[0][dim] cs = [ acoords.select((add_coord(x, dstart), add_coord(x, dstop))) for x in coords1d.coordinates ] elif len(expansion) == 3: # use a explicit step size dstart = make_coord_delta(expansion[0]) dstop = make_coord_delta(expansion[1]) step = make_coord_delta(expansion[2]) cs = [ UniformCoordinates1d(add_coord(x, dstart), add_coord(x, dstop), step) for x in coords1d.coordinates ] else: raise ValueError("Invalid expansion attrs for '%s'" % dim) return ArrayCoordinates1d(np.concatenate([c.coordinates for c in cs]), **coords1d.properties)
def get_modified_coordinates1d(self, coord, dim): """ Get the desired 1d coordinates for the given dimension, depending on the selection attr for the given dimension:: Parameters ---------- coords : Coordinates The requested input coordinates dim : str Dimension for doing the selection Returns ------- coords1d : ArrayCoordinates1d The selected coordinates for the given dimension. """ if dim != "time": return coord[dim] times = coord["time"] delta = np.datetime64(self.year) new_times = [ add_coord(c, delta - c.astype("datetime64[Y]")) for c in times.coordinates ] return ArrayCoordinates1d(new_times, name="time")
def _eval(self, coordinates, output=None, _selector=None): """Evaluates this nodes using the supplied coordinates. Parameters ---------- coordinates : podpac.Coordinates {requested_coordinates} output : podpac.UnitsDataArray, optional {eval_output} _selector: callable(coordinates, request_coordinates) {eval_selector} Returns ------- {eval_return} """ # The size of this kernel is used to figure out the expanded size full_kernel = self.kernel # expand the coordinates # The next line effectively drops extra coordinates, so we have to add those later in case the # source is some sort of reduction Node. kernel_dims = [kd for kd in coordinates.dims if kd in self.kernel_dims] missing_dims = [kd for kd in coordinates.dims if kd not in self.kernel_dims] exp_coords = [] exp_slice = [] for dim in kernel_dims: coord = coordinates[dim] s = full_kernel.shape[self.kernel_dims.index(dim)] if s == 1 or not isinstance(coord, (UniformCoordinates1d, ArrayCoordinates1d)): exp_coords.append(coord) exp_slice.append(slice(None)) continue if isinstance(coord, UniformCoordinates1d): s_start = -s // 2 s_end = max(s // 2 - ((s + 1) % 2), 1) # The 1e-14 is for floating point error because if endpoint is slightly # in front of step * N then the endpoint is excluded # ALSO: MUST use size instead of step otherwise floating point error # makes the xarray arrays not align. The following HAS to be true: # np.diff(coord.coordinates).mean() == coord.step exp_coords.append( UniformCoordinates1d( add_coord(coord.start, s_start * coord.step), add_coord(coord.stop, s_end * coord.step + 1e-14 * coord.step), size=coord.size - s_start + s_end, # HAVE to use size, see note above **coord.properties ) ) exp_slice.append(slice(-s_start, -s_end)) elif isinstance(coord, ArrayCoordinates1d): if not coord.is_monotonic or coord.size < 2: exp_coords.append(coord) exp_slice.append(slice(None)) continue arr_coords = coord.coordinates delta_start = arr_coords[1] - arr_coords[0] extra_start = np.arange(arr_coords[0] - delta_start * (s // 2), arr_coords[0], delta_start) delta_end = arr_coords[-1] - arr_coords[-2] # The 1e-14 is for floating point error to make sure endpoint is included extra_end = np.arange( arr_coords[-1] + delta_end, arr_coords[-1] + delta_end * (s // 2) + delta_end * 1e-14, delta_end ) arr_coords = np.concatenate([extra_start, arr_coords, extra_end]) exp_coords.append(ArrayCoordinates1d(arr_coords, **coord.properties)) exp_slice.append(slice(extra_start.size, -extra_end.size)) # Add missing dims back in -- this is needed in case the source is a reduce node. exp_coords += [coordinates[d] for d in missing_dims] # Create expanded coordinates exp_slice = tuple(exp_slice) expanded_coordinates = Coordinates(exp_coords, crs=coordinates.crs, validate_crs=False) if settings["DEBUG"]: self._expanded_coordinates = expanded_coordinates # evaluate source using expanded coordinates, convolve, and then slice out original coordinates source = self.source.eval(expanded_coordinates, _selector=_selector) kernel_dims_u = kernel_dims kernel_dims = self.kernel_dims sum_dims = [d for d in kernel_dims if d not in source.dims] # Sum out the extra dims full_kernel = full_kernel.sum(axis=tuple([kernel_dims.index(d) for d in sum_dims])) exp_slice = [exp_slice[i] for i in range(len(kernel_dims_u)) if kernel_dims_u[i] not in sum_dims] kernel_dims = [d for d in kernel_dims if d in source.dims] # Put the kernel axes in the correct order # The (if d in kernel_dims) takes care of "output", which can be optionally present full_kernel = full_kernel.transpose([kernel_dims.index(d) for d in source.dims if (d in kernel_dims)]) # Check for extra dimensions in the source and reshape the kernel appropriately if any([d not in kernel_dims for d in source.dims if d != "output"]): new_axis = [] new_exp_slice = [] for d in source.dims: if d in kernel_dims: new_axis.append(slice(None)) new_exp_slice.append(exp_slice[kernel_dims.index(d)]) else: new_axis.append(None) new_exp_slice.append(slice(None)) full_kernel = full_kernel[new_axis] exp_slice = new_exp_slice if np.any(np.isnan(source)): method = "direct" else: method = "auto" if ("output" not in source.dims) or ("output" in source.dims and "output" in kernel_dims): result = scipy.signal.convolve(source, full_kernel, mode="same", method=method) else: # source with multiple outputs result = np.stack( [ scipy.signal.convolve(source.sel(output=output), full_kernel, mode="same", method=method) for output in source.coords["output"] ], axis=source.dims.index("output"), ) result = result[exp_slice] if output is None: missing_dims = [d for d in coordinates.dims if d not in source.dims] output = self.create_output_array(coordinates.drop(missing_dims), data=result) else: output[:] = result return output
def _eval(self, coordinates, output=None, _selector=None): """Evaluates this nodes using the supplied coordinates. Parameters ---------- coordinates : podpac.Coordinates {requested_coordinates} output : podpac.UnitsDataArray, optional {eval_output} _selector: callable(coordinates, request_coordinates) {eval_selector} Returns ------- {eval_return} """ # The size of this kernel is used to figure out the expanded size full_kernel = self._get_full_kernel(coordinates) # expand the coordinates # The next line effectively drops extra coordinates, so we have to add those later in case the # source is some sort of reduction Node. kernel_dims = [kd for kd in coordinates.dims if kd in self.kernel_dims] missing_dims = [ kd for kd in coordinates.dims if kd not in self.kernel_dims ] exp_coords = [] exp_slice = [] for dim in kernel_dims: coord = coordinates[dim] s = full_kernel.shape[self.kernel_dims.index(dim)] if s == 1 or not isinstance(coord, UniformCoordinates1d): exp_coords.append(coord) exp_slice.append(slice(None)) continue s_start = -s // 2 s_end = max(s // 2 - ((s + 1) % 2), 1) # The 1e-07 is for floating point error because if endpoint is slightly # in front of step * N then the endpoint is excluded exp_coords.append( UniformCoordinates1d( add_coord(coord.start, s_start * coord.step), add_coord(coord.stop, s_end * coord.step + 1e-07 * coord.step), coord.step, **coord.properties)) exp_slice.append(slice(-s_start, -s_end)) # Add missing dims back in -- this is needed in case the source is a reduce node. exp_coords += [coordinates[d] for d in missing_dims] # exp_slice += [slice(None) for d in missing_dims] # Create expanded coordinates exp_slice = tuple(exp_slice) expanded_coordinates = Coordinates(exp_coords, crs=coordinates.crs, validate_crs=False) if settings["DEBUG"]: self._expanded_coordinates = expanded_coordinates # evaluate source using expanded coordinates, convolve, and then slice out original coordinates source = self.source.eval(expanded_coordinates, _selector=_selector) # Check dimensions if any([d not in kernel_dims for d in source.dims if d != "output"]): raise ValueError( "Kernel dims must contain all of the dimensions in source but not all of {} is in kernel_dims={}" .format(source.dims, kernel_dims)) full_kernel = self._get_full_kernel(coordinates) kernel_dims = self.kernel_dims sum_dims = [d for d in kernel_dims if d not in source.dims] # Sum out the extra dims full_kernel = full_kernel.sum( axis=tuple([kernel_dims.index(d) for d in sum_dims])) kernel_dims = [d for d in kernel_dims if d in source.dims] # Put the kernel axes in the correct order # The (if d in kernel_dims) takes care of "output", which can be optionally present full_kernel = full_kernel.transpose( [kernel_dims.index(d) for d in source.dims if (d in kernel_dims)]) if np.any(np.isnan(source)): method = "direct" else: method = "auto" if ("output" not in source.dims) or ("output" in source.dims and "output" in kernel_dims): result = scipy.signal.convolve(source, full_kernel, mode="same", method=method) else: # source with multiple outputs result = np.stack( [ scipy.signal.convolve(source.sel(output=output), full_kernel, mode="same", method=method) for output in source.coords["output"] ], axis=source.dims.index("output"), ) result = result[exp_slice] if output is None: missing_dims = [ d for d in coordinates.dims if d not in source.dims ] output = self.create_output_array(coordinates.drop(missing_dims), data=result) else: output[:] = result return output
def eval(self, coordinates, output=None): """Evaluates this nodes using the supplied coordinates. Parameters ---------- coordinates : podpac.Coordinates {requested_coordinates} output : podpac.UnitsDataArray, optional {eval_output} Returns ------- {eval_return} """ # This should be aligned with coordinates' dimension order # The size of this kernel is used to figure out the expanded size self._full_kernel = self.get_full_kernel(coordinates) if len(self._full_kernel.shape) != len(coordinates.shape): raise ValueError( "shape mismatch, kernel does not match source data (%s != %s)" % (self._full_kernel.shape, coordinates.shape)) # expand the coordinates exp_coords = [] exp_slice = [] for dim, s in zip(coordinates.dims, self._full_kernel.shape): coord = coordinates[dim] if s == 1 or not isinstance(coord, UniformCoordinates1d): exp_coords.append(coord) exp_slice.append(slice(None)) continue s_start = -s // 2 s_end = s // 2 - ((s + 1) % 2) # The 1e-07 is for floating point error because if endpoint is slightly # in front of step * N then the endpoint is excluded exp_coords.append( UniformCoordinates1d( add_coord(coord.start, s_start * coord.step), add_coord(coord.stop, s_end * coord.step + 1e-07 * coord.step), coord.step, **coord.properties)) exp_slice.append(slice(-s_start, -s_end)) exp_slice = tuple(exp_slice) self._expanded_coordinates = Coordinates(exp_coords) # evaluate source using expanded coordinates, convolve, and then slice out original coordinates source = self.source.eval(self._expanded_coordinates) if np.any(np.isnan(source)): method = 'direct' else: method = 'auto' result = scipy.signal.convolve(source, self._full_kernel, mode='same', method=method) result = result[exp_slice] if output is None: output = self.create_output_array(coordinates, data=result) else: output[:] = result return output