diff --git a/setup.py b/setup.py index 4591226..54e85d1 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ setup( name="xpdtools", - version='0.6.0', + version='0.8.0', packages=find_packages(), description="data processing module", zip_safe=False, diff --git a/xpdtools/__init__.py b/xpdtools/__init__.py index ef7eb44..906d362 100644 --- a/xpdtools/__init__.py +++ b/xpdtools/__init__.py @@ -1 +1 @@ -__version__ = '0.6.0' +__version__ = "0.6.0" diff --git a/xpdtools/calib4.py b/xpdtools/calib4.py new file mode 100644 index 0000000..5a0d18d --- /dev/null +++ b/xpdtools/calib4.py @@ -0,0 +1,200 @@ +import numpy as np + +import tifffile as tf + + +###move this to findringcenter function +def clickpoints(slyce, thresh): + """ + Takes a slyce and threshold value and adds to the "pixels" attribute of Slyce class the indices of 1 or 2 points (in the 1-D slyce array) that correspond to points on the inner ring. + + Parameters: + slyce: 1-D numpy array of image data corresponding to a vertical or horizontal slice through the tiff image + thresh: np.float64 corresponding to a percentage of the maximum value in image numpy array + + """ + p1 = np.argmax(slyce['data']) + p2 = np.argmax(slyce['data'][:p1] + slyce['data'][p1 + 1 :]) + + # if statement to determine if max points are large enough to actually be on center ring. If so, the pixel attribute of Slyce is modified to include these indices + if slyce['data'][p2] >= thresh: + slyce['pixels'].append(p1) + slyce['pixels'].append(p2) + + elif slyce['data'][p1] >= thresh: + + slyce['pixels'].append(p1) + + +def finddistance(a, b): + """ + Finds distance between two points. + + Parameters: + a: numpy array, list, or tuple containing a coordinates, where a[0] contains a row index and a[1] contains col index + b: numpy array, list, or tuple containing a coordinates, where a[0] contains a row index and a[1] contains col index + + Returns: + np.float64 corresponding to distance between two points + """ + + dif1 = a[1] - b[1] + dif2 = a[0] - b[0] + d_sq = np.abs(dif1 ** 2 + dif2 ** 2) + + return np.sqrt(d_sq) + + +def findringcenter(image, thres=0.2, d=20): + """ + Takes a numpy array corresponding to a tiff image and finds the pixel index of the center of the inner ring. + + Parameters: + image: numpy array of tiff image + d: integer corresponding distance around image center (distinct from inner ring center) + Returns: + Pixel indices of an estimate of center point of inner ring. + + + """ + thresh = thres * np.max(image) + + s = image.shape + # number of rows divided by 2 + r = s[0] // 2 + # number of columns divided by 2 + c = s[1] // 2 + + # take 10 slices within a range of 'd' away from the cetner of the image and puts them into a list + + slyces=[] + for dd in [0, -d, d, -d/2, d/2]: + for k in ['v', 'h']: + if k=='v': + slyces.append({'direction': 'v','index': int(c+dd), 'data': list(image[:, int(c+dd)]), 'pixels':[]}) + else: + slyces.append({'direction': 'h', 'index': int(r+dd), 'data': list(image[int(r+dd),:]), 'pixels':[]}) + + + + + + + points2click = [] + for slyce in slyces: + clickpoints(slyce,thresh) + + if slyce['direction'] == "v": + for pixel in slyce['pixels']: + points2click.append([pixel, slyce['index']]) + if slyce['direction'] == "h": + for pixel in slyce['pixels']: + points2click.append([slyce['index'], pixel]) + + # coords keeps track of the coordinates that are tested to find the center of the inner ring + coords = [] + # spread keeps track of the range of point distances from the tested point to the points that have been identified on the center ring + spread = [] + + # the center of the ring is determined by testing a range of points around the center of the image and calculating the distances + # between that point and the points that have been identified on the inner ring through the clickpoints() function + # the point that has the smallest range of distances is identified as the center + for row in range(int(r - 3 * d), int(r + 3 * d)): + for col in range(int(c - 3 * d), int(c + 3 * d)): + pointdist = [] + for point in points2click: + pointdist.append(finddistance([row, col], point)) + spread.append(max(pointdist) - min(pointdist)) + coords.append([row, col]) + + center = coords[spread.index(min(spread))] + + return center + + +def zerocross(lines): + """ + Takes in 1-D numpy array and determines array indices corresponding to points where an adjacent value has a different sign and where the difference between the two values before and after zero-crossing exceeds a certain threshold + + Parameters: + lines: 1-D numpy array + Returns: + z: list of indices in lines that correspond to both a change in sign of adjacent values and significant difference in these values + + """ + z = [] + for i in range(1, len(lines) - 1): + if (np.sign(lines[i]) == -1) and (np.sign(lines[i - 1]) in [0, 1]): + if np.abs(lines[i] - lines[i - 1]) > np.max(lines) / 80.0: + z.append(i) + return z + + +def findrings(image): + + """ + Takes a numpy array representing a tiff image and returns the pixel indices of the center point and points on rings 0,1,2,5 + *Function only works when rings are relatively centered on image* + Parameters: + image: numpy array of tiff image + Returns: + pixel index of point on center ring and pixel indices of points on rings 0,1,2,5 + + """ + + if not isinstance(image, np.ndarray): + raise RuntimeError("input type must be ndarray") + + center_pt = findringcenter(image) + + values=[] + for i in [-2,-1,0,1,2]: + values.append(list(image[center_pt[0]+i, :])) + + value_cs = np.zeros(np.shape(values[0])) + + + # create list of median values in horizontal center slyce to avoid hot pixel values + for i, blank in enumerate(value_cs): + value_cs[i] = np.median(np.array([values[0][i], values[1][i], values[2][i], values[3][i], values[4][i]])) + + + # take half of center slyce from the center of the inner ring out + halfcs = list(value_cs[(center_pt[1]) :]) + + # find derivative of values on the slyce + dx = 1 + deriv = list(np.gradient(halfcs, dx)) + + rings = zerocross(deriv) + clickpts = [] + firstpoint = [center_pt[0], rings[0] + center_pt[1]] + + # tests a series of conditions to determine if there is a peak at a certain index in 1D array + # tests to make sure that peak is real and not noise/small fluctuation + if ( + (halfcs[rings[0]] > 0.7 * np.max(halfcs)) + or (halfcs[rings[0] - 1] > 0.7 * np.max(halfcs)) + or (halfcs[rings[0] + 1] > 0.7 * np.max(halfcs)) + ): + clickpts.append(rings[0]) + clickpts.append(rings[1]) + clickpts.append(rings[2]) + clickpts.append(rings[5]) + else: + clickpts.append(rings[1]) + clickpts.append(rings[2]) + clickpts.append(rings[3]) + clickpts.append(rings[6]) + + # find indices of points that shoudl be "clicked" in tiff image array + points_image = [] + for clickpt in clickpts: + points_image.append([center_pt[0], clickpt + center_pt[1]]) + + print(points_image) + print(center_pt) + + return points_image, center_pt + + diff --git a/xpdtools/tests/test_pipelines.py b/xpdtools/tests/test_pipelines.py index 9cc7e68..d07b5d4 100644 --- a/xpdtools/tests/test_pipelines.py +++ b/xpdtools/tests/test_pipelines.py @@ -8,7 +8,8 @@ max_intensity_mean, max_gr_mean, pca_pipeline, - amorphsivity_pipeline) + amorphsivity_pipeline, +) from xpdtools.pipelines.raw_pipeline import ( pipeline_order, namespace as g_namespace, @@ -199,7 +200,7 @@ def test_pca_pipeline(): def test_amorphous_pipeline(): pdf = Stream() ns = amorphsivity_pipeline(pdf) - L = ns['amorphsivity'].sink_to_list() + L = ns["amorphsivity"].sink_to_list() a = np.ones(10) pdf.emit(a) assert L[0] == np.sum(a[6:]) diff --git a/xpdtools/tests/test_ringfinding.py b/xpdtools/tests/test_ringfinding.py new file mode 100644 index 0000000..912a8c2 --- /dev/null +++ b/xpdtools/tests/test_ringfinding.py @@ -0,0 +1,54 @@ +from xpdtools.calib4 import findrings +import numpy as np + +import tifffile as tf + +import pyFAI + +from pkg_resources import resource_filename as rs_fn + +import os +import pytest + + +DATA_DIR=rs_fn("xpdtools", "data/") + +filename=["Ni_onTape_forSlimes_andWahsers_20180812-234250_fe70b9_0001.tiff", +'Ni_calib_20180811-191034_63e554_0001.tiff', 'Ni_pin_20181101-075909_973de2_0001.tiff', +'Ni_calib_20180920-230956_0eedc4_0001.tiff','Ni_calib_20180923-133223_c2a848_0001.tiff', +'Ni_20180922-001850_2a1c3b_0001.tiff', 'Ni_cryostream_bracket_20190402-220917_229125_0001_dark_corrected_img.tiff', +'sub_20170802-212828_Ni_LongSoham_start_ct_30_3bda69_0001.tiff'] + + +@pytest.mark.parametrize("filename,poni", [(filename[0], os.path.splitext(filename[0])[0]+'.edf'), + (filename[1], os.path.splitext(filename[1])[0]+'.edf'), + (filename[2], os.path.splitext(filename[2])[0]+'.edf'), + (filename[3], os.path.splitext(filename[3])[0]+'.edf'), + (filename[4], os.path.splitext(filename[4])[0]+'.edf'), + (filename[5], os.path.splitext(filename[5])[0]+'.edf'), + (filename[6], os.path.splitext(filename[6])[0]+'.edf'), + (filename[7], os.path.splitext(filename[7])[0]+'.edf') ]) + + + +def test_ringfinding(filename, poni): + impath = os.path.join(DATA_DIR, filename) + imarray = tf.imread(impath) + pointsimage, center_pt = findrings(imarray) + d = pyFAI.load(poni) + centerx = d.getFit2D()["centerX"] + centery = d.getFit2D()["centerY"] + assert ( + abs(center_pt[1] - centerx) <= 8 and abs(center_pt[0] - centery) <= 8 + ) + + +def test_2Darray(): + with pytest.raises(IndexError): + findrings(np.random.rand(2048)) + + +@pytest.mark.parametrize("wrong_input", [[1, 2, 3], 3, 2.7, (2, 3), "banana"]) +def test_inputtype(wrong_input): + with pytest.raises(RuntimeError): + findrings(wrong_input) diff --git a/xpdtools/vis_calib.py b/xpdtools/vis_calib.py new file mode 100644 index 0000000..ea8990d --- /dev/null +++ b/xpdtools/vis_calib.py @@ -0,0 +1,45 @@ +from xpdtools.calib4 import findrings +import numpy as np +import tifffile as tf +from pkg_resources import resource_filename as rs_fn +import os +import matplotlib as mpl +mpl.use("TkAgg") +import matplotlib.pyplot as plt + +DATA_DIR=rs_fn("xpdtools", "data/") +filename=["Ni_onTape_forSlimes_andWahsers_20180812-234250_fe70b9_0001.tiff", +'Ni_calib_20180811-191034_63e554_0001.tiff', 'Ni_pin_20181101-075909_973de2_0001.tiff', +'Ni_calib_20180920-230956_0eedc4_0001.tiff','Ni_calib_20180923-133223_c2a848_0001.tiff', +'Ni_20180922-001850_2a1c3b_0001.tiff', 'Ni_cryostream_bracket_20190402-220917_229125_0001_dark_corrected_img.tiff', +'sub_20170802-212828_Ni_LongSoham_start_ct_30_3bda69_0001.tiff'] +impath = os.path.join(DATA_DIR, filename[0]) +imarray = tf.imread(impath) +points_image, center_pt=findrings(imarray) + +new_img = np.empty((imarray.shape[0], imarray.shape[1], 4)) +max_img = np.amax(imarray) +min_img = np.amin(imarray) +for i, row in enumerate(new_img): + for j, elem in enumerate(row): + old_val = imarray[i][j] + scaled = (old_val - min_img) / (max_img - min_img) + new_img[i][j] = np.array( + [1.0 - scaled, 1.0 - scaled, 1.0 - scaled, 1.0] + ) + +for point_image in points_image: + for i in range(-2, 2): + for j in range(-2, 2): + new_img[point_image[0] + i][point_image[1] + j] = np.array( + [1.0, 0.0, 0.0, 1.0] + ) + +for i in range(-2, 2): + for j in range(-2, 2): + new_img[center_pt[0] + i][center_pt[1] + j] = np.array( + [1.0, 0.0, 0.0, 1.0] + ) + +plt.imshow(new_img) +plt.show()