How to make a cube with horiztonal grid of 1 cube and vertical grid of another - python-iris

I need to generate a cube that has the horizontal grid of one cube and the vertical grid of another (to make a cube for pressure on rho levels from a temperature cube and a u wind cube). The documentation is lacking context and I can't find anything useful by googling. I image doing something like copying the temperature cube, fidding with the sigma and delta in the cube and then running factory.update on the cube, but I can't quite work out the syntax.

A HybridHeightFactory is attached to a cube, and produces an "altitude" coordinate on request.
It needs to be linked to a suitable surface-altitude coordinate to work
-- which means it's not so simple to move it to a cube with a different horizontal grid.
So I think "factory.update" is not a great route, it is simpler to just make + attach a new one.
The plan will go something like...
orog = hgrid_cube.coord('surface_altitude')
sigma = vgrid_cube.coord('sigma')
delta = vgrid_cube.coord('level_height')
factory = iris.aux_factory.HybridHeightFactory(delta=delta, sigma=sigma, orography=orog)
new_cube = ...
new_cube.add_aux_coord(orog, (2, 3)) # or whatever dimensions
new_cube.add_aux_coord(sigma, (0,)) # or whatever dimensions
new_cube.add_aux_coord(delta, (0,)) # or whatever dimensions
new_cube.add_aux_factory(factory)
Note: in making "new_cube" from the old data, you may need to remove the existing aux factory too.

def make_p_rho_cube(temp, u_wind):
'''
Given a temperature cube (on p level but theta levels)
and a u_wind cube (on rho levels but staggered)
create a cube for pressure on rho levels - on p points
but not non-staggered horizontal grid
'''
# make a pressure cube. Grid is a new one - horizontal grid
# is as temperature, but
# vertical grid is like u_wind. copy temperature cube then change
# name and units and vertical grid. NB need to set stash code as well
p_rho_cube = temp.copy()
p_rho_cube.rename('air_pressure')
p_rho_cube.units = 'Pa'
p_rho_cube.attributes['STASH'] = iris.fileformats.pp.STASH(1, 0, 407)
# now create and use a new hybrid height factory
# surface altitude on theta pts
surface_alt = temp.coord('surface_altitude')
# vertical grid from wind field
sigma = u_wind.coord('sigma')
delta = u_wind.coord('level_height')
# make a hybrid height factory with these variables
factory = iris.aux_factory.HybridHeightFactory(delta=delta, sigma=sigma,
orography=surface_alt)
# delete the old co-ordinates after saving their dimensions
surface_altitude_dim = p_rho_cube.coord_dims('surface_altitude')
p_rho_cube.remove_coord('surface_altitude')
sigma_dim = p_rho_cube.coord_dims('sigma')
p_rho_cube.remove_coord('sigma')
level_height_dim = p_rho_cube.coord_dims('level_height')
p_rho_cube.remove_coord('level_height')
p_rho_cube.remove_aux_factory(p_rho_cube.aux_factories[0])
# add the new ones
p_rho_cube.add_aux_coord(surface_alt, surface_altitude_dim)
p_rho_cube.add_aux_coord(sigma, sigma_dim)
p_rho_cube.add_aux_coord(delta, level_height_dim)
p_rho_cube.add_aux_factory(factory)
return p_rho_cube

Related

Calculate the non-projected area inside a contour line created by Basemap

