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 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
def plot_feature_well(tc, gs): """ Plotting function for the feature well. Args: tc (TransectContainer): The container for the main plot. log (axis): A matplotlib axis. gs (GridSpec): A matplotlib gridspec. """ fname = tc.settings['curve_display'] logs = tc.log.get(tc.feature_well) if not logs: # There was no data for this well, so there won't be a feature plot. Notice.fail("There's no well data for feature well " + tc.feature_well) return gs Z = logs.data['DEPT'] curves = ['GR', 'DT', 'DPHI_SAN', 'NPHI_SAN', 'DTS', 'RT_HRLT', 'RHOB', 'DRHO'] window = tc.settings.get('curve_smooth_window') or 51 ntracks = 5 lw = 1.0 smooth = True naxes = 0 ncurv_per_track = np.zeros(ntracks) if getattr(tc.log, 'striplog', None): ncurv_per_track[0] = 1 for curve in curves: naxes += 1 params = get_curve_params(curve, fname) ncurv_per_track[params['track']] += 1 axss = plt.subplot(gs[2:, -5]) axs0 = [axss, axss.twiny()] axs1 = [plt.subplot(gs[2:, -4])] axs2 = [plt.subplot(gs[2:, -3])] axs3 = [plt.subplot(gs[2:, -2])] axs4 = [plt.subplot(gs[2:, -1])] axs = [axs0, axs1, axs2, axs3, axs4] if getattr(tc.log, 'striplog', None): legend = Legend.default() try: logs.striplog[tc.log.striplog].plot_axis(axs0[0], legend=legend) except KeyError: # In fact, this striplog doesn't exist. Notice.fail("There is no such striplog" + tc.log.striplog) # And move on... axs0[0].set_ylim([Z[-1], 0]) label_shift = np.zeros(len(axs)) for curve in curves: try: values = logs.data[curve] except ValueError: Notice.warning("Curve not present: "+curve) values = np.empty_like(Z) values[:] = np.nan params = get_curve_params(curve, fname) i = params['track'] j = 0 label_shift[i] += 1 linOrlog = params['logarithmic'] sxticks = np.array(params['xticks']) xticks = np.array(sxticks, dtype=float) whichticks = 'major' if linOrlog == 'log': midline = np.log(np.mean(xticks)) xpos = midline whichticks = 'minor' else: midline = np.mean(xticks) xpos = midline if smooth: values = utils.rolling_median(values, window) if curve == 'GR': j = 1 # second axis in first track label_shift[i] = 1 if params['fill_left_cond']: # do the fill for the lithology track axs[i][j].fill_betweenx(Z, params['xleft'], values, facecolor=params['fill_left'], alpha=1.0, zorder=11) if (curve == 'DPHI_SAN') and params['fill_left_cond']: # do the fill for the neutron porosity track try: nphi = utils.rolling_median(logs.data['NPHI_SAN'], window) except ValueError: Notice.warning("No NPHI in this well") nphi = np.empty_like(Z) nphi[:] = np.nan axs[i][j].fill_betweenx(Z, nphi, values, where=nphi >= values, facecolor=params['fill_left'], alpha=1.0, zorder=11) axs[i][j].fill_betweenx(Z, nphi, values, where=nphi <= values, facecolor='#8C1717', alpha=0.5, zorder=12) if curve == 'DRHO': blk_drho = 3.2 values += blk_drho # this is a hack to get DRHO on RHOB scale axs[i][j].fill_betweenx(Z, blk_drho, values, where=nphi <= values, facecolor='#CCCCCC', alpha=0.5, zorder=12) # fill right if params['fill_right_cond']: axs[i][j].fill_betweenx(Z, values, params['xright'], facecolor=params['fill_right'], alpha=1.0, zorder=12) # plot curve axs[i][j].plot(values, Z, color=params['hexcolor'], lw=lw, zorder=13) # set scale of curve axs[i][j].set_xlim([params['xleft'], params['xright']]) # ------------------------------------------------- # # curve labels # ------------------------------------------------- # trans = transforms.blended_transform_factory(axs[i][j].transData, axs[i][j].transData) magic = -Z[-1] / 12. axs[i][j].text(xpos, magic - (magic/4)*(label_shift[i]-1), curve, horizontalalignment='center', verticalalignment='bottom', fontsize=12, color=params['hexcolor'], transform=trans) # curve units units = '${}$'.format(params['units']) if label_shift[i] <= 1: axs[i][j].text(xpos, magic*0.5, units, horizontalalignment='center', verticalalignment='top', fontsize=12, color='k', transform=trans) # ------------------------------------------------- # # scales and tickmarks # ------------------------------------------------- # axs[i][j].set_xscale(linOrlog) axs[i][j].set_ylim([Z[-1], 0]) axs[i][j].axes.xaxis.set_ticks(xticks) axs[i][j].axes.xaxis.set_ticklabels(sxticks, fontsize=8) for label in axs[i][j].axes.xaxis.get_ticklabels(): label.set_rotation(90) axs[i][j].tick_params(axis='x', direction='out') axs[i][j].xaxis.tick_top() axs[i][j].xaxis.set_label_position('top') axs[i][j].xaxis.grid(True, which=whichticks, linewidth=0.25, linestyle='-', color='0.75', zorder=100) axs[i][j].yaxis.grid(True, which=whichticks, linewidth=0.25, linestyle='-', color='0.75', zorder=100) axs[i][j].yaxis.set_ticks(np.arange(0, max(Z), 100)) if i != 0: axs[i][j].set_yticklabels("") # ------------------------------------------------- # # End of curve loop # ------------------------------------------------- # # Add Depth label axs[0][0].text(0, 1.05, 'MD\n$m$', fontsize='10', horizontalalignment='center', verticalalignment='center', transform=axs[0][0].transAxes) axs[0][0].axes.yaxis.get_ticklabels() axs[0][0].axes.xaxis.set_ticklabels('') for label in axs[0][0].axes.yaxis.get_ticklabels(): label.set_rotation(90) label.set_fontsize(10) for label in axs[1][0].axes.xaxis.get_ticklabels(): label.set_rotation(90) label.set_fontsize(10) # Add Tops try: if os.path.exists(tc.tops_file): tops = utils.get_tops(tc.tops_file) topx = get_curve_params('DT', fname) topmidpt = np.amax((topx)['xright']) # plot tops for i in range(ntracks): for mkr, depth in tops.iteritems(): # draw horizontal bars at the top position axs[i][-1].axhline(y=depth, xmin=0.01, xmax=.99, color='b', lw=2, alpha=0.5, zorder=100) # draw text box at the right edge of the last track axs[-1][-1].text(x=topmidpt, y=depth, s=mkr, alpha=0.5, color='k', fontsize='8', horizontalalignment='center', verticalalignment='center', zorder=10000, bbox=dict(facecolor='white', edgecolor='k', alpha=0.25, lw=0.25), weight='light') except AttributeError: Notice.warning("No tops for this well") except TypeError: # We didn't get a tops file so move along. print "No tops for this well" return gs
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
'coffee_rings': 0, 'distort': False, 'scribble': False, } for k, v in defaults.items(): if cfg.get(k) is None: cfg[k] = v cfg['outfile'] = args.out # Gather files to work on, then go and do them. if os.path.isfile(target): Notice.hr_header("Processing file: {}".format(target)) main(target, cfg) Notice.hr_header("Done") elif os.path.isdir(target): if args.recursive: Notice.info("Looking for SEGY files in {} and its subdirectories".format(target)) for target in utils.walk(target, "\\.se?gy$"): Notice.hr_header("Processing file: {}".format(target)) main(target, cfg) else: Notice.info("Finding SEGY files in {}".format(target)) for target in utils.listdir(target, "\\.se?gy$"): Notice.hr_header("Processing file: {}".format(target)) main(target, cfg) Notice.hr_header("Done") else: Notice.fail("Not a file or directory.")
def plot_feature_well(tc, gs): """ Plotting function for the feature well. Args: tc (TransectContainer): The container for the main plot. log (axis): A matplotlib axis. gs (GridSpec): A matplotlib gridspec. """ fname = tc.settings['curve_display'] logs = tc.log.get(tc.feature_well) if not logs: # There was no data for this well, so there won't be a feature plot. Notice.fail("There's no well data for feature well " + tc.feature_well) return gs Z = logs.data['DEPT'] curves = [ 'GR', 'DT', 'DPHI_SAN', 'NPHI_SAN', 'DTS', 'RT_HRLT', 'RHOB', 'DRHO' ] window = tc.settings.get('curve_smooth_window') or 51 ntracks = 5 lw = 1.0 smooth = True naxes = 0 ncurv_per_track = np.zeros(ntracks) if getattr(tc.log, 'striplog', None): ncurv_per_track[0] = 1 for curve in curves: naxes += 1 params = get_curve_params(curve, fname) ncurv_per_track[params['track']] += 1 axss = plt.subplot(gs[2:, -5]) axs0 = [axss, axss.twiny()] axs1 = [plt.subplot(gs[2:, -4])] axs2 = [plt.subplot(gs[2:, -3])] axs3 = [plt.subplot(gs[2:, -2])] axs4 = [plt.subplot(gs[2:, -1])] axs = [axs0, axs1, axs2, axs3, axs4] if getattr(tc.log, 'striplog', None): legend = Legend.default() try: logs.striplog[tc.log.striplog].plot_axis(axs0[0], legend=legend) except KeyError: # In fact, this striplog doesn't exist. Notice.fail("There is no such striplog" + tc.log.striplog) # And move on... axs0[0].set_ylim([Z[-1], 0]) label_shift = np.zeros(len(axs)) for curve in curves: try: values = logs.data[curve] except ValueError: Notice.warning("Curve not present: " + curve) values = np.empty_like(Z) values[:] = np.nan params = get_curve_params(curve, fname) i = params['track'] j = 0 label_shift[i] += 1 linOrlog = params['logarithmic'] sxticks = np.array(params['xticks']) xticks = np.array(sxticks, dtype=float) whichticks = 'major' if linOrlog == 'log': midline = np.log(np.mean(xticks)) xpos = midline whichticks = 'minor' else: midline = np.mean(xticks) xpos = midline if smooth: values = utils.rolling_median(values, window) if curve == 'GR': j = 1 # second axis in first track label_shift[i] = 1 if params['fill_left_cond']: # do the fill for the lithology track axs[i][j].fill_betweenx(Z, params['xleft'], values, facecolor=params['fill_left'], alpha=1.0, zorder=11) if (curve == 'DPHI_SAN') and params['fill_left_cond']: # do the fill for the neutron porosity track try: nphi = utils.rolling_median(logs.data['NPHI_SAN'], window) except ValueError: Notice.warning("No NPHI in this well") nphi = np.empty_like(Z) nphi[:] = np.nan axs[i][j].fill_betweenx(Z, nphi, values, where=nphi >= values, facecolor=params['fill_left'], alpha=1.0, zorder=11) axs[i][j].fill_betweenx(Z, nphi, values, where=nphi <= values, facecolor='#8C1717', alpha=0.5, zorder=12) if curve == 'DRHO': blk_drho = 3.2 values += blk_drho # this is a hack to get DRHO on RHOB scale axs[i][j].fill_betweenx(Z, blk_drho, values, where=nphi <= values, facecolor='#CCCCCC', alpha=0.5, zorder=12) # fill right if params['fill_right_cond']: axs[i][j].fill_betweenx(Z, values, params['xright'], facecolor=params['fill_right'], alpha=1.0, zorder=12) # plot curve axs[i][j].plot(values, Z, color=params['hexcolor'], lw=lw, zorder=13) # set scale of curve axs[i][j].set_xlim([params['xleft'], params['xright']]) # ------------------------------------------------- # # curve labels # ------------------------------------------------- # trans = transforms.blended_transform_factory(axs[i][j].transData, axs[i][j].transData) magic = -Z[-1] / 12. axs[i][j].text(xpos, magic - (magic / 4) * (label_shift[i] - 1), curve, horizontalalignment='center', verticalalignment='bottom', fontsize=12, color=params['hexcolor'], transform=trans) # curve units units = '${}$'.format(params['units']) if label_shift[i] <= 1: axs[i][j].text(xpos, magic * 0.5, units, horizontalalignment='center', verticalalignment='top', fontsize=12, color='k', transform=trans) # ------------------------------------------------- # # scales and tickmarks # ------------------------------------------------- # axs[i][j].set_xscale(linOrlog) axs[i][j].set_ylim([Z[-1], 0]) axs[i][j].axes.xaxis.set_ticks(xticks) axs[i][j].axes.xaxis.set_ticklabels(sxticks, fontsize=8) for label in axs[i][j].axes.xaxis.get_ticklabels(): label.set_rotation(90) axs[i][j].tick_params(axis='x', direction='out') axs[i][j].xaxis.tick_top() axs[i][j].xaxis.set_label_position('top') axs[i][j].xaxis.grid(True, which=whichticks, linewidth=0.25, linestyle='-', color='0.75', zorder=100) axs[i][j].yaxis.grid(True, which=whichticks, linewidth=0.25, linestyle='-', color='0.75', zorder=100) axs[i][j].yaxis.set_ticks(np.arange(0, max(Z), 100)) if i != 0: axs[i][j].set_yticklabels("") # ------------------------------------------------- # # End of curve loop # ------------------------------------------------- # # Add Depth label axs[0][0].text(0, 1.05, 'MD\n$m$', fontsize='10', horizontalalignment='center', verticalalignment='center', transform=axs[0][0].transAxes) axs[0][0].axes.yaxis.get_ticklabels() axs[0][0].axes.xaxis.set_ticklabels('') for label in axs[0][0].axes.yaxis.get_ticklabels(): label.set_rotation(90) label.set_fontsize(10) for label in axs[1][0].axes.xaxis.get_ticklabels(): label.set_rotation(90) label.set_fontsize(10) # Add Tops try: if os.path.exists(tc.tops_file): tops = utils.get_tops(tc.tops_file) topx = get_curve_params('DT', fname) topmidpt = np.amax((topx)['xright']) # plot tops for i in range(ntracks): for mkr, depth in tops.iteritems(): # draw horizontal bars at the top position axs[i][-1].axhline(y=depth, xmin=0.01, xmax=.99, color='b', lw=2, alpha=0.5, zorder=100) # draw text box at the right edge of the last track axs[-1][-1].text(x=topmidpt, y=depth, s=mkr, alpha=0.5, color='k', fontsize='8', horizontalalignment='center', verticalalignment='center', zorder=10000, bbox=dict(facecolor='white', edgecolor='k', alpha=0.25, lw=0.25), weight='light') except AttributeError: Notice.warning("No tops for this well") except TypeError: # We didn't get a tops file so move along. print "No tops for this well" return gs
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