def get_inverse_transform(self, im_ref, mode='affine'): aff_im_self = self.im_file.affine aff_im_ref = im_ref.im_file.affine if mode == 'affine': transform = np.matmul(np.linalg.inv(aff_im_ref), aff_im_self) else: T_self, R_self, Sc_self, Sh_self = affines.decompose44(aff_im_self) T_ref, R_ref, Sc_ref, Sh_ref = affines.decompose44(aff_im_ref) if mode == 'translation': T_transform = T_self - T_ref R_transform = np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1]]) Sc_transform = np.array([1.0, 1.0, 1.0]) transform = affines.compose(T_transform, R_transform, Sc_transform) elif mode == 'rigid': T_transform = T_self - T_ref R_transform = np.matmul(np.linalg.inv(R_ref), R_self) Sc_transform = np.array([1.0, 1.0, 1.0]) transform = affines.compose(T_transform, R_transform, Sc_transform) elif mode == 'rigid_scaling': T_transform = T_self - T_ref R_transform = np.matmul(np.linalg.inv(R_ref), R_self) Sc_transform = Sc_self / Sc_ref transform = affines.compose(T_transform, R_transform, Sc_transform) else: transform = np.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]) return transform
def generate_elements(self): '''Initialize all optical elements. After any of the ``elem_pos``, ``elem_uncertainty`` or ``uncertainty`` attributes has changed, `generate_elements` needs to be called to regenerate the positions of the individual elements using - the global position of ``Parallel.pos4d`` - the position of each element ``Parallel.elem_pos`` relativ to the global position - the global uncertainty `uncertainty`. - the uncertainty for individual facets. ''' self.elements = [] for i in range(len(self.elem_pos)): # _parse_position_keywords pops off keywords, thus operate on a copy here elem_args = self.elem_args.copy() # check if elem_args is the same for every element specific_elem_args = {} for k, v in elem_args.items(): if isinstance(v, list) and (len(v) == len(self.elem_pos)): specific_elem_args[k] = v[i] else: specific_elem_args[k] = v if 'name' not in specific_elem_args: specific_elem_args['name'] = 'Elem {0} in {1}'.format(i, self.name) elem_pos4d = _parse_position_keywords(specific_elem_args) telem, relem, zelem, Selem = decompose44(elem_pos4d) if not np.allclose(Selem, 0.): raise ValueError('pos4 for elem includes shear, which is not supported here.') e_center, e_rot, e_zoom, stemp = decompose44(self.elem_pos[i]) tsigelem, rsigelem, zsigelem, stemp = decompose44(self.elem_uncertainty[i]) if not np.allclose(stemp, 0.): raise SimulationSetupError('Shear is not supported in the elem uncertainty.') # Will be able to write this so much better in python 3.5, # but for now I don't want to nest np.dot too much so here it goes f_pos4d = np.eye(4) for m in reversed([self.pos4d, # global position of ParallelElement self.uncertainty, # uncertainty in global positioning translation2aff(tsigelem), # uncertaintig in translation for elem translation2aff(e_center), # translate elem center to global center translation2aff(telem), # offset for all elem. Usually 0. mat2aff(rsigelem), # uncertainty in rotation for elem mat2aff(e_rot), # Rotation of individual elem mat2aff(relem), # Rotation for all elem, e.g. CAT gratings zoom2aff(zsigelem), # uncertainty in the zoom zoom2aff(e_zoom), # zoom of individual elem zoom2aff(zelem), # sets size for all elem ]): assert m.shape == (4, 4) f_pos4d = np.dot(m, f_pos4d) self.elements.append(self.elem_class(pos4d = f_pos4d, id_num=self.id_num_offset + i, **specific_elem_args))
def parametric_surface(self, phi=None, z=None, display={}): '''Parametric description of the tube. This is just another way to obtain the shape of the tube, e.g. for visualization. Parameters ---------- phi : np.array ``phi`` is the angle around the tube profile. Set to ``None`` to use the extend of the element itself. z : np.array The coordiantes along the radius coordinate. Set to ``None`` to use the extend of the element itself. Returns ------- xyzw : np.array Ring coordinates in global homogeneous coordinate system. ''' phi = np.linspace(self.coos_limits[0][0], self.coos_limits[0][1], self.n_points) \ if phi is None else np.asanyarray(phi) trans, rot, zoom, shear = decompose44(self.pos4d) z = self.coos_limits[1] if z is None else np.asanyarray(z) / zoom[2] if (phi.ndim != 1) or (z.ndim != 1): raise ValueError('input parameters have 1-dim shape.') phi, z = np.meshgrid(phi, z) x = np.cos(phi) y = np.sin(phi) w = np.ones_like(z) coos = np.array([x, y, z, w]).T return np.einsum('...ij,...j', self.pos4d, coos)
def process_photons(self, photons): intersect, interpos, inter_local = self.intersect( photons['dir'], photons['pos']) photons['pos'][intersect, :] = interpos[intersect, :] self.add_output_cols(photons, self.loc_coos_name + self.detpix_name) # Add ID number to ID col, if requested if self.id_col is not None: photons[self.id_col][intersect] = self.id_num # Set position in different coordinate systems photons['pos'][intersect] = interpos[intersect] photons[self.loc_coos_name[0]][intersect] = inter_local[intersect, 0] photons[self.loc_coos_name[1]][intersect] = inter_local[intersect, 1] trans, rot, zoom, shear = decompose44(self.pos4d) if np.isclose(zoom[0], zoom[1]): photons[self.detpix_name[0]][intersect] = inter_local[ intersect, 0] * zoom[0] / self.pixsize else: warnings.warn( 'Pixel coordinate for elliptical mirrors not implemented.', PixelSizeWarning) photons[self.detpix_name[1]][intersect] = inter_local[intersect, 1] / self.pixsize return photons
def generate_facets(self, facet_class, facet_args={}): ''' Example ------- from marxs.optics.grating import FlatGrating gsa = GSA( ... args ...) gsa.generate_facets(FlatGrating, {'d': 0.002}) ''' self.facets = [] facet_pos4d = _parse_position_keywords(facet_args) tfacet, rfacet, zfacet, Sfacet = decompose44(facet_pos4d) if not np.allclose(Sfacet, 0.): raise ValueError('pos4 for facet includes shear, which is not supported for gratings.') name = facet_args.pop('name', '') gas_center = self.calc_ideal_center() Tgas = translation2aff(gas_center) for i in range(len(self.facet_pos)): f_center, rrown, ztemp, stemp = decompose44(self.facet_pos[i]) Tfacetgas = translation2aff(-np.array(gas_center) + f_center) tsigfacet, rsigfacet, ztemp, stemp = decompose44(self.facet_uncertainty[i]) if not np.allclose(ztemp, 1.): raise FacetPlacementError('Zoom is not supported in the facet uncertainty.') if not np.allclose(stemp, 0.): raise FacetPlacementError('Shear is not supported in the facet uncertainty.') # Will be able to write this so much better in python 3.5, # but for now I don't want to nest np.dot too much so here it goes f_pos4d = np.eye(4) for m in reversed([self.pos4d, # any change between GAS system and global # coordiantes, e.g. if x is not optical axis Tgas, # move to center of GAS self.uncertainty, # uncertainty in GAS positioning Tfacetgas, # translate facet center to GAS center translation2aff(tsigfacet), # uncertaintig in translation for facet translation2aff(tfacet), # any additional offset of facet. Probably 0 mat2aff(rsigfacet), # uncertainty in rotation for facet mat2aff(rfacet), # Any rotation of facet, e.g. for CAT gratings mat2aff(rrown), # rotate grating normal to be normal to Rowland torus zoom2aff(zfacet), # sets size of grating ]): assert m.shape == (4, 4) f_pos4d = np.dot(m, f_pos4d) self.facets.append(facet_class(pos4d = f_pos4d, name='{0} Facet {1} in GAS {2}'.format(name, i, self.name), **facet_args))
def get_directions(self): """ This function return the X, Y, and Z axes of the image Returns: X, Y and Z axes of the image """ direction_matrix = self.header.get_best_affine() T_self, R_self, Sc_self, Sh_self = affines.decompose44(direction_matrix) return R_self[0:3, 0], R_self[0:3, 1], R_self[0:3, 2]
def __init__(self, pixsize=1, **kwargs): self.pixsize = pixsize super(FlatDetector, self).__init__(**kwargs) t, r, zoom, s = decompose44(self.pos4d) self.npix = [0, 0] self.centerpix = [0, 0] for i in (0, 1): z = zoom[i + 1] self.npix[i] = int(np.round(2. * z / self.pixsize)) if (2. * z / self.pixsize - self.npix[i]) > 1e-3: warnings.warn('Detector size is not an integer multiple of pixel size in direction {0}. It will be rounded.'.format('xy'[i]), PixelSizeWarning) self.centerpix[i] = (self.npix[i] - 1) / 2
def __init__(self, pixsize, **kwargs): self.pixsize = pixsize super(FlatDetector, self).__init__(**kwargs) t, r, zoom, s = decompose44(self.pos4d) self.npix = [0, 0] self.centerpix = [0, 0] for i in (0, 1): z = zoom[i + 1] self.npix[i] = 2 * z // self.pixsize if (2. * z / self.pixsize - self.npix[i]) > 1e-3: warn('Detector size is not an integer multiple of pixel size. It will be rounded.') self.centerpix[i] = (self.npix[i] - 1) / 2
def xyz_rpy(transformation): """Decompose a transformation matrix into translation and euler angles :param transformation: A 4x4 transformation matrix :return: A tuple of (x, y, z, r, p, y) where (x, y, z) are coordinates of a cartesian translation and (r, p, y) are roll, pitch, yaw applied in that order around fixed axis """ T, R, _, _ = decompose44(transformation) return (T[0], T[1], T[2]), mat2euler(R)
def __init__(self, pixsize=1, ignore_pixel_warning=False, **kwargs): self.pixsize = pixsize super().__init__(**kwargs) t, r, zoom, s = decompose44(self.pos4d) self.npix = [0, 0] self.centerpix = [0, 0] for i in (0, 1): z = zoom[i + 1] self.npix[i] = int(np.round(2. * z / self.pixsize)) if (np.abs(2. * z / self.pixsize - self.npix[i]) > 1e-2) and not ignore_pixel_warning: warnings.warn('Detector size is not an integer multiple of pixel size in direction {0}. It will be rounded.'.format('xy'[i]), SimulationSetupWarning) self.centerpix[i] = (self.npix[i] - 1) / 2
def __init__(self, pixsize=1, **kwargs): self.pixsize = pixsize super(FlatDetector, self).__init__(**kwargs) t, r, zoom, s = decompose44(self.pos4d) self.npix = [0, 0] self.centerpix = [0, 0] for i in (0, 1): z = zoom[i + 1] self.npix[i] = int(np.round(2. * z / self.pixsize)) if np.abs(2. * z / self.pixsize - self.npix[i]) > 1e-2: warnings.warn( 'Detector size is not an integer multiple of pixel size in direction {0}. It will be rounded.' .format('xy'[i]), PixelSizeWarning) self.centerpix[i] = (self.npix[i] - 1) / 2
def _plot_mayavi(self, viewer=None): from tvtk.tools import visual visual.set_viewer(viewer) trans, rot, zoom, shear = decompose44(self.pos4d) # turn into valid color tuple self.display['color'] = get_color(self.display) # setting color here is more global than in the next line # because this automatically changes the diffuse, ambient, etc. color, too. b = visual.box(pos=trans, size=tuple(np.abs(zoom) * 2), axis=np.dot([1.,0.,0.], rot), color=self.display['color'], viewer=viewer) # No safety net here like for color converting to a tuple. # If the advnaced properties are set you are on your own. for n in b.property.trait_names(): if n in self.display: setattr(b.property, n, self.display[n])
def xml(self, doc, link): """Represent this visual element as xml""" # Extract scale and shear from transformation _, _, Z, S = decompose44(self.trans) z = np.mean(Z) meta = _URDFVisual.meta[self.node.name] if not meta.non_uniform_scale and not np.allclose(Z, z): raise ValueError('Non uniform scale not supported for node ' f'{self.node.name}') if not meta.shear and not np.allclose(S, 0): raise ValueError('Scaling which induces shear is not supported ' f'for node {self.node.name}') fields = self.node.fields visual = doc.createElement('visual') xyz, rpy = xyz_rpy(self.trans) origin = doc.createElement('origin') origin.setAttribute('xyz', ' '.join(str(a) for a in xyz)) origin.setAttribute('rpy', ' '.join(str(a) for a in rpy)) visual.appendChild(origin) geometry = doc.createElement('geometry') if self.node.name == 'Box': size = [float(a) for a in fields['size'].split()] box = doc.createElement('box') box.setAttribute('size', ' '.join(str(a) for a in Z * size)) geometry.appendChild(box) elif self.node.name == 'Cylinder': cylinder = doc.createElement('cylinder') cylinder.setAttribute('radius', str(float(fields['radius']) * z)) cylinder.setAttribute('length', str(float(fields['height']) * z)) geometry.appendChild(cylinder) elif self.node.name == 'Sphere': sphere = doc.createElement('sphere') sphere.setAttribute('radius', str(float(fields['radius']) * z)) geometry.appendChild(sphere) elif self.node.name == 'Mesh': mesh = doc.createElement('mesh') mesh.setAttribute('filename', str(fields['url'])) mesh.setAttribute('scale', ' '.join(str(a) for a in Z)) geometry.appendChild(mesh) visual.appendChild(geometry) link.appendChild(visual)
def _plot_mayavi(self, viewer=None): from tvtk.tools import visual visual.set_viewer(viewer) trans, rot, zoom, shear = decompose44(self.pos4d) # turn into valid color tuple self.display['color'] = get_color(self.display) # setting color here is more global than in the next line # because this automatically changes the diffuse, ambient, etc. color, too. b = visual.box(pos=trans, size=tuple(np.abs(zoom) * 2), axis=np.dot([1., 0., 0.], rot), color=self.display['color'], viewer=viewer) # No safety net here like for color converting to a tuple. # If the advnaced properties are set you are on your own. for n in b.property.trait_names(): if n in self.display: setattr(b.property, n, self.display[n])
def process_photons(self, photons, intersect, interpos, inter_local): photons['pos'][intersect, :] = interpos[intersect, :] self.add_output_cols(photons, self.loc_coos_name + self.detpix_name) # Add ID number to ID col, if requested if self.id_col is not None: photons[self.id_col][intersect] = self.id_num # Set position in different coordinate systems photons['pos'][intersect] = interpos[intersect] photons[self.loc_coos_name[0]][intersect] = inter_local[intersect, 0] photons[self.loc_coos_name[1]][intersect] = inter_local[intersect, 1] trans, rot, zoom, shear = decompose44(self.pos4d) if np.isclose(zoom[0], zoom[1]): photons[self.detpix_name[0]][intersect] = inter_local[intersect, 0] * zoom[0] / self.pixsize else: warnings.warn('Pixel coordinate for elliptical mirrors not implemented.', PixelSizeWarning) photons[self.detpix_name[1]][intersect] = inter_local[intersect, 1] / self.pixsize return photons
def __resample(self): """ Resample image to standard shape; this occurs only at (re)-initialization time """ shape = self.__img.shape affine = None hdr = self.__hdr try: affine = hdr.Affine except: print("header: {}".format(hdr)) raise self.TransformError( "Image header must contain affine transformation information in `.Affine` attribute." ) _, R, Z, S = affine3d.decompose44(hdr.Affine) # If any of the __mm_per_voxel items are negative, keep the original zoom at those locations: mm_per_voxel = np.atleast_1d(self.__mm_per_voxel).astype("float") if len(mm_per_voxel) not in [1, len(Z)]: raise RuntimeError( "`mm_per_voxel` must be a scalar value or tuple of length {}.". format(len(Z))) if any(mm_per_voxel < 0): if len(mm_per_voxel) == 1: mm_per_voxel = Z else: mm_per_voxel[mm_per_voxel < 0] = Z[mm_per_voxel < 0] # If there are shears, bail out (we don't support that yet): if S.sum() != 0: raise self.TransformError( "Image affine includes shear, which is not supported in this version." ) # See if any rotations are necessary (we don't support that yet): if np.any(np.eye(R.shape[0]).astype(R.dtype) != R): raise self.TransformError( "Image affine includes rotation, which is not supported in this version" ) # Now apply scaling Z = Z / np.array(mm_per_voxel) self.__img = interpolation.zoom(self.__img, Z)
def intersect(self, dir, pos, transform=True): '''Calculate the intersection point between a ray and the element Parameters ---------- dir : `numpy.ndarray` of shape (N, 4) homogeneous coordinates of the direction of the ray pos : `numpy.ndarray` of shape (N, 4) homogeneous coordinates of a point on the ray transform : bool If ``True``, input is in global coordinates and needs to be transformed here for the calculations; if ``False`` input is in local coordinates. Returns ------- intersect : boolean array of length N ``True`` if an intersection point is found. interpos : `numpy.ndarray` of shape (N, 4) homogeneous coordinates of the intersection point. Values are set to ``np.nan`` is no intersecton point is found. interpos_local : `numpy.ndarray` of shape (N, 2) phi, z coordiantes (in the local frame) for one of the intersection points. If both intersection points are required, reset ``self.inner`` and call this function again. ''' # This could be moved to a general function if not np.all(dir[:, 3] == 0): raise ValueError('First input must be direction vectors.') # Could test pos, too... if transform: invpos4d = np.linalg.inv(self.pos4d) dir = np.dot(invpos4d, dir.T).T pos = np.dot(invpos4d, pos.T).T xyz = h2e(pos) dir_e = h2e(dir) # Solve quadratic equation in steps. # a12 = (-xr +- sqrt(xr - r**2(x**2 - R**2))) xy = xyz[:, :2] r = dir[:, :2] c = np.sum(xy**2, axis=1) - 1. b = 2 * np.sum(xy * r, axis=1) a = np.sum(r**2, axis=1) underroot = b**2 - 4 * a * c # List of intersect in xy plane. intersect = (underroot >= 0) i = intersect # just a shorthand because it's used so much below interpos_local = np.ones((pos.shape[0], 2)) interpos_local[:] = np.nan interpos = np.ones_like(pos) interpos[:] = np.nan if intersect.sum() > 0: i_ind = intersect.nonzero()[0] denom = 2 * a[i] a1 = (-b[i] + np.sqrt(underroot[i])) / denom a2 = (-b[i] - np.sqrt(underroot[i])) / denom xy_1 = xy[i, :] + a1[:, np.newaxis] * r[i, :] phi_1 = np.arctan2(xy_1[:, 1], xy_1[:, 0]) xy_2 = xy[i, :] + a2[:, np.newaxis] * r[i, :] phi_2 = np.arctan2(xy_2[:, 1], xy_2[:, 0]) # 1, 2 look like hits in x,y but might still miss in z z_1 = xyz[i, 2] + a1 * dir[i, 2] z_2 = xyz[i, 2] + a2 * dir[i, 2] hit_1 = ((a1 >= 0) & (np.abs(z_1) <= 1.) & angle_between( phi_1, self.coos_limits[0][0], self.coos_limits[0][1])) hit_2 = ((a2 >= 0) & (np.abs(z_2) <= 1.) & angle_between( phi_2, self.coos_limits[0][0], self.coos_limits[0][1])) # If both 1 and 2 are hits, use the closer one hit_1[hit_2 & (a2 < a1)] = False hit_2[hit_1 & (a2 >= a1)] = False intersect[i_ind] = hit_1 | hit_2 # Set values into array from either point 1 or 2 interpos_local[i_ind[hit_1], 0] = phi_1[hit_1] interpos_local[i_ind[hit_1], 1] = z_1[hit_1] interpos_local[i_ind[hit_2], 0] = phi_2[hit_2] interpos_local[i_ind[hit_2], 1] = z_2[hit_2] # Calculate pos for point 1 or 2 in local xyz coord system interpos[i_ind[hit_1], :] = e2h( xyz[i_ind, :] + a1[:, None] * dir_e[i_ind, :], 1)[hit_1, :] interpos[i_ind[hit_2], :] = e2h( xyz[i_ind, :] + a2[:, None] * dir_e[i_ind, :], 1)[hit_2, :] trans, rot, zoom, shear = decompose44(self.pos4d) # interpos_local in z direction is in local coordinates, i.e. # the x coordiante is 0..1, but we want that in units of the # global coordinate system. interpos_local[:, 1] = interpos_local[:, 1] * zoom[2] interpos = np.dot(self.pos4d, interpos.T).T return intersect, interpos, interpos_local
def __getitem__(self, value): if value == 'R': trans, rot, zoom, shear = decompose44(self.pos4d) return zoom[0] else: return super().__getitem__(value)
def _reset_trans(self, trans): _, _, Z, S = decompose44(trans) return scale_matrix(Z) * shear_matrix(S)
def intersect(self, dir, pos, transform=True): '''Calculate the intersection point between a ray and the element Parameters ---------- dir : `numpy.ndarray` of shape (N, 4) homogeneous coordinates of the direction of the ray pos : `numpy.ndarray` of shape (N, 4) homogeneous coordinates of a point on the ray transform : bool If ``True``, input is in global coordinates and needs to be transformed here for the calculations; if ``False`` input is in local coordinates. Returns ------- intersect : boolean array of length N ``True`` if an intersection point is found. interpos : `numpy.ndarray` of shape (N, 4) homogeneous coordinates of the intersection point. Values are set to ``np.nan`` is no intersecton point is found. interpos_local : `numpy.ndarray` of shape (N, 2) phi, z coordiantes (in the local frame) for one of the intersection points. If both intersection points are required, reset ``self.inner`` and call this function again. ''' # This could be moved to a general function if not np.all(dir[:, 3] == 0): raise ValueError('First input must be direction vectors.') # Could test pos, too... if transform: invpos4d = np.linalg.inv(self.pos4d) dir = np.dot(invpos4d, dir.T).T pos = np.dot(invpos4d, pos.T).T xyz = h2e(pos) dir_e = h2e(dir) # Solve quadratic equation in steps. a12 = (-xr +- sqrt(xr - r**2(x**2 - R**2))) xy = xyz[:, :2] r = dir[:, :2] c = np.sum(xy**2, axis=1) - 1. b = 2 * np.sum(xy * r, axis=1) a = np.sum(r**2, axis=1) underroot = b**2 - 4 * a * c # List of intersect in xy plane. intersect = (underroot >= 0) i = intersect # just a shorthand because it's used so much below interpos_local = np.ones((pos.shape[0], 2)) interpos_local[:] = np.nan interpos = np.ones_like(pos) interpos[:] = np.nan if intersect.sum() > 0: i_ind = intersect.nonzero()[0] denom = 2 * a[i] a1 = (- b[i] + np.sqrt(underroot[i])) / denom a2 = (- b[i] - np.sqrt(underroot[i])) / denom xy_1 = xy[i, :] + a1[:, np.newaxis] * r[i, :] phi_1 = np.arctan2(xy_1[:, 1], xy_1[:, 0]) xy_2 = xy[i, :] + a2[:, np.newaxis] * r[i, :] phi_2 = np.arctan2(xy_2[:, 1], xy_2[:, 0]) # 1, 2 look like hits in x,y but might still miss in z z_1 = xyz[i, 2] + a1 * dir[i, 2] z_2 = xyz[i, 2] + a2 * dir[i, 2] hit_1 = ((a1 >= 0) & (np.abs(z_1) <= 1.) & angle_between(phi_1, self.coos_limits[0][0], self.coos_limits[0][1])) hit_2 = ((a2 >= 0) & (np.abs(z_2) <= 1.) & angle_between(phi_2, self.coos_limits[0][0], self.coos_limits[0][1])) # If both 1 and 2 are hits, use the closer one hit_1[hit_2 & (a2 < a1)] = False hit_2[hit_1 & (a2 >= a1)] = False intersect[i_ind] = hit_1 | hit_2 # Set values into array from either point 1 or 2 interpos_local[i_ind[hit_1], 0] = phi_1[hit_1] interpos_local[i_ind[hit_1], 1] = z_1[hit_1] interpos_local[i_ind[hit_2], 0] = phi_2[hit_2] interpos_local[i_ind[hit_2], 1] = z_2[hit_2] # Calculate pos for point 1 or 2 in local xyz coord system interpos[i_ind[hit_1], :] = e2h(xyz[i_ind, :] + a1[:, None] * dir_e[i_ind, :], 1)[hit_1, :] interpos[i_ind[hit_2], :] = e2h(xyz[i_ind, :] + a2[:, None] * dir_e[i_ind, :], 1)[hit_2, :] trans, rot, zoom, shear = decompose44(self.pos4d) # interpos_local in z direction is in local coordinates, i.e. # the x coordiante is 0..1, but we want that in units of the # global coordinate system. interpos_local[:, 1] = interpos_local[:, 1] * zoom[2] interpos = np.dot(self.pos4d, interpos.T).T return intersect, interpos, interpos_local
def __getitem__(self, value): if value == 'R': trans, rot, zoom, shear = decompose44(self.pos4d) return zoom[0] else: return super(Cylinder, self).__getitem__(value)
def intersect(self, dir, pos, transform=True): '''Calculate the intersection point between a ray and the element Parameters ---------- dir : `numpy.ndarray` of shape (N, 4) homogeneous coordinates of the direction of the ray pos : `numpy.ndarray` of shape (N, 4) homogeneous coordinates of a point on the ray transform : bool If ``True``, input is in global coordinates and needs to be transformed here for the calculations; if ``False`` input is in local coordinates. Returns ------- intersect : boolean array of length N ``True`` if an intersection point is found. interpos : `numpy.ndarray` of shape (N, 4) homogeneous coordinates of the intersection point. Values are set to ``np.nan`` is no intersecton point is found. interpos_local : `numpy.ndarray` of shape (N, 2) phi, z coordiantes (in the local frame) for one of the intersection points. If both intersection points are required, reset ``self.inner`` and call this function again. ''' # This could be moved to a general function if not np.all(dir[:, 3] == 0): raise ValueError('First input must be direction vectors.') # Could test pos, too... if transform: invpos4d = np.linalg.inv(self.pos4d) dir = np.dot(invpos4d, dir.T).T pos = np.dot(invpos4d, pos.T).T xyz = h2e(pos) # Solve quadratic equation in steps. a12 = (-xr +- sqrt(xr - r**2(x**2 - R**2))) xy = xyz[:, :2] r = dir[:, :2] underroot = (np.einsum('ij,ij->i', xy, r))**2 - np.sum(r**2, axis=1) * (np.sum(xy**2, axis=1) - 1.) intersect = (underroot >= 0) i = intersect # just a shorthand because it's used so much below interpos_local = np.ones((pos.shape[0], 2)) interpos_local[:] = np.nan interpos = np.ones_like(pos) interpos[:] = np.nan if intersect.sum() > 0: b = np.sum(xy[i] * r[i], axis=1) denom = np.sum(r[i]**2, axis=1) a1 = (- b + np.sqrt(underroot[i])) / denom a2 = (- b - np.sqrt(underroot[i])) / denom x1 = xy[i, :] + a1[:, np.newaxis] * r[i, :] apick = np.where(self._inwardsoutwards * np.sum(x1 * r[i, :], axis=1) >=0, a1, a2) xy_p = xy[i, :] + apick[:, np.newaxis] * r[i, :] phi = np.arctan2(xy_p[:, 1], xy_p[:, 0]) # Shift phi by offset, then wrap to that it is in range [-pi, pi] interpos_local[i, 0] = (phi - self.phi_offset + np.pi) % (2 * np.pi) - np.pi # Those look like they hit in the xy plane. # Still possible to miss if z axis is too large. # Calculate z-coordiante at intersection interpos_local[intersect, 1] = xyz[i, 2] + apick * dir[i, 2] interpos[i, :2] = xy_p interpos[i, 2] = interpos_local[i, 1] interpos[i, 3] = 1 # set those elements on intersect that miss in z to False trans, rot, zoom, shear = decompose44(self.pos4d) z_p = interpos[i, 2] intersect[i.nonzero()[0][np.abs(z_p) > 1]] = False # Now reset everything to nan that does not intersect interpos_local[~i, :] = np.nan # interpos_local in z direction is in local coordinates, i.e. # the x coordiante is 0..1, but we want that in units of the # global coordinate system. interpos_local[:, 1] = interpos_local[:, 1] * zoom[2] interpos[~i, :] = np.nan interpos = np.dot(self.pos4d, interpos.T).T return intersect, interpos, interpos_local
def register3D_SIFT_wrapper(dataset_files, in_folder, out_folder, reg_config): """ Registers the similarity transformation exploiting the sift3D library. https://github.com/bbrister/SIFT3D returns transforms for each time point which subsequently applied to each image? """ import matlab.engine import scipy.io as spio import os import shutil import pylab as plt from tqdm import tqdm import time import pylab as plt import Visualisation.imshowpair as imshowpair from skimage.exposure import rescale_intensity # start the python matlab engine. eng = matlab.engine.start_matlab() fio.mkdir( out_folder ) # check that the output folder exists, create if does not exist. print 'registration' if reg_config['mode'] == 1: tforms = [] translate_matrixs = [] datasetsave_files = np.hstack([ dataset_files[2 * i + 1].replace(in_folder, out_folder) for i in range(len(dataset_files) // 2) ]) for i in tqdm(range(len(dataset_files) // 2)): t1 = time.time() im1file = dataset_files[2 * i] im2file = dataset_files[2 * i + 1] print 'registering SIFT' tmatrix = eng.register3D_SIFT_wrapper(im1file, im2file, datasetsave_files[i], reg_config['downsample'], reg_config['lib_path'], reg_config['return_img']) tmatrix = np.asarray(tmatrix) tforms.append(tmatrix) print tmatrix """ if matlab doesn't save matrix then we use python to do so. """ if reg_config['return_img'] != 1: print 'reading image' im1 = fio.read_multiimg_PIL(im1file) im2 = fio.read_multiimg_PIL(im2file) print 'finished reading image' # restrict the tranformation to affine. affine = np.zeros((4, 4)) affine[:-1, :] = tmatrix.copy() affine[-1] = np.array([0, 0, 0, 1]) # decomposition to zero out scaling and shear (rigid transformation) T, R, Z, S = decompose44(affine) affine = compose(T, R, np.ones(3), np.zeros(3)) # no scaling, no shears. print 'affine matrix' print affine im2 = np.uint8( tf.apply_affine_tform(im2.transpose(1, 2, 0), affine, np.array(im1.shape)[[1, 2, 0]])) im2 = im2.transpose(2, 0, 1) # realign and correct the translation artifact. im2, translate_matrix = align_centers(im1, im2) translate_matrixs.append(translate_matrix) print 'saving' fio.save_multipage_tiff(im2, datasetsave_files[i]) t2 = time.time() print 'elapsed time: ', t2 - t1 if reg_config['mode'] == 2: print 'running sequential registration' datasetsave_files = np.hstack( [f.replace(in_folder, out_folder) for f in dataset_files]) translate_matrixs = [] if reg_config['return_img'] == 1: tforms = eng.register3D_SIFT_wrapper_batch( dataset_files, datasetsave_files, reg_config['downsample'], reg_config['lib_path'], reg_config['mode']) else: print 'running python mode' tforms = [] # set the fixed image. fixed = fio.read_multiimg_PIL(dataset_files[0]) fio.save_multipage_tiff(fixed, datasetsave_files[0]) fixed_file = datasetsave_files[0] for i in tqdm(range(len(dataset_files) - 1)): moving_file = dataset_files[i + 1] print 'registering SIFT' tmatrix = eng.register3D_SIFT_wrapper(fixed_file, moving_file, datasetsave_files[i + 1], reg_config['downsample'], reg_config['lib_path'], reg_config['return_img']) tmatrix = np.asarray(tmatrix) tforms.append(tmatrix) """ Apply the transforms. """ im1 = fio.read_multiimg_PIL(fixed_file) im2 = fio.read_multiimg_PIL(moving_file) affine = np.zeros((4, 4)) affine[:-1, :] = tmatrix.copy() affine[-1] = np.array([0, 0, 0, 1]) # decomposition T, R, Z, S = decompose44(affine) # S is shear! affine = compose(T, R, np.ones(3), np.zeros( 3)) # constant scale, allow shear.(since there is wiggle?) print 'compose affine' print affine print 'applying transform' im2 = np.uint8( tf.apply_affine_tform(im2.transpose(1, 2, 0), affine, np.array(im1.shape)[[1, 2, 0]])) im2 = im2.transpose(2, 0, 1) # realign and correct the translation artifact. print 'realigning centers' im2, translate_matrix = align_centers(im1, im2) translate_matrixs.append(translate_matrix) # translate_matrixs.append(affine) fio.save_multipage_tiff(im2, datasetsave_files[i + 1]) comb = imshowpair.checkerboard_imgs(im1[:, 1000], im2[:, 1000], grid=(10, 10)) plt.figure() plt.imshow(rescale_intensity(comb / 255.)) plt.show() # update the savefile. fixed_file = datasetsave_files[i + 1] tforms = np.array(tforms) translate_matrixs = np.array(translate_matrixs) spio.savemat( os.path.join(out_folder, 'tforms_view_align.mat'), { 'files': dataset_files, 'config': reg_config, 'view_tforms': tforms, 'translate_tforms': translate_matrixs }) """ Stop the matlab engine? to prevent hangups. """ # eng.quit() return (tforms, translate_matrixs)
def generate_elements(self): '''Initialize all optical elements. After any of the ``elem_pos``, ``elem_uncertainty`` or ``uncertainty`` attributes has changed, `generate_elements` needs to be called to regenerate the positions of the individual elements using - the global position of ``Parallel.pos4d`` - the position of each element ``Parallel.elem_pos`` relativ to the global position - the global uncertainty `uncertainty`. - the uncertainty for individual facets. ''' self.elements = [] for i in range(len(self.elem_pos)): # _parse_position_keywords pops off keywords, thus operate on a copy here elem_args = self.elem_args.copy() # check if elem_args is the same for every element specific_elem_args = {} for k, v in elem_args.items(): if isinstance(v, list) and (len(v) == len(self.elem_pos)): specific_elem_args[k] = v[i] else: specific_elem_args[k] = v if 'name' not in specific_elem_args: specific_elem_args['name'] = 'Elem {0} in {1}'.format( i, self.name) elem_pos4d = _parse_position_keywords(specific_elem_args) telem, relem, zelem, Selem = decompose44(elem_pos4d) if not np.allclose(Selem, 0.): raise ValueError( 'pos4 for elem includes shear, which is not supported here.' ) e_center, e_rot, e_zoom, stemp = decompose44(self.elem_pos[i]) tsigelem, rsigelem, zsigelem, stemp = decompose44( self.elem_uncertainty[i]) if not np.allclose(stemp, 0.): raise SimulationSetupError( 'Shear is not supported in the elem uncertainty.') # Will be able to write this so much better in python 3.5, # but for now I don't want to nest np.dot too much so here it goes f_pos4d = np.eye(4) for m in reversed([ self.pos4d, # global position of ParallelElement self.uncertainty, # uncertainty in global positioning translation2aff( tsigelem), # uncertaintig in translation for elem translation2aff( e_center), # translate elem center to global center translation2aff(telem), # offset for all elem. Usually 0. mat2aff(rsigelem), # uncertainty in rotation for elem mat2aff(e_rot), # Rotation of individual elem mat2aff( relem), # Rotation for all elem, e.g. CAT gratings zoom2aff(zsigelem), # uncertainty in the zoom zoom2aff(e_zoom), # zoom of individual elem zoom2aff(zelem), # sets size for all elem ]): assert m.shape == (4, 4) f_pos4d = np.dot(m, f_pos4d) self.elements.append( self.elem_class(pos4d=f_pos4d, id_num=i, **specific_elem_args))
def jip_align(source_file, target_file, outdir, jipdir, prefix="w", auto=False, non_linear=False, fslconfig=DEFAULT_FSL_PATH): """ Register a source image to a taget image using the 'jip_align' command. Parameters ---------- source_file: str (mandatory) the source Nifti image. target_file: str (mandatory) the target Nifti masked image. outdir: str (mandatory) the destination folder. jipdir: str (mandatory) the jip binary path. prefix: str (optional, default 'w') prefix the generated file with this character. auto: bool (optional, default False) if set control the JIP window with the script. non_linear: bool (optional, default False) in the automatic mode, decide or not to compute the non-linear deformation field. fslconfig: str (optional) the FSL .sh configuration file. Returns ------- register_file: str the registered image. register_mask_file: str the registered and masked image. native_masked_file: str the masked image in the native space. """ # Check input image orientation: must be the same same_orient, orients = check_orientation([source_file, target_file]) if not same_orient: print( "[WARNING] Source file '{0}' ({2}) and taget file '{1}' ({3}) " "must have the same orientation for JIP to work properly.".format( source_file, target_file, orients[0], orients[1])) # Try to init the affine deformation # ToDo: find a way to init the JIP deformation if False: nb_cpus = multiprocessing.cpu_count() init_file = os.path.join(outdir, "init_affine.mat") init = AffineInitializer() init.inputs.fixed_image = target_file init.inputs.moving_image = source_file init.inputs.num_threads = nb_cpus init.inputs.out_file = init_file init.run() init_source_file = os.path.join(outdir, "init_source.nii.gz") at = ApplyTransforms() at.inputs.dimension = 3 at.inputs.input_image = source_file at.inputs.reference_image = target_file at.inputs.output_image = init_source_file at.inputs.transforms = init_file at.inputs.interpolation = "BSpline" at.inputs.num_threads = nb_cpus at.run() # From https://github.com/ANTsX/ANTs/wiki/ITK-affine-transform-conversion # From https://github.com/netstim/leaddbs/blob/master/helpers/ea_antsmat2mat.m init_mat = loadmat(init_file) afftransform = init_mat["AffineTransform_double_3_3"] m_center = init_mat["fixed"] mat = numpy.eye(4) mat[:3, :3] = afftransform[:9].reshape(3, 3).T m_translation = afftransform[9:] m_offset = m_translation + m_center - numpy.dot(mat[:3, :3], m_center) mat[:3, 3] = m_offset.squeeze() mat = numpy.linalg.inv(mat) #rotation = swap_affine(axes="LPS") #mat = numpy.dot(rotation, mat) ras_to_lps = numpy.ones((4, 4)) ras_to_lps[2, :2] = -1 ras_to_lps[:2, 2:] = -1 mat = mat * ras_to_lps print(mat) trans, rot, zoom, shear = decompose44(mat) euler = numpy.asarray(mat2euler(rot, axes='sxyz')) * 180. / numpy.pi print(trans, euler, zoom, shear) align_file = os.path.join(outdir, "align.com") im = nibabel.load(source_file) orig = im.affine[:3, 3] print(orig) shift = orig - trans with open(align_file, "wt") as of: of.write("set registration-translations {0} \n".format( " ".join([str(elem) for elem in shift]))) # Change current working directory cwd = os.getcwd() os.chdir(outdir) # Only support uncompressed Nifti source_file = ungzip_file(source_file, prefix="", outdir=outdir) target_file = ungzip_file(target_file, prefix="", outdir=outdir) # Create jip environment jip_envriron = os.environ jip_envriron["JIP_HOME"] = os.path.dirname(jipdir) if "PATH" in jip_envriron: jip_envriron["PATH"] = jip_envriron["PATH"] + ":" + jipdir else: jip_envriron["PATH"] = jipdir # Copy source file align_file = os.path.join(outdir, "align.com") cmd = ["align", source_file, "-t", target_file] if auto: if non_linear: auto_cmd = cmd + ["-L", "111111111111", "-W", "111", "-a"] else: auto_cmd = cmd + ["-L", "111111000000", "-W", "000", "-A"] if os.path.isfile(align_file): auto_cmd += ["-I"] subprocess.call(auto_cmd, env=jip_envriron) else: print(" ".join(cmd)) subprocess.call(cmd, env=jip_envriron) if not os.path.isfile(align_file): raise ValueError( "No 'align.com' file in '{0}' folder. JIP has probably failed: " "'{1}'".format(outdir, " ".join(cmd))) # Get the apply nonlinear deformation jip batches aplly_nonlin_batch = os.path.join( os.path.dirname(preproc.__file__), "resources", "apply_nonlin.com") aplly_inv_nonlin_batch = os.path.join( os.path.dirname(preproc.__file__), "resources", "apply_inv_nonlin.com") # Resample the source image register_file = os.path.join(outdir, prefix + os.path.basename(source_file)) cmd = ["jip", aplly_nonlin_batch, source_file, register_file] subprocess.call(cmd, env=jip_envriron) # Apply mask if os.path.isfile(register_file + ".gz"): os.remove(register_file + ".gz") register_mask_fileroot = os.path.join( outdir, "m" + prefix + os.path.basename(source_file).split(".")[0]) register_mask_file = apply_mask( input_file=register_file, output_fileroot=register_mask_fileroot, mask_file=target_file, fslconfig=fslconfig) # Send back masked image to original space register_mask_file = ungzip_file(register_mask_file, prefix="", outdir=outdir) native_masked_file = os.path.join( outdir, "n" + prefix + os.path.basename(register_mask_file)) cmd = ["jip", aplly_inv_nonlin_batch, register_mask_file, native_masked_file] subprocess.call(cmd, env=jip_envriron) # Restore current working directory and gzip output os.chdir(cwd) register_file = gzip_file( register_file, prefix="", outdir=outdir, remove_original_file=True) register_mask_file = gzip_file( register_mask_file, prefix="", outdir=outdir, remove_original_file=True) native_masked_file = gzip_file( native_masked_file, prefix="", outdir=outdir, remove_original_file=True) return register_file, register_mask_file, native_masked_file, align_file
def intersect(self, dir, pos, transform=True): '''Calculate the intersection point between a ray and the element Parameters ---------- dir : `numpy.ndarray` of shape (N, 4) homogeneous coordinates of the direction of the ray pos : `numpy.ndarray` of shape (N, 4) homogeneous coordinates of a point on the ray transform : bool If ``True``, input is in global coordinates and needs to be transformed here for the calculations; if ``False`` input is in local coordinates. Returns ------- intersect : boolean array of length N ``True`` if an intersection point is found. interpos : `numpy.ndarray` of shape (N, 4) homogeneous coordinates of the intersection point. Values are set to ``np.nan`` is no intersecton point is found. interpos_local : `numpy.ndarray` of shape (N, 2) phi, z coordiantes (in the local frame) for one of the intersection points. If both intersection points are required, reset ``self.inner`` and call this function again. ''' # This could be moved to a general function if not np.all(dir[:, 3] == 0): raise ValueError('First input must be direction vectors.') # Could test pos, too... if transform: invpos4d = np.linalg.inv(self.pos4d) dir = np.dot(invpos4d, dir.T).T pos = np.dot(invpos4d, pos.T).T xyz = h2e(pos) # Solve quadratic equation in steps. a12 = (-xr +- sqrt(xr - r**2(x**2 - R**2))) xy = xyz[:, :2] r = dir[:, :2] underroot = (np.einsum( 'ij,ij->i', xy, r))**2 - np.sum(r**2, axis=1) * (np.sum(xy**2, axis=1) - 1.) intersect = (underroot >= 0) i = intersect # just a shorthand because it's used so much below interpos_local = np.ones((pos.shape[0], 2)) interpos_local[:] = np.nan interpos = np.ones_like(pos) interpos[:] = np.nan if intersect.sum() > 0: b = np.sum(xy[i] * r[i], axis=1) denom = np.sum(r[i]**2, axis=1) a1 = (-b + np.sqrt(underroot[i])) / denom a2 = (-b - np.sqrt(underroot[i])) / denom x1 = xy[i, :] + a1[:, np.newaxis] * r[i, :] apick = np.where( self._inwardsoutwards * np.sum(x1 * r[i, :], axis=1) >= 0, a1, a2) xy_p = xy[i, :] + apick[:, np.newaxis] * r[i, :] phi = np.arctan2(xy_p[:, 1], xy_p[:, 0]) # Shift phi by offset, then wrap to that it is in range [-pi, pi] interpos_local[ i, 0] = (phi - self.phi_offset + np.pi) % (2 * np.pi) - np.pi # Those look like they hit in the xy plane. # Still possible to miss if z axis is too large. # Calculate z-coordiante at intersection interpos_local[intersect, 1] = xyz[i, 2] + apick * dir[i, 2] interpos[i, :2] = xy_p interpos[i, 2] = interpos_local[i, 1] interpos[i, 3] = 1 # set those elements on intersect that miss in z to False trans, rot, zoom, shear = decompose44(self.pos4d) z_p = interpos[i, 2] intersect[i.nonzero()[0][np.abs(z_p) > 1]] = False # Now reset everything to nan that does not intersect interpos_local[~i, :] = np.nan # interpos_local in z direction is in local coordinates, i.e. # the x coordiante is 0..1, but we want that in units of the # global coordinate system. interpos_local[:, 1] = interpos_local[:, 1] * zoom[2] interpos[~i, :] = np.nan interpos = np.dot(self.pos4d, interpos.T).T return intersect, interpos, interpos_local