I am currently trying to determine the area inside specfic contour lines on a Mollweide map projection using Basemap. Specifically, what I'm looking for is the area of various credible intervals in square degrees (or degrees2) of an astronomical event on the celestial sphere. The plot is shown below:
Fortunately, a similar question has already been answered before that helps considerably. The method outlined in the answer is able to account for holes within the contour as well which is a necessity for my use case. My adapted code for this particular method is provided below:
# generate a regular lat/lon grid.
nlats = 300; nlons = 300; delta_lon = 2.*np.pi/(nlons-1); delta_lat = np.pi/(nlats-1)
lats = (0.5*np.pi-delta_lat*np.indices((nlats,nlons))[0,:,:])
lons = (delta_lon*np.indices((nlats,nlons))[1,:,:] - np.pi)
map = Basemap(projection='moll',lon_0=0, celestial=True)
# compute native map projection coordinates of lat/lon grid
x, y = map(lons*180./np.pi, lats*180./np.pi)
areas = []
cred_ints = [0.5,0.9]
for k in range(len(cred_ints)):
cs = map.contourf(x,y,p1,levels=[0.0,cred_ints[k]]) ## p1 is the cumulative distribution across all points in the sky (usually determined via KDE on the data)
##organizing paths and computing individual areas
paths = cs.collections[0].get_paths()
#help(paths[0])
area_of_individual_polygons = []
for p in paths:
sign = 1 ##<-- assures that area of first(outer) polygon will be summed
verts = p.vertices
codes = p.codes
idx = np.where(codes==Path.MOVETO)[0]
vert_segs = np.split(verts,idx)[1:]
code_segs = np.split(codes,idx)[1:]
for code, vert in zip(code_segs,vert_segs):
##computing the area of the polygon
area_of_individual_polygons.append(sign*Polygon(vert[:-1]).area)
sign = -1 ##<-- assures that the other (inner) polygons will be subtracted
##computing total area
total_area = np.sum(area_of_individual_polygons)
print(total_area)
areas.append(total_area)
print(areas)
As far as I can tell this method works beautifully... except for one small wrinkle: this calculates the area using the projected coordinate units. I'm not entirely sure what the units are in this case but they are definitely not degrees2 (the calculated areas are on the order of 1013 units2... maybe the units are pixels?). As alluded to earlier, what I'm looking for is how to calculate the equivalent area in the global coordinate units, i.e. in degrees2.
Is there a way to convert the area calculated in the projected domain back into the global domain in square degrees? Or perhaps is there a way to modify this method so that it determines the area in degrees2 from the get go?
Any help will be greatly appreciated!
For anyone that comes across this question, while I didn't figure out a way to directly convert the projected area back into the global domain, I did develop a new solution by transforming the contour path vertices (but this time defined in the lat/lon coordinate system) via an area preserving sinusoidal projection:
where φ is the latitude, λ is the longitude, and λ0 is the longitude of the central meridian.
This flat projection means you can just use the package Shapely to determine the area of the polygon defined by the projected vertices (in square units for a radius of 1 unit, or more simply steradians). Multiplying this number by (180/π)2 will give you the area in square degrees for the contour in question.
Fortunately, only minor adjustments to the code mentioned in the OP was needed to achieve this. The final code is provided below:
# generate a regular lat/lon grid.
nlats = 300; nlons = 300;
delta_lat = np.pi/(nlats-1); delta_lon = 2.*np.pi/(nlons-1);
lats = (0.5*np.pi-delta_lat*np.indices((nlats,nlons))[0,:,:])
lons = (delta_lon*np.indices((nlats,nlons))[1,:,:])
### FOLLOWING CODE DETERMINES CREDIBLE INTERVAL SKY AREA IN DEG^2 ###
# collect and organize contour data for each credible interval
cred_ints = [0.5,0.9]
ci_areas = []
for k in range(len(cred_ints)):
cs = plt.contourf(lons,lats,p1,levels=[0,cred_ints[k]]) ## p1 is the cumulative distribution across all points in the sky (usually determined via KDE of the dataset in question)
paths = cs.collections[0].get_paths()
##organizing paths and computing individual areas
area_of_individual_polygons = []
for p in paths:
sign = 1 ##<-- assures that area of first(outer) polygon will be summed
vertices = p.vertices
codes = p.codes
idx = np.where(codes==Path.MOVETO)[0]
verts_segs = np.split(vertices,idx)[1:]
for verts in verts_segs:
# transforming the coordinates via an area preserving sinusoidal projection
x = (verts[:,0] - (0)*np.ones_like(verts[:,0]))*np.cos(verts[:,1])
y = verts[:,1]
verts_proj = np.stack((x,y), axis=1)
##computing the area of the polygon
area_of_individual_polygons.append(sign*Polygon(verts_proj[:-1]).area)
sign = -1 ##<-- assures that the other(inner) polygons/holes will be subtracted
##computing total area
total_area = ((180/np.pi)**2)*np.sum(area_of_individual_polygons)
ci_areas.append(total_area)

