Source code for imports.gocad.props

"""
Contains the PROPS class
"""
import os
import csv
import sys
import logging
from collections import defaultdict
import numpy



[docs]class PROPS: ''' This class holds generic 3d data and properties e.g. information extracted from 3d binary files (e.g. from GOCAD 'PROP_FILE') information attached to a set of XYZ points (e.g. from GOCAD 'PATOM', 'PVRTX') ''' def __init__(self, class_name, debug_level): # Set up logging, use an attribute of class name so it is only called once if not hasattr(PROPS, 'logger'): PROPS.logger = logging.getLogger(__name__) # Create console handler handler = logging.StreamHandler(sys.stdout) # Create formatter formatter = logging.Formatter('%(asctime)s -- %(name)s -- %(levelname)s - %(message)s') # Add formatter to ch handler.setFormatter(formatter) # Add handler to logger and set level PROPS.logger.addHandler(handler) PROPS.logger.setLevel(debug_level) self.logger = PROPS.logger self.file_name = "" ''' Name of binary file associated with GOCAD file ''' self.data_sz = 0 ''' Number of bytes in number in binary file, usually 1, 2 or 4 ''' self.data_type = "f" ''' Type of data in binary file e.g. 'h' = short 2-byte int, 'f' = float, 'b' - byte, 'rgba' - 4-byte RGBA ''' self.signed_int = False ''' Is True iff binary data is a signed integer else False ''' self.data_3d = numpy.zeros((0, 0, 0)) ''' Property data collected from binary file, value is float, stored as a 3d numpy array. ''' self.data_xyz = defaultdict(list) ''' Property data attached to XYZ points (index is XYZ coordinate) ''' self.data_ijk = defaultdict(list) ''' Property data attached to XYZ points (index is IJK int indexes) ''' self.data_stats = {'min': sys.float_info.max, 'max': -sys.float_info.max} ''' Property data statistics: min & max ''' self.colour_map = {} ''' If colour map was specified, then it is stored here, integer is the key, value is (R,G,B,A) where R, G, B, A are floats ''' self.colourmap_name = "" ''' Name of colour map ''' self.class_name = class_name ''' Property class names ''' self.no_data_marker = None ''' Value representing 'no data' values ''' self.is_index_data = False ''' Uses 'data_3d' to hold index to colour table or rock label table ''' self.rock_label_table = {} ''' Table specifying names of rocks , key is an integer, value is the label ''' self.offset = 0 ''' Offset within binary file ''' def __repr__(self): ''' A print friendly representation ''' return "\nPROPS START\n self = {:}\n".format(hex(id(self))) + \ " file_name = {:}\n".format(repr(self.file_name)) + \ " data_sz = {:d}\n".format(self.data_sz) + \ " data_type = {:}\n".format(repr(self.data_type)) + \ " signed_int = {:}\n".format(repr(self.signed_int)) + \ " data_3d = {:}\n".format(repr(self.data_3d)) + \ " data_xyz = {:}\n".format(repr(self.data_xyz)) + \ " data_stats = {:}\n".format(repr(self.data_stats)) + \ " colour_map = {:}\n".format(repr(self.colour_map)) + \ " colourmap_name = {:}\n".format(self.colourmap_name) + \ " class_name = {:}\n".format(self.class_name) + \ " no_data_marker = {:}\n".format(repr(self.no_data_marker)) + \ " is_index_data = {:}\n".format(repr(self.is_index_data)) + \ " rock_label_table = {:}\n".format(repr(self.rock_label_table)) + \ " str_data_type = {:}\n".format(repr(self.get_str_data_type())) + \ "PROPS END\n\n"
[docs] def get_str_data_type(self): ''' Returns a string form of the data type of the volume data e.g. "INT_16", "FLOAT_32", "UINT_8", "RGBA" ''' if self.data_type == 'f' and self.data_sz == 4: return "FLOAT_32" if self.data_type == 'h' and self.data_sz == 2: if self.signed_int: return "INT_16" return "UINT_16" if self.data_type == 'b': if self.signed_int: return "INT_8" return "UINT_8" if self.data_type == 'rgba': return "RGBA" return ""
[docs] def make_numpy_dtype(self): ''' Returns a string that can be passed to 'numpy' to read a binary file It takes the 'data_type' of 'f', 'b', h' & 'rgba' ''' # Prepare 'numpy' binary float integer signed/unsigned data types # Using '>' to tell 'numpy' that it is big-endian # Using upper case to tell 'numpy' that it is unsigned integer # 'numpy' recognises 'h' as a 2-byte integer and 'b' as byte if self.data_type == 'h' or self.data_type == 'b': if not self.signed_int: return numpy.dtype('>'+self.data_type.upper()) return numpy.dtype('>'+self.data_type) # R,G,B,A 4-byte array of colour if self.data_type == 'rgba': return numpy.dtype([('r', numpy.uint8), ('g', numpy.uint8), ('b', numpy.uint8), ('a', numpy.uint8)]) # Floating point i.e. data_type = 'f' return numpy.dtype('>'+self.data_type+str(self.data_sz))
[docs] def read_colour_table_csv(self, csv_file, transp_list): ''' Reads an RGB colour table from CSV file for use in VOXET colours CSV Format: col#1: integer index, col#2: R-value, col#3: G-value, col#4: B-value csv_file - filename of CSV file to read, without path Sets the 'colour_map' and 'rock_label_table' class attibutes 'colour_map' is a dict, key is integer, value is (R,G,B,A) tuple of floats 'rock_label_table' is a dict, key is integer, value is string ''' col_tab = {} lab_tab = {} if not os.path.isfile(csv_file): self.logger.error("Cannot find CSV file: %s", csv_file) sys.exit(1) try: csv_filehandle = open(csv_file, 'r') csv_reader = csv.reader(csv_filehandle) for row in csv_reader: a_val = 1.0 if int(row[0]) in transp_list: a_val = 0.0 col_tab[int(row[0])] = (float(row[2]), float(row[3]), float(row[4]), a_val) lab_tab[int(row[0])] = row[1] except OSError as os_exc: self.logger.error("Cannot read CSV file %s %s", csv_file, os_exc) sys.exit(1) self.colour_map = col_tab self.rock_label_table = lab_tab
[docs] def assign_to_3d(self, x_val, y_val, z_val, fltp): ''' Assigns a value to 3d array x,y,z - XYZ integer array indexes fltp - floating point value to be assigned ''' self.data_3d[x_val][y_val][z_val] = fltp self.__calc_minmax(fltp)
[docs] def assign_to_xyz(self, xyz, val): ''' Assigns a value to xyz dict xyz - (X,Y,Z) tuple array indexes (floats) val - value to be assigned (float or tuple) ''' self.data_xyz[xyz] = val if isinstance(val, float) or isinstance(val, numpy.float32): self.__calc_minmax(val)
[docs] def assign_to_ijk(self, ijk, val): ''' Assigns a value to ijk dict ijk - (I,J,K) tuple array indexes (ints) val - value to be assigned (float or tuple) ''' self.data_ijk[ijk] = val if isinstance(val, float) or isinstance(val, numpy.float32): self.__calc_minmax(val)
[docs] def append_to_xyz(self, xyz, val): ''' Appends a value to xyz dict xyz - (X,Y,Z) tuple array indexes (floats) val - value to be assigned ''' x,y,z = xyz if not isinstance(x, float) or not is_instance(y, float) or \ not isinstance(z, float): self.logger.error("Internal error: adding float index to int array %s", repr(xyz)) sys.exit(1) self.data_xyz[xyz].append(val)
[docs] def append_to_ijk(self, ijk, val): ''' Appends a value to xyz dict ijk - (I,J,K) tuple array indexes (ints) val - value to be assigned ''' i,j,k = ijk if not isinstance(i, int) or not is_instance(j, int) or \ not isinstance(k, int): self.logger.error("Internal error: adding float index to int array %s", repr(ijk)) sys.exit(1) self.data_ijk[ijk].append(val)
def __calc_minmax(self, fltp): ''' Calculates minimum & maximum of floating point value and stores result locally in 'data_stats' fp - floating point value (numpy or python float) ''' try: if fltp > self.data_stats['max']: # Convert to python float self.data_stats['max'] = float(fltp) if fltp < self.data_stats['min']: # Convert to python float self.data_stats['min'] = float(fltp) except ValueError: pass