def AngleResolved2Polar(data, output_size, hole=True, dtype=None): """ Converts an angle resolved image to polar (aka azymuthal) projection data (model.DataArray): The image that was projected on the CCD after being relfected on the parabolic mirror. The flat line of the D shape is expected to be horizontal, at the top. It needs PIXEL_SIZE and AR_POLE metadata. Pixel size is the sensor pixel size * binning / magnification. output_size (int): The size of the output DataArray (assumed to be square) hole (boolean): Crop the pole if True dtype (numpy dtype): intermediary dtype for computing the theta/phi data returns (model.DataArray): converted image in polar view """ assert(len(data.shape) == 2) # => 2D with greyscale # TODO: separate raw projection to another function, named AngleResolved2Rectangular() # Get the metadata try: pixel_size = data.metadata[model.MD_PIXEL_SIZE] mirror_x, mirror_y = data.metadata[model.MD_AR_POLE] parabola_f = data.metadata.get(model.MD_AR_PARABOLA_F, AR_PARABOLA_F) except KeyError: raise ValueError("Metadata required: MD_PIXEL_SIZE, MD_AR_POLE.") if dtype is None: dtype = numpy.float64 # Crop the input image to half circle cropped_image = _CropHalfCircle(data, pixel_size, (mirror_x, mirror_y), hole) theta_data = numpy.empty(shape=cropped_image.shape, dtype=dtype) phi_data = numpy.empty(shape=cropped_image.shape, dtype=dtype) omega_data = numpy.empty(shape=cropped_image.shape) # For each pixel of the input ndarray, input metadata is used to # calculate the corresponding theta, phi and radiant intensity image_x, image_y = cropped_image.shape jj = numpy.linspace(0, image_y - 1, image_y) xpix = mirror_x - jj for i in xrange(image_x): ypix = (i - mirror_y) + (2 * parabola_f) / pixel_size[1] theta, phi, omega = _FindAngle(data, xpix, ypix, pixel_size) theta_data[i, :] = theta phi_data[i, :] = phi omega_data[i, :] = cropped_image[i] / omega # Convert into polar coordinates h_output_size = output_size / 2 theta = theta_data * (h_output_size / math.pi * 2) phi = phi_data theta_data = numpy.cos(phi) * theta phi_data = numpy.sin(phi) * theta # Interpolation into 2d array # xi = numpy.linspace(-h_output_size, h_output_size, 2 * h_output_size + 1) # yi = numpy.linspace(-h_output_size, h_output_size, 2 * h_output_size + 1) # qz = mlab.griddata(phi_data.flat, theta_data.flat, omega_data.flat, xi, yi, interp="linear") # FIXME: need rotation (=swap axes), but swapping theta/phi slows down the # interpolation by 3 ?! with warnings.catch_warnings(): # Some points might be so close that they are identical (within float # precision). It's fine, no need to generate a warning. warnings.simplefilter("ignore", DuplicatePointWarning) triang = Triangulation(theta_data.flat, phi_data.flat) # TODO use the standard matplotlib.tri.Triangulation # + matplotlib.tri.LinearTriInterpolator interp = triang.linear_interpolator(omega_data.flat, default_value=0) qz = interp[-h_output_size:h_output_size:complex(0, output_size), # Y - h_output_size:h_output_size:complex(0, output_size)] # X qz = qz.swapaxes(0, 1)[:, ::-1] # rotate by 90° result = model.DataArray(qz, data.metadata) return result
def AngleResolved2Rectangular(data, output_size, hole=True, dtype=None): """ Converts an angle resolved image to equirectangular (aka cylindrical) projection (ie, phi/theta axes) data (model.DataArray): The image that was projected on the CCD after being relfected on the parabolic mirror. The flat line of the D shape is expected to be horizontal, at the top. It needs PIXEL_SIZE and AR_POLE metadata. Pixel size is the sensor pixel size * binning / magnification. output_size (int, int): The size of the output DataArray (theta, phi), not including the theta/phi angles at the first row/column hole (boolean): Crop the pole if True dtype (numpy dtype): intermediary dtype for computing the theta/phi data returns (model.DataArray): converted image in equirectangular view """ assert(len(data.shape) == 2) # => 2D with greyscale # Get the metadata try: pixel_size = data.metadata[model.MD_PIXEL_SIZE] mirror_x, mirror_y = data.metadata[model.MD_AR_POLE] parabola_f = data.metadata.get(model.MD_AR_PARABOLA_F, AR_PARABOLA_F) except KeyError: raise ValueError("Metadata required: MD_PIXEL_SIZE, MD_AR_POLE.") if dtype is None: dtype = numpy.float64 # Crop the input image to half circle cropped_image = _CropHalfCircle(data, pixel_size, (mirror_x, mirror_y), hole) theta_data = numpy.empty(shape=cropped_image.shape, dtype=dtype) phi_data = numpy.empty(shape=cropped_image.shape, dtype=dtype) omega_data = numpy.empty(shape=cropped_image.shape) # For each pixel of the input ndarray, input metadata is used to # calculate the corresponding theta, phi and radiant intensity image_x, image_y = cropped_image.shape jj = numpy.linspace(0, image_y - 1, image_y) xpix = mirror_x - jj for i in xrange(image_x): ypix = (i - mirror_y) + (2 * parabola_f) / pixel_size[1] theta, phi, omega = _FindAngle(data, xpix, ypix, pixel_size) theta_data[i, :] = theta phi_data[i, :] = phi omega_data[i, :] = cropped_image[i] / omega # compute new mask phi_lin = numpy.linspace(0, 2 * math.pi, output_size[1]) theta_lin = numpy.linspace(0, math.pi / 2, output_size[0]) phi_grid, theta_grid = numpy.meshgrid(phi_lin, theta_lin) a = (1 / (4 * parabola_f)) xcut = AR_XMAX - AR_PARABOLA_F # length vector c = (2 * (a * numpy.cos(phi_grid) * numpy.sin(theta_grid) + a)) ** -1 x = -numpy.sin(theta_grid) * numpy.cos(phi_grid) * c z = numpy.cos(theta_grid) * c mask = numpy.ones(output_size) mask[(x > xcut) | (theta_grid < (4 * numpy.pi / 180)) | (z < AR_FOCUS_DISTANCE)] = 0 # TODO: can probably choose a selection here to speed up interpolation. # This is a silly fix but it works. Prevents extrapolation which leads to errors theta_data = numpy.tile(theta_data, (1, 3)) phi_data = numpy.append(numpy.append(phi_data - 2 * math.pi, phi_data, axis=1), phi_data + 2 * math.pi, axis=1) ARdata = numpy.tile(omega_data, (1, 3)) with warnings.catch_warnings(): # Some points might be so close that they are identical (within float # precision). It's fine, no need to generate a warning. warnings.simplefilter("ignore", DuplicatePointWarning) triang = Triangulation(phi_data.flat, theta_data.flat) interp = triang.linear_interpolator(ARdata.flat, default_value=0) qz = interp[0:numpy.pi / 2:complex(0, output_size[0]), 0:2 * numpy.pi:complex(0, output_size[1])] qz = numpy.roll(qz, qz.shape[1] // 2, axis=1) qz_masked = qz * mask # TODO: put theta/phi angles in metadata? # attach theta as first column qz_masked = numpy.append(theta_lin.reshape(theta_lin.shape[0], 1), qz_masked, axis=1) # attach phi as first row phi_lin = numpy.append([[0]], phi_lin.reshape(1, phi_lin.shape[0]), axis=1) qz_masked = numpy.append(phi_lin, qz_masked, axis=0) result = model.DataArray(qz_masked, data.metadata) return result
def AngleResolved2Polar(data, output_size, hole=True, dtype=None): """ Converts an angle resolved image to polar (aka azymuthal) projection data (model.DataArray): The image that was projected on the CCD after being relfected on the parabolic mirror. The flat line of the D shape is expected to be horizontal, at the top. It needs PIXEL_SIZE and AR_POLE metadata. Pixel size is the sensor pixel size * binning / magnification. output_size (int): The size of the output DataArray (assumed to be square) hole (boolean): Crop the pole if True dtype (numpy dtype): intermediary dtype for computing the theta/phi data returns (model.DataArray): converted image in polar view """ assert (len(data.shape) == 2) # => 2D with greyscale # TODO: separate raw projection to another function, named AngleResolved2Rectangular() # Get the metadata try: pixel_size = data.metadata[model.MD_PIXEL_SIZE] mirror_x, mirror_y = data.metadata[model.MD_AR_POLE] parabola_f = data.metadata.get(model.MD_AR_PARABOLA_F, AR_PARABOLA_F) except KeyError: raise ValueError("Metadata required: MD_PIXEL_SIZE, MD_AR_POLE.") if dtype is None: dtype = numpy.float64 # Crop the input image to half circle cropped_image = _CropHalfCircle(data, pixel_size, (mirror_x, mirror_y), hole) theta_data = numpy.empty(shape=cropped_image.shape, dtype=dtype) phi_data = numpy.empty(shape=cropped_image.shape, dtype=dtype) omega_data = numpy.empty(shape=cropped_image.shape) # For each pixel of the input ndarray, input metadata is used to # calculate the corresponding theta, phi and radiant intensity image_x, image_y = cropped_image.shape jj = numpy.linspace(0, image_y - 1, image_y) xpix = mirror_x - jj for i in xrange(image_x): ypix = (i - mirror_y) + (2 * parabola_f) / pixel_size[1] theta, phi, omega = _FindAngle(data, xpix, ypix, pixel_size) theta_data[i, :] = theta phi_data[i, :] = phi omega_data[i, :] = cropped_image[i] / omega # Convert into polar coordinates h_output_size = output_size / 2 theta = theta_data * (h_output_size / math.pi * 2) phi = phi_data theta_data = numpy.cos(phi) * theta phi_data = numpy.sin(phi) * theta # Interpolation into 2d array # FIXME: griddata() uses a 3x less memory (and especially, doesn't leak), # but it's 5x slower # => create a cpython function calling libqhull directly? # grid_x, grid_y = numpy.mgrid[-h_output_size:h_output_size:output_size * 1j, # - h_output_size:h_output_size:output_size * 1j] # # #One way possible to increase the speed is to discard points too close from # #each other. Everything < 1 px apart is pretty useless. As they are # #spatially ordered, it's shouldn't be too costly to check # #See kmeans clustering maybe? # #nt, np, no = [theta_data[0, 0]], [phi_data[0, 0]], [omega_data[0, 0]] # #for t, p, o in zip(theta_data.flat, phi_data.flat, omega_data.flat): # # if math.hypot(t - nt[-1], p - np[-1]) > 0.9: # # nt.append(t) # # np.append(p) # # no.append(o) # #logging.warning("Reduce size to %d", len(no)) # #qz = griddata((nt, np), no, (grid_x, grid_y), method='linear', fill_value=0) # qz = griddata((theta_data.flat, phi_data.flat), omega_data.flat, (grid_x, grid_y), # method='linear', fill_value=0) # qz = qz[:, ::-1] # Warning: Uses a lot of memory, which is not recovered until the thread is # ended. Every thread will use a different memory pool. # FIXME: need rotation (=swap axes), but swapping theta/phi slows down the # interpolation by 3 ?! with warnings.catch_warnings(): # Some points might be so close that they are identical (within float # precision). It's fine, no need to generate a warning. warnings.simplefilter("ignore", DuplicatePointWarning) triang = Triangulation( theta_data.flat, phi_data.flat) # FIXME: Leaks memory when run in a separate thread interp = triang.linear_interpolator(omega_data.flat, default_value=0) qz = interp[-h_output_size:h_output_size:complex(0, output_size), # Y -h_output_size:h_output_size:complex(0, output_size)] # X qz = qz.swapaxes(0, 1)[:, ::-1] # rotate by 90° # TODO use the standard matplotlib.tri.Triangulation, but it uses 3x more memory # also leaks, and is slower! # triang = Triangulation(phi_data.flat, theta_data.flat) # interp = LinearTriInterpolator(triang, omega_data.flat) # xi, yi = numpy.meshgrid(numpy.linspace(-h_output_size, h_output_size, output_size), # numpy.linspace(-h_output_size, h_output_size, output_size)) # qz = interp(xi, yi) # qz = qz[:, ::-1] result = model.DataArray(qz, data.metadata) return result
def AngleResolved2Polar(data, output_size, hole=True, dtype=None): """ Converts an angle resolved image to polar (aka azymuthal) projection data (model.DataArray): The image that was projected on the CCD after being relfected on the parabolic mirror. The flat line of the D shape is expected to be horizontal, at the top. It needs PIXEL_SIZE and AR_POLE metadata. Pixel size is the sensor pixel size * binning / magnification. output_size (int): The size of the output DataArray (assumed to be square) hole (boolean): Crop the pole if True dtype (numpy dtype): intermediary dtype for computing the theta/phi data returns (model.DataArray): converted image in polar view """ assert(len(data.shape) == 2) # => 2D with greyscale # TODO: separate raw projection to another function, named AngleResolved2Rectangular() # Get the metadata try: pixel_size = data.metadata[model.MD_PIXEL_SIZE] mirror_x, mirror_y = data.metadata[model.MD_AR_POLE] parabola_f = data.metadata.get(model.MD_AR_PARABOLA_F, AR_PARABOLA_F) except KeyError: raise ValueError("Metadata required: MD_PIXEL_SIZE, MD_AR_POLE.") if dtype is None: dtype = numpy.float64 # Crop the input image to half circle cropped_image = _CropHalfCircle(data, pixel_size, (mirror_x, mirror_y), hole) theta_data = numpy.empty(shape=cropped_image.shape, dtype=dtype) phi_data = numpy.empty(shape=cropped_image.shape, dtype=dtype) omega_data = numpy.empty(shape=cropped_image.shape) # For each pixel of the input ndarray, input metadata is used to # calculate the corresponding theta, phi and radiant intensity image_x, image_y = cropped_image.shape jj = numpy.linspace(0, image_y - 1, image_y) xpix = mirror_x - jj for i in xrange(image_x): ypix = (i - mirror_y) + (2 * parabola_f) / pixel_size[1] theta, phi, omega = _FindAngle(data, xpix, ypix, pixel_size) theta_data[i, :] = theta phi_data[i, :] = phi omega_data[i, :] = cropped_image[i] / omega # Convert into polar coordinates h_output_size = output_size / 2 theta = theta_data * (h_output_size / math.pi * 2) phi = phi_data theta_data = numpy.cos(phi) * theta phi_data = numpy.sin(phi) * theta # Interpolation into 2d array # FIXME: griddata() uses a 3x less memory (and especially, doesn't leak), # but it's 5x slower # => create a cpython function calling libqhull directly? # grid_x, grid_y = numpy.mgrid[-h_output_size:h_output_size:output_size * 1j, # - h_output_size:h_output_size:output_size * 1j] # # #One way possible to increase the speed is to discard points too close from # #each other. Everything < 1 px apart is pretty useless. As they are # #spatially ordered, it's shouldn't be too costly to check # #See kmeans clustering maybe? # #nt, np, no = [theta_data[0, 0]], [phi_data[0, 0]], [omega_data[0, 0]] # #for t, p, o in zip(theta_data.flat, phi_data.flat, omega_data.flat): # # if math.hypot(t - nt[-1], p - np[-1]) > 0.9: # # nt.append(t) # # np.append(p) # # no.append(o) # #logging.warning("Reduce size to %d", len(no)) # #qz = griddata((nt, np), no, (grid_x, grid_y), method='linear', fill_value=0) # qz = griddata((theta_data.flat, phi_data.flat), omega_data.flat, (grid_x, grid_y), # method='linear', fill_value=0) # qz = qz[:, ::-1] # Warning: Uses a lot of memory, which is not recovered until the thread is # ended. Every thread will use a different memory pool. # FIXME: need rotation (=swap axes), but swapping theta/phi slows down the # interpolation by 3 ?! with warnings.catch_warnings(): # Some points might be so close that they are identical (within float # precision). It's fine, no need to generate a warning. warnings.simplefilter("ignore", DuplicatePointWarning) triang = Triangulation(theta_data.flat, phi_data.flat) # FIXME: Leaks memory when run in a separate thread interp = triang.linear_interpolator(omega_data.flat, default_value=0) qz = interp[-h_output_size:h_output_size:complex(0, output_size), # Y - h_output_size:h_output_size:complex(0, output_size)] # X qz = qz.swapaxes(0, 1)[:, ::-1] # rotate by 90° # TODO use the standard matplotlib.tri.Triangulation, but it uses 3x more memory # also leaks, and is slower! # triang = Triangulation(phi_data.flat, theta_data.flat) # interp = LinearTriInterpolator(triang, omega_data.flat) # xi, yi = numpy.meshgrid(numpy.linspace(-h_output_size, h_output_size, output_size), # numpy.linspace(-h_output_size, h_output_size, output_size)) # qz = interp(xi, yi) # qz = qz[:, ::-1] result = model.DataArray(qz, data.metadata) return result
def AngleResolved2Rectangular(data, output_size, hole=True, dtype=None): """ Converts an angle resolved image to equirectangular (aka cylindrical) projection (ie, phi/theta axes) data (model.DataArray): The image that was projected on the CCD after being relfected on the parabolic mirror. The flat line of the D shape is expected to be horizontal, at the top. It needs PIXEL_SIZE and AR_POLE metadata. Pixel size is the sensor pixel size * binning / magnification. output_size (int, int): The size of the output DataArray (theta, phi), not including the theta/phi angles at the first row/column hole (boolean): Crop the pole if True dtype (numpy dtype): intermediary dtype for computing the theta/phi data returns (model.DataArray): converted image in equirectangular view """ assert (len(data.shape) == 2) # => 2D with greyscale # Get the metadata try: pixel_size = data.metadata[model.MD_PIXEL_SIZE] mirror_x, mirror_y = data.metadata[model.MD_AR_POLE] parabola_f = data.metadata.get(model.MD_AR_PARABOLA_F, AR_PARABOLA_F) except KeyError: raise ValueError("Metadata required: MD_PIXEL_SIZE, MD_AR_POLE.") if dtype is None: dtype = numpy.float64 # Crop the input image to half circle cropped_image = _CropHalfCircle(data, pixel_size, (mirror_x, mirror_y), hole) theta_data = numpy.empty(shape=cropped_image.shape, dtype=dtype) phi_data = numpy.empty(shape=cropped_image.shape, dtype=dtype) omega_data = numpy.empty(shape=cropped_image.shape) # For each pixel of the input ndarray, input metadata is used to # calculate the corresponding theta, phi and radiant intensity image_x, image_y = cropped_image.shape jj = numpy.linspace(0, image_y - 1, image_y) xpix = mirror_x - jj for i in xrange(image_x): ypix = (i - mirror_y) + (2 * parabola_f) / pixel_size[1] theta, phi, omega = _FindAngle(data, xpix, ypix, pixel_size) theta_data[i, :] = theta phi_data[i, :] = phi omega_data[i, :] = cropped_image[i] / omega # compute new mask phi_lin = numpy.linspace(0, 2 * math.pi, output_size[1]) theta_lin = numpy.linspace(0, math.pi / 2, output_size[0]) phi_grid, theta_grid = numpy.meshgrid(phi_lin, theta_lin) a = (1 / (4 * parabola_f)) xcut = AR_XMAX - AR_PARABOLA_F # length vector c = (2 * (a * numpy.cos(phi_grid) * numpy.sin(theta_grid) + a))**-1 x = -numpy.sin(theta_grid) * numpy.cos(phi_grid) * c z = numpy.cos(theta_grid) * c mask = numpy.ones(output_size) mask[(x > xcut) | (theta_grid < (4 * numpy.pi / 180)) | (z < AR_FOCUS_DISTANCE)] = 0 # TODO: can probably choose a selection here to speed up interpolation. # This is a silly fix but it works. Prevents extrapolation which leads to errors theta_data = numpy.tile(theta_data, (1, 3)) phi_data = numpy.append(numpy.append(phi_data - 2 * math.pi, phi_data, axis=1), phi_data + 2 * math.pi, axis=1) ARdata = numpy.tile(omega_data, (1, 3)) with warnings.catch_warnings(): # Some points might be so close that they are identical (within float # precision). It's fine, no need to generate a warning. warnings.simplefilter("ignore", DuplicatePointWarning) triang = Triangulation(phi_data.flat, theta_data.flat) interp = triang.linear_interpolator(ARdata.flat, default_value=0) qz = interp[0:numpy.pi / 2:complex(0, output_size[0]), 0:2 * numpy.pi:complex(0, output_size[1])] qz = numpy.roll(qz, qz.shape[1] // 2, axis=1) qz_masked = qz * mask # TODO: put theta/phi angles in metadata? # attach theta as first column qz_masked = numpy.append(theta_lin.reshape(theta_lin.shape[0], 1), qz_masked, axis=1) # attach phi as first row phi_lin = numpy.append([[0]], phi_lin.reshape(1, phi_lin.shape[0]), axis=1) qz_masked = numpy.append(phi_lin, qz_masked, axis=0) result = model.DataArray(qz_masked, data.metadata) return result