import numpy as np
import matplotlib.pyplot as plt
import argparse

def fractal_dimension(array, max_box_size=None, min_box_size=1, n_samples=20, n_offsets=0, plot=False):
    """Calculates the fractal dimension of a 3D numpy array.

    Args:
        array (np.ndarray): The array to calculate the fractal dimension of.
        max_box_size (int): The largest box size, given as the power of 2 so that
                            2**max_box_size gives the sidelength of the largest box.
        min_box_size (int): The smallest box size, given as the power of 2 so that
                            2**min_box_size gives the sidelength of the smallest box.
                            Default value 1.
        n_samples (int): number of scales to measure over.
        n_offsets (int): number of offsets to search over to find the smallest set N(s) to
                       cover  all voxels>0.
        plot (bool): set to true to see the analytical plot of a calculation.


    """
    # determine the scales to measure on
    if max_box_size == None:
        # default max size is the largest power of 2 that fits in the smallest dimension of the array:
        max_box_size = int(np.floor(np.log2(np.min(array.shape))))
    scales = np.floor(np.logspace(max_box_size, min_box_size, num=n_samples, base=2))
    scales = np.unique(scales)  # remove duplicates that could occur as a result of the floor

    # get the locations of all non-zero pixels
    locs = np.where(array > 0)
    voxels = np.array([(x, y, z) for x, y, z in zip(*locs)])

    # count the minimum amount of boxes touched
    Ns = []
    # loop over all scales
    for scale in scales:
        touched = []
        if n_offsets == 0:
            offsets = [0]
        else:
            offsets = np.linspace(0, scale, n_offsets)
        # search over all offsets
        for offset in offsets:
            bin_edges = [np.arange(0, i, scale) for i in array.shape]
            bin_edges = [np.hstack([0 - offset, x + offset]) for x in bin_edges]
            H1, e = np.histogramdd(voxels, bins=bin_edges)
            touched.append(np.sum(H1 > 0))
        Ns.append(touched)
    Ns = np.array(Ns)

    # From all sets N found, keep the smallest one at each scale
    Ns = Ns.min(axis=1)

    # Only keep scales at which Ns changed
    scales = np.array([np.min(scales[Ns == x]) for x in np.unique(Ns)])

    Ns = np.unique(Ns)
    Ns = Ns[Ns > 0]
    scales = scales[:len(Ns)]
    # perform fit
    coeffs = np.polyfit(np.log(1 / scales), np.log(Ns), 1)

    # make plot
    if plot:
        fig, ax = plt.subplots(figsize=(8, 6))
        ax.scatter(np.log(1 / scales), np.log(np.unique(Ns)), c="teal", label="Measured ratios")
        ax.set_ylabel("$\log N(\epsilon)$")
        ax.set_xlabel("$\log 1/ \epsilon$")
        fitted_y_vals = np.polyval(coeffs, np.log(1 / scales))
        ax.plot(np.log(1 / scales), fitted_y_vals, "k--", label=f"Fit: {np.round(coeffs[0],3)}X+{coeffs[1]}")
        ax.legend();
    return (coeffs[0])


import os
import SimpleITK as sitk
def main():
    parser = argparse.ArgumentParser()

    parser.add_argument("-o", "--outputfile", help="output file's name", type=str,
                        default='../HFD3D.txt')
    parser.add_argument("-m", "--inputmask", help="input mask root", type=str,
                        default='/mnt/data9/cam/mask/')
    parser.add_argument("-r", "--inputimgs", help="input data root", type=str,
                        default='/mnt/data9/cam/pre/')
    args = parser.parse_args()
    filename = args.inputmask
    raw=args.inputimgs
    f=open(args.outputfile,'w')
    f.writelines('name' + ',' + '3d fractals' + '\n')
    for item in os.listdir(raw):
        data=sitk.ReadImage(os.path.join(filename,item))
        raw_img=sitk.ReadImage(os.path.join(raw,item))
        img=sitk.GetArrayFromImage(raw_img)
        mask = sitk.GetArrayFromImage(data)
        img=img*mask
        arr = np.array(img, dtype=np.uint8)
        map=np.zeros((arr.shape[0],arr.shape[1],256))
        for i in range(arr.shape[0]):
            for j in range(arr.shape[1]):
                map[i,j,arr[i,j]]=1
        fd = fractal_dimension(map, n_samples=20, n_offsets=10, plot=False)
        print(item,fd)
        f.writelines(item+','+str(fd)+'\n')
    f.close()
main()

