def sfs2shtools(self, lighting):
        '''
            convert sfs SH to shtools
            --we use shtools to rotate the coordinate:
            we use shtools to rotate the object:

            we need to use x convention, 
            we use shtools to rotate the coordinate:
            we need to use x convention, 
            alpha_x = pi/2 (clock-wise rotate along z axis by pi/2)
            beta_x = -pi/2 (contour clock-wise rotate along new x by pi/2)
            gamma_x = 0
            then y convention is:
            alpha_y = alpha_x - pi/2 = 0
            beta_y = beta_x = -pi/2
            gamma_y = gamma_x + pi/2 = pi/2
            reference: https://shtools.oca.eu/shtools/pyshrotaterealcoef.html
        '''
        new_lighting = np.zeros(lighting.shape)
        n = lighting.shape[0]
        for i in range(n):
            shMatrix = shtools_sh2matrix(lighting[i,:], self.SH_DEGREE)
            # rotate coordinate
            shMatrix = SHRotateRealCoef(shMatrix, np.array([0, -np.pi/2, np.pi/2]), self.dj)
            # rotate object
            #shMatrix = SHRotateRealCoef(shMatrix, np.array([np.pi/2, -np.pi/2, 0]), self.dj)
            new_lighting[i,:] = shtools_matrix2vec(shMatrix)
        return new_lighting
def process_SH(sh):
    '''
        preprocess the lighting:
        normalize the lighting so the DC component will be within 
        range 0.7 to 0.9
    '''
    
    # if sh is a 3x9 vector, conver it to gray
    if sh.shape[0] == 3:
        # RGB2Gray
        coeffs_matrix_R = shtools_sh2matrix(sh[0,:], SH_DEGREE)
        tmp_envMap_R = MakeGridDH(coeffs_matrix_R, lmax=255, lmax_calc=SH_DEGREE, sampling=2, norm=4)

        coeffs_matrix_G = shtools_sh2matrix(sh[1,:], SH_DEGREE)
        tmp_envMap_G = MakeGridDH(coeffs_matrix_G, lmax=255, lmax_calc=SH_DEGREE, sampling=2, norm=4)

        coeffs_matrix_B = shtools_sh2matrix(sh[2,:], SH_DEGREE)
        tmp_envMap_B = MakeGridDH(coeffs_matrix_B, lmax=255, lmax_calc=SH_DEGREE, sampling=2, norm=4)

        tmp_envMap = 0.299*tmp_envMap_R + 0.587*tmp_envMap_G + 0.114*tmp_envMap_B  # from opencv document
        tmp_SH = pyshtools.expand.SHExpandDH(tmp_envMap, sampling=2, lmax_calc=2, norm=4)
        sh = shtools_matrix2vec(tmp_SH)
        
    
    coeffs_matrix = shtools_sh2matrix(sh, SH_DEGREE)
    # random rotate
    dj = djpi2(SH_DEGREE)
    sh_angle = np.random.rand()*2*np.pi
    coeffs_matrix = SHRotateRealCoef(coeffs_matrix, np.array([sh_angle, 0, 0]), dj)

    factor = (np.random.rand()*0.2 + 0.7)/sh[0]
    tmp_envMap = MakeGridDH(coeffs_matrix, lmax=255, lmax_calc=SH_DEGREE, sampling=2, norm=4)
    tmp_envMap = tmp_envMap/factor
    tmp_SH = pyshtools.expand.SHExpandDH(tmp_envMap, sampling=2, lmax_calc=2, norm=4)
    
    sh = shtools_matrix2vec(tmp_SH)
    sh = sh[...,None]

    if sh[0] < 0.5 or sh[0] > 1.0:
        # normalize it again if it is too dark or bright
        factor = (np.random.rand()*0.2 + 0.7)/sh[0]
        coeffs_matrix = shtools_sh2matrix(sh, SH_DEGREE)
        tmp_envMap = MakeGridDH(coeffs_matrix, lmax=255, lmax_calc=SH_DEGREE, sampling=2, norm=4)
        tmp_envMap = tmp_envMap*factor
        tmp_SH = pyshtools.expand.SHExpandDH(tmp_envMap, sampling=2, lmax_calc=2, norm=4)
    
        sh = shtools_matrix2vec(tmp_SH)
        sh = sh[...,None]
    return sh, factor, sh_angle