From 4b9d4ba7c6fde77c42235f69d4801b56c72691ab Mon Sep 17 00:00:00 2001 From: caron <j.caron@fz-juelich.de> Date: Tue, 3 Mar 2020 15:31:19 +0100 Subject: [PATCH] Cleanup! Deleted print statements, superfluous log, TODOs, added a few comments. --- .gitlab-ci.yml | 2 +- docs/index.rst | 1 + docs/vis.rst | 4 +++ environment.yml | 2 +- setup.cfg | 4 +-- setup.py | 9 ------ src/empyre/fields/field.py | 59 ++---------------------------------- src/empyre/fields/vectors.py | 20 +++--------- src/empyre/vis/colors.py | 7 +---- src/empyre/vis/plot2d.py | 4 +-- src/empyre/vis/tools.py | 9 +++++- 11 files changed, 27 insertions(+), 94 deletions(-) create mode 100644 docs/vis.rst diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 6dda245..db2a7a1 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -68,7 +68,7 @@ pypi: script: - pip install twine - python setup.py sdist bdist_wheel - - twine upload -u __token__ -p $PYPI_ACCESS_TOKEN dist/* + - twine upload -u __token__ -p $PYPI_ACCESS_TOKEN dist/* # -u user -p password upload_source rules: # similar to only/except, but newer! # Job is executed if branch is master AND if a tag is building which matches the regular expression! # ONLY executes if commit to master has a tag, ^:start, $:end, valid example: "1.2.3", no "-dev" at the end! diff --git a/docs/index.rst b/docs/index.rst index c14ca79..6d83a3d 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -13,6 +13,7 @@ Welcome to EMPyRe's documentation! :caption: Contents: fields + vis Indices and tables diff --git a/docs/vis.rst b/docs/vis.rst new file mode 100644 index 0000000..f0c4506 --- /dev/null +++ b/docs/vis.rst @@ -0,0 +1,4 @@ +The vis visualization submodule +=============================== + +vis docu here! \ No newline at end of file diff --git a/environment.yml b/environment.yml index b2f1059..3ce80b6 100644 --- a/environment.yml +++ b/environment.yml @@ -27,7 +27,7 @@ dependencies: - matplotlib=3.1 - Pillow=6.1 - cmocean=2.0 - #- qt=5.9 # TODO: only needed for mayavi? + #- qt=5.9 # TODO: only needed for mayavi? which version? - mayavi=4.6 # TODO: Get rid of! # Testing: - pytest=5.0 diff --git a/setup.cfg b/setup.cfg index d4c1a6a..95906f1 100644 --- a/setup.cfg +++ b/setup.cfg @@ -7,12 +7,12 @@ [metadata] name = empyre -version = 0.1.0.dev0 +version = 0.0.0 author = Jan Caron author-email = j.caron@fz-juelich.de description = Electron Microscopy Python Reconstruction long-description = file: README.rst -url = https://jugit.fz-juelich.de/empyre/empyre +url = https://iffgit.fz-juelich.de/empyre/empyre license = GPLv3 classifiers = Development Status :: 3 - alpha diff --git a/setup.py b/setup.py index 01fdcb4..092bcc7 100644 --- a/setup.py +++ b/setup.py @@ -38,12 +38,3 @@ for style_path in style_files: # Run setup (reads metadata & options from setup.cfg): print(R'running setup.py') setup() - - -# TODO: HOW TO GET JUTIL??? - -# TODO: Also create conda recipe! -# TODO: Handle extras via metapackage (depends on pyramid-base that holds the core code and extras?) -# TODO: https://docs.conda.io/projects/conda-build/en/latest/user-guide/tutorials/build-pkgs.html - -# TODO: MATPLOTLIB STYLE INSTALL: https://stackoverflow.com/questions/35851201/how-can-i-share-matplotlib-style diff --git a/src/empyre/fields/field.py b/src/empyre/fields/field.py index 4be36bb..c590e0b 100644 --- a/src/empyre/fields/field.py +++ b/src/empyre/fields/field.py @@ -16,14 +16,6 @@ from scipy.ndimage import interpolation __all__ = ['Field'] -# TODO: Forward numpys ndim parameter here? - -# TODO: Search for .data ! Not usable anymore! - -# TODO: overwrite property ndim? - -# TODO: get rid of some _log and print stuff! - class Field(NDArrayOperatorsMixin): """Container class for storing multidimensional scalar or vector fields. @@ -168,26 +160,9 @@ class Field(NDArrayOperatorsMixin): outputs = kwargs.pop('out', ()) outputs = kwargs.pop('out', (None,)*ufunc.nout) # Defaults to tuple of None (currently: nout=1 all the time) outputs_arr = tuple([np.asarray(out) if isinstance(out, Field) else out for out in outputs]) - nl = '\n ' - str_inputs = [str(inp) for inp in inputs] - self._log.debug(f' ufunc: {ufunc}') - self._log.debug(f' method: {method}') - self._log.debug(f' inputs: {nl.join(str_inputs)}') - self._log.debug(f' # inp.: {len(inputs)}') - self._log.debug(f' outputs: {str(outputs)}') - self._log.debug(f' out_arr: {str(outputs_arr)}') - self._log.debug(f' kwargs: {kwargs}') - self._log.debug(f' nin: {ufunc.nin}') - self._log.debug(f' nout: {ufunc.nout}') - self._log.debug(f' self: {str(self)}') # Cannot handle items that have __array_ufunc__ (other than our own). - print(f'inputs+outputs: {inputs+outputs}') for item in inputs + outputs: - print(f'type(item): {type(item)}') - print(f'hasattr(item, __array_ufunc__): {hasattr(item, "__array_ufunc__")}') - print(f'isinstance(item, Field): {isinstance(item, Field)}') if hasattr(item, '__array_ufunc__') and not isinstance(item, Field): # Something else with __array_ufunc__: - print(f'type(item).__array_ufunc__: {type(item).__array_ufunc__}') if type(item).__array_ufunc__ is not np.ndarray.__array_ufunc__: # Can't handle other overrides: return NotImplemented # TODO: BIGGEST NOTE HERE: Delegate work to ndarray.__array_ufunc__! @@ -201,13 +176,12 @@ class Field(NDArrayOperatorsMixin): # TODO: for security, newaxis is not allowed (most other indexing works though), because scale would be unknown! # 1 input (has to be a Field, otherwise we wouldn't be here): if len(inputs) == 1: - self._log.debug('-inputs: 1 ------') + self._log.debug(f'__array_ufunc__ inputs: {len(inputs)}') field = inputs[0] scale_new = field.scale vector_new = field.vector # Preprocess axis keyword if it exists: axis = kwargs.get('axis', False) # Default must not be None, because None is a possible setting! - self._log.debug(f' axis: {axis}') full_reduction = False if axis is not False: ax_full = tuple(range(len(field.dim))) # All axes (minus a possible component axis for vector Fields)! @@ -220,59 +194,39 @@ class Field(NDArrayOperatorsMixin): if ax_full_wc[-1] in axis: # User explicitely wants component reduction (can only be true for vector): vector_new = False # Force scalar field! scale_new = tuple([s for i, s in enumerate(field.scale) if i not in axis]) # Drop axis from scale! - self._log.debug(f' kwargs: {kwargs}') - self._log.debug(f' full_reduction: {full_reduction}') inputs_arr = np.asarray(field) # Convert inputs that are Fields to ndarrays to avoid recursion! data_new = getattr(ufunc, method)(inputs_arr, out=outputs_arr, **kwargs) if full_reduction: # Premature return because the result is no longer a Field: return data_new # More than 1 input (at least one has to be a Field, otherwise we wouldn't be here): elif len(inputs) > 1: - self._log.debug(f'-inputs: {len(inputs)} ------') is_field = [isinstance(inp, Field) for inp in inputs] - self._log.debug(f'is_field: {is_field}') is_vector = [getattr(inp, 'vector', False) for inp in inputs] - self._log.debug(f'is_vector: {is_vector}') # Determine scale: if np.sum(is_field) > 1: # More than one input is a Field objects: scales = [inp.scale for i, inp in enumerate(inputs) if is_field[i]] # Only takes scales of Field obj.! - self._log.debug(f'scales: {scales}') scale_new = scales[0] err_msg = f'Scales of all Field objects must match! Given scales: {scales}!' assert all(scale == scale_new for scale in scales), err_msg else: # Only one input is a field, pick the scale of that one: scale_new = inputs[np.argmax(is_field)].scale # argmax returns the index of first True! - self._log.debug(f'scale_new: {scale_new}') # Determine vector: vector_new = True if np.any(is_vector) else False # Output is vector field if any input is a vector field! - self._log.debug(f'vector_new: {vector_new}') if np.sum(is_vector) > 1: # More than one input is a vector Field objects: ncomps = [inp.ncomp for i, inp in enumerate(inputs) if is_vector[i]] # Only takes ncomp of v.-Fields! - self._log.debug(f'ncomps: {ncomps}') err_msg = f'# of components of all Field objects must match! Given ncomps: {ncomps}!' assert all(ncomp == ncomps[0] for ncomp in ncomps), err_msg # Append new axis at the end of non vector objects to broadcast to components: if np.any(is_vector): inputs = list(inputs) - self._log.debug(f'len(input_arr): {len(inputs)}') for i, inp in enumerate(inputs): - self._log.debug(f'i: {i}') - self._log.debug(f'is_vector[i]: {is_vector[i]}') - self._log.debug(f'isinstance(inp, Number): {isinstance(inp, Number)}') if not is_vector[i] and not isinstance(inp, Number): # Numbers work for broadcasting anyway: - inp_arr = np.asarray(inp) # For broadcasting, try to cast as ndarray! - self._log.debug(f'inp_arr.shape: {inp_arr.shape}') - self._log.debug(f'inp.shape), len(scale_new): {len(np.asarray(inp).shape)}, {len(scale_new)}') if len(np.asarray(inp).shape) == len(scale_new): # No. of dimensions w/o comp., have to match! - self._log.debug(f'inputs[i]: {inputs[i]}') - inputs[i] = np.asarray(inputs[i])[..., np.newaxis] + inputs[i] = np.asarray(inputs[i])[..., np.newaxis] # Broadcasting, try to cast as ndarray! inputs = tuple(inputs) # Convert inputs that are Fields to ndarrays to avoid recursion and determine data_new: inputs_arr = tuple([np.asarray(inp) if isinstance(inp, Field) else inp for inp in inputs]) - self._log.debug(f'kwargs: {kwargs}') data_new = getattr(ufunc, method)(*inputs_arr, out=outputs_arr, **kwargs) - self._log.debug(f'data_new.shape: {data_new.shape}') - # TODO: Test clip (also ufunc?? think not...) seems to work, but what about others? # Return results: result = Field(data_new, scale_new, vector_new) return result @@ -373,15 +327,11 @@ class Field(NDArrayOperatorsMixin): The padded `Field` object. """ - print(f'pad_width: {pad_width}') if isinstance(pad_width, Number): # Paddding is the same for each dimension (make sure it is a tuple)! pad_width = (pad_width,) * len(self.dim) - print(f'pad_width: {pad_width}') pad_width = [(p, p) if isinstance(p, Number) else p for p in pad_width] - print(f'pad_width: {pad_width}') if self.vector: # Append zeros to padding, so component axis stays as is: pad_width = pad_width + [(0, 0)] - print(f'pad_width: {pad_width}') data_new = np.pad(self.data, pad_width, mode, **kwargs) return Field(data_new, self.scale, self.vector) @@ -411,16 +361,12 @@ class Field(NDArrayOperatorsMixin): assert all([n_i >= 0 for n_i in n]), 'All entries of n must be positive integers!' # Pad if necessary (use padded 'field' from here on), formula for clarity: (n - dim % n) % n pad_width = [(0, (n[i] - self.dim[i] % n[i]) % n[i]) for i in range(len(self.dim))] - print(f'bin: pad_width: {pad_width}') field = self.pad(pad_width, mode='edge') # Create new shape used for binning (mean over every second axis will be taken): bin_shape = list(np.ravel([(field.dim[i]//n[i], n[i]) for i in range(len(field.dim))])) - print(f'bin_shape: {bin_shape}') mean_axes = np.arange(1, 2*len(field.dim), 2) # every 2nd axis! - print(f'mean_axes: {mean_axes}') if self.vector: # Vector field: bin_shape += [field.ncomp] # Append component axis (they stay unchanged) - print(f'bin_shape: {bin_shape}') # Bin data and scale accordingly: data_new = field.data.reshape(bin_shape).mean(axis=tuple(mean_axes)) scale_new = tuple([field.scale[i] * n[i] for i in range(len(field.dim))]) @@ -573,6 +519,7 @@ class Field(NDArrayOperatorsMixin): if self.vector: # Vector fields need to scale components according to masked amplitude mask_vec = np.logical_and(mask, amp <= vmax) # Only vmax is important! data = amp / np.where(mask_vec, 1, amp) # TODO: needs testing! + # TODO: Test np.clip(field) (also ufunc?? think not...) seems to work, but what about others? else: # For scalar fields, just delegate to the numpy function: data = np.clip(self.data, vmin, vmax) return Field(data, self.scale, self.vector) diff --git a/src/empyre/fields/vectors.py b/src/empyre/fields/vectors.py index 32bcf9a..f6925fd 100644 --- a/src/empyre/fields/vectors.py +++ b/src/empyre/fields/vectors.py @@ -17,11 +17,6 @@ __all__ = ['create_vector_homog', 'create_vector_vortex', 'create_vector_skyrmio _log = logging.getLogger(__name__) -# TODO: TEST!!! -# TODO: ALL Docstrings! -# TODO: LOGGING! - - def create_vector_homog(dim, phi=0, theta=None, scale=1): """Field subclass implementing a homogeneous vector field with 3 components in 2 or 3 dimensions. @@ -72,6 +67,7 @@ def create_vector_vortex(dim, center=None, core_r=0, oop_r=None, axis=0, scale=1 only 2D. To invert chirality, multiply the resulting Field object by -1. # TODO: WRONG? """ + _log.debug('Calling create_vector_vortex') assert len(dim) in (2, 3), 'Vortex can only be build in 2 or 3 dimensions!' # Find indices of the vortex plane axes: idx_uv = [0, 1, 2] @@ -176,6 +172,7 @@ def create_vector_skyrmion(dim, center=None, phi_0=0, skyrm_d=None, wall_d=None, theta /= np.abs(theta).max() / np.pi return theta + _log.debug('Calling create_vector_skyrmion') assert len(dim) in (2, 3), 'Skyrmion can only be build in 2 or 3 dimensions!' # Find indices of the skyrmion plane axes: idx_uv = [0, 1, 2] @@ -202,11 +199,10 @@ def create_vector_skyrmion(dim, center=None, phi_0=0, skyrm_d=None, wall_d=None, rr = np.hypot(coords_uv[0], coords_uv[1]) phi = np.arctan2(coords_uv[0], coords_uv[1]) - phi_0 theta = _theta(rr) - w_comp = np.cos(theta) v_comp = np.sin(theta) * np.sin(phi) u_comp = np.sin(theta) * np.cos(phi) - + # Expansion to 3D if necessary and component shuffling: if len(dim) == 3: # Expand to 3D: w_comp = np.expand_dims(w_comp, axis=axis) v_comp = np.expand_dims(v_comp, axis=axis) @@ -263,25 +259,17 @@ def create_vector_singularity(dim, center=None, scale=1): coordinates (e.g. coordinate 1 lies between the first and the second pixel). """ # TODO: What does negating do here? Senke / Quelle (YES IT DOES!)? swell and sink? ISSUE! INVITE PEOPLE!!! + _log.debug('Calling create_vector_singularity') # Find default values: if center is None: center = tuple([d / 2 for d in dim]) assert len(dim) == len(center), f"Length of dim ({len(dim)}) and center ({len(center)}) don't match!" # Setup coordinates, shape is (c, z, y, x), if 3D, or (c, y, x), if 2D (c: components): coords = np.indices(dim) + 0.5 # 0.5 to get to pixel/voxel center! - print(f'coords.shape: {coords.shape}') - print(f'center: {center}') - print(f'dim: {dim}') center = np.asarray(center, dtype=float) bc_shape = (len(dim,),) + (1,)*len(dim) # Shape for broadcasting, (1,1,1,3) for 3D, (1,1,2) for 2D! - print(f'bc_shape: {bc_shape}') coords = coords - center.reshape(bc_shape) # Shift by center (append 1s for broadcasting)! rr = np.sqrt(np.sum([coords[i]**2 for i in range(len(dim))], axis=0)) - print(f'rr.max(): {rr.max()}') - print(f'coords.shape: {coords.shape}') - print(f'rr.shape: {rr.shape}') data = coords / (rr + 1E-30) # Normalise amplitude (keep direction), rr (z,y,x) is broadcasted to data (c,z,y,x)! - print(f'np.moveaxis(data, 0, -1).shape: {np.moveaxis(data, 0, -1).shape}') - print(f'data: {data}') data = data.T # (c,z,y,x) -> (x,y,z,c) return Field(data=data, scale=scale, vector=True) diff --git a/src/empyre/vis/colors.py b/src/empyre/vis/colors.py index efbc9ab..46b7d96 100644 --- a/src/empyre/vis/colors.py +++ b/src/empyre/vis/colors.py @@ -65,10 +65,6 @@ class Colormap3D(colors.Colormap, metaclass=abc.ABCMeta): self._log.debug('Calling rgb_from_vector') x, y, z = np.asarray(vector) R = np.sqrt(x ** 2 + y ** 2 + z ** 2) - print(f'----------RGB') - print(f'vector: {vector}') - print(f'R: {R}') - print(f'vector.shape: {vector.shape}') R_max = vmax if vmax is not None else R.max() + 1E-30 # FIRST color dimension: HUE (1D ring/angular direction) phi = np.asarray(np.arctan2(y, x)) @@ -502,8 +498,7 @@ def interpolate_color(fraction, start, end): cmaps = {'cubehelix_standard': ColormapCubehelix(), 'cubehelix_reverse': ColormapCubehelix(reverse=True), - 'cubehelix_circular': ColormapCubehelix(start=1, rot=1, - minLight=0.5, maxLight=0.5, sat=2), + 'cubehelix_circular': ColormapCubehelix(start=1, rot=1, minLight=0.5, maxLight=0.5, sat=2), 'perception_circular': ColormapPerception(), 'hls_circular': ColormapHLS(), 'classic_circular': ColormapClassic(), diff --git a/src/empyre/vis/plot2d.py b/src/empyre/vis/plot2d.py index d4984c9..cdf9830 100644 --- a/src/empyre/vis/plot2d.py +++ b/src/empyre/vis/plot2d.py @@ -127,7 +127,7 @@ def contour(field, axis=None, **kwargs): # Create coordinates (respecting the field scale, +0.5: pixel center!): vv, uu = (np.indices(squeezed_field.dim) + 0.5) * np.asarray(squeezed_field.scale)[:, None, None] # Set kwargs defaults without overriding possible user input: - kwargs.setdefault('levels', [0.5]) # TODO: Check all kwargs.setdefault if shiftable to style sheet (only universal) + kwargs.setdefault('levels', [0.5]) kwargs.setdefault('colors', 'k') kwargs.setdefault('linestyles', 'dotted') kwargs.setdefault('linewidths', 2) @@ -361,7 +361,7 @@ def quiver(field, axis=None, color_angles=False, cmap=None, n_bin='auto', bin_wi elif squeezed_indices[0] == 2: # Slice of the zy-plane with x squeezed: u_comp = y_comp v_comp = z_comp - # Set specific defaults for quiver kwargs: # TODO: Check if they can go into empyre style sheet: + # Set specific defaults for quiver kwargs: kwargs.setdefault('edgecolor', colors.cmaps['transparent_black'](amplitude).reshape(-1, 4)) kwargs.setdefault('scale', 1/np.max(squeezed_field.scale)) kwargs.setdefault('width', np.max(squeezed_field.scale)) diff --git a/src/empyre/vis/tools.py b/src/empyre/vis/tools.py index 350ac03..2d5b3a4 100644 --- a/src/empyre/vis/tools.py +++ b/src/empyre/vis/tools.py @@ -5,6 +5,7 @@ """This module provides helper functions to the vis module.""" +import logging from numbers import Number import numpy as np @@ -14,6 +15,7 @@ from ..fields.field import Field __all__ = ['new', 'savefig', 'calc_figsize'] +_log = logging.getLogger(__name__) def new(nrows=1, ncols=1, mode='image', figsize=None, textwidth=None, width_scale=1, aspect=None, **kwargs): @@ -64,6 +66,7 @@ def new(nrows=1, ncols=1, mode='image', figsize=None, textwidth=None, width_scal additional kwargs are passed to `~matplotlib.pyplot.subplots`. """ + _log.debug('Calling new') assert mode in ('image', 'plot'), "mode has to be 'image', or 'plot'!" if figsize is None and textwidth is not None: # Only then is all this necessary: if aspect is None: @@ -95,7 +98,9 @@ def savefig(fname, **kwargs): ----- Uses the 'empyre-save' stylesheet (installed together with EMPyRe to control the saving behaviour. Any kwargs are passed to :func:`~matplotlib.pyplot.savefig`. + """ + _log.debug('Calling savefig') with plt.style.context('empyre-save'): plt.savefig(fname, **kwargs) @@ -122,8 +127,10 @@ def calc_figsize(textwidth, width_scale=1, aspect=1): Notes ----- - Based on code from Florian Winkler. + Based on snippet from Florian Winkler. + """ + _log.debug('Calling calc_figsize') GOLDEN_RATIO = (1 + np.sqrt(5)) / 2 # Aesthetic ratio! INCHES_PER_POINT = 1.0 / 72.27 # Convert points to inch, LaTeX constant, apparently... textwidth_in = textwidth * INCHES_PER_POINT # Width of the text in inches -- GitLab