def rectify_pair(im1, im2, rpc1, rpc2, x, y, w, h, out1, out2, A=None, m=None, flag='rpc'): """ Rectify a ROI in a pair of Pleiades images. Args: im1, im2: paths to the two Pleiades images (usually jp2 or tif) rpc1, rpc2: paths to the two xml files containing RPC data x, y, w, h: four integers defining the rectangular ROI in the first image. (x, y) is the top-left corner, and (w, h) are the dimensions of the rectangle. out1, out2: paths to the output crops A (optional): 3x3 numpy array containing the pointing error correction for im2. This matrix is usually estimated with the pointing_accuracy module. m (optional): Nx4 numpy array containing a list of sift matches, in the full image coordinates frame flag (default: 'rpc'): option to decide wether to use rpc of sift matches for the fundamental matrix estimation. This function uses the parameter subsampling_factor from the config module. If the factor z > 1 then the output images will be subsampled by a factor z. The output matrices H1, H2, and the ranges are also updated accordingly: Hi = Z*Hi with Z = diag(1/z,1/z,1) and disp_min = disp_min/z (resp _max) Returns: H1, H2: Two 3x3 matrices representing the rectifying homographies that have been applied to the two (big) images. disp_min, disp_max: horizontal disparity range """ # read RPC data rpc1 = rpc_model.RPCModel(rpc1) rpc2 = rpc_model.RPCModel(rpc2) # compute rectifying homographies if flag == 'rpc': H1, H2, disp_min, disp_max = compute_rectification_homographies( im1, im2, rpc1, rpc2, x, y, w, h, A, m) else: H1, H2, disp_min, disp_max = compute_rectification_homographies_sift( im1, im2, rpc1, rpc2, x, y, w, h) # compute output images size roi = [[x, y], [x + w, y], [x + w, y + h], [x, y + h]] pts1 = common.points_apply_homography(H1, roi) x0, y0, w0, h0 = common.bounding_box2D(pts1) # check that the first homography maps the ROI in the positive quadrant np.testing.assert_allclose(np.round([x0, y0]), 0, atol=.01) # apply homographies and do the crops TODO XXX FIXME cleanup here #homography_cropper.crop_and_apply_homography(out1, im1, H1, w0, h0, cfg['subsampling_factor'], True) #homography_cropper.crop_and_apply_homography(out2, im2, H2, w0, h0, cfg['subsampling_factor'], True) common.image_apply_homography(out1, im1, H1, w0, h0) common.image_apply_homography(out2, im2, H2, w0, h0) # If subsampling_factor'] the homographies are altered to reflect the zoom if cfg['subsampling_factor'] != 1: from math import floor, ceil # update the H1 and H2 to reflect the zoom Z = np.eye(3) Z[0, 0] = Z[1, 1] = 1.0 / cfg['subsampling_factor'] H1 = np.dot(Z, H1) H2 = np.dot(Z, H2) disp_min = floor(disp_min / cfg['subsampling_factor']) disp_max = ceil(disp_max / cfg['subsampling_factor']) return H1, H2, disp_min, disp_max
def cloud_water_image_domain(out, w, h, H, rpc, roi_gml=None, cld_gml=None, wat_msk=None): """ Computes a mask for pixels masked by clouds, water, or out of image domain. Args: out: path to the output image file. w, h: (w, h) are the dimensions of the output image mask. H: 3x3 numpy array representing the homography that transforms the original full image into the rectified tile. rpc: paths to the xml file containing the rpc coefficients of the image. RPC model is used with SRTM data to derive the water mask. roi_gml (optional, default None): path to a gml file containing a mask defining the area contained in the full image cld_gml (optional, default None): path to a gml file containing a mask defining the areas covered by clouds wat_msk (optional): path to a tiff file containing a water mask. Returns: True if the tile is completely masked, False otherwise. """ if (os.path.isfile(out) and cfg['skip_existing']): print 'Mask %s already exists, skip.' % out # put the coefficients of the homography in a string hij = ' '.join(['%f' % x for x in H.flatten()]) # image domain mask if roi_gml is None: # initialize to 255 common.run('plambda zero:%dx%d "x 255 +" -o %s' % (w, h, out)) else: common.run('cldmask %d %d -h "%s" %s %s' % (w, h, hij, roi_gml, out)) if common.is_image_black(out): # if we are already out, return return True # cloud mask if cld_gml is not None: cld_msk = common.tmpfile('.png') common.run('cldmask %d %d -h "%s" %s %s' % (w, h, hij, cld_gml, cld_msk)) # cld msk has to be inverted. # TODO: add flag to the cldmask binary, to avoid using read/write the # msk one more time for this common.run('plambda %s "255 x -" -o %s' % (cld_msk, cld_msk)) intersection(out, out, cld_msk) if wat_msk is not None: # water mask (tiff) wat_msk_crop = common.tmpfile('.png') common.image_apply_homography(wat_msk_crop, wat_msk, H, w, h) intersection(out, out, wat_msk_crop) else: # water mask (srtm) water_msk = common.tmpfile('.png') env = os.environ.copy() env['SRTM4_CACHE'] = cfg['srtm_dir'] common.run( 'watermask %d %d -h "%s" %s %s' % (w, h, hij, rpc, water_msk), env) intersection(out, out, water_msk) return common.is_image_black(out)
def cloud_water_image_domain(out, w, h, H, rpc, roi_gml=None, cld_gml=None, wat_msk=None): """ Computes a mask for pixels masked by clouds, water, or out of image domain. Args: out: path to the output image file. w, h: (w, h) are the dimensions of the output image mask. H: 3x3 numpy array representing the homography that transforms the original full image into the rectified tile. rpc: paths to the xml file containing the rpc coefficients of the image. RPC model is used with SRTM data to derive the water mask. roi_gml (optional, default None): path to a gml file containing a mask defining the area contained in the full image cld_gml (optional, default None): path to a gml file containing a mask defining the areas covered by clouds wat_msk (optional): path to a tiff file containing a water mask. Returns: True if the tile is completely masked, False otherwise. """ if (os.path.isfile(out) and cfg['skip_existing']): print 'Mask %s already exists, skip.' % out # put the coefficients of the homography in a string hij = ' '.join(['%f' % x for x in H.flatten()]) # image domain mask if roi_gml is None: # initialize to 255 common.run('plambda zero:%dx%d "x 255 +" -o %s' % (w, h, out)) else: common.run('cldmask %d %d -h "%s" %s %s' % (w, h, hij, roi_gml, out)) if common.is_image_black(out): # if we are already out, return return True # cloud mask if cld_gml is not None: cld_msk = common.tmpfile('.png') common.run('cldmask %d %d -h "%s" %s %s' % (w, h, hij, cld_gml, cld_msk)) # cld msk has to be inverted. # TODO: add flag to the cldmask binary, to avoid using read/write the # msk one more time for this common.run('plambda %s "255 x -" -o %s' % (cld_msk, cld_msk)) intersection(out, out, cld_msk) if wat_msk is not None: # water mask (tiff) wat_msk_crop = common.tmpfile('.png') common.image_apply_homography(wat_msk_crop, wat_msk, H, w, h) intersection(out, out, wat_msk_crop) else: # water mask (srtm) water_msk = common.tmpfile('.png') env = os.environ.copy() env['SRTM4_CACHE'] = cfg['srtm_dir'] common.run('watermask %d %d -h "%s" %s %s' % (w, h, hij, rpc, water_msk), env) intersection(out, out, water_msk) return common.is_image_black(out)
def rectify_pair(im1, im2, rpc1, rpc2, x, y, w, h, out1, out2, A=None, sift_matches=None, method="rpc"): """ Rectify a ROI in a pair of images. Args: im1, im2: paths to two image files rpc1, rpc2: paths to the two xml files containing RPC data x, y, w, h: four integers defining the rectangular ROI in the first image. (x, y) is the top-left corner, and (w, h) are the dimensions of the rectangle. out1, out2: paths to the output rectified crops A (optional): 3x3 numpy array containing the pointing error correction for im2. This matrix is usually estimated with the pointing_accuracy module. sift_matches (optional): Nx4 numpy array containing a list of sift matches, in the full image coordinates frame method (default: 'rpc'): option to decide wether to use rpc of sift matches for the fundamental matrix estimation. This function uses the parameter subsampling_factor from the config module. If the factor z > 1 then the output images will be subsampled by a factor z. The output matrices H1, H2, and the ranges are also updated accordingly: Hi = Z * Hi with Z = diag(1/z, 1/z, 1) and disp_min = disp_min / z (resp _max) Returns: H1, H2: Two 3x3 matrices representing the rectifying homographies that have been applied to the two original (large) images. disp_min, disp_max: horizontal disparity range """ # read RPC data rpc1 = rpc_model.RPCModel(rpc1) rpc2 = rpc_model.RPCModel(rpc2) # compute real or virtual matches if method == "rpc": # find virtual matches from RPC camera models matches = rpc_utils.matches_from_rpc(rpc1, rpc2, x, y, w, h, cfg["n_gcp_per_axis"]) # correct second image coordinates with the pointing correction matrix if A is not None: matches[:, 2:] = common.points_apply_homography(np.linalg.inv(A), matches[:, 2:]) else: matches = sift_matches # compute rectifying homographies H1, H2, F = rectification_homographies(matches, x, y, w, h) if cfg["register_with_shear"]: # compose H2 with a horizontal shear to reduce the disparity range a = np.mean(rpc_utils.altitude_range(rpc1, x, y, w, h)) lon, lat, alt = rpc_utils.ground_control_points(rpc1, x, y, w, h, a, a, 4) x1, y1 = rpc1.inverse_estimate(lon, lat, alt)[:2] x2, y2 = rpc2.inverse_estimate(lon, lat, alt)[:2] m = np.vstack([x1, y1, x2, y2]).T m = np.vstack({tuple(row) for row in m}) # remove duplicates due to no alt range H2 = register_horizontally_shear(m, H1, H2) # compose H2 with a horizontal translation to center disp range around 0 if sift_matches is not None: sift_matches = filter_matches_epipolar_constraint(F, sift_matches, cfg["epipolar_thresh"]) if len(sift_matches) < 10: print "WARNING: no registration with less than 10 matches" else: H2 = register_horizontally_translation(sift_matches, H1, H2) # compute disparity range disp_m, disp_M = disparity_range(rpc1, rpc2, x, y, w, h, H1, H2, sift_matches, A) # compute output images size roi = [[x, y], [x + w, y], [x + w, y + h], [x, y + h]] pts1 = common.points_apply_homography(H1, roi) x0, y0, w0, h0 = common.bounding_box2D(pts1) # check that the first homography maps the ROI in the positive quadrant np.testing.assert_allclose(np.round([x0, y0]), 0, atol=0.01) # apply homographies and do the crops TODO XXX FIXME cleanup here # homography_cropper.crop_and_apply_homography(out1, im1, H1, w0, h0, cfg['subsampling_factor'], True) # homography_cropper.crop_and_apply_homography(out2, im2, H2, w0, h0, cfg['subsampling_factor'], True) common.image_apply_homography(out1, im1, H1, w0, h0) common.image_apply_homography(out2, im2, H2, w0, h0) # if subsampling_factor'] the homographies are altered to reflect the zoom if cfg["subsampling_factor"] != 1: Z = np.eye(3) Z[0, 0] = Z[1, 1] = 1.0 / cfg["subsampling_factor"] H1 = np.dot(Z, H1) H2 = np.dot(Z, H2) disp_m = np.floor(disp_m / cfg["subsampling_factor"]) disp_M = np.ceil(disp_M / cfg["subsampling_factor"]) return H1, H2, disp_m, disp_M
def crop_and_apply_homography(im_out, im_in, H, w, h, subsampling_factor=1, convert_to_gray=False): """ Warps a piece of a Pleiades (panchro or ms) image with a homography. Args: im_out: path to the output image im_in: path to the input (tif) full Pleiades image H: numpy array containing the 3x3 homography matrix w, h: size of the output image subsampling_factor (optional, default=1): when set to z>1, will result in the application of the homography Z*H where Z = diag(1/z, 1/z, 1), so the output will be zoomed out by a factor z. The output image will be (w/z, h/z) convert_to_gray (optional, default False): it set to True, and if the input image has 4 channels, it is converted to gray before applying zoom and homographies. Returns: nothing The homography has to be used as: coord_out = H coord_in. The produced output image corresponds to coord_out in [0, w] x [0, h]. The warp is made by Pascal Monasse's binary named 'homography'. """ # crop a piece of the big input image, to which the homography will be # applied # warning: as the crop uses integer coordinates, be careful to round off # (x0, y0) before modifying the homograpy. You want the crop and the # translation representing it do exactly the same thing. pts = [[0, 0], [w, 0], [w, h], [0, h]] inv_H_pts = common.points_apply_homography(np.linalg.inv(H), pts) x0, y0, w0, h0 = common.bounding_box2D(inv_H_pts) x0, y0 = np.floor([x0, y0]) w0, h0 = np.ceil([w0, h0]) crop_fullres = common.image_crop_LARGE(im_in, x0, y0, w0, h0) # This filter is needed (for panchro images) because the original PLEAIDES # SENSOR PERFECT images are aliased if (common.image_pix_dim(crop_fullres) == 1 and subsampling_factor == 1 and cfg['use_pleiades_unsharpening']): tmp = image_apply_pleiades_unsharpening_filter(crop_fullres) common.run('rm -f %s' % crop_fullres) crop_fullres = tmp # convert to gray if common.image_pix_dim(crop_fullres) == 4: if convert_to_gray: crop_fullres = common.pansharpened_to_panchro(crop_fullres) # compensate the homography with the translation induced by the preliminary # crop, then apply the homography and crop. H = np.dot(H, common.matrix_translation(x0, y0)) # Since the objective is to compute a zoomed out homographic transformation # of the input image, to save computations we zoom out the image before # applying the homography. If Z is the matrix representing the zoom out and # H the homography matrix, this trick consists in applying Z*H*Z^{-1} to # the zoomed image Z*Im instead of applying Z*H to the original image Im. if subsampling_factor == 1: common.image_apply_homography(im_out, crop_fullres, H, w, h) return else: assert(subsampling_factor >= 1) # H becomes Z*H*Z^{-1} Z = np.eye(3); Z[0,0] = Z[1,1] = 1 / float(subsampling_factor) H = np.dot(Z, H) H = np.dot(H, np.linalg.inv(Z)) # w, and h are updated accordingly w = int(w / subsampling_factor) h = int(h / subsampling_factor) # the DCT zoom is NOT SAFE when the input image size is not a multiple # of the zoom factor tmpw, tmph = common.image_size(crop_fullres) tmpw, tmph = int(tmpw / subsampling_factor), int(tmph / subsampling_factor) crop_fullres_safe = common.image_crop_tif(crop_fullres, 0, 0, tmpw * subsampling_factor, tmph * subsampling_factor) common.run('rm -f %s' % crop_fullres) # zoom out the input image (crop_fullres) crop_zoom_out = common.image_safe_zoom_fft(crop_fullres_safe, subsampling_factor) common.run('rm -f %s' % crop_fullres_safe) # apply the homography to the zoomed out crop common.image_apply_homography(im_out, crop_zoom_out, H, w, h) return
def crop_and_apply_homography(im_out, im_in, H, w, h, subsampling_factor=1, convert_to_gray=False): """ Warps a piece of a Pleiades (panchro or ms) image with a homography. Args: im_out: path to the output image im_in: path to the input (tif) full Pleiades image H: numpy array containing the 3x3 homography matrix w, h: size of the output image subsampling_factor (optional, default=1): when set to z>1, will result in the application of the homography Z*H where Z = diag(1/z, 1/z, 1), so the output will be zoomed out by a factor z. The output image will be (w/z, h/z) convert_to_gray (optional, default False): it set to True, and if the input image has 4 channels, it is converted to gray before applying zoom and homographies. Returns: nothing The homography has to be used as: coord_out = H coord_in. The produced output image corresponds to coord_out in [0, w] x [0, h]. The warp is made by Pascal Monasse's binary named 'homography'. """ # crop a piece of the big input image, to which the homography will be # applied # warning: as the crop uses integer coordinates, be careful to round off # (x0, y0) before modifying the homograpy. You want the crop and the # translation representing it do exactly the same thing. pts = [[0, 0], [w, 0], [w, h], [0, h]] inv_H_pts = common.points_apply_homography(np.linalg.inv(H), pts) x0, y0, w0, h0 = common.bounding_box2D(inv_H_pts) x0, y0 = np.floor([x0, y0]) w0, h0 = np.ceil([w0, h0]) crop_fullres = common.image_crop_LARGE(im_in, x0, y0, w0, h0) # This filter is needed (for panchro images) because the original PLEAIDES # SENSOR PERFECT images are aliased if (common.image_pix_dim(crop_fullres) == 1 and subsampling_factor == 1 and cfg['use_pleiades_unsharpening']): tmp = image_apply_pleiades_unsharpening_filter(crop_fullres) common.run('rm -f %s' % crop_fullres) crop_fullres = tmp # convert to gray if common.image_pix_dim(crop_fullres) == 4: if convert_to_gray: crop_fullres = common.pansharpened_to_panchro(crop_fullres) # compensate the homography with the translation induced by the preliminary # crop, then apply the homography and crop. H = np.dot(H, common.matrix_translation(x0, y0)) # Since the objective is to compute a zoomed out homographic transformation # of the input image, to save computations we zoom out the image before # applying the homography. If Z is the matrix representing the zoom out and # H the homography matrix, this trick consists in applying Z*H*Z^{-1} to # the zoomed image Z*Im instead of applying Z*H to the original image Im. if subsampling_factor == 1: common.image_apply_homography(im_out, crop_fullres, H, w, h) return else: assert (subsampling_factor >= 1) # H becomes Z*H*Z^{-1} Z = np.eye(3) Z[0, 0] = Z[1, 1] = 1 / float(subsampling_factor) H = np.dot(Z, H) H = np.dot(H, np.linalg.inv(Z)) # w, and h are updated accordingly w = int(w / subsampling_factor) h = int(h / subsampling_factor) # the DCT zoom is NOT SAFE when the input image size is not a multiple # of the zoom factor tmpw, tmph = common.image_size(crop_fullres) tmpw, tmph = int(tmpw / subsampling_factor), int(tmph / subsampling_factor) crop_fullres_safe = common.image_crop_tif(crop_fullres, 0, 0, tmpw * subsampling_factor, tmph * subsampling_factor) common.run('rm -f %s' % crop_fullres) # zoom out the input image (crop_fullres) crop_zoom_out = common.image_safe_zoom_fft(crop_fullres_safe, subsampling_factor) common.run('rm -f %s' % crop_fullres_safe) # apply the homography to the zoomed out crop common.image_apply_homography(im_out, crop_zoom_out, H, w, h) return
def rectify_pair(im1, im2, rpc1, rpc2, x, y, w, h, out1, out2, A=None, m=None, flag='rpc'): """ Rectify a ROI in a pair of Pleiades images. Args: im1, im2: paths to the two Pleiades images (usually jp2 or tif) rpc1, rpc2: paths to the two xml files containing RPC data x, y, w, h: four integers defining the rectangular ROI in the first image. (x, y) is the top-left corner, and (w, h) are the dimensions of the rectangle. out1, out2: paths to the output crops A (optional): 3x3 numpy array containing the pointing error correction for im2. This matrix is usually estimated with the pointing_accuracy module. m (optional): Nx4 numpy array containing a list of sift matches, in the full image coordinates frame flag (default: 'rpc'): option to decide wether to use rpc of sift matches for the fundamental matrix estimation. This function uses the parameter subsampling_factor from the config module. If the factor z > 1 then the output images will be subsampled by a factor z. The output matrices H1, H2, and the ranges are also updated accordingly: Hi = Z*Hi with Z = diag(1/z,1/z,1) and disp_min = disp_min/z (resp _max) Returns: H1, H2: Two 3x3 matrices representing the rectifying homographies that have been applied to the two (big) images. disp_min, disp_max: horizontal disparity range """ # read RPC data rpc1 = rpc_model.RPCModel(rpc1) rpc2 = rpc_model.RPCModel(rpc2) # compute rectifying homographies if flag == 'rpc': H1, H2, disp_min, disp_max = compute_rectification_homographies( im1, im2, rpc1, rpc2, x, y, w, h, A, m) else: H1, H2, disp_min, disp_max = compute_rectification_homographies_sift( im1, im2, rpc1, rpc2, x, y, w, h) # compute output images size roi = [[x, y], [x+w, y], [x+w, y+h], [x, y+h]] pts1 = common.points_apply_homography(H1, roi) x0, y0, w0, h0 = common.bounding_box2D(pts1) # check that the first homography maps the ROI in the positive quadrant np.testing.assert_allclose(np.round([x0, y0]), 0, atol=.01) # apply homographies and do the crops TODO XXX FIXME cleanup here #homography_cropper.crop_and_apply_homography(out1, im1, H1, w0, h0, cfg['subsampling_factor'], True) #homography_cropper.crop_and_apply_homography(out2, im2, H2, w0, h0, cfg['subsampling_factor'], True) common.image_apply_homography(out1, im1, H1, w0, h0) common.image_apply_homography(out2, im2, H2, w0, h0) # If subsampling_factor'] the homographies are altered to reflect the zoom if cfg['subsampling_factor'] != 1: from math import floor, ceil # update the H1 and H2 to reflect the zoom Z = np.eye(3) Z[0, 0] = Z[1, 1] = 1.0 / cfg['subsampling_factor'] H1 = np.dot(Z, H1) H2 = np.dot(Z, H2) disp_min = floor(disp_min / cfg['subsampling_factor']) disp_max = ceil(disp_max / cfg['subsampling_factor']) return H1, H2, disp_min, disp_max
def rectify_pair(im1, im2, rpc1, rpc2, x, y, w, h, out1, out2, A=None, sift_matches=None, method='rpc'): """ Rectify a ROI in a pair of images. Args: im1, im2: paths to two image files rpc1, rpc2: paths to the two xml files containing RPC data x, y, w, h: four integers defining the rectangular ROI in the first image. (x, y) is the top-left corner, and (w, h) are the dimensions of the rectangle. out1, out2: paths to the output rectified crops A (optional): 3x3 numpy array containing the pointing error correction for im2. This matrix is usually estimated with the pointing_accuracy module. sift_matches (optional): Nx4 numpy array containing a list of sift matches, in the full image coordinates frame method (default: 'rpc'): option to decide wether to use rpc of sift matches for the fundamental matrix estimation. This function uses the parameter subsampling_factor from the config module. If the factor z > 1 then the output images will be subsampled by a factor z. The output matrices H1, H2, and the ranges are also updated accordingly: Hi = Z * Hi with Z = diag(1/z, 1/z, 1) and disp_min = disp_min / z (resp _max) Returns: H1, H2: Two 3x3 matrices representing the rectifying homographies that have been applied to the two original (large) images. disp_min, disp_max: horizontal disparity range """ # read RPC data rpc1 = rpc_model.RPCModel(rpc1) rpc2 = rpc_model.RPCModel(rpc2) # compute real or virtual matches if method == 'rpc': # find virtual matches from RPC camera models matches = rpc_utils.matches_from_rpc(rpc1, rpc2, x, y, w, h, cfg['n_gcp_per_axis']) # correct second image coordinates with the pointing correction matrix if A is not None: matches[:, 2:] = common.points_apply_homography(np.linalg.inv(A), matches[:, 2:]) else: matches = sift_matches # compute rectifying homographies H1, H2, F = rectification_homographies(matches, x, y, w, h) # compose H2 with a horizontal translation to center disp range around 0 if sift_matches is not None: sift_matches = filter_matches_epipolar_constraint(F, sift_matches, cfg['epipolar_thresh']) if len(sift_matches) < 10: print 'WARNING: no registration with less than 10 matches' else: H2 = register_horizontally(sift_matches, H1, H2) # compute disparity range disp_m, disp_M = disparity_range(rpc1, rpc2, x, y, w, h, H1, H2, sift_matches, A) # compute output images size roi = [[x, y], [x+w, y], [x+w, y+h], [x, y+h]] pts1 = common.points_apply_homography(H1, roi) x0, y0, w0, h0 = common.bounding_box2D(pts1) # check that the first homography maps the ROI in the positive quadrant np.testing.assert_allclose(np.round([x0, y0]), 0, atol=.01) # apply homographies and do the crops TODO XXX FIXME cleanup here #homography_cropper.crop_and_apply_homography(out1, im1, H1, w0, h0, cfg['subsampling_factor'], True) #homography_cropper.crop_and_apply_homography(out2, im2, H2, w0, h0, cfg['subsampling_factor'], True) common.image_apply_homography(out1, im1, H1, w0, h0) common.image_apply_homography(out2, im2, H2, w0, h0) # if subsampling_factor'] the homographies are altered to reflect the zoom if cfg['subsampling_factor'] != 1: Z = np.eye(3) Z[0, 0] = Z[1, 1] = 1.0 / cfg['subsampling_factor'] H1 = np.dot(Z, H1) H2 = np.dot(Z, H2) disp_m = np.floor(disp_m / cfg['subsampling_factor']) disp_M = np.ceil(disp_M / cfg['subsampling_factor']) return H1, H2, disp_m, disp_M