def load_kd_bricks(self,fn=None): if fn is None: fn = '%s_kd_bricks.h5' % self.ds if self.comm.rank != 0: self.comm.recv_array(self.comm.rank-1, tag=self.comm.rank-1) try: f = h5py.File(fn,"a") for node in self.tree.depth_traverse(): i = node.node_id if node.grid != -1: data = [f["brick_%s_%s" % (hex(i), field)][:].astype('float64') for field in self.fields] node.data = PartitionedGrid(node.grid.id, data, node.l_corner.copy(), node.r_corner.copy(), node.dims.astype('int64')) self.bricks.append(node.data) self.brick_dimensions.append(node.dims) self.bricks = np.array(self.bricks) self.brick_dimensions = np.array(self.brick_dimensions) self._initialized=True f.close() del f except: pass if self.comm.rank != (self.comm.size-1): self.comm.send_array([0],self.comm.rank+1, tag=self.comm.rank)
def load_kd_bricks(self, fn=None): if fn is None: fn = f"{self.ds}_kd_bricks.h5" if self.comm.rank != 0: self.comm.recv_array(self.comm.rank - 1, tag=self.comm.rank - 1) try: f = h5py.File(fn, mode="a") for node in self.tree.depth_traverse(): i = node.node_id if node.grid != -1: data = [ f[f"brick_{hex(i)}_{field}"][:].astype("float64") for field in self.fields ] node.data = PartitionedGrid( node.grid.id, data, node.l_corner.copy(), node.r_corner.copy(), node.dims.astype("int64"), ) self.bricks.append(node.data) self.brick_dimensions.append(node.dims) self.bricks = np.array(self.bricks) self.brick_dimensions = np.array(self.brick_dimensions) self._initialized = True f.close() del f except Exception: pass if self.comm.rank != (self.comm.size - 1): self.comm.send_array([0], self.comm.rank + 1, tag=self.comm.rank)
def render(self, camera, zbuffer=None): """Renders an image using the provided camera Parameters ---------- camera: :class:`yt.visualization.volume_rendering.camera.Camera` instance A volume rendering camera. Can be any type of camera. zbuffer: :class:`yt.visualization.volume_rendering.zbuffer_array.Zbuffer` instance # noqa: E501 A zbuffer array. This is used for opaque sources to determine the z position of the source relative to other sources. Only useful if you are manually calling render on multiple sources. Scene.render uses this internally. Returns ------- A :class:`yt.data_objects.image_array.ImageArray` instance containing the rendered image. """ self.zbuffer = zbuffer self.set_sampler(camera) if self.sampler is None: raise RuntimeError( "No sampler set. This is likely a bug as it should never happen." ) data = self.data_source dx = data["dx"].to("unitary").value[:, None] xyz = np.stack([data[_].to("unitary").value for _ in "x y z".split()], axis=-1) LE = xyz - dx / 2 RE = xyz + dx / 2 mylog.debug("Gathering data") dt = np.stack(list(self.volume.data) + [*LE.T, *RE.T], axis=-1).reshape(1, len(dx), 14, 1) mask = np.full(dt.shape[1:], 1, dtype=np.uint8) dims = np.array([1, 1, 1], dtype="int64") pg = PartitionedGrid(0, dt, mask, LE.flatten(), RE.flatten(), dims, n_fields=1) mylog.debug("Casting rays") self.sampler(pg, oct=self.volume.octree) mylog.debug("Done casting rays") self.current_image = self.finalize_image(camera, self.sampler.aimage) if zbuffer is None: self.zbuffer = ZBuffer( self.current_image, np.full(self.current_image.shape[:2], np.inf)) return self.current_image
def identify_contours(data_source, field, min_val, max_val, cached_fields=None): tree = ContourTree() gct = TileContourTree(min_val, max_val) total_contours = 0 contours = {} node_ids = [] DLE = data_source.ds.domain_left_edge masks = dict((g.id, m) for g, m in data_source.blocks) for (g, node, (sl, dims, gi)) in data_source.tiles.slice_traverse(): g.field_parameters.update(data_source.field_parameters) node.node_ind = len(node_ids) nid = node.node_id node_ids.append(nid) values = g[field][sl].astype("float64") contour_ids = np.zeros(dims, "int64") - 1 mask = masks[g.id][sl].astype("uint8") total_contours += gct.identify_contours(values, contour_ids, mask, total_contours) new_contours = tree.cull_candidates(contour_ids) tree.add_contours(new_contours) # Now we can create a partitioned grid with the contours. LE = (DLE + g.dds * gi).in_units("code_length").ndarray_view() RE = LE + (dims * g.dds).in_units("code_length").ndarray_view() pg = PartitionedGrid(g.id, [contour_ids.view("float64")], mask, LE, RE, dims.astype("int64")) contours[nid] = (g.Level, node.node_ind, pg, sl) node_ids = np.array(node_ids).astype("int64") if node_ids.size == 0: return 0, {} trunk = data_source.tiles.tree.trunk mylog.info("Linking node (%s) contours.", len(contours)) link_node_contours(trunk, contours, tree, node_ids) mylog.info("Linked.") #joins = tree.cull_joins(bt) #tree.add_joins(joins) joins = tree.export() contour_ids = defaultdict(list) pbar = get_pbar("Updating joins ... ", len(contours)) final_joins = np.unique(joins[:, 1]) for i, nid in enumerate(sorted(contours)): level, node_ind, pg, sl = contours[nid] ff = pg.my_data[0].view("int64") update_joins(joins, ff, final_joins) contour_ids[pg.parent_grid_id].append((sl, ff)) pbar.update(i) pbar.finish() rv = dict() rv.update(contour_ids) # NOTE: Because joins can appear in both a "final join" and a subsequent # "join", we can't know for sure how many unique joins there are without # checking if no cells match or doing an expensive operation checking for # the unique set of final join values. return final_joins.size, rv
def get_brick_data(self, node): if node.data is not None and not node.dirty: return node.data grid = self.ds.index.grids[node.grid - self._id_offset] dds = grid.dds.ndarray_view() gle = grid.LeftEdge.ndarray_view() nle = node.get_left_edge() nre = node.get_right_edge() li = np.rint((nle - gle) / dds).astype("int32") ri = np.rint((nre - gle) / dds).astype("int32") dims = (ri - li).astype("int32") assert np.all(grid.LeftEdge <= nle) assert np.all(grid.RightEdge >= nre) if grid in self.current_saved_grids and not node.dirty: dds = self.current_vcds[self.current_saved_grids.index(grid)] else: dds = [] vcd = grid.get_vertex_centered_data( self.fields, smoothed=True, no_ghost=self.no_ghost ) for i, field in enumerate(self.fields): if self.log_fields[i]: dds.append(np.log10(vcd[field].astype("float64"))) else: dds.append(vcd[field].astype("float64")) self.current_saved_grids.append(grid) self.current_vcds.append(dds) if self.data_source.selector is None: mask = np.ones(dims, dtype="uint8") else: mask = self.data_source.selector.fill_mask(grid)[ li[0] : ri[0], li[1] : ri[1], li[2] : ri[2] ].astype("uint8") data = [ d[li[0] : ri[0] + 1, li[1] : ri[1] + 1, li[2] : ri[2] + 1].copy() for d in dds ] brick = PartitionedGrid( grid.id, data, mask, nle.copy(), nre.copy(), dims.astype("int64") ) node.data = brick node.dirty = False if not self._initialized: self.brick_dimensions.append(dims) return brick
def off_axis_projection( data_source, center, normal_vector, width, resolution, item, weight=None, volume=None, no_ghost=False, interpolated=False, north_vector=None, num_threads=1, method="integrate", ): r"""Project through a dataset, off-axis, and return the image plane. This function will accept the necessary items to integrate through a volume at an arbitrary angle and return the integrated field of view to the user. Note that if a weight is supplied, it will multiply the pre-interpolated values together, then create cell-centered values, then interpolate within the cell to conduct the integration. Parameters ---------- data_source : ~yt.data_objects.static_output.Dataset or ~yt.data_objects.data_containers.YTSelectionDataContainer This is the dataset or data object to volume render. center : array_like The current 'center' of the view port -- the focal point for the camera. normal_vector : array_like The vector between the camera position and the center. width : float or list of floats The current width of the image. If a single float, the volume is cubical, but if not, it is left/right, top/bottom, front/back resolution : int or list of ints The number of pixels in each direction. item: string The field to project through the volume weight : optional, default None If supplied, the field will be pre-multiplied by this, then divided by the integrated value of this field. This returns an average rather than a sum. volume : `yt.extensions.volume_rendering.AMRKDTree`, optional The volume to ray cast through. Can be specified for finer-grained control, but otherwise will be automatically generated. no_ghost: bool, optional Optimization option. If True, homogenized bricks will extrapolate out from grid instead of interpolating from ghost zones that have to first be calculated. This can lead to large speed improvements, but at a loss of accuracy/smoothness in resulting image. The effects are less notable when the transfer function is smooth and broad. Default: True interpolated : optional, default False If True, the data is first interpolated to vertex-centered data, then tri-linearly interpolated along the ray. Not suggested for quantitative studies. north_vector : optional, array_like, default None A vector that, if specified, restricts the orientation such that the north vector dotted into the image plane points "up". Useful for rotations num_threads: integer, optional, default 1 Use this many OpenMP threads during projection. method : string The method of projection. Valid methods are: "integrate" with no weight_field specified : integrate the requested field along the line of sight. "integrate" with a weight_field specified : weight the requested field by the weighting field and integrate along the line of sight. "sum" : This method is the same as integrate, except that it does not multiply by a path length when performing the integration, and is just a straight summation of the field along the given axis. WARNING: This should only be used for uniform resolution grid datasets, as other datasets may result in unphysical images. or camera movements. Returns ------- image : array An (N,N) array of the final integrated values, in float64 form. Examples -------- >>> image = off_axis_projection(ds, [0.5, 0.5, 0.5], [0.2,0.3,0.4], ... 0.2, N, "temperature", "density") >>> write_image(np.log10(image), "offaxis.png") """ if method not in ("integrate", "sum"): raise NotImplementedError( "Only 'integrate' or 'sum' methods are valid for off-axis-projections" ) if interpolated: raise NotImplementedError( "Only interpolated=False methods are currently implemented " "for off-axis-projections") data_source = data_source_or_all(data_source) item = data_source._determine_fields([item])[0] # Assure vectors are numpy arrays as expected by cython code normal_vector = np.array(normal_vector, dtype="float64") if north_vector is not None: north_vector = np.array(north_vector, dtype="float64") # Add the normal as a field parameter to the data source # so line of sight fields can use it data_source.set_field_parameter("axis", normal_vector) # Sanitize units if not hasattr(center, "units"): center = data_source.ds.arr(center, "code_length") if not hasattr(width, "units"): width = data_source.ds.arr(width, "code_length") if hasattr(data_source.ds, "_sph_ptypes"): if method != "integrate": raise NotImplementedError("SPH Only allows 'integrate' method") sph_ptypes = data_source.ds._sph_ptypes fi = data_source.ds.field_info[item] raise_error = False ptype = sph_ptypes[0] ppos = [f"particle_position_{ax}" for ax in "xyz"] # Assure that the field we're trying to off-axis project # has a field type as the SPH particle type or if the field is an # alias to an SPH field or is a 'gas' field if item[0] in data_source.ds.known_filters: if item[0] not in sph_ptypes: raise_error = True else: ptype = item[0] ppos = ["x", "y", "z"] elif fi.alias_field: if fi.alias_name[0] not in sph_ptypes: raise_error = True elif item[0] != "gas": ptype = item[0] else: if fi.name[0] not in sph_ptypes and fi.name[0] != "gas": raise_error = True if raise_error: raise RuntimeError( "Can only perform off-axis projections for SPH fields, " "Received '%s'" % (item, )) normal = np.array(normal_vector) normal = normal / np.linalg.norm(normal) # If north_vector is None, we set the default here. # This is chosen so that if normal_vector is one of the # cartesian coordinate axes, the projection will match # the corresponding on-axis projection. if north_vector is None: vecs = np.identity(3) t = np.cross(vecs, normal).sum(axis=1) ax = t.argmax() east_vector = np.cross(vecs[ax, :], normal).ravel() north = np.cross(normal, east_vector).ravel() else: north = np.array(north_vector) north = north / np.linalg.norm(north) east_vector = np.cross(north, normal).ravel() # if weight is None: buf = np.zeros((resolution[0], resolution[1]), dtype="float64") x_min = center[0] - width[0] / 2 x_max = center[0] + width[0] / 2 y_min = center[1] - width[1] / 2 y_max = center[1] + width[1] / 2 z_min = center[2] - width[2] / 2 z_max = center[2] + width[2] / 2 finfo = data_source.ds.field_info[item] ounits = finfo.output_units bounds = [x_min, x_max, y_min, y_max, z_min, z_max] if weight is None: for chunk in data_source.chunks([], "io"): off_axis_projection_SPH( chunk[ptype, ppos[0]].to("code_length").d, chunk[ptype, ppos[1]].to("code_length").d, chunk[ptype, ppos[2]].to("code_length").d, chunk[ptype, "mass"].to("code_mass").d, chunk[ptype, "density"].to("code_density").d, chunk[ptype, "smoothing_length"].to("code_length").d, bounds, center.to("code_length").d, width.to("code_length").d, chunk[item].in_units(ounits), buf, normal_vector, north, ) # Assure that the path length unit is in the default length units # for the dataset by scaling the units of the smoothing length path_length_unit = data_source.ds._get_field_info( (ptype, "smoothing_length")).units path_length_unit = Unit(path_length_unit, registry=data_source.ds.unit_registry) default_path_length_unit = data_source.ds.unit_system["length"] buf *= data_source.ds.quan( 1, path_length_unit).in_units(default_path_length_unit) item_unit = data_source.ds._get_field_info(item).units item_unit = Unit(item_unit, registry=data_source.ds.unit_registry) funits = item_unit * default_path_length_unit else: # if there is a weight field, take two projections: # one of field*weight, the other of just weight, and divide them weight_buff = np.zeros((resolution[0], resolution[1]), dtype="float64") wounits = data_source.ds.field_info[weight].output_units for chunk in data_source.chunks([], "io"): off_axis_projection_SPH( chunk[ptype, ppos[0]].to("code_length").d, chunk[ptype, ppos[1]].to("code_length").d, chunk[ptype, ppos[2]].to("code_length").d, chunk[ptype, "mass"].to("code_mass").d, chunk[ptype, "density"].to("code_density").d, chunk[ptype, "smoothing_length"].to("code_length").d, bounds, center.to("code_length").d, width.to("code_length").d, chunk[item].in_units(ounits), buf, normal_vector, north, weight_field=chunk[weight].in_units(wounits), ) for chunk in data_source.chunks([], "io"): off_axis_projection_SPH( chunk[ptype, ppos[0]].to("code_length").d, chunk[ptype, ppos[1]].to("code_length").d, chunk[ptype, ppos[2]].to("code_length").d, chunk[ptype, "mass"].to("code_mass").d, chunk[ptype, "density"].to("code_density").d, chunk[ptype, "smoothing_length"].to("code_length").d, bounds, center.to("code_length").d, width.to("code_length").d, chunk[weight].to(wounits), weight_buff, normal_vector, north, ) normalization_2d_utility(buf, weight_buff) item_unit = data_source.ds._get_field_info(item).units item_unit = Unit(item_unit, registry=data_source.ds.unit_registry) funits = item_unit myinfo = { "field": item, "east_vector": east_vector, "north_vector": north_vector, "normal_vector": normal_vector, "width": width, "units": funits, "type": "SPH smoothed projection", } return ImageArray(buf, funits, registry=data_source.ds.unit_registry, info=myinfo) sc = Scene() data_source.ds.index if item is None: field = data_source.ds.field_list[0] mylog.info("Setting default field to %s", field.__repr__()) funits = data_source.ds._get_field_info(item).units vol = KDTreeVolumeSource(data_source, item) vol.num_threads = num_threads if weight is None: vol.set_field(item) else: # This is a temporary field, which we will remove at the end. weightfield = ("index", "temp_weightfield") def _make_wf(f, w): def temp_weightfield(a, b): tr = b[f].astype("float64") * b[w] return tr.d return temp_weightfield data_source.ds.field_info.add_field(weightfield, sampling_type="cell", function=_make_wf(item, weight)) # Now we have to tell the dataset to add it and to calculate # its dependencies.. deps, _ = data_source.ds.field_info.check_derived_fields([weightfield]) data_source.ds.field_dependencies.update(deps) vol.set_field(weightfield) vol.set_weight_field(weight) ptf = ProjectionTransferFunction() vol.set_transfer_function(ptf) camera = sc.add_camera(data_source) camera.set_width(width) if not is_sequence(resolution): resolution = [resolution] * 2 camera.resolution = resolution if not is_sequence(width): width = data_source.ds.arr([width] * 3) normal = np.array(normal_vector) normal = normal / np.linalg.norm(normal) camera.position = center - width[2] * normal camera.focus = center # If north_vector is None, we set the default here. # This is chosen so that if normal_vector is one of the # cartesian coordinate axes, the projection will match # the corresponding on-axis projection. if north_vector is None: vecs = np.identity(3) t = np.cross(vecs, normal).sum(axis=1) ax = t.argmax() east_vector = np.cross(vecs[ax, :], normal).ravel() north = np.cross(normal, east_vector).ravel() else: north = np.array(north_vector) north = north / np.linalg.norm(north) camera.switch_orientation(normal, north) sc.add_source(vol) vol.set_sampler(camera, interpolated=False) assert vol.sampler is not None fields = [vol.field] if vol.weight_field is not None: fields.append(vol.weight_field) mylog.debug("Casting rays") for (grid, mask) in data_source.blocks: data = [] for f in fields: # strip units before multiplying by mask for speed grid_data = grid[f] units = grid_data.units data.append( data_source.ds.arr(grid_data.d * mask, units, dtype="float64")) pg = PartitionedGrid( grid.id, data, mask.astype("uint8"), grid.LeftEdge, grid.RightEdge, grid.ActiveDimensions.astype("int64"), ) grid.clear_data() vol.sampler(pg, num_threads=num_threads) image = vol.finalize_image(camera, vol.sampler.aimage) image = ImageArray(image, funits, registry=data_source.ds.unit_registry, info=image.info) if weight is not None: data_source.ds.field_info.pop(("index", "temp_weightfield")) if method == "integrate": if weight is None: dl = width[2].in_units(data_source.ds.unit_system["length"]) image *= dl else: mask = image[:, :, 1] == 0 image[:, :, 0] /= image[:, :, 1] image[mask] = 0 return image[:, :, 0]
def off_axis_projection(data_source, center, normal_vector, width, resolution, item, weight=None, volume=None, no_ghost=False, interpolated=False, north_vector=None, num_threads=1, method='integrate'): r"""Project through a dataset, off-axis, and return the image plane. This function will accept the necessary items to integrate through a volume at an arbitrary angle and return the integrated field of view to the user. Note that if a weight is supplied, it will multiply the pre-interpolated values together, then create cell-centered values, then interpolate within the cell to conduct the integration. Parameters ---------- data_source : ~yt.data_objects.static_output.Dataset or ~yt.data_objects.data_containers.YTSelectionDataContainer This is the dataset or data object to volume render. center : array_like The current 'center' of the view port -- the focal point for the camera. normal_vector : array_like The vector between the camera position and the center. width : float or list of floats The current width of the image. If a single float, the volume is cubical, but if not, it is left/right, top/bottom, front/back resolution : int or list of ints The number of pixels in each direction. item: string The field to project through the volume weight : optional, default None If supplied, the field will be pre-multiplied by this, then divided by the integrated value of this field. This returns an average rather than a sum. volume : `yt.extensions.volume_rendering.AMRKDTree`, optional The volume to ray cast through. Can be specified for finer-grained control, but otherwise will be automatically generated. no_ghost: bool, optional Optimization option. If True, homogenized bricks will extrapolate out from grid instead of interpolating from ghost zones that have to first be calculated. This can lead to large speed improvements, but at a loss of accuracy/smoothness in resulting image. The effects are less notable when the transfer function is smooth and broad. Default: True interpolated : optional, default False If True, the data is first interpolated to vertex-centered data, then tri-linearly interpolated along the ray. Not suggested for quantitative studies. north_vector : optional, array_like, default None A vector that, if specified, restricts the orientation such that the north vector dotted into the image plane points "up". Useful for rotations num_threads: integer, optional, default 1 Use this many OpenMP threads during projection. method : string The method of projection. Valid methods are: "integrate" with no weight_field specified : integrate the requested field along the line of sight. "integrate" with a weight_field specified : weight the requested field by the weighting field and integrate along the line of sight. "sum" : This method is the same as integrate, except that it does not multiply by a path length when performing the integration, and is just a straight summation of the field along the given axis. WARNING: This should only be used for uniform resolution grid datasets, as other datasets may result in unphysical images. or camera movements. Returns ------- image : array An (N,N) array of the final integrated values, in float64 form. Examples -------- >>> image = off_axis_projection(ds, [0.5, 0.5, 0.5], [0.2,0.3,0.4], ... 0.2, N, "temperature", "density") >>> write_image(np.log10(image), "offaxis.png") """ if method not in ['integrate', 'sum']: raise NotImplementedError( "Only 'integrate' or 'sum' methods are valid for off-axis-projections" ) if interpolated: raise NotImplementedError( "Only interpolated=False methods are currently implemented for off-axis-projections" ) data_source = data_source_or_all(data_source) # Sanitize units if not hasattr(center, "units"): center = data_source.ds.arr(center, 'code_length') if not hasattr(width, "units"): width = data_source.ds.arr(width, 'code_length') sc = Scene() data_source.ds.index if item is None: field = data_source.ds.field_list[0] mylog.info('Setting default field to %s' % field.__repr__()) funits = data_source.ds._get_field_info(item).units vol = VolumeSource(data_source, item) if weight is None: vol.set_field(item) else: # This is a temporary field, which we will remove at the end. weightfield = ("index", "temp_weightfield") def _make_wf(f, w): def temp_weightfield(a, b): tr = b[f].astype("float64") * b[w] return b.apply_units(tr, a.units) return temp_weightfield data_source.ds.field_info.add_field(weightfield, sampling_type="cell", function=_make_wf(item, weight)) # Now we have to tell the dataset to add it and to calculate # its dependencies.. deps, _ = data_source.ds.field_info.check_derived_fields([weightfield]) data_source.ds.field_dependencies.update(deps) vol.set_field(weightfield) vol.set_weight_field(weight) ptf = ProjectionTransferFunction() vol.set_transfer_function(ptf) camera = sc.add_camera(data_source) camera.set_width(width) if not iterable(resolution): resolution = [resolution] * 2 camera.resolution = resolution if not iterable(width): width = data_source.ds.arr([width] * 3) normal = np.array(normal_vector) normal = normal / np.linalg.norm(normal) camera.position = center - width[2] * normal camera.focus = center # If north_vector is None, we set the default here. # This is chosen so that if normal_vector is one of the # cartesian coordinate axes, the projection will match # the corresponding on-axis projection. if north_vector is None: vecs = np.identity(3) t = np.cross(vecs, normal).sum(axis=1) ax = t.argmax() east_vector = np.cross(vecs[ax, :], normal).ravel() north = np.cross(normal, east_vector).ravel() else: north = np.array(north_vector) north = north / np.linalg.norm(north) camera.switch_orientation(normal, north) sc.add_source(vol) vol.set_sampler(camera, interpolated=False) assert (vol.sampler is not None) fields = [vol.field] if vol.weight_field is not None: fields.append(vol.weight_field) mylog.debug("Casting rays") for i, (grid, mask) in enumerate(data_source.blocks): data = [] for f in fields: # strip units before multiplying by mask for speed grid_data = grid[f] units = grid_data.units data.append( data_source.ds.arr(grid_data.d * mask, units, dtype='float64')) pg = PartitionedGrid(grid.id, data, mask.astype('uint8'), grid.LeftEdge, grid.RightEdge, grid.ActiveDimensions.astype("int64")) grid.clear_data() vol.sampler(pg, num_threads=num_threads) image = vol.finalize_image(camera, vol.sampler.aimage) image = ImageArray(image, funits, registry=data_source.ds.unit_registry, info=image.info) if weight is not None: data_source.ds.field_info.pop(("index", "temp_weightfield")) if method == "integrate": if weight is None: dl = width[2].in_units(data_source.ds.unit_system["length"]) image *= dl else: mask = image[:, :, 1] == 0 image[:, :, 0] /= image[:, :, 1] image[mask] = 0 return image[:, :, 0]