Plot elements of specific size

I'm plotting a polygon made of edges and vertices. I'd like to plot these elements at a specific size or proportion: whether the polygon has 10 or 1000 vertices, I'd like the elements to be drawn at the same size. When zooming in and out of the vector image, element size would remain static.
For example, define a canvas of 100inx100in and draw lines .1in thick (and save to a pdf).
Currently, it seems impossible since, e.g., the LineWidth, MarkerSize, and FontSize are relative to the screen instead of the canvas. This means that when you zoom into the figure, the elements keep their size wrt screen. One option is to scale their size according to the zoom level. However, then the large polygon wouldn't necessarily fit the screen.
There are two ways that I see to resolve this, both seem impossible:
Define the size properties wrt the canvas and not the screen.
Go to the proper zoom level, and draw all elements even if they aren't in the figure clip region (save to a pdf).
Questions on the subject asked about specific elements such as lines or markers. The suggested solutions were to draw with alternative functions such as patch() and rectangle().
In that case, I'll forsake matlab's clunky drawing mechanism altogether, export the data, and draw in svg. But it would be a shame since matlab has powerful tools such as different marker shapes or a force graph.
Am I missing something fundamental or is this the worst design I've seen lately?
Duplicate:
www.mathworks.com/matlabcentral/answers/1569953-plot-elements-of-specific-size
Matt J. observed that, in fact, when saving a pdf, there's no resolution limit regardless of the figure limitation.
http://www.mathworks.com/matlabcentral/answers/1569953-plot-elements-of-specific-size
Then, we can do the following:
Draw a small proof-of-concept plot with the right proportion between elements (markers, edges, and fonts). Save the data-unit-to-point ratio (sc0 below). Alternatively, you can use the same constant for all your drawings, considering this matlab's default drawing ratio.
Draw a plot of any complexity with similar proportions.
Scale it to have the same ratio as the saved one.
Save to pdf.
For example:
% draw a vertical polyline with n vertices
n = 5; % polyline size
y = 0:n;
plot( zeros( size(y) ), y, '-o', 'LineWidth', 2, 'MarkerSize', 10 );
axis equal;
% scale
sc0 = 51; % ratio calculated by data_units_to_points_ratio() from the initial (designed) fig of a polyline of size 5
sc = data_unit_to_point_ratio() / sc0;
scale_fig_objects( sc );
% save
print( 'plot.pdf' );
If you change n=100, the figure would be a proportional mess (a thin line, markers not showing), but the pdf would be fine, having the same segment (vertex to edge) proportion.
Functions used:
% Based on Matt's suggestion
function conversionFactor = data_unit_to_point_ratio()
set( gcf, 'Units', 'points' );
DU = diff(xlim); % width of figure in data units
hfig = gcf;
P = hfig.Position(3); % width of figure in points
conversionFactor = P / DU; % conversion factor, data units to points
and
function scale_fig_objects( s )
hs = findobj;
for i = 1:length( hs )
h = hs(i);
t = h.Type;
if strcmpi( t, 'line' ) || strcmpi( t, 'GraphPlot' )
h.LineWidth = h.LineWidth * s;
h.MarkerSize = h.MarkerSize * s;
elseif strcmpi( t, 'scatter' )
h.SizeData = h.SizeData * s^2; % it's a squared factor!
elseif strcmpi( t, 'text' )
h.FontSize = h.FontSize * s;
end
end

Invariant scale geometry

