def __init__(self, **kwargs): Notice.title() Notice.hr_header("Initializing") params = kwargs.get('params') layers = kwargs.get('layers') potfields = kwargs.get('potfields') data = kwargs.get('data') velocity = kwargs.get('velocity') super(TransectContainer, self).__init__(params) print "Starting {}, id {}, file {}".format(self.title, self.id, self.config_file) # Set up 'data' — the transect line — from shapefile. self.data = None with fiona.open(data['transect_file']) as c: for line in c: if line['properties']['id'] == self.id: self.data = shape(line['geometry']) if not self.data: Notice.fail("No transect with ID "+self.id) # self.data.length holds the length of the transect in metres # But often we want ints, and sometimes the number of samples. # This will give us a nice linspace. Put them in params. params['length'] = self.length = int(np.floor(self.data.length)) params['nsamples'] = self.nsamples = self.length + 1 params['linspace'] = self.linspace = np.linspace(0, self.length, self.nsamples) self.time = time.strftime("%Y/%m/%d %H:%M", time.localtime()) self.tops_file = data['tops_file'] self.log = LogContainer(data['well_dir'], params) self.velocity = self.__velocity_factory(velocity, params) self.seismic = SeismicContainer(data['seismic_dir'], self.velocity, params) self.horizons = HorizonContainer(data['horizon_dir'], self.velocity, params) self.elevation = ElevationContainer(data['elevation_file'], params) self.bedrock = BedrockContainer(data['bedrock_dir'], params) self.potfield = PotfieldContainer(potfields, params) self.locmap = LocmapContainer(layers, params)
def __init__(self, **kwargs): Notice.title() Notice.hr_header("Initializing") params = kwargs.get('params') layers = kwargs.get('layers') potfields = kwargs.get('potfields') data = kwargs.get('data') velocity = kwargs.get('velocity') super(TransectContainer, self).__init__(params) print "Starting {}, id {}, file {}".format(self.title, self.id, self.config_file) # Set up 'data' — the transect line — from shapefile. self.data = None with fiona.open(data['transect_file']) as c: for line in c: if line['properties']['id'] == self.id: self.data = shape(line['geometry']) if not self.data: Notice.fail("No transect with ID " + self.id) # self.data.length holds the length of the transect in metres # But often we want ints, and sometimes the number of samples. # This will give us a nice linspace. Put them in params. params['length'] = self.length = int(np.floor(self.data.length)) params['nsamples'] = self.nsamples = self.length + 1 params['linspace'] = self.linspace = np.linspace( 0, self.length, self.nsamples) self.time = time.strftime("%Y/%m/%d %H:%M", time.localtime()) self.tops_file = data['tops_file'] self.log = LogContainer(data['well_dir'], params) self.velocity = self.__velocity_factory(velocity, params) self.seismic = SeismicContainer(data['seismic_dir'], self.velocity, params) self.horizons = HorizonContainer(data['horizon_dir'], self.velocity, params) self.elevation = ElevationContainer(data['elevation_file'], params) self.bedrock = BedrockContainer(data['bedrock_dir'], params) self.potfield = PotfieldContainer(potfields, params) self.locmap = LocmapContainer(layers, params)
def plot(self): """ Generates plot for the transect. """ # Set the extent to the length? Or keep them all the same? self.extents[0] = 0 self.extents[1] = self.data.length Notice.hr_header("Updating") # update the containers self.velocity.update(self.data) self.log.update(self.data) self.seismic.update(self.data) self.horizons.update(self.data) self.elevation.update(self.data) self.bedrock.update(self.data) self.potfield.update(self.data) self.locmap.update(self.data) Notice.hr_header("Plotting") plot(self) plt.show()
def main(target, cfg): """ Puts everything together. """ t0 = time.time() ##################################################################### # # READ SEGY # ##################################################################### if cfg['segy_library'].lower() == 'obspy': s = Seismic.from_segy_with_obspy(target, params={'ndim': cfg['ndim']}) else: s = Seismic.from_segy(target, params={'ndim': cfg['ndim']}) # Set the line and/or xline number. try: n, xl = cfg['number'] except: n, xl = cfg['number'], 0.5 # Set the direction. if (s.ndim) == 2: direction = ['inline'] elif cfg['direction'].lower()[0] == 'i': direction = ['inline'] elif cfg['direction'].lower()[0] in ['x', 'c']: # Allow 'crossline' too. direction = ['xline'] elif cfg['direction'].lower()[0] == 't': direction = ['tslice'] else: direction = ['xline', 'inline'] # Get the data. try: ss = [ Seismic.from_seismic(s, n=n, direction=d) for n, d in zip((n, xl), direction) ] except IndexError: # Perhaps misinterpreted 2D as 3D s = Seismic.from_segy(target, params={'ndim': 2}) direction = ['inline'] ss = [ Seismic.from_seismic(s, n=n, direction=d) for n, d in zip((n, xl), direction) ] clip_val = np.percentile(s.data, cfg['percentile']) if clip_val < 10: fstr = '{:.3f}' elif clip_val < 100: fstr = '{:.2f}' elif clip_val < 1000: fstr = '{:.1f}' else: fstr = '{:.0f}' # Notify user of parameters. Notice.info("n_traces {}".format(s.ntraces)) Notice.info("n_samples {}".format(s.nsamples)) Notice.info("dt {}".format(s.dt)) Notice.info("t_start {}".format(s.tstart)) Notice.info("t_end {}".format(s.tend)) Notice.info("max_val " + fstr.format(np.amax(s.data))) Notice.info("min_val " + fstr.format(np.amin(s.data))) Notice.info("clip_val " + fstr.format(clip_val)) t1 = time.time() Notice.ok("Read data in {:.1f} s".format(t1 - t0)) ##################################################################### # # MAKE PLOT # ##################################################################### Notice.hr_header("Plotting") # Plot size parameters. wsl = 6 # Width of sidelabel, inches mih = 11 # Minimum plot height, inches fhh = 5 # File header box height, inches m = 0.75 # basic unit of margins, inches # Margins, CSS like: top, right, bottom, left. mt, mr, mb, ml = m, 1.5 * m, m, 1.5 * m mm = 2 * m # padded margin between seismic and label # Determine plot dimensions. Kind of laborious and repetitive (see below). if cfg['plot_width']: seismic_width = cfg['plot_width'] - wsl - mm - ml - mr tpi = max([s.ntraces for s in ss]) / seismic_width else: tpi = cfg['tpi'] if cfg['plot_height']: seismic_height = max( mih, cfg['plot_height']) - mb - 0.75 * (len(ss) - 1) - mt seismic_height_raw = seismic_height / len(ss) ips = seismic_height_raw / (s.tbasis[-1] - s.tbasis[0]) else: ips = cfg['ips'] # Width is determined by seismic width, plus sidelabel, plus margins. # Height is given by ips, but with a minimum of mih inches. if 'tslice' in direction: seismic_width = [s.ntraces / tpi for s in ss] seismic_height_raw = max([s.nxlines for s in ss]) / tpi else: seismic_width = [s.ntraces / tpi for s in ss] seismic_height_raw = ips * (s.tbasis[-1] - s.tbasis[0]) w = ml + max(seismic_width) + mm + wsl + mr # inches seismic_height = len(ss) * seismic_height_raw h_reqd = mb + seismic_height + 0.75 * (len(ss) - 1) + mt # inches h = max(mih, h_reqd) # Calculate where to start sidelabel and seismic data. # Depends on whether sidelabel is on the left or right. if cfg['sidelabel'] == 'right': ssl = (ml + max(seismic_width) + mm) / w # Start of side label (ratio) seismic_left = ml / w else: ssl = ml / w seismic_left = (ml + wsl + mm) / w adj = max(0, h - h_reqd) / 2 seismic_bottom = (mb / h) + adj / h seismic_width_fraction = [sw / w for sw in seismic_width] seismic_height_fraction = seismic_height_raw / h # Publish some notices so user knows plot size. Notice.info("plot width {:.2f} in".format(w)) Notice.info("plot height {:.2f} in".format(h)) # Make the figure. fig = plt.figure(figsize=(w, h), facecolor='w') # Set the tickformat. tickfmt = mtick.FormatStrFormatter('%.0f') # Could definitely do better for default fontsize than 10. # Ideally would be adaptive to plot size. cfg['fontsize'] = cfg['fontsize'] or 10 # Plot title. if cfg['title']: # Deal with Windows paths: \1 gets interpreted as a group by regex. newt = re.sub(r'\\', '@@@@@', target) temp = re.sub(r'_filename', newt, cfg['title']) title = re.sub(r'@@@', r'\\', temp) title_ax = fig.add_axes([ssl, 1 - mt / h, wsl / w, mt / h]) title_ax = plotter.plot_title(title_ax, title, fs=1.4 * cfg['fontsize'], cfg=cfg) # Plot title. if cfg['subtitle']: date = str(datetime.date.today()) subtitle = re.sub(r'_date', date, cfg['subtitle']) subtitle_ax = fig.add_axes([ssl, 1 - mt / h, wsl / w, mt / h], label='subtitle') title_ax = plotter.plot_subtitle(subtitle_ax, subtitle, fs=0.75 * cfg['fontsize'], cfg=cfg) # Plot text header. start = (h - 1.5 * mt - fhh) / h head_ax = fig.add_axes([ssl, start, wsl / w, fhh / h]) head_ax = plotter.plot_header(head_ax, s.header, fs=9, cfg=cfg, version=__version__) # Plot histogram. # Params for histogram plot. pady = 0.75 / h # 0.75 inch padx = 0.75 / w # 0.75 inch cstrip = 0.3 / h # color_strip height = 0.3 in charth = 1.25 / h # height of charts = 1.25 in chartw = wsl / w - mr / w - padx # or ml/w for left-hand sidelabel; same thing chartx = (ssl + padx) histy = 1.5 * mb / h + charth + pady # Plot colourbar under histogram. clrbar_ax = fig.add_axes([chartx, histy - cstrip, chartw, cstrip]) clrbar_ax = plotter.plot_colourbar(clrbar_ax, cmap=cfg['cmap']) # Plot histogram itself. hist_ax = fig.add_axes([chartx, histy, chartw, charth]) hist_ax = plotter.plot_histogram(hist_ax, s.data, tickfmt, cfg) # Plot spectrum. specy = 1.5 * mb / h spec_ax = fig.add_axes([chartx, specy, chartw, charth]) try: colour = utils.rgb_to_hex(cfg['highlight_colour']) spec_ax = s.plot_spectrum( ax=spec_ax, tickfmt=tickfmt, ntraces=20, fontsize=cfg['fontsize'], colour=colour, ) except: pass # No spectrum, oh well. for i, line in enumerate(ss): # Add the seismic axis. ax = fig.add_axes([ seismic_left, seismic_bottom + i * seismic_height_fraction + i * pady, seismic_width_fraction[i], seismic_height_fraction ]) # Plot seismic data. if cfg['display'].lower() in ['vd', 'varden', 'variable', 'both']: im = ax.imshow(line.data.T, cmap=cfg['cmap'], clim=[-clip_val, clip_val], extent=[ line.olineidx[0], line.olineidx[-1], 1000 * line.tbasis[-1], line.tbasis[0] ], aspect='auto', interpolation=cfg['interpolation']) if np.argmin(seismic_width) == i: cax = utils.add_subplot_axes(ax, [1.01, 0.02, 0.01, 0.2]) _ = plt.colorbar(im, cax=cax) if cfg['display'].lower() in ['wiggle', 'both']: ax = line.wiggle_plot( cfg['number'], direction, ax=ax, skip=cfg['skip'], gain=cfg['gain'], rgb=cfg['colour'], alpha=cfg['opacity'], lw=cfg['lineweight'], ) valid = ['vd', 'varden', 'variable', 'wiggle', 'both'] if cfg['display'].lower() not in valid: Notice.fail("You must specify the display: wiggle, vd, both.") return # Seismic axis annotations. ax.set_ylabel(utils.LABELS[line.ylabel], fontsize=cfg['fontsize']) ax.set_xlabel(utils.LABELS[line.xlabel], fontsize=cfg['fontsize'], ha='center') ax.tick_params(axis='both', labelsize=cfg['fontsize'] - 2) ax.xaxis.set_major_formatter(tickfmt) ax.yaxis.set_major_formatter(tickfmt) if ('tslice' not in direction): ax.set_ylim(1000 * cfg['trange'][1] or 1000 * line.tbasis[-1], 1000 * cfg['trange'][0]) # Crossing point. Will only work for non-arb lines. try: ax.axvline(ss[i - 1].slineidx[0], c=utils.rgb_to_hex(cfg['highlight_colour']), alpha=0.5) except IndexError: pass # Nevermind. # Grid, optional. if cfg['grid_time'] or cfg['grid_traces']: ax.grid() for l in ax.get_xgridlines(): l.set_color(utils.rgb_to_hex(cfg['grid_colour'])) l.set_linestyle('-') if cfg['grid_traces']: l.set_linewidth(1) else: l.set_linewidth(0) l.set_alpha(min(1, cfg['grid_alpha'])) for l in ax.get_ygridlines(): l.set_color(utils.rgb_to_hex(cfg['grid_colour'])) l.set_linestyle('-') if cfg['grid_time']: if 'tslice' in direction: l.set_linewidth(1) else: l.set_linewidth(1.4) else: l.set_linewidth(0) l.set_alpha(min(1, 2 * cfg['grid_alpha'])) # Watermark. if cfg['watermark_text']: ax = plotter.watermark_seismic(ax, cfg) # Make parasitic (top) axis for labeling CDP number. if (s.data.ndim > 2) and ('tslice' not in direction): ylim = ax.get_ylim() par1 = ax.twiny() par1.spines["top"].set_position(("axes", 1.0)) par1.plot(line.slineidx, np.zeros_like(line.slineidx), alpha=0) par1.set_xlabel(utils.LABELS[line.slabel], fontsize=cfg['fontsize']) par1.set_ylim(ylim) # Adjust ticks tx = par1.get_xticks() newtx = [ line.slineidx[len(line.slineidx) * (i // len(tx))] for i, _ in enumerate(tx) ] par1.set_xticklabels(newtx, fontsize=cfg['fontsize'] - 2) t2 = time.time() Notice.ok("Built plot in {:.1f} s".format(t2 - t1)) ##################################################################### # # SAVE FILE # ##################################################################### Notice.hr_header("Saving") dname, fname, ext = utils.path_bits(target) outfile = cfg['outfile'] or '' if not os.path.splitext(outfile)[1]: outfile = os.path.join(cfg['outfile'] or dname, fname + '.png') fig.savefig(outfile) t3 = time.time() Notice.info("output file {}".format(outfile)) Notice.ok("Saved output in {:.1f} s".format(t3 - t2)) if cfg['stain_paper'] or cfg['coffee_rings'] or cfg['distort'] or cfg[ 'scribble']: fname = os.path.splitext(outfile)[0] + ".stupid.png" fig.savefig(fname) else: return ##################################################################### # # SAVE STUPID FILE # ##################################################################### Notice.hr_header("Applying the stupidity") stupid_image = Image.open(fname) if cfg['stain_paper']: utils.stain_paper(stupid_image) utils.add_rings(stupid_image, cfg['coffee_rings']) if cfg['scribble']: utils.add_scribble(stupid_image) # Trick to remove remaining semi-transparent pixels. result = Image.new("RGB", stupid_image.size, (255, 255, 255)) result.paste(stupid_image) result.save(fname) t4 = time.time() Notice.info("output file {}".format(fname)) Notice.ok("Saved stupidity in {:.1f} s".format(t4 - t3)) return
'--demo', action='store_true', help='Run with the demo file, data/31_81_PR.png.') parser.add_argument('-v', '--version', action='store_true', help='Get the version number.') args = parser.parse_args() if args.version: Notice.info(__version__) sys.exit() Notice.title() target = args.filename with args.config as f: cfg = yaml.safe_load(f) Notice.hr_header("Initializing") Notice.info("config {}".format(args.config.name)) # Fill in 'missing' fields in cfg. cfg = {k: cfg.get(k, v) for k, v in utils.DEFAULTS.items()} cfg['outfile'] = args.out cfg['ndim'] = args.ndim or cfg['ndim'] if args.demo: target = './data/31_81_PR.sgy' # Go do it! try: globule = glob.iglob(target, recursive=True) # Python 3.5+ except: globule = glob.iglob(target) # Python < 3.5
def main(target, cfg): """ Puts everything together. """ t0 = time.time() # Read the file. section = readSEGY(target, unpack_headers=True) # Calculate some things. # NB Getting nsamples and dt from the first trace assumes that all # traces are the same length, which is not a safe assumption in SEGY v2. ninlines = section.traces[-1].header.trace_sequence_number_within_line last_tr = section.traces[-1].header.trace_sequence_number_within_segy_file nxlines = last_tr / ninlines nsamples = section.traces[0].header.number_of_samples_in_this_trace dt = section.traces[0].header.sample_interval_in_ms_for_this_trace ntraces = len(section.traces) tbase = 0.001 * np.arange(0, nsamples * dt, dt) tstart = 0 tend = np.amax(tbase) # Make the data array. data = np.vstack([t.data for t in section.traces]).T threed = False if nxlines > 1: # Then it's a 3D and `data` is an ensemble. threed = True cube = np.reshape(data.T, (ninlines, nxlines, nsamples)) l = cfg['number'] if cfg['direction'].lower()[0] == 'i': direction = 'inline' ntraces = nxlines l *= ninlines if (l < 1) else 1 data = cube[l, :, :].T else: direction = 'xline' ntraces = ninlines l *= nxlines if (l < 1) else 1 data = cube[:, l, :].T # Collect some other data. Use a for loop because there are several. elev, esp, ens, tsq = [], [], [], [] for i, trace in enumerate(section.traces): elev.append(trace.header.receiver_group_elevation) esp.append(trace.header.energy_source_point_number) tsq.append(trace.header.trace_sequence_number_within_line) if threed: trs = [] if direction == 'inline': cdp_label_text = 'Crossline number' trace_label_text = 'Trace number' ens.append(trace.header.for_3d_poststack_data_this_field_is_for_cross_line_number) trs.append(trace.header.for_3d_poststack_data_this_field_is_for_in_line_number) else: cdp_label_text = 'Inline number' trace_label_text = 'Trace number' ens.append(trace.header.for_3d_poststack_data_this_field_is_for_in_line_number) trs.append(trace.header.for_3d_poststack_data_this_field_is_for_cross_line_number) line_no = min(trs) else: cdp_label_text = 'CDP number' trace_label_text = 'Trace number' ens.append(trace.header.ensemble_number) min_tr, max_tr = 0, ntraces traces = (min_tr, max_tr) clip_val = np.percentile(data, cfg['percentile']) # Notify user of parameters Notice.info("n_traces {}".format(ntraces)) Notice.info("n_samples {}".format(nsamples)) Notice.info("dt {}".format(dt)) Notice.info("t_start {}".format(tstart)) Notice.info("t_end {}".format(tend)) Notice.info("max_val {}".format(np.amax(data))) Notice.info("min_val {}".format(np.amin(data))) Notice.info("clip_val {}".format(clip_val)) t1 = time.time() Notice.ok("Read data in {:.1f} s".format(t1-t0)) ##################################################################### # # MAKE PLOT # ##################################################################### Notice.hr_header("Plotting") ################################## # Plot size parameters # Some constants fs = cfg['fontsize'] wsl = 6 # Width of sidelabel, inches mih = 12 # Minimum plot height, inches fhh = 5 # File header box height, inches m = 0.5 # basic unit of margins, inches # Margins, CSS like: top, right, bottom, left. mt, mr, mb, ml = m, 2 * m, m, 2 * m mm = m # padded margin between seismic and label # Width is determined by seismic width, plus sidelabel, plus margins. seismic_width = ntraces / cfg['tpi'] w = ml + seismic_width + mm + wsl + mr # inches # Height is given by ips, but with a minimum of mih inches seismic_height = cfg['ips'] * (tbase[-1] - tbase[0]) / 1000 h_reqd = mb + seismic_height + mt # inches h = max(mih, h_reqd) # Calculate where to start sidelabel and seismic data. # Depends on whether sidelabel is on the left or right. if cfg['sidelabel'] == 'right': ssl = (ml + seismic_width + mm) / w # Start of side label (ratio) seismic_left = ml / w else: ssl = ml / w seismic_left = (ml + wsl + mm) / w adj = max(0, h - h_reqd) / 2 seismic_bottom = (mb / h) + adj / h seismic_width_fraction = seismic_width / w seismic_height_fraction = seismic_height / h # Publish some notices so user knows plot size. Notice.info("Width of plot {} in".format(w)) Notice.info("Height of plot {} in".format(h)) ################################## # Make the figure. fig = plt.figure(figsize=(w, h), facecolor='w') # Add the main seismic axis. ax = fig.add_axes([seismic_left, seismic_bottom, seismic_width_fraction, seismic_height_fraction ]) # make parasitic axes for labeling CDP number par1 = ax.twiny() par1.spines["top"].set_position(("axes", 1.0)) tickfmt = mtick.FormatStrFormatter('%.0f') par1.plot(ens, np.zeros_like(ens)) par1.set_xlabel(cdp_label_text, fontsize=fs-2) par1.set_xticklabels(par1.get_xticks(), fontsize=fs-2) par1.xaxis.set_major_formatter(tickfmt) # Plot title title_ax = fig.add_axes([ssl, 1-mt/h, wsl/w, mt/(h)]) title_ax = plotter.plot_title(title_ax, target, fs=1.5*fs, cfg=cfg) if threed: title_ax.text(0.0, 0.0, '{} {}'.format(direction.title(), line_no)) # Plot text header. s = section.textual_file_header.decode() start = (h - 1.5*mt - fhh) / h head_ax = fig.add_axes([ssl, start, wsl/w, fhh/h]) head_ax = plotter.plot_header(head_ax, s, fs=fs-1, cfg=cfg) # Plot histogram. # Params for histogram plot pady = 0.75 / h # 0.75 inch padx = 0.75 / w # 0.75 inch cstrip = 0.3/h # color_strip height = 0.3 in charth = 1.5/h # height of charts = 1.5 in chartw = wsl/w - mr/w - padx # or ml/w for left-hand sidelabel; same thing chartx = (ssl + padx) histy = 1.5 * mb/h + charth + pady # Plot colourbar under histogram clrbar_ax = fig.add_axes([chartx, histy - cstrip, chartw, cstrip]) clrbar_ax = plotter.plot_colourbar(clrbar_ax, cmap=cfg['cmap']) # Plot histogram itself hist_ax = fig.add_axes([chartx, histy, chartw, charth]) hist_ax = plotter.plot_histogram(hist_ax, data, tickfmt, percentile=cfg['percentile'], fs=fs) # Plot spectrum. specy = 1.5 * mb/h spec_ax = fig.add_axes([chartx, specy, chartw, charth]) try: spec_ax = plotter.plot_spectrum(spec_ax, data, dt, tickfmt, ntraces=20, fontsize=fs) except: pass # Plot seismic data. if cfg['display'].lower() in ['vd', 'varden', 'variable']: _ = ax.imshow(data, cmap=cfg['cmap'], clim=[-clip_val, clip_val], extent=[0, ntraces, tbase[-1], tbase[0]], aspect='auto' ) elif cfg['display'].lower() == 'wiggle': ax = plotter.wiggle_plot(ax, data, tbase, ntraces, skip=cfg['skip'], gain=cfg['gain'], rgb=cfg['colour'], alpha=cfg['opacity'], lw=cfg['lineweight'] ) ax.set_ylim(ax.get_ylim()[::-1]) elif cfg['display'].lower() == 'both': # variable density goes on first _ = ax.imshow(data, cmap=cfg['cmap'], clim=[-clip_val, clip_val], extent=[0, ntraces, tbase[-1], tbase[0]], aspect='auto' ) # wiggle plots go on top ax = plotter.wiggle_plot(ax, data, tbase, ntraces, skip=cfg['skip'], gain=cfg['gain'], rgb=cfg['colour'], alpha=cfg['opacity'], lw=cfg['lineweight'] ) # ax.set_ylim(ax.get_ylim()[::-1]) else: Notice.fail("You need to specify the type of display: wiggle or vd") # Seismic axis annotations. ax = plotter.decorate_seismic(ax, traces, trace_label_text, tickfmt, cfg) # Watermark. if cfg['watermark_text']: Notice.info("Adding watermark") ax = plotter.watermark_seismic(ax, cfg) t2 = time.time() Notice.ok("Built plot in {:.1f} s".format(t2-t1)) ##################################################################### # # SAVE FILE # ##################################################################### Notice.hr_header("Saving") if cfg['stain_paper'] or cfg['coffee_rings'] or cfg['distort'] or cfg['scribble']: stupid = True else: stupid = False s = "Saved image file {} in {:.1f} s" if cfg['outfile']: if os.path.isfile(cfg['outfile']): outfile = cfg['outfile'] else: # is directory stem, ext = os.path.splitext(os.path.split(target)[1]) outfile = os.path.join(cfg['outfile'], stem + '.png') stem, _ = os.path.splitext(outfile) # Needed for stupidity. fig.savefig(outfile) t3 = time.time() Notice.ok(s.format(outfile, t3-t2)) else: # Do the default: save a PNG in the same dir as the target. stem, _ = os.path.splitext(target) fig.savefig(stem) t3 = time.time() Notice.ok(s.format(stem+'.png', t3-t2)) if stupid: fig.savefig(stem + ".stupid.png") else: return ##################################################################### # # SAVE STUPID FILE # ##################################################################### Notice.hr_header("Applying the stupidity") stupid_image = Image.open(stem + ".stupid.png") if cfg['stain_paper']: utils.stain_paper(stupid_image) utils.add_rings(stupid_image, cfg['coffee_rings']) if cfg['scribble']: utils.add_scribble(stupid_image) stupid_image.save(stem + ".stupid.png") s = "Saved stupid file stupid.png in {:.1f} s" t4 = time.time() Notice.ok(s.format(t4-t3)) return
nargs='?', help='The path to a SEGY file.') parser.add_argument('-o', '--out', metavar='Output file', type=str, nargs='?', default='', help='The path to an output file.') parser.add_argument('-R', '--recursive', action='store_true', help='Descend into subdirectories.') args = parser.parse_args() target = args.filename with args.config as f: cfg = yaml.load(f) Notice.hr_header("Initializing") Notice.info("Config {}".format(args.config.name)) # Fill in 'missing' fields in cfg. defaults = {'line': 'inline', 'number': 0.5, 'sidelabel': 'right', 'tpi': 10, 'ips': 1, 'skip': 2, 'display': 'vd', 'gain': 1.0, 'percentile': 99.0, 'colour': [0, 0, 0], 'opacity': 1.0, 'lineweight': 0.2,
def main(target, cfg): """ Puts everything together. """ t0 = time.time() # Read the file. section = readSEGY(target, unpack_headers=True) # Calculate some things nsamples = section.traces[0].header.number_of_samples_in_this_trace dt = section.traces[0].header.sample_interval_in_ms_for_this_trace ntraces = len(section.traces) tbase = 0.001 * np.arange(0, nsamples * dt, dt) tstart = 0 tend = np.amax(tbase) wsd = ntraces / cfg['tpi'] # Build the data container elev, esp, ens, tsq = [], [], [], [] # energy source point number data = np.zeros((nsamples, ntraces)) for i, trace in enumerate(section.traces): data[:, i] = trace.data elev.append(trace.header.receiver_group_elevation) esp.append(trace.header.energy_source_point_number) ens.append(trace.header.ensemble_number) tsq.append(trace.header.trace_sequence_number_within_line) clip_val = np.percentile(data, 99.0) # Notify user of parameters Notice.info("n_traces {}".format(ntraces)) Notice.info("n_samples {}".format(nsamples)) Notice.info("dt {}".format(dt)) Notice.info("t_start {}".format(tstart)) Notice.info("t_end {}".format(tend)) Notice.info("max_val {}".format(np.amax(data))) Notice.info("min_val {}".format(np.amin(data))) Notice.info("clip_val {}".format(clip_val)) t1 = time.time() Notice.ok("Read data successfully in {:.1f} s".format(t1-t0)) ##################################################################### # # MAKE PLOT # ##################################################################### Notice.hr_header("Plotting") ################################## # Plot size parameters # Some constants wsl = 6 # Width of sidelabel mih = 10 # Minimum plot height fhh = 5 # File header height m = 0.5 # margin in inches # Margins, CSS like mt, mb, ml, mr = m, m, 2 * m, 2 * m mm = mr / 2 # padded margin between seismic and label # Width is determined by tpi, plus a constant for the sidelabel, plus 1 in w = ml + wsd + wsl + mr + mm # Height is given by ips, but with a minimum of 8 inches, plus 1 in h = max(mih, cfg['ips'] * (np.amax(tbase) - np.amin(tbase)) / 1000 + mt + mb) # More settings ssl = (ml + wsd + mm) / w # Start of side label (ratio) fs = cfg['fontsize'] Notice.info("Width of plot {} in".format(w)) Notice.info("Height of plot {} in".format(h)) ################################## # Make the figure. fig = plt.figure(figsize=(w, h), facecolor='w') ax = fig.add_axes([ml / w, mb / h, wsd / w, (h - mb - mt) / h]) # make parasitic axes for labeling CDP number par1 = ax.twiny() par1.spines["top"].set_position(("axes", 1.0)) tickfmt = mtick.FormatStrFormatter('%.0f') par1.plot(ens, np.zeros_like(ens)) par1.set_xlabel("CDP number", fontsize=fs-2) par1.set_xticklabels(par1.get_xticks(), fontsize=fs-2) par1.xaxis.set_major_formatter(tickfmt) ax = wiggle_plot(ax, data, tbase, ntraces, skip=cfg['skip'], gain=cfg['gain'], rgb=cfg['colour'], alpha=cfg['opacity'], lw=cfg['lineweight'] ) ax = decorate_seismic(ax, ntraces, tickfmt, fs) # Plot title title_ax = fig.add_axes([ssl, 1-mt/h, wsl/w, mt/(2*h)]) title_ax = plot_title(title_ax, target, fs=fs) # Plot text header. s = str(section.textual_file_header)[2:-1] start = (h - mt - fhh) / h head_ax = fig.add_axes([ssl, start, wsl/w, fhh/h]) head_ax = plot_header(head_ax, s, fs) # Plot histogram. pad = 0.05 charty = 0.125 # height of chart xhist = (ssl + pad) whist = (1 - ssl - (ml/w)) - 2 * pad hist_ax = fig.add_axes([xhist, 1.5 * mb/h + charty + pad, whist, charty]) hist_ax = plot_histogram(hist_ax, data, fs) # Plot spectrum. spec_ax = fig.add_axes([xhist, 1.5 * mb/h, whist, charty]) spec_ax = plot_spectrum(spec_ax, data, dt, fs) t2 = time.time() Notice.ok("Built plot in {:.1f} s".format(t2-t1)) ##################################################################### # # SAVE FILE # ##################################################################### Notice.hr_header("Saving") s = "Saved image file {} in {:.1f} s" if cfg['outfile']: fig.savefig(cfg['outfile']) t3 = time.time() Notice.ok(s.format(cfg['outfile'], t3-t2)) else: stem, _ = os.path.splitext(target) fig.savefig(stem) t3 = time.time() Notice.ok(s.format(stem+'.png', t3-t2)) return
Notice.title() parser = argparse.ArgumentParser(description='Plot a SEGY file.') parser.add_argument("-c", "--config", metavar="config file", type=argparse.FileType('r'), default="config.yaml", nargs="?", help="The name of a YAML config file.") parser.add_argument('filename', metavar='SEGY file', type=str, nargs='?', help='The path to a SEGY file.') parser.add_argument('-o', '--out', metavar='Output file', type=str, nargs='?', default='', help='The path to an output file.') args = parser.parse_args() target = args.filename with args.config as f: cfg = yaml.load(f) Notice.hr_header("Initializing") Notice.info("Filename {}".format(target)) Notice.info("Config {}".format(args.config.name)) cfg['outfile'] = args.out main(target, cfg) Notice.hr_header("Done")
def main(target, cfg): """ Puts everything together. """ t0 = time.time() ##################################################################### # # READ SEGY # ##################################################################### s = Seismic.from_segy(target, params={'ndim': cfg['ndim']}) # Set the line and/or xline number. try: n, xl = cfg['number'] except: n, xl = cfg['number'], 0.5 # Set the direction. if (s.ndim) == 2: direction = ['inline'] elif cfg['direction'].lower()[0] == 'i': direction = ['inline'] elif cfg['direction'].lower()[0] == 'x': direction = ['xline'] elif cfg['direction'].lower()[0] == 't': direction = ['tslice'] else: direction = ['inline', 'xline'] # Get the data. ss = [Seismic.from_seismic(s, n=n, direction=d) for n, d in zip((n, xl), direction)] data = [s.data for s in ss] clip_val = np.percentile(s.data, cfg['percentile']) # Notify user of parameters. Notice.info("n_traces {}".format(s.ntraces)) Notice.info("n_samples {}".format(s.nsamples)) Notice.info("dt {}".format(s.dt)) Notice.info("t_start {}".format(s.tstart)) Notice.info("t_end {}".format(s.tend)) Notice.info("max_val {:.3f}".format(np.amax(s.data))) Notice.info("min_val {:.3f}".format(np.amin(s.data))) Notice.info("clip_val {:.3f}".format(clip_val)) t1 = time.time() Notice.ok("Read data in {:.1f} s".format(t1-t0)) ##################################################################### # # MAKE PLOT # ##################################################################### Notice.hr_header("Plotting") # Plot size parameters. fs = cfg['fontsize'] wsl = 6 # Width of sidelabel, inches mih = 12 # Minimum plot height, inches fhh = 5 # File header box height, inches m = 0.5 # basic unit of margins, inches # Margins, CSS like: top, right, bottom, left. mt, mr, mb, ml = m, 2 * m, m, 2 * m mm = m # padded margin between seismic and label # Width is determined by seismic width, plus sidelabel, plus margins. # Height is given by ips, but with a minimum of mih inches. if 'tslice' in direction: print('doing tslice') seismic_width = max([s.ninlines for s in ss]) / cfg['tpi'] seismic_height_raw = max([s.nxlines for s in ss]) / cfg['tpi'] print(seismic_width, seismic_height_raw) else: seismic_width = max([s.ntraces for s in ss]) / cfg['tpi'] seismic_height_raw = cfg['ips'] * (s.tbasis[-1] - s.tbasis[0]) w = ml + seismic_width + mm + wsl + mr # inches seismic_height = len(ss) * seismic_height_raw h_reqd = mb + seismic_height + 0.75*(len(ss)-1) + mt # inches h = max(mih, h_reqd) # Calculate where to start sidelabel and seismic data. # Depends on whether sidelabel is on the left or right. if cfg['sidelabel'] == 'right': ssl = (ml + seismic_width + mm) / w # Start of side label (ratio) seismic_left = ml / w else: ssl = ml / w seismic_left = (ml + wsl + mm) / w adj = max(0, h - h_reqd) / 2 seismic_bottom = (mb / h) + adj / h seismic_width_fraction = seismic_width / w seismic_height_fraction = seismic_height_raw / h # Publish some notices so user knows plot size. Notice.info("Width of plot {} in".format(w)) Notice.info("Height of plot {} in".format(h)) # Make the figure. fig = plt.figure(figsize=(w, h), facecolor='w') # Set the tickformat. tickfmt = mtick.FormatStrFormatter('%.0f') # Plot title. if cfg['filename']: title_ax = fig.add_axes([ssl, 1-mt/h, wsl/w, mt/h]) title_ax = plotter.plot_title(title_ax, target, fs=1.5*fs, cfg=cfg) # Plot text header. start = (h - 1.5*mt - fhh) / h head_ax = fig.add_axes([ssl, start, wsl/w, fhh/h]) head_ax = plotter.plot_header(head_ax, s.header, fs=fs-1, cfg=cfg) # Plot histogram. # Params for histogram plot. pady = 0.75 / h # 0.75 inch padx = 0.75 / w # 0.75 inch cstrip = 0.3/h # color_strip height = 0.3 in charth = 1.5/h # height of charts = 1.5 in chartw = wsl/w - mr/w - padx # or ml/w for left-hand sidelabel; same thing chartx = (ssl + padx) histy = 1.5 * mb/h + charth + pady # Plot colourbar under histogram. clrbar_ax = fig.add_axes([chartx, histy - cstrip, chartw, cstrip]) clrbar_ax = plotter.plot_colourbar(clrbar_ax, cmap=cfg['cmap']) # Plot histogram itself. hist_ax = fig.add_axes([chartx, histy, chartw, charth]) hist_ax = plotter.plot_histogram(hist_ax, s.data, tickfmt, percentile=cfg['percentile'], fs=fs) # Plot spectrum. specy = 1.5 * mb/h spec_ax = fig.add_axes([chartx, specy, chartw, charth]) try: spec_ax = s.plot_spectrum(ax=spec_ax, tickfmt=tickfmt, ntraces=20, fontsize=fs) except: pass for i, line in enumerate(ss): # Add the seismic axis. ax = fig.add_axes([seismic_left, seismic_bottom + i*seismic_height_fraction + i*pady, seismic_width_fraction, seismic_height_fraction ]) # Plot seismic data. if cfg['display'].lower() in ['vd', 'varden', 'variable', 'both']: _ = ax.imshow(line.data.T, cmap=cfg['cmap'], clim=[-clip_val, clip_val], extent=[0, line.ntraces, 1000*line.tbasis[-1], line.tbasis[0]], aspect='auto' ) # This does not work: should cut off line at cfg['tmax'] # ax.set_ylim(1000*cfg['tmax'] or 1000*line.tbasis[-1], line.tbasis[0]) if cfg['display'].lower() in ['wiggle', 'both']: ax = line.wiggle_plot(cfg['number'], direction, ax=ax, skip=cfg['skip'], gain=cfg['gain'], rgb=cfg['colour'], alpha=cfg['opacity'], lw=cfg['lineweight'], tmax=cfg['tmax'], ) if cfg['display'].lower() not in ['vd', 'varden', 'variable', 'wiggle', 'both']: Notice.fail("You must specify the type of display: wiggle, vd, both.") return # Seismic axis annotations. fs = cfg['fontsize'] - 2 ax.set_xlim([0, line.data.shape[0]]) ax.set_ylabel(line.ylabel, fontsize=fs) ax.set_xlabel(line.xlabel, fontsize=fs, horizontalalignment='center') ax.set_xticklabels(ax.get_xticks(), fontsize=fs) ax.set_yticklabels(ax.get_yticks(), fontsize=fs) ax.xaxis.set_major_formatter(tickfmt) ax.yaxis.set_major_formatter(tickfmt) # Watermark. if cfg['watermark_text']: ax = plotter.watermark_seismic(ax, cfg) # Make parasitic axes for labeling CDP number. par1 = ax.twiny() par1.spines["top"].set_position(("axes", 1.0)) par1.plot(s.xlines, np.zeros_like(s.xlines)) par1.set_xlabel(line.xlabel, fontsize=fs) par1.set_xticklabels(par1.get_xticks(), fontsize=fs) par1.xaxis.set_major_formatter(tickfmt) t2 = time.time() Notice.ok("Built plot in {:.1f} s".format(t2-t1)) ##################################################################### # # SAVE FILE # ##################################################################### Notice.hr_header("Saving") dname, fname, ext = utils.path_bits(target) outfile = cfg['outfile'] or '' if not os.path.splitext(outfile)[1]: outfile = os.path.join(cfg['outfile'] or dname, fname + '.png') fig.savefig(outfile) t3 = time.time() Notice.ok("Saved image file {} in {:.1f} s".format(outfile, t3-t2)) if cfg['stain_paper'] or cfg['coffee_rings'] or cfg['distort'] or cfg['scribble']: fname = os.path.splitext(outfile)[0] + ".stupid.png" fig.savefig(fname) else: return ##################################################################### # # SAVE STUPID FILE # ##################################################################### Notice.hr_header("Applying the stupidity") stupid_image = Image.open(fname) if cfg['stain_paper']: utils.stain_paper(stupid_image) utils.add_rings(stupid_image, cfg['coffee_rings']) if cfg['scribble']: utils.add_scribble(stupid_image) stupid_image.save(fname) t4 = time.time() Notice.ok("Saved stupid file {} in {:.1f} s".format(fname, t4-t3)) return