agilescientific / striplog

Lithology and stratigraphic logs for wells or outcrop.

Home Page:https://code.agilescientific.com/striplog

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Binary striplog

kwinkunks opened this issue · comments

Maybe a binary striplog is a special thing? Where the main component property is True or False... then you could allow operations like dilate and erode. (Can't really see how you could allow this on non-binary logs).

Typically dilate and erode functions (e.g., for binary images) require the user to specify a "structuring" or "selection" element. For a binary striplog I would think this will just be a single numeric value representing the height the depth/elevation window over which to min/max?

Also, should these methods enforce that the log is "clean", i.e. no gaps or overlaps?

Good questions. I had a go in this notebook > https://github.com/agile-geoscience/striplog/blob/master/tutorial/Striplog_with_a_binary_flag.ipynb

But yes, these require a strictly True/False value for the primary component... Not sure how it would handle gaps (or what one would want it to do with gaps).

I was chatting to Matteo Nicolli about 'greyscale' operations too, but I don't know much about those.

Anyway, as far as generalizing beds based on thickness goes, I quite like the result from a binary closing:

image

Where PAM is prune > anneal > merge (the current way to do this)

Depending on the implementation, greyscale operations might be an easy generalization. Binary erode/dilate/opening/closing are really just special cases (0 & 1 values only) of more general algorithms:

# single operations w/ bool
dilated[i] = any(original[neighborhood(i)])
eroded[i] = all(original[neighborhood(i)])

# single operations w/ numeric 
dilated[i] = max(original[neighborhood(i)]) 
eroded[i] = min(original[neighborhood(i)]) 

I didn't see that plot or an implementation in the notebook, but I'm curious how you did it.

Originally I was thinking this would be an operation on intervals (figure out how much the top/base should be shifted, or if the interval should be removed), but another option that would accommodate numeric values would be to convert to_log (at a suitably fine resolution), perform operations on the log array, and then convert back from_log (setting cutoffs between unique values in the log).

This is a little hacky so I won't PR it, but here's an example:

from scipy.ndimage import morphology

for iv in s:
    iv.data.update(iv.primary)

def dilate(s, field, height=0.75, step=0.01):
    
    blog, basis, _ = s.to_log(step=step, field='pay', return_meta=True)
    
    blog = morphology.binary_dilation(blog, structure=np.ones(int(height/step)))
    
    return Striplog.from_log(blog, cutoff=0.5, components=comps[::-1], basis=basis)
    

def erode(s, field, height=0.75, step=0.01):
    
    blog, basis, _ = s.to_log(step=step, field='pay', return_meta=True)
    
    blog = morphology.binary_erosion(blog, structure=np.ones(int(height/step)))
    
    return Striplog.from_log(blog, cutoff=0.5, components=comps[::-1], basis=basis)


dilated = dilate(s, 'pay')
for iv in dilated:
    iv.data.update(iv.primary)

eroded = erode(s, 'pay')
for iv in eroded:
    iv.data.update(iv.primary)

opened = dilate(eroded, 'pay')

closed = erode(dilated, 'pay')

I've double checked that the blog array comes out right, but for some reason it seems like the components get swapped and I have to use comps[::-1] list in from_log. Don't know why that's happening.

In any case, I made the structuring element a bit large for emphasis, but here's the plot:

binary_morphology_demo

Did some more hacky stuff (to get around non-numeric components, etc.) and came up with this for the grey version (with three components mapping to log values of [1,2,3]):

grey_morphology_demo

@rgmyr: brilliant!!

@rgmyr Ah, sorry, it's only on the develop branch and I linked to master... Check it out here >> https://github.com/agile-geoscience/striplog/blob/develop/tutorial/Striplog_with_a_binary_flag.ipynb

Scroll down to Dilate and erode and note that you'd need to be on the bleeding edge striplog for this to work.

I have a bit of nonsense in there to try to make sure we have something that can be interpreted as a binary striplog, but at its heart this is doing the same thing you're doing. I put all the binary ops in one function, since they all basically work in the same way.

I started a generalize function to perform generalization in an even more abstract way, but as you can see I abandoned it... I was thinking it would handle binary and non-binary logs, and give the user a really high-level way to do these things.

In this example (below) I computed net:gross as I think the user might want to know how the generalization is potentially changing some statistics... it would be interesting to explore ways to do the generalization in a way that preserves the statistics of the striplog!

image

Hmm... I'm not sure about analogous operations with e.g., a constant sum constraint. I might post something on stack exchange and see if anyone has any neat ideas -- maybe there's something clever that can be done with a variable size structuring element, but I'm not really sure how to formulate that as a solvable optimization problem with a unique solution.

The only hacky thing I can think of is an iterative approach where we take the output of opening or closing and just erode to lower n:g and dilate to raise it, until we can't get any closer to the original n:g. I'd actually be interested to see what happens with that, might give it a shot in the near future.

FWIW, I just pushed v0.8.1 with binary morphology. I wanted to avoid depending on scipy (not sure why) so hacked my own binary operators... as a result they can't do greyscale morphology or 'weird' (non-boxcar) structuring elements.

If anyone feels like implementing the greyscale operations, especially if it's not too hard w/o scipy, then that would be lovely. Or I guess we can add scipy as a dependency and get it almost for 'free' (or however @rgmyr did it above).

We do now depend on scipy, so we should implement the gray-level version of this algo.