I am writing a mesh editor where I have manipulators with the help of which I change the vertices of the mesh. The task is to render the manipulators with constant dimensions, which would not change when changing the camera and viewport parameters. The projection matrix is perspective. I will be grateful for ideas how to implement the invariant scale geometry.
If I got it right you want to render some markers (for example vertex drag editation area) with the same visual size for any depth they are rendered to.
There are 2 approaches for this:
scale with depth
compute perpendicular distance to camera view (simple dot product) and scale the marker size so it has the same visual size invariant on the depth.
So if P0 is your camera position and Z is your camera view direction unit vector (usually Z axis). Then for any position P compute the scale like this:
depth = dot(P-P0,Z)
Now the scale depends on wanted visual size0 at some specified depth0. Now using triangle similarity we want:
size/dept = size0/depth0
size = size0*depth/depth0
so render your marker with size or scale depth/depth0. In case of using scaling you need to scale around your target position P otherwise your marker would shift to the sides (so translate, scale, translate back).
compute screen position and use non perspective rendering
so you transform target coordinates the same way as the graphic pipeline does until you got the screen x,y position. Remember it and in pass that will render your markers just use that instead of real position. For this rendering pass either use some constant depth (distance from camera) or use non perspective view matrix.
For more info see Understanding 4x4 homogenous transform matrices
[Edit1] pixel size
you need to use FOVx,FOVy projection angles and view/screen resolution (xs,ys) for that. That means if depth is znear and coordinate is at half of the angle then the projected coordinate will go to edge of screen:
tan(FOVx/2) = (xs/2)*pixelx/znear
tan(FOVy/2) = (ys/2)*pixely/znear
---------------------------------
pixelx = 2*znear*tan(FOVx/2)/xs
pixely = 2*znear*tan(FOVy/2)/ys
Where pixelx,pixely is size (per axis) representing single pixel visually at depth znear. In case booth sizes are the same (so pixel is square) you have all you need. In case they are not equal (pixel is not square) then you need to render markers in screen axis aligned coordinates so approach #2 is more suitable for such case.
So if you chose depth0=znear then you can set size0 as n*pixelx and/or n*pixely to get the visual size of n pixels. Or use any dept0 and rewrite the computation to:
pixelx = 2*depth0*tan(FOVx/2)/xs
pixely = 2*depth0*tan(FOVy/2)/ys
Just to be complete:
size0x = size_in_pixels*(2*depth0*tan(FOVx/2)/xs)
size0y = size_in_pixels*(2*depth0*tan(FOVy/2)/ys)
-------------------------------------------------
sizex = size_in_pixels*(2*depth0*tan(FOVx/2)/xs)*(depth/depth0)
sizey = size_in_pixels*(2*depth0*tan(FOVy/2)/ys)*(depth/depth0)
---------------------------------------------------------------
sizex = size_in_pixels*(2*tan(FOVx/2)/xs)*(depth)
sizey = size_in_pixels*(2*tan(FOVy/2)/ys)*(depth)
---------------------------------------------------------------
sizex = size_in_pixels*2*depth*tan(FOVx/2)/xs
sizey = size_in_pixels*2*depth*tan(FOVy/2)/ys

Three.js—rotation around arbitrary line

