How To Use HAPPy:
Import packages
Hint
If running in the IPython console, consider running %matplotlib to enable
interactive plots. If running in the Jupyter Notebook, use %matplotlib
inline.
>>> # First import matplotlib (for plotting) and skan
>>> from matplotlib import pyplot as plt
>>> from skan import draw
>>> import numpy as np
>>> from skimage import exposure
>>>
>>> # Then import the radial hydride packagess
>>> from HAPPY import import_image
>>> from HAPPY import cropping_functions as crop
>>> from HAPPY import plot_functions as plt_f
>>> from HAPPY import radial_hydride_fraction as RHF
>>> from HAPPY import branching as branch
>>> from HAPPY import crack_path as cp
>>> from HAPPY import image_processing
Importing Image
First, import the image using the
import_imagecommand. Transpose the image if necessary using thetransposeargument to make the radial direction vertical.The
cropImagefunction applies a rectangular crop to the image to remove scale bars, or if you have a specific rectangular region you want to look at.Input Scale Bar Value in Scale_Bar_Micron_Value and Pixels_In_Scale_Bar, the scale bar will then be calculated.
>>> # Load image
>>> original_image = import_image.image(image_path ='data/520-5b.png', transpose = False)
>>> cropped_image = crop.cropImage(original_image, crop_bottom=50, crop_top=0, crop_left=0, crop_right=0)
>>> crop1 = cropped_image
>>> # Input the value of the scale bar in microns
>>> Scale_Bar_Micron_Value = 100
>>> #Input how many pixels are in your scale bar
>>> Pixels_In_Scale_Bar = 165.5
>>> Scale_Bar_Value_In_Meters = Scale_Bar_Micron_Value*(1e-6)
>>> scale = Scale_Bar_Value_In_Meters/Pixels_In_Scale_Bar
>>> scale_um = scale*1e6
>>> location = 'lower right'
>>>
>>>
>>> # Plot image
>>> plt_f.plot(img=cropped_image, title='Loaded image',scale=scale, location=location)
Additional Cropping
The second crop function is cropping_tube, which should be used if
the micrograph is curved and removes black pats of the image which are
not the tube. A crop_param of around 0.1-0.2 is reccomended.
>>> # Crop tube
>>> cropped_image, crop_threshold = crop.cropping_tube(cropped_image,
... crop_param = 0.2, size_param = 1000, dilation_param = 10)
...
>>> # Plot comparison
>>> plt_f.plot_comparison(crop1, 'Original image crop', cropped_image, 'Tube crop',scale=scale,
... location=location)
Image Processing
Grain contast or uneven lighting can be minimised through the
application of a gaussian blur in the minimize_grain_contrast
function. A value of 10 seems to work for most cases.
>>> # Remove grain contrast
>>> removed_grains = image_processing.minimize_grain_contrast(cropped_image, sigma = 10)
>>>
>>> # Plot image
>>> plt_f.plot(img=cropped_image, title='Minimised grain contrast', scale=scale, location=location)
>>>
>>> # Plot the histogram for removed grains so that we can see where we should threshold
>>> histogram = plt_f.plot_hist(removed_grains)
>>>
>>> # Print an approximate threshold value which should work well
>>> print('Approximate threshold: {0:.3f}'.format(
... 2*np.nanmedian(removed_grains)-np.nanpercentile(removed_grains, 90)))
Thresholding
After this, the image is thresholded using the simple_threshold
function. The threshold value should be set using the threshold
argument. Small features, less than a given size in microns
small_obj can optionally be removed. Note it is important not too
over threshold the image, guidance of a value to threshold is shown
above and can be determined by investigating the histograms plotted
above.
>>> # Apply threshold
>>> thres = image_processing.simple_threshold(removed_grains,scale_um, crop_threshold,
... threshold = 0.98, small_obj = 40)
...
>>> # Plot the thresholded image and compare it to the original image:
>>> plt_f.plot_comparison(cropped_image, 'Original Image', thres,'Thresholded Image', scale=scale,location=location)
The first step is to perform the hough line transform hough_rad
there are a few input parameters that should be considered: -
num_peaks: should be changed dependent on the type of micrograph, if
your hydrides are straight and not very interconnected a small value of
around 2 is good, if in one box, there are many branches that need to be
picked up, this value should be increased accordingly to a value of 5 or
more. - min_dist, min_angle and val are pre-set and seem to
work for most cases.
>>> # Apply Hough transform
>>> angle_list,len_list = RHF.hough_rad(thres, num_peaks=2, scale=scale, location=location)
>>> #Non weighted radial hydride fraction
>>> radial, circumferential = RHF.RHF_no_weighting_factor(angle_list, len_list)
>>>
>>> print('The non-weighted RHF is {0:.4f}'.format(radial))
>>> #Weighted Radial Hydride Fraction
>>> fraction = RHF.weighted_RHF_calculation(angle_list, len_list)
>>>
>>> print('The weighted RHF is: {0:.4f}'.format(fraction))
Other Methods for Radial Hydride Fraction Calculation
Here all four different RHF calculation methods are shown in the graph
>>> #chu radial hydride calculation
>>> deg_angle_list = np.rad2deg(angle_list)
>>>
>>> radial_list_chu=[]
>>> circum_list_chu = []
>>>
>>> for k in deg_angle_list:
... if (k>0 and k<40) or (k>-40 and k<0) :
... radial_list_chu.append(len_list)
... elif (k>50 and k<90) or (k>-90 and k<-50):
... circum_list_chu.append(len_list)
...
...
>>> rad_hyd_chu = np.sum(radial_list_chu)
>>> cir_hyd_chu = np.sum(circum_list_chu)
>>>
>>>
>>> RHFChu = rad_hyd_chu/(rad_hyd_chu+cir_hyd_chu)
>>>
>>>
>>> #RHF 40 deg
>>> radial_list_40=[]
>>> circum_list_40 = []
>>>
>>> for k in deg_angle_list:
... if (k>0 and k<40) or (k>-40 and k<0) :
... radial_list_40.append(len_list)
... elif (k>=40 and k<90) or (k>-90 and k<=-40):
... circum_list_40.append(len_list)
...
...
>>> rad_hyd_40 = np.sum(radial_list_40)
>>> cir_hyd_40 = np.sum(circum_list_40)
>>>
>>>
>>> RHF40 = rad_hyd_40/(rad_hyd_40+cir_hyd_40)
>>>
>>> import pandas as pd
>>> # intialise data of lists.
>>> data = {"RHF": [RHF40,radial,fraction,RHFChu]
... }
...
>>> # Create DataFrame
>>> df = pd.DataFrame(data,index=["40 Degrees", "45 Degrees", "Weighted", "Chu"])
>>> display(df)
>>>
>>> #d = {"one": [1.0, 2.0, 3.0, 4.0], "two": [4.0, 3.0, 2.0, 1.0]}
>>>
Mean Hydride Length
Code for determining the MHL
>>> from scipy import ndimage
>>>
>>> hydride_len = []
>>> label, num_features = ndimage.label(thres > 0.1)
>>> slices = ndimage.find_objects(label)
>>> for feature in np.arange(num_features):
... hydride_len.append(scale_um*label[slices[feature]].shape[1])
...
>>> #print(hydride_len)
>>> print(np.mean(hydride_len))
Branch Length Fraction
Here we want to determine the extent of branching within the microstrucutre, this is done in two ways: - In image form where the branches are coloured differently to the main hydride - BLF the length fraction of branches with respect to the toatal length of all hydrides in the microstrucutre
>>> # Calculate the branch length fraction
>>> skel,is_main,BLF = branch.branch_classification(thres);
>>>
>>>
>>> # Plot branching image
>>> fig, ax = plt.subplots(figsize=(10,6))
>>> ax = draw.overlay_skeleton_2d_class(
... skel,
... skeleton_color_source=lambda s: is_main,
... skeleton_colormap='spring',
... axes=ax
... )
...
>>> plt.axis('off')
>>> plt.title('Branched hydrides')
>>> #plt_f.addScaleBar(ax[0], scale=scale, location=location)
>>> plt_f.addArrows(ax[0])
>>>
>>> print('The BLF is: {0:.4f}'.format(BLF))
Crack Path
Here we want to determine potential crack paths through the
microstrucutre, we input the thresholded image thres. After running
once, the area around that path (radius set with kernel_size) is
discounted, then the process is repeated num_runs times. Here the
distance_weight makes moving in the circumferential direction more
costly, note when comparing different micrographs, ensure that this
parameter it is kept constant. We reccomend a weighting of 1.5 and a
kernel size of 20.
>>> # Determing potential crack paths
>>> edist, path_list, cost_list = cp.det_crack_path(thres, crop_threshold, num_runs=5, kernel_size=20,distance_weight=1.5)
>>> # Plot possible crack paths
>>> fig, ax = plt.subplots(figsize=(10,6))
>>> list_costs = []
>>>
>>> for n, (p, c) in enumerate(zip(path_list, cost_list)):
...
... im = ax.imshow(thres, cmap='gray')
...
... #if n==0:
... # plt.colorbar(im,fraction=0.03, pad=0.01)
... ax.scatter(p[:,1], p[:,0], s=10, alpha=0.1)
... ax.text(p[-1][1], p[-1][0], s=str(n), c='w', bbox=dict(facecolor='black', edgecolor='black'))
... plt.axis('off')
... print('Run #{0}\tCost = {1:.2f}'.format(n,c))
... list_costs.append(c)
...
>>> plt_f.addScaleBar(ax, scale=scale, location=location)
>>> plt_f.addArrows(ax)
>>> # Histograms for plotting the costs of each path
>>> plt.hist(list_costs, bins=5, cumulative = True, color = "cornflowerblue", ec="cornflowerblue", label = "Cumulative Distribution Function")
>>> plt.hist(list_costs, bins=5, color = "lightpink", ec="lightpink", label = "Normal Histogram")
>>> plt.legend()
>>> plt.xlabel('Cost', fontsize="12")
>>> plt.ylabel('Frequency',fontsize="12")
>>> plt.title('Paths of Lowest Cost', fontweight="bold", fontsize="15")
>>> plt.show()
You can chose to skeletonize the image if you want, not reccomended unless there are too many hydrides to be able to distinguish between them.
>>> from skimage.morphology import skeletonize
>>> skeletonised = skeletonize(thres)
>>> plt.imshow(skeletonised,cmap='gray')
>>> plt.axis('off')
>>>
>>>
>>>