def _read_SES3D(fh, headonly=False): """ Internal SES3D parsing routine. """ # Import here to avoid circular imports. from obspy.core import AttribDict, Trace, Stream # Read the header. component = fh.readline().split()[0].lower() npts = int(fh.readline().split()[-1]) delta = float(fh.readline().split()[-1]) # Skip receiver location line. fh.readline() rec_loc = fh.readline().split() rec_x, rec_y, rec_z = list(map(float, [rec_loc[1], rec_loc[3], rec_loc[5]])) # Skip the source location line. fh.readline() src_loc = fh.readline().split() src_x, src_y, src_z = list(map(float, [src_loc[1], src_loc[3], src_loc[5]])) # Read the data. if headonly is False: data = np.array(list(map(float, fh.readlines())), dtype="float32") else: data = np.array([]) ses3d = AttribDict() ses3d.receiver_latitude = rotations.colat2lat(rec_x) ses3d.receiver_longitude = rec_y ses3d.receiver_depth_in_m = rec_z ses3d.source_latitude = rotations.colat2lat(src_x) ses3d.source_longitude = src_y ses3d.source_depth_in_m = src_z header = { "delta": delta, "channel": COMPONENT_MAP[component], "ses3d": ses3d, "npts": npts } # Setup Obspy Stream/Trace structure. tr = Trace(data=data, header=header) # Small check. if headonly is False and npts != tr.stats.npts: msg = "The sample count specified in the header does not match " + \ "the actual data count." warnings.warn(msg) return Stream(traces=[tr])
def _read_SES3D(fh, headonly=False): """ Internal SES3D parsing routine. """ # Import here to avoid circular imports. from obspy.core import AttribDict, Trace, Stream # Read the header. component = fh.readline().split()[0].lower() npts = int(fh.readline().split()[-1]) delta = float(fh.readline().split()[-1]) # Skip receiver location line. fh.readline() rec_loc = fh.readline().split() rec_x, rec_y, rec_z = map(float, [rec_loc[1], rec_loc[3], rec_loc[5]]) # Skip the source location line. fh.readline() src_loc = fh.readline().split() src_x, src_y, src_z = map(float, [src_loc[1], src_loc[3], src_loc[5]]) # Read the data. if headonly is False: data = np.array(map(float, fh.readlines()), dtype="float32") else: data = np.array([]) ses3d = AttribDict() ses3d.receiver_latitude = rotations.colat2lat(rec_x) ses3d.receiver_longitude = rec_y ses3d.receiver_depth_in_m = rec_z ses3d.source_latitude = rotations.colat2lat(src_x) ses3d.source_longitude = src_y ses3d.source_depth_in_m = src_z header = { "delta": delta, "channel": COMPONENT_MAP[component], "ses3d": ses3d, "npts": npts } # Setup Obspy Stream/Trace structure. tr = Trace(data=data, header=header) # Small check. if headonly is False and npts != tr.stats.npts: msg = "The sample count specified in the header does not match " + \ "the actual data count." warnings.warn(msg) return Stream(traces=[tr])
def __str__(self): """ Eases interactive working. """ ret_str = "Raw SES3D Model (split in %i parts)\n" % \ (self.setup["total_cpu_count"]) ret_str += "\tSetup:\n" ret_str += "\t\tLatitude: {:.2f} - {:.2f}\n".format(*[ rotations.colat2lat(_i) for _i in self.setup["physical_boundaries_x"][::-1] ]) ret_str += "\t\tLongitude: %.2f - %.2f\n" % \ self.setup["physical_boundaries_y"] ret_str += "\t\tDepth in km: {:.2f} - {:.2f}\n".format(*[ 6371 - _i / 1000 for _i in self.setup["physical_boundaries_z"][::-1] ]) ret_str += "\t\tTotal element count: %i\n" % \ self.setup["total_element_count"] ret_str += "\t\tTotal collocation point count: %i (without " \ "duplicates: %i)\n" % ( self.setup["total_point_count"], self.setup["total_point_count_without_duplicates"]) ret_str += "\tMemory requirement per component: %.1f MB\n" % \ ((self.setup["total_point_count_without_duplicates"] * 4) / (1024.0 ** 2)) ret_str += "\tAvailable components: %s\n" % (", ".join( sorted(self.components.keys()))) ret_str += "\tAvailable derived components: %s\n" % (", ".join( sorted(self.available_derived_components))) ret_str += "\tParsed components: %s" % (", ".join( sorted(self.parsed_components.keys()))) return ret_str
def __str__(self): """ Eases interactive working. """ ret_str = "Raw SES3D Model (split in %i parts)\n" % \ (self.setup["total_cpu_count"]) ret_str += "\tSetup:\n" ret_str += "\t\tLatitude: {:.2f} - {:.2f}\n".format( *[rotations.colat2lat(_i) for _i in self.setup["physical_boundaries_x"][::-1]]) ret_str += "\t\tLongitude: %.2f - %.2f\n" % \ self.setup["physical_boundaries_y"] ret_str += "\t\tDepth in km: {:.2f} - {:.2f}\n".format( *[6371 - _i / 1000 for _i in self.setup["physical_boundaries_z"][::-1]]) ret_str += "\t\tTotal element count: %i\n" % \ self.setup["total_element_count"] ret_str += "\t\tTotal grid point count: %i\n" % \ self.setup["total_point_count"] ret_str += "\tMemory requirement per component: %.1f MB\n" % \ ((self.setup["total_point_count"] * 4) / (1024.0 ** 2)) ret_str += "\tAvailable components: %s\n" % (", ".join( sorted(self.components.keys()))) ret_str += "\tAvailable derived components: %s\n" % (", ".join( sorted(self.available_derived_components))) ret_str += "\tParsed components: %s" % (", ".join( sorted(self.parsed_components.keys()))) return ret_str
def unrotated_center(self): c_lng = rotations.get_center_angle(self.max_longitude, self.min_longitude) c_lat = rotations.colat2lat(rotations.get_center_angle( rotations.lat2colat(self.max_latitude), rotations.lat2colat(self.min_latitude))) Point = collections.namedtuple("CenterPoint", ["longitude", "latitude"]) return Point(longitude=c_lng, latitude=c_lat)
def unrotated_center(self): c_lng = rotations.get_center_angle(self.max_longitude, self.min_longitude) c_lat = rotations.colat2lat( rotations.get_center_angle(rotations.lat2colat(self.max_latitude), rotations.lat2colat(self.min_latitude))) Point = collections.namedtuple("CenterPoint", ["longitude", "latitude"]) return Point(longitude=c_lng, latitude=c_lat)
def __init__(self, directory, domain, model_type="earth_model"): """ The init function. :param directory: The directory where the earth model or kernel is located. :param model_type: Determined the type of model loaded. Currently two are supported: * earth_model - The standard SES3D model files (default) * kernel - The kernels. Identifies by lots of grad_* files. * wavefield - The raw wavefields. """ self.directory = directory self.boxfile = os.path.join(self.directory, "boxfile") if not os.path.exists(self.boxfile): msg = "boxfile not found. Wrong directory?" raise ValueError(msg) # Read the boxfile. self.setup = self._read_boxfile() self.domain = domain self.model_type = model_type self.one_d_model = OneDimensionalModel("ak135-f") if model_type == "earth_model": # Now check what different models are available in the directory. # This information is also used to infer the degree of the used # lagrange polynomial. components = ["A", "B", "C", "lambda", "mu", "rhoinv", "Q"] self.available_derived_components = ["vp", "vsh", "vsv", "rho"] self.components = {} self.parsed_components = {} for component in components: files = glob.glob( os.path.join(directory, "%s[0-9]*" % component)) if len(files) != len(self.setup["subdomains"]): continue # Check that the naming is continuous. all_good = True for _i in range(len(self.setup["subdomains"])): if os.path.join(directory, "%s%i" % (component, _i)) in files: continue all_good = False break if all_good is False: msg = "Naming for component %s is off. It will be skipped." warnings.warn(msg) continue # They also all need to have the same size. if len(set([os.path.getsize(_i) for _i in files])) != 1: msg = ("Component %s has the right number of model files " "but they are not of equal size") % component warnings.warn(msg) continue # Sort the files by ascending number. files.sort(key=lambda x: int( re.findall(r"\d+$", (os.path.basename(x)))[0])) self.components[component] = {"filenames": files} elif model_type == "wavefield": components = ["vz", "vx", "vy", "vz"] self.available_derived_components = [] self.components = {} self.parsed_components = {} for component in components: files = glob.glob(os.path.join(directory, "%s_*_*" % component)) if not files: continue timesteps = collections.defaultdict(list) for filename in files: timestep = int(os.path.basename(filename).split("_")[-1]) timesteps[timestep].append(filename) for timestep, filenames in timesteps.items(): self.components["%s %s" % (component, timestep)] = \ {"filenames": sorted( filenames, key=lambda x: int( os.path.basename(x).split("_")[1]))} elif model_type == "kernel": # Now check what different models are available in the directory. # This information is also used to infer the degree of the used # lagrange polynomial. components = ["grad_cp", "grad_csh", "grad_csv", "grad_rho"] self.available_derived_components = [] self.components = {} self.parsed_components = {} for component in components: files = glob.glob( os.path.join(directory, "%s_[0-9]*" % component)) if len(files) != len(self.setup["subdomains"]): continue if len(set([os.path.getsize(_i) for _i in files])) != 1: msg = ("Component %s has the right number of model files " "but they are not of equal size") % component warnings.warn(msg) continue # Sort the files by ascending number. files.sort(key=lambda x: int( re.findall(r"\d+$", (os.path.basename(x)))[0])) self.components[component] = {"filenames": files} else: msg = "model_type '%s' not known." % model_type raise ValueError(msg) # All files for a single component have the same size. Now check that # all files have the same size. unique_filesizes = len( list( set([ os.path.getsize(_i["filenames"][0]) for _i in self.components.values() ]))) if unique_filesizes != 1: msg = ("The different components in the folder do not have the " "same number of samples") raise ValueError(msg) # Now calculate the lagrange polynomial degree. All necessary # information is present. size = os.path.getsize( list(self.components.values())[0]["filenames"][0]) sd = self.setup["subdomains"][0] x, y, z = sd["index_x_count"], sd["index_y_count"], sd["index_z_count"] self.lagrange_polynomial_degree = \ int(round(((size * 0.25) / (x * y * z)) ** (1.0 / 3.0) - 1)) self._calculate_final_dimensions() # Setup the boundaries. self.lat_bounds = [ rotations.colat2lat(_i) for _i in self.setup["physical_boundaries_x"][::-1] ] self.lng_bounds = self.setup["physical_boundaries_y"] self.depth_bounds = [ 6371 - _i / 1000.0 for _i in self.setup["physical_boundaries_z"] ] self.collocation_points_lngs = self._get_collocation_points_along_axis( self.lng_bounds[0], self.lng_bounds[1], self.setup["point_count_in_y"]) self.collocation_points_lats = self._get_collocation_points_along_axis( self.lat_bounds[0], self.lat_bounds[1], self.setup["point_count_in_x"]) self.collocation_points_depth = \ self._get_collocation_points_along_axis( self.depth_bounds[1], self.depth_bounds[0], self.setup["point_count_in_z"])[::-1]
def plot_depth_slice(self, component, depth_in_km): """ Plots a depth slice. """ lat_bounds = [rotations.colat2lat(_i) for _i in self.setup["physical_boundaries_x"][::-1]] lng_bounds = self.setup["physical_boundaries_y"] depth_bounds = [6371 - _i / 1000 for _i in self.setup["physical_boundaries_z"]] data = self.parsed_components[component] available_depths = np.linspace(*depth_bounds, num=data.shape[2])[::-1] depth_index = np.argmin(np.abs(available_depths - depth_in_km)) lon, lat = np.meshgrid( np.linspace(*lng_bounds, num=data.shape[1]), np.linspace(*lat_bounds, num=data.shape[0])) if self.rotation_axis and self.rotation_angle_in_degree: lon_shape = lon.shape lat_shape = lat.shape lon.shape = lon.size lat.shape = lat.size lat, lon = rotations.rotate_lat_lon(lat, lon, self.rotation_axis, self.rotation_angle_in_degree) lon.shape = lon_shape lat.shape = lat_shape # Get the center of the map. lon_0 = lon.min() + lon.ptp() / 2.0 lat_0 = lat.min() + lat.ptp() / 2.0 plt.figure(0) # Attempt to zoom into the region of interest. max_extend = max(lon.ptp(), lat.ptp()) extend_used = max_extend / 180.0 if extend_used < 0.5: x_buffer = 0.2 * lon.ptp() y_buffer = 0.2 * lat.ptp() m = Basemap(projection='merc', resolution="l", #lat_0=lat_0, lon_0=lon_0, llcrnrlon=lon.min() - x_buffer, urcrnrlon=lon.max() + x_buffer, llcrnrlat=lat.min() - y_buffer, urcrnrlat=lat.max() + y_buffer) else: m = Basemap(projection='ortho', lon_0=lon_0, lat_0=lat_0, resolution="c") m.drawcoastlines() m.fillcontinents("0.9", zorder=0) m.drawmapboundary(fill_color="white") m.drawparallels(np.arange(-80.0, 80.0, 10.0), labels=[1, 0, 0, 0]) m.drawmeridians(np.arange(-170.0, 170.0, 10.0), labels=[0, 0, 0, 1]) m.drawcountries() x, y = m(lon, lat) im = m.pcolormesh(x, y, data[::-1, :, depth_index], cmap=tomo_colormap) # Add colorbar and potentially unit. cm = m.colorbar(im, "right", size="3%", pad='2%') if component in UNIT_DICT: cm.set_label(UNIT_DICT[component], fontsize="x-large", rotation=0) plt.suptitle("Depth slice of %s at %i km" % (component, int(depth_in_km)), size="large") def _on_button_press(event): if event.button != 1 or not event.inaxes: return lon, lat = m(event.xdata, event.ydata, inverse=True) # Convert to colat to ease indexing. colat = rotations.lat2colat(lat) x_range = (self.setup["physical_boundaries_x"][1] - self.setup["physical_boundaries_x"][0]) x_frac = (colat - self.setup["physical_boundaries_x"][0]) / x_range x_index = int(((self.setup["boundaries_x"][1] - self.setup["boundaries_x"][0]) * x_frac) + self.setup["boundaries_x"][0]) y_range = (self.setup["physical_boundaries_y"][1] - self.setup["physical_boundaries_y"][0]) y_frac = (lon - self.setup["physical_boundaries_y"][0]) / y_range y_index = int(((self.setup["boundaries_y"][1] - self.setup["boundaries_y"][0]) * y_frac) + self.setup["boundaries_y"][0]) plt.figure(1, figsize=(3, 8)) depths = available_depths values = data[x_index, y_index, :] plt.plot(values, depths) plt.grid() plt.ylim(depths[-1], depths[0]) plt.show() plt.close() plt.figure(0) plt.gcf().canvas.mpl_connect('button_press_event', _on_button_press) plt.show()
def read_SES3D(file_or_file_object, *args, **kwargs): """ Turns a SES3D file into a obspy.core.Stream object. SES3D files do not contain a starttime and thus the first first sample will always begin at 1970-01-01T00:00:00. The data will be a floating point array of the ground velocity in meters per second. Furthermore every trace will have a trace.stats.ses3d dictionary which contains the following six keys: * receiver_latitude * receiver_longitde * receiver_depth_in_m * source_latitude * source_longitude * source_depth_in_m The network, station, and location attributes of the trace will be empty, and the channel will be set to either 'X' (south component), 'Y' (east component), or 'Z' (vertical component). """ # Import here to avoid circular imports. from obspy.core import AttribDict, Trace, Stream # Make sure that it is a file like object. if not hasattr(file_or_file_object, "read"): with open(file_or_file_object, "rb") as open_file: file_or_file_object = StringIO(open_file.read()) # Read the header. component = file_or_file_object.readline().split()[0].lower() npts = int(file_or_file_object.readline().split()[-1]) delta = float(file_or_file_object.readline().split()[-1]) # Skip receiver location line. file_or_file_object.readline() rec_loc = file_or_file_object.readline().split() rec_x, rec_y, rec_z = map(float, [rec_loc[1], rec_loc[3], rec_loc[5]]) # Skip the source location line. file_or_file_object.readline() src_loc = file_or_file_object.readline().split() src_x, src_y, src_z = map(float, [src_loc[1], src_loc[3], src_loc[5]]) # Read the data. data = np.array(map(float, file_or_file_object.readlines()), dtype="float32") # Setup Obspy Stream/Trace structure. tr = Trace(data=data) tr.stats.delta = delta # Map the channel attributes. tr.stats.channel = { "theta": "X", "phi": "Y", "r": "Z"}[component] tr.stats.ses3d = AttribDict() tr.stats.ses3d.receiver_latitude = rotations.colat2lat(rec_x) tr.stats.ses3d.receiver_longitude = rec_y tr.stats.ses3d.receiver_depth_in_m = rec_z tr.stats.ses3d.source_latitude = rotations.colat2lat(src_x) tr.stats.ses3d.source_longitude = src_y tr.stats.ses3d.source_depth_in_m = src_z # Small check. if npts != tr.stats.npts: msg = "The sample count specified in the header does not match " + \ "the actual data count." warnings.warn(msg) return Stream(traces=[tr])
def test_RotateMomentTensor(): """ Tests the moment tensor rotations. """ # A full rotation should not change anything. Mrr, Mtt, Mpp, Mrt, Mrp, Mtp = rotations.rotate_moment_tensor( 1, 2, 3, 4, 5, 6, 7, 8, [9, 10, 11], 360) np.testing.assert_almost_equal(Mrr, 1.0, 6) np.testing.assert_almost_equal(Mtt, 2.0, 6) np.testing.assert_almost_equal(Mpp, 3.0, 6) np.testing.assert_almost_equal(Mrt, 4.0, 6) np.testing.assert_almost_equal(Mrp, 5.0, 6) np.testing.assert_almost_equal(Mtp, 6.0, 6) # The following ones are tested against a well proven Matlab script by # Andreas Fichtner. Mrr, Mtt, Mpp, Mrt, Mrp, Mtp = rotations.rotate_moment_tensor( -0.704, 0.071, 0.632, 0.226, -0.611, 3.290, rotations.colat2lat(26.08), -21.17, [0, 1, 0], 57.5) Mrr_new, Mtt_new, Mpp_new, Mrt_new, Mrp_new, Mtp_new = \ [-0.70400000, 2.04919171, -1.34619171, 0.02718681, -0.65089007, 2.83207047] np.testing.assert_almost_equal(Mrr, Mrr_new, 6) np.testing.assert_almost_equal(Mtt, Mtt_new, 6) np.testing.assert_almost_equal(Mpp, Mpp_new, 6) np.testing.assert_almost_equal(Mrt, Mrt_new, 6) np.testing.assert_almost_equal(Mrp, Mrp_new, 6) np.testing.assert_almost_equal(Mtp, Mtp_new, 6) # Another example. Mrr, Mtt, Mpp, Mrt, Mrp, Mtp = rotations.rotate_moment_tensor( -0.818, -1.300, 2.120, 1.720, 2.290, -0.081, rotations.colat2lat(53.51), -9.87, [np.sqrt(2) / 2, -np.sqrt(2) / 2, 0], -31.34) Mrr_new, Mtt_new, Mpp_new, Mrt_new, Mrp_new, Mtp_new = \ [-0.81800000, -0.69772178, 1.51772178, 2.55423451, 1.29552541, 1.30522545] np.testing.assert_almost_equal(Mrr, Mrr_new, 6) np.testing.assert_almost_equal(Mtt, Mtt_new, 6) np.testing.assert_almost_equal(Mpp, Mpp_new, 6) np.testing.assert_almost_equal(Mrt, Mrt_new, 6) np.testing.assert_almost_equal(Mrp, Mrp_new, 6) np.testing.assert_almost_equal(Mtp, Mtp_new, 6) # The same as before, but with a non-normalized axis. Should work just # as well. Mrr, Mtt, Mpp, Mrt, Mrp, Mtp = rotations.rotate_moment_tensor( -0.818, -1.300, 2.120, 1.720, 2.290, -0.081, rotations.colat2lat(53.51), -9.87, [11.12, -11.12, 0], -31.34) Mrr_new, Mtt_new, Mpp_new, Mrt_new, Mrp_new, Mtp_new = \ [-0.81800000, -0.69772178, 1.51772178, 2.55423451, 1.29552541, 1.30522545] np.testing.assert_almost_equal(Mrr, Mrr_new, 6) np.testing.assert_almost_equal(Mtt, Mtt_new, 6) np.testing.assert_almost_equal(Mpp, Mpp_new, 6) np.testing.assert_almost_equal(Mrt, Mrt_new, 6) np.testing.assert_almost_equal(Mrp, Mrp_new, 6) np.testing.assert_almost_equal(Mtp, Mtp_new, 6) # One more. Mrr, Mtt, Mpp, Mrt, Mrp, Mtp = rotations.rotate_moment_tensor( 0.952, -1.030, 0.076, 0.226, -0.040, -0.165, rotations.colat2lat(63.34), 55.80, [np.sqrt(3) / 3, -np.sqrt(3) / 3, -np.sqrt(3) / 3], 123.45) Mrr_new, Mtt_new, Mpp_new, Mrt_new, Mrp_new, Mtp_new = \ [0.95200000, -0.41458722, -0.53941278, -0.09170855, 0.21039378, 0.57370606] np.testing.assert_almost_equal(Mrr, Mrr_new, 6) np.testing.assert_almost_equal(Mtt, Mtt_new, 6) np.testing.assert_almost_equal(Mpp, Mpp_new, 6) np.testing.assert_almost_equal(Mrt, Mrt_new, 6) np.testing.assert_almost_equal(Mrp, Mrp_new, 6) np.testing.assert_almost_equal(Mtp, Mtp_new, 6)
def plot_depth_slice(self, component, depth_in_km): """ Plots a depth slice. """ lat_bounds = [ rotations.colat2lat(_i) for _i in self.setup["physical_boundaries_x"][::-1] ] lng_bounds = self.setup["physical_boundaries_y"] depth_bounds = [ 6371 - _i / 1000 for _i in self.setup["physical_boundaries_z"] ] data = self.parsed_components[component] available_depths = np.linspace(*depth_bounds, num=data.shape[2])[::-1] depth_index = np.argmin(np.abs(available_depths - depth_in_km)) lon, lat = np.meshgrid(np.linspace(*lng_bounds, num=data.shape[1]), np.linspace(*lat_bounds, num=data.shape[0])) if self.rotation_axis and self.rotation_angle_in_degree: lon_shape = lon.shape lat_shape = lat.shape lon.shape = lon.size lat.shape = lat.size lat, lon = rotations.rotate_lat_lon(lat, lon, self.rotation_axis, self.rotation_angle_in_degree) lon.shape = lon_shape lat.shape = lat_shape # Get the center of the map. lon_0 = lon.min() + lon.ptp() / 2.0 lat_0 = lat.min() + lat.ptp() / 2.0 plt.figure(0) # Attempt to zoom into the region of interest. max_extend = max(lon.ptp(), lat.ptp()) extend_used = max_extend / 180.0 if extend_used < 0.5: x_buffer = 0.2 * lon.ptp() y_buffer = 0.2 * lat.ptp() m = Basemap( projection='merc', resolution="l", #lat_0=lat_0, lon_0=lon_0, llcrnrlon=lon.min() - x_buffer, urcrnrlon=lon.max() + x_buffer, llcrnrlat=lat.min() - y_buffer, urcrnrlat=lat.max() + y_buffer) else: m = Basemap(projection='ortho', lon_0=lon_0, lat_0=lat_0, resolution="c") m.drawcoastlines() m.fillcontinents("0.9", zorder=0) m.drawmapboundary(fill_color="white") m.drawparallels(np.arange(-80.0, 80.0, 10.0), labels=[1, 0, 0, 0]) m.drawmeridians(np.arange(-170.0, 170.0, 10.0), labels=[0, 0, 0, 1]) m.drawcountries() x, y = m(lon, lat) im = m.pcolormesh(x, y, data[::-1, :, depth_index], cmap=tomo_colormap) # Add colorbar and potentially unit. cm = m.colorbar(im, "right", size="3%", pad='2%') if component in UNIT_DICT: cm.set_label(UNIT_DICT[component], fontsize="x-large", rotation=0) plt.suptitle("Depth slice of %s at %i km" % (component, int(depth_in_km)), size="large") def _on_button_press(event): if event.button != 1 or not event.inaxes: return lon, lat = m(event.xdata, event.ydata, inverse=True) # Convert to colat to ease indexing. colat = rotations.lat2colat(lat) x_range = (self.setup["physical_boundaries_x"][1] - self.setup["physical_boundaries_x"][0]) x_frac = (colat - self.setup["physical_boundaries_x"][0]) / x_range x_index = int(((self.setup["boundaries_x"][1] - self.setup["boundaries_x"][0]) * x_frac) + self.setup["boundaries_x"][0]) y_range = (self.setup["physical_boundaries_y"][1] - self.setup["physical_boundaries_y"][0]) y_frac = (lon - self.setup["physical_boundaries_y"][0]) / y_range y_index = int(((self.setup["boundaries_y"][1] - self.setup["boundaries_y"][0]) * y_frac) + self.setup["boundaries_y"][0]) plt.figure(1, figsize=(3, 8)) depths = available_depths values = data[x_index, y_index, :] plt.plot(values, depths) plt.grid() plt.ylim(depths[-1], depths[0]) plt.show() plt.close() plt.figure(0) plt.gcf().canvas.mpl_connect('button_press_event', _on_button_press) plt.show()
def __init__(self, directory, domain, model_type="earth_model"): """ The init function. :param directory: The directory where the earth model or kernel is located. :param model_type: Determined the type of model loaded. Currently two are supported: * earth_model - The standard SES3D model files (default) * kernel - The kernels. Identifies by lots of grad_* files. * wavefield - The raw wavefields. """ self.directory = directory self.boxfile = os.path.join(self.directory, "boxfile") if not os.path.exists(self.boxfile): msg = "boxfile not found. Wrong directory?" raise ValueError(msg) # Read the boxfile. self.setup = self._read_boxfile() self.domain = domain self.model_type = model_type self.one_d_model = OneDimensionalModel("ak135-f") if model_type == "earth_model": # Now check what different models are available in the directory. # This information is also used to infer the degree of the used # lagrange polynomial. components = ["A", "B", "C", "lambda", "mu", "rhoinv", "Q"] self.available_derived_components = ["vp", "vsh", "vsv", "rho"] self.components = {} self.parsed_components = {} for component in components: files = glob.glob( os.path.join(directory, "%s[0-9]*" % component)) if len(files) != len(self.setup["subdomains"]): continue # Check that the naming is continuous. all_good = True for _i in xrange(len(self.setup["subdomains"])): if os.path.join(directory, "%s%i" % (component, _i)) in files: continue all_good = False break if all_good is False: msg = "Naming for component %s is off. It will be skipped." warnings.warn(msg) continue # They also all need to have the same size. if len(set([os.path.getsize(_i) for _i in files])) != 1: msg = ("Component %s has the right number of model files " "but they are not of equal size") % component warnings.warn(msg) continue # Sort the files by ascending number. files.sort(key=lambda x: int(re.findall(r"\d+$", (os.path.basename(x)))[0])) self.components[component] = {"filenames": files} elif model_type == "wavefield": components = ["vz", "vx", "vy", "vz"] self.available_derived_components = [] self.components = {} self.parsed_components = {} for component in components: files = glob.glob(os.path.join(directory, "%s_*_*" % component)) if not files: continue timesteps = collections.defaultdict(list) for filename in files: timestep = int(os.path.basename(filename).split("_")[-1]) timesteps[timestep].append(filename) for timestep, filenames in timesteps.iteritems(): self.components["%s %s" % (component, timestep)] = \ {"filenames": sorted( filenames, key=lambda x: int( os.path.basename(x).split("_")[1]))} elif model_type == "kernel": # Now check what different models are available in the directory. # This information is also used to infer the degree of the used # lagrange polynomial. components = ["grad_cp", "grad_csh", "grad_csv", "grad_rho"] self.available_derived_components = [] self.components = {} self.parsed_components = {} for component in components: files = glob.glob( os.path.join(directory, "%s_[0-9]*" % component)) if len(files) != len(self.setup["subdomains"]): continue if len(set([os.path.getsize(_i) for _i in files])) != 1: msg = ("Component %s has the right number of model files " "but they are not of equal size") % component warnings.warn(msg) continue # Sort the files by ascending number. files.sort(key=lambda x: int( re.findall(r"\d+$", (os.path.basename(x)))[0])) self.components[component] = {"filenames": files} else: msg = "model_type '%s' not known." % model_type raise ValueError(msg) # All files for a single component have the same size. Now check that # all files have the same size. unique_filesizes = len(list(set([ os.path.getsize(_i["filenames"][0]) for _i in self.components.itervalues()]))) if unique_filesizes != 1: msg = ("The different components in the folder do not have the " "same number of samples") raise ValueError(msg) # Now calculate the lagrange polynomial degree. All necessary # information is present. size = os.path.getsize(self.components.values()[0]["filenames"][0]) sd = self.setup["subdomains"][0] x, y, z = sd["index_x_count"], sd["index_y_count"], sd["index_z_count"] self.lagrange_polynomial_degree = \ int(round(((size * 0.25) / (x * y * z)) ** (1.0 / 3.0) - 1)) self._calculate_final_dimensions() # Setup the boundaries. self.lat_bounds = [ rotations.colat2lat(_i) for _i in self.setup["physical_boundaries_x"][::-1]] self.lng_bounds = self.setup["physical_boundaries_y"] self.depth_bounds = [ 6371 - _i / 1000.0 for _i in self.setup["physical_boundaries_z"]] self.collocation_points_lngs = self._get_collocation_points_along_axis( self.lng_bounds[0], self.lng_bounds[1], self.setup["point_count_in_y"]) self.collocation_points_lats = self._get_collocation_points_along_axis( self.lat_bounds[0], self.lat_bounds[1], self.setup["point_count_in_x"]) self.collocation_points_depth = \ self._get_collocation_points_along_axis( self.depth_bounds[1], self.depth_bounds[0], self.setup["point_count_in_z"])[::-1]
def read_SES3D(file_or_file_object, *args, **kwargs): """ Turns a SES3D file into a obspy.core.Stream object. SES3D files do not contain a starttime and thus the first first sample will always begin at 1970-01-01T00:00:00. The data will be a floating point array of the ground velocity in meters per second. Furthermore every trace will have a trace.stats.ses3d dictionary which contains the following six keys: * receiver_latitude * receiver_longitde * receiver_depth_in_m * source_latitude * source_longitude * source_depth_in_m The network, station, and location attributes of the trace will be empty, and the channel will be set to either 'X' (south component), 'Y' (east component), or 'Z' (vertical component). """ # Make sure that it is a file like object. if not hasattr(file_or_file_object, "read"): with open(file_or_file_object, "rb") as open_file: file_or_file_object = StringIO(open_file.read()) # Read the header. component = file_or_file_object.readline().split()[0].lower() npts = int(file_or_file_object.readline().split()[-1]) delta = float(file_or_file_object.readline().split()[-1]) # Skip receiver location line. file_or_file_object.readline() rec_loc = file_or_file_object.readline().split() rec_x, rec_y, rec_z = map(float, [rec_loc[1], rec_loc[3], rec_loc[5]]) # Skip the source location line. file_or_file_object.readline() src_loc = file_or_file_object.readline().split() src_x, src_y, src_z = map(float, [src_loc[1], src_loc[3], src_loc[5]]) # Read the data. data = np.array(map(float, file_or_file_object.readlines()), dtype="float32") # Setup Obspy Stream/Trace structure. tr = Trace(data=data) tr.stats.delta = delta # Map the channel attributes. tr.stats.channel = {"theta": "X", "phi": "Y", "r": "Z"}[component] tr.stats.ses3d = AttribDict() tr.stats.ses3d.receiver_latitude = rotations.colat2lat(rec_x) tr.stats.ses3d.receiver_longitude = rec_y tr.stats.ses3d.receiver_depth_in_m = rec_z tr.stats.ses3d.source_latitude = rotations.colat2lat(src_x) tr.stats.ses3d.source_longitude = src_y tr.stats.ses3d.source_depth_in_m = src_z # Small check. if npts != tr.stats.npts: msg = "The sample count specified in the header does not match " + \ "the actual data count." warnings.warn(msg) return Stream(traces=[tr])
def test_RotateMomentTensor(self): """ Tests the moment tensor rotations. """ # A full rotation should not change anything. Mrr, Mtt, Mpp, Mrt, Mrp, Mtp = rotations.rotate_moment_tensor(1, 2, 3, 4, 5, 6, 7, 8, [9, 10, 11], 360) self.assertAlmostEqual(Mrr, 1.0, 6) self.assertAlmostEqual(Mtt, 2.0, 6) self.assertAlmostEqual(Mpp, 3.0, 6) self.assertAlmostEqual(Mrt, 4.0, 6) self.assertAlmostEqual(Mrp, 5.0, 6) self.assertAlmostEqual(Mtp, 6.0, 6) # The following ones are tested against a well proven Matlab script by # Andreas Fichtner. Mrr, Mtt, Mpp, Mrt, Mrp, Mtp = rotations.rotate_moment_tensor( \ -0.704, 0.071, 0.632, 0.226, -0.611, 3.290, rotations.colat2lat(26.08), -21.17, [0, 1, 0], 57.5) Mrr_new, Mtt_new, Mpp_new, Mrt_new, Mrp_new, Mtp_new = \ [-0.70400000, 2.04919171, -1.34619171, 0.02718681, -0.65089007, 2.83207047] self.assertAlmostEqual(Mrr, Mrr_new, 6) self.assertAlmostEqual(Mtt, Mtt_new, 6) self.assertAlmostEqual(Mpp, Mpp_new, 6) self.assertAlmostEqual(Mrt, Mrt_new, 6) self.assertAlmostEqual(Mrp, Mrp_new, 6) self.assertAlmostEqual(Mtp, Mtp_new, 6) # Another example. Mrr, Mtt, Mpp, Mrt, Mrp, Mtp = rotations.rotate_moment_tensor( \ -0.818, -1.300, 2.120, 1.720, 2.290, -0.081, rotations.colat2lat(53.51), -9.87, [np.sqrt(2) / 2, -np.sqrt(2) / 2, 0], -31.34) Mrr_new, Mtt_new, Mpp_new, Mrt_new, Mrp_new, Mtp_new = \ [-0.81800000, -0.69772178, 1.51772178, 2.55423451, 1.29552541, 1.30522545] self.assertAlmostEqual(Mrr, Mrr_new, 6) self.assertAlmostEqual(Mtt, Mtt_new, 6) self.assertAlmostEqual(Mpp, Mpp_new, 6) self.assertAlmostEqual(Mrt, Mrt_new, 6) self.assertAlmostEqual(Mrp, Mrp_new, 6) self.assertAlmostEqual(Mtp, Mtp_new, 6) # The same as before, but with a non-normalized axis. Should work just # as well. Mrr, Mtt, Mpp, Mrt, Mrp, Mtp = rotations.rotate_moment_tensor( \ -0.818, -1.300, 2.120, 1.720, 2.290, -0.081, rotations.colat2lat(53.51), -9.87, [11.12, -11.12, 0], -31.34) Mrr_new, Mtt_new, Mpp_new, Mrt_new, Mrp_new, Mtp_new = \ [-0.81800000, -0.69772178, 1.51772178, 2.55423451, 1.29552541, 1.30522545] self.assertAlmostEqual(Mrr, Mrr_new, 6) self.assertAlmostEqual(Mtt, Mtt_new, 6) self.assertAlmostEqual(Mpp, Mpp_new, 6) self.assertAlmostEqual(Mrt, Mrt_new, 6) self.assertAlmostEqual(Mrp, Mrp_new, 6) self.assertAlmostEqual(Mtp, Mtp_new, 6) # One more. Mrr, Mtt, Mpp, Mrt, Mrp, Mtp = rotations.rotate_moment_tensor( \ 0.952, -1.030, 0.076, 0.226, -0.040, -0.165, rotations.colat2lat(63.34), 55.80, [np.sqrt(3) / 3, -np.sqrt(3) / 3, -np.sqrt(3) / 3], 123.45) Mrr_new, Mtt_new, Mpp_new, Mrt_new, Mrp_new, Mtp_new = \ [0.95200000, -0.41458722, -0.53941278, -0.09170855, 0.21039378, 0.57370606] self.assertAlmostEqual(Mrr, Mrr_new, 6) self.assertAlmostEqual(Mtt, Mtt_new, 6) self.assertAlmostEqual(Mpp, Mpp_new, 6) self.assertAlmostEqual(Mrt, Mrt_new, 6) self.assertAlmostEqual(Mrp, Mrp_new, 6) self.assertAlmostEqual(Mtp, Mtp_new, 6)
def plot_depth_slice(self, component, depth_in_km): """ Plots a depth slice. :param component: The component to plot. :type component: basestring :param depth_in_km: The depth in km to plot. If the exact depth does not exists, the nearest neighbour will be plotted. :type depth_in_km: integer or float """ lat_bounds = [rotations.colat2lat(_i) for _i in self.setup["physical_boundaries_x"][::-1]] lng_bounds = self.setup["physical_boundaries_y"] depth_bounds = [6371 - _i / 1000.0 for _i in self.setup["physical_boundaries_z"]] data = self.parsed_components[component] available_depths = np.linspace(*depth_bounds, num=data.shape[2]) depth_index = np.argmin(np.abs(available_depths - depth_in_km)) actual_depth = available_depths[depth_index] lngs = self._get_collocation_points_along_axis( lng_bounds[0], lng_bounds[1], data.shape[1]) lats = self._get_collocation_points_along_axis( lat_bounds[0], lat_bounds[1], data.shape[0]) lon, lat = np.meshgrid(lngs, lats) if self.rotation_axis and self.rotation_angle_in_degree: lon_shape = lon.shape lat_shape = lat.shape lon.shape = lon.size lat.shape = lat.size lat, lon = rotations.rotate_lat_lon(lat, lon, self.rotation_axis, self.rotation_angle_in_degree) lon.shape = lon_shape lat.shape = lat_shape # Get the center of the map. lon_0 = lon.min() + lon.ptp() / 2.0 lat_0 = lat.min() + lat.ptp() / 2.0 plt.figure(0) # Attempt to zoom into the region of interest. max_extend = max(lon.ptp(), lat.ptp()) extend_used = max_extend / 180.0 if extend_used < 0.5: # Calculate approximate width and height in meters. width = lon.ptp() height = lat.ptp() width *= 110000 * 1.1 height *= 110000 * 1.1 # Lambert azimuthal equal area projection. Equal area projections # are useful for interpreting features and this particular one also # does not distort features a lot on regional scales. m = Basemap(projection='laea', resolution="l", width=width, height=height, lat_0=lat_0, lon_0=lon_0) else: m = Basemap(projection='ortho', lon_0=lon_0, lat_0=lat_0, resolution="c") m.drawcoastlines() m.fillcontinents("0.9", zorder=0) m.drawmapboundary(fill_color="white") m.drawparallels(np.arange(-80.0, 80.0, 10.0), labels=[1, 0, 0, 0]) m.drawmeridians(np.arange(-170.0, 170.0, 10.0), labels=[0, 0, 0, 1]) m.drawcountries() x, y = m(lon, lat) depth_data = data[::-1, :, depth_index] vmin, vmax = depth_data.min(), depth_data.max() vmedian = np.median(depth_data) offset = max(abs(vmax - vmedian), abs(vmedian - vmin)) if vmax - vmin == 0: offset = 0.01 vmin = vmedian - offset vmax = vmedian + offset im = m.pcolormesh(x, y, depth_data, cmap=tomo_colormap, vmin=vmin, vmax=vmax) # Add colorbar and potentially unit. cm = m.colorbar(im, "right", size="3%", pad='2%') if component in UNIT_DICT: cm.set_label(UNIT_DICT[component], fontsize="x-large", rotation=0) plt.suptitle("Depth slice of %s at %i km" % ( component, int(round(actual_depth))), size="large") border = rotations.get_border_latlng_list( rotations.colat2lat(self.setup["physical_boundaries_x"][0]), rotations.colat2lat(self.setup["physical_boundaries_x"][1]), self.setup["physical_boundaries_y"][0], self.setup["physical_boundaries_y"][1], rotation_axis=self.rotation_axis, rotation_angle_in_degree=self.rotation_angle_in_degree) border = np.array(border) lats = border[:, 0] lngs = border[:, 1] lngs, lats = m(lngs, lats) m.plot(lngs, lats, color="black", lw=2, path_effects=[ PathEffects.withStroke(linewidth=4, foreground="white")]) def _on_button_press(event): if event.button != 1 or not event.inaxes: return lon, lat = m(event.xdata, event.ydata, inverse=True) # Convert to colat to ease indexing. colat = rotations.lat2colat(lat) x_range = (self.setup["physical_boundaries_x"][1] - self.setup["physical_boundaries_x"][0]) x_frac = (colat - self.setup["physical_boundaries_x"][0]) / x_range x_index = int(((self.setup["boundaries_x"][1] - self.setup["boundaries_x"][0]) * x_frac) + self.setup["boundaries_x"][0]) y_range = (self.setup["physical_boundaries_y"][1] - self.setup["physical_boundaries_y"][0]) y_frac = (lon - self.setup["physical_boundaries_y"][0]) / y_range y_index = int(((self.setup["boundaries_y"][1] - self.setup["boundaries_y"][0]) * y_frac) + self.setup["boundaries_y"][0]) plt.figure(1, figsize=(3, 8)) depths = available_depths values = data[x_index, y_index, :] plt.plot(values, depths) plt.grid() plt.ylim(depths[-1], depths[0]) plt.show() plt.close() plt.figure(0) plt.gcf().canvas.mpl_connect('button_press_event', _on_button_press) plt.show()