I am starting with Three.js so I might have misunderstood some basics of the concept. I have a usual 3d scene with a hierarchy like this:
.
+-container #(0,0,0) (Object3d, no own geometry)
+-child 1 #(1,1,1)
+-child 2 #(1, -2, 5)
+-child 3 #(-4, -2, -3)
.
.
. more should come
all »children« of the »container« are imported models from Blender. What I would like to do is to rotate the whole container around a pivot axis based on the current selection, which should be one of the children.
Image three cubes in Blender, all selected with the 3d cursor at center of first in location and center of transformation. A rotation transforms all cubes, but the rotation is relative to the first in selection.
In terms of three.js, what would like to do is to rotate the container, so that the rotation is applied to all children.
To do that I think that the following steps should do the trick:
create a matrix,
translate that matrix by the negative of the selected objects position
rotate that matrix
translate the matrix back to the selected objects position
apply the transform to the container
I have tried the following code but the result is just wrong:
var sp = selection.position.clone(),
m = new THREE.Matrix4();
selection.localToWorld(sp);
m.setPosition(sp.clone().negate());
//I've used makeRotationX for testing purposes, should be replaced with quaternion rotation later on…
m = m.multiply(new THREE.Matrix4().makeRotationX(2*180/Math.PI));
m = m.multiply(new THREE.Matrix4().makeTranslation(sp.x,sp.y,sp.z));
this._container.applyMatrix(m);
Thanks for help!
UPDATE
sign error—this works:
var sp = selection.position.clone(),
m = new THREE.Matrix4();
m.makeTranslation(sp.x,sp.y,sp.z);
m.multiply(new THREE.Matrix4().makeRotationX(0.1));
m.multiply(new THREE.Matrix4().makeTranslation(-sp.x,-sp.y,-sp.z));
this._container.applyMatrix(m);
BUT that code does not really look that good, creating three matrices for that single operating seems to bit of overhead, what is the usual »three.js-way«?
UPDATE #2
Due to the comment here is an image describing what I would like to do:
The »arrows« at the origin stand for the parent container and the cube, the sphere and the cone are its »children«. The red line shows the line I would like rotate the parent around, this way the rotation is applied to all children.
rotateOnAxis() takes a Vector as axis, so the line the objects rotates around crosses its origin.

How to position an axes in a figure relative to another axes?

When laying out a figure in MATLAB, typing axis equal ensures that no matter what the figure dimensions, the axes will always be square:
My current problem is that I want to add a second axes to this plot. Usually, that's no problem; I would just type axes([x1 y1 x2 y2]), and a new square figure would be added with corners at (x1, y1), (x2, y2), which is a fixed location relative to the figure. The problem is, I want this new axes to be located at a fixed location relative to the first axes.
So, my questions are:
Does anyone know how I can position an axes in a figure by specifying the location relative to another axes?
Assuming I can do 1, how can I have this new axes remain in the same place even if I resize the figure?
An axis position property is relative to its parent container. Therefore, one possibility is to create a transparent panel with the same size as the first axis, then inside it create the second axis, and set its location and size as needed. The position specified would be as if it were relative to the first axis.
Now we need to always maintain the panel to be the same size/location as the first axis. Usually this can be done using LINKPROP which links a property of multiple graphic objects (panel and axis) to be the same, namely the 'Position' property.
However, this would fail in your case: when calling axis image, it fixes the data units to be the same in every direction by setting aspect ratio properties like 'PlotBoxAspectRatio' and 'DataAspectRatio'. The sad news is that the 'Position' property will not reflect the change in size, thus breaking the above solution. Here is an example to illustrate the problem: if you query the position property before/after issuing the axis image call, it will be the same:
figure, plot(1:10,1:10)
get(gca,'Position')
pause(1)
axis image
get(gca,'Position')
Fortunately for us, there is a submission on FEX (plotboxpos) that solves this exact issue, and returns the actual position of the plotting region of the axis. Once we have that, it's a matter of syncing the panel position to the axis position. One trick is to create a event listener for when the axis changes size (it appears that the 'TightInset' property changes unlike the 'Position' property, so that could be the trigger in our case).
I wrapped the above in a function AXESRELATIVE for convenience: you call it as you would the builtin AXES function. The only difference is you give it as first argument the handle to the axis you want to relatively-position the newly created axis against. It returns handles to both the new axis and its containing panel.
Here is an example usage:
%# automatic resize only works for normalized units
figure
hParentAx = axes('Units','normalized');
axis(hParentAx, 'image')
%# create a new axis positioned at normalized units with w.r.t the previous axis
%# the axis should maintain its relative position on resizing the figure
[hAx hPan] = axesRelative(hParentAx, ...
'Units','normalized', 'Position',[0.7 0.1 0.1 0.1]);
set(hAx, 'Color','r')
And the function implementation:
function [hAx hPan] = axesRelative(hParentAx, varargin)
%# create panel exactly on top of parent axis
s = warning('off', 'MATLAB:hg:ColorSpec_None');
hPan = uipanel('Parent',get(hParentAx, 'Parent'), ...
'BorderType','none', 'BackgroundColor','none', ...
'Units',get(hParentAx,'Units'), 'Position',plotboxpos(hParentAx));
warning(s)
%# sync panel to always match parent axis position
addlistener(handle(hParentAx), ...
{'TightInset' 'Position' 'PlotBoxAspectRatio' 'DataAspectRatio'}, ...
'PostSet',#(src,ev) set(hPan, 'Position',plotboxpos(hParentAx)) );
%# create new axis under the newly created panel
hAx = axes('Parent',hPan, varargin{:});
end
On a completely different note: before you recent edit, I got the impression that you were trying to produce a scatter plot of images (i.e like a usual scatter plot, but with full images instead of points).
What you suggested (from what I understand) is creating one axis for each image, and setting its position corresponding to the x/y coordinates of the point.
My solution is to use the IMAGE/IMAGESC functions and draw the small images by explicitly setting the 'XData' and 'YData' properties to shift and scale the images appropriately. The beauty of this is it require a single axis, and doesn't suffer from having to deal with resizing issues..
Here is a sample implementation for that:
%# create fan-shaped coordinates
[R,PHI] = meshgrid(linspace(1,2,5), linspace(0,pi/2,10));
X = R.*cos(PHI); Y = R.*sin(PHI);
X = X(:); Y = Y(:);
num = numel(X);
%# images at each point (they don't have to be the same)
img = imread('coins.png');
img = repmat({img}, [num 1]);
%# plot scatter of images
SCALE = 0.2; %# image size along the biggest dimension
figure
for i=1:num
%# compute XData/YData vectors of each image
[h w] = size(img{i});
if h>w
scaleY = SCALE;
scaleX = SCALE * w/h;
else
scaleX = SCALE;
scaleY = SCALE * h/w;
end
xx = linspace(-scaleX/2, scaleX/2, h) + X(i);
yy = linspace(-scaleY/2, scaleY/2, w) + Y(i);
%# note: we are using the low-level syntax of the function
image('XData',xx, 'YData',yy, 'CData',img{i}, 'CDataMapping','scaled')
end
axis image, axis ij
colormap gray, colorbar
set(gca, 'CLimMode','auto')
This is usually the sort of thing you can take care of with a custom 'ResizeFcn' for your figure which will adjust the position and size of the smaller axes with respect the the larger. Here's an example of a resize function that maintains the size of a subaxes so that it is always 15% the size of the larger square axes and located in the bottom right corner:
function resizeFcn(src,event,hAxes,hSubAxes)
figurePosition = get(get(hAxes,'Parent'),'Position');
axesPosition = get(hAxes,'Position').*figurePosition([3 4 3 4]);
width = axesPosition(3);
height = axesPosition(4);
minExtent = min(width,height);
newPosition = [axesPosition(1)+(width-minExtent)/2+0.8*minExtent ...
axesPosition(2)+(height-minExtent)/2+0.05*minExtent ...
0.15*minExtent ...
0.15*minExtent];
set(hSubAxes,'Units','pixels','Position',newPosition);
end
And here's an example of its use:
hFigure = figure('Units','pixels'); %# Use pixel units for figure
hAxes = axes('Units','normalized'); %# Normalized axes units so it auto-resizes
axis(hAxes,'image'); %# Make the axes square
hSubAxes = axes('Units','pixels'); %# Use pixel units for subaxes
set(hFigure,'ResizeFcn',{#resizeFcn,hAxes,hSubAxes}); %# Set resize function

Resources