# AUTOGENERATED! DO NOT EDIT! File to edit: ../../nbs/CLI/pc.ipynb.

# %% auto 0
__all__ = ['gix2bool', 'bool2gix', 'ras2pc', 'pc2ras', 'pc_hix', 'pc_sort', 'pc_union', 'pc_intersect', 'pc_diff', 'pc_logic_ras',
           'pc_logic_pc', 'pc_select_data']

# %% ../../nbs/CLI/pc.ipynb 4
import logging
import zarr
import numpy as np
import numexpr as ne
import time

import dask
from dask import array as da
from dask.distributed import Client, LocalCluster, progress

from .logging import mc_logger
import moraine as mr

# %% ../../nbs/CLI/pc.ipynb 5
@mc_logger
def gix2bool(gix:str, # point cloud grid index
             is_pc:str, # output, output bool array
             shape:tuple[int], # shape of one image (nlines,width)
             chunks:tuple[int]= (1000,1000), # output chunk size 
            ):
    '''Convert pc grid index to bool 2d array'''
    logger = logging.getLogger(__name__)
    is_pc_path = is_pc

    gix_zarr = zarr.open(gix,mode='r')
    logger.zarr_info('gix',gix_zarr)
    assert gix_zarr.ndim == 2, "gix dimentation is not 2."
    logger.info('loading gix into memory.')
    gix = zarr.open(gix,mode='r')[:]

    logger.info('calculate the bool array')
    is_pc = np.zeros(shape,dtype=bool)
    is_pc[gix[0],gix[1]] = True

    is_pc_zarr = zarr.open(is_pc_path,'w',shape=shape,dtype=bool,chunks=chunks)
    logger.zarr_info('is_pc',is_pc_zarr)
    logger.info('write the bool array.')
    is_pc_zarr[:] = is_pc
    logger.info('write done.')

# %% ../../nbs/CLI/pc.ipynb 6
@mc_logger
def bool2gix(is_pc:str, # input bool array
             gix:str, # output, point cloud grid index
             chunks:int=100000, # output point chunk size
            ):
    '''Convert bool 2d array to grid index'''
    gix_path = gix
    logger = logging.getLogger(__name__)

    is_pc_zarr = zarr.open(is_pc,'r')
    logger.zarr_info('is_pc', is_pc_zarr)
    logger.info('loading is_pc into memory.')
    is_pc = zarr.open(is_pc,mode='r')[:]
    
    logger.info('calculate the index')
    gix = np.stack(np.where(is_pc))

    gix_zarr = zarr.open(gix_path,'w',shape=gix.shape,dtype=bool,chunks=(2,chunks))
    logger.zarr_info('gix', gix_zarr)
    logger.info('write the gix.')
    gix_zarr[:] = gix
    logger.info('write done.')

# %% ../../nbs/CLI/pc.ipynb 7
@mc_logger
def ras2pc(gix:str, # point cloud grid index
           ras:str|list, # path (in string) or list of path for raster data
           pc:str|list, # output, path (in string) or list of path for point cloud data
           chunks:int=None, # output point chunk size, same as gix by default
          ):
    '''Convert raster data to point cloud data'''
    logger = logging.getLogger(__name__)

    gix_zarr = zarr.open(gix,mode='r')
    if chunks is None: chunks = gix_zarr.chunks[1]
    logger.zarr_info(gix,gix_zarr)
    assert gix_zarr.ndim == 2, "gix dimentation is not 2."

    logger.info('loading gix into memory.')
    gix = zarr.open(gix,mode='r')[:]
    n_pc = gix.shape[1]

    if isinstance(ras,str):
        assert isinstance(pc,str)
        ras_list = [ras]; pc_list = [pc]
    else:
        assert isinstance(ras,list); assert isinstance(pc,list)
        ras_list = ras; pc_list = pc
        n_data = len(ras_list)

    logger.info('starting dask local cluster.')
    with LocalCluster(processes=False, threads_per_worker=2) as cluster, Client(cluster) as client:
        logger.info('dask local cluster started.')
        _pc_list = ()
        for ras_path, pc_path in zip(ras_list,pc_list):
            logger.info(f'start to slice on {ras_path}')
            ras_zarr = zarr.open(ras_path,'r'); logger.zarr_info(ras_path, ras_zarr)
            ras = da.from_zarr(ras_path,chunks=ras_zarr.chunks); logger.darr_info('ras',ras)
            with dask.config.set(**{'array.slicing.split_large_chunks': False}):
                pc = ras.reshape(-1,*ras.shape[2:])[np.ravel_multi_index((gix[0],gix[1]),dims=ras.shape[:2])]
            logger.darr_info('pc', pc)
            logger.info('rechunk pc data:')
            pc = pc.rechunk((chunks,*pc.chunksize[1:]))
            logger.darr_info('pc', pc)
            _pc = pc.to_zarr(pc_path,overwrite=True,compute=False)
            # _pc.visualize(filename=f'_pc.svg')
            logger.info(f'saving to {pc_path}.')
            _pc_list += (_pc,)

        logger.info('computing graph setted. doing all the computing.')
        futures = client.persist(_pc_list)
        progress(futures,notebook=False)
        time.sleep(0.1)
        da.compute(futures)
        logger.info('computing finished.')

    logger.info('dask cluster closed.')

# %% ../../nbs/CLI/pc.ipynb 12
@mc_logger
def pc2ras(gix:str, # point cloud grid index
           pc:str|list, # path (in string) or list of path for point cloud data
           ras:str|list, # output, path (in string) or list of path for raster data
           shape:tuple[int], # shape of one image (nlines,width)
           chunks:tuple[int]=(1000,1000), # output chunk size
          ):
    '''Convert point cloud data to raster data, filled with nan'''
    logger = logging.getLogger(__name__)

    gix_zarr = zarr.open(gix,mode='r')
    logger.zarr_info('gix', gix_zarr)
    assert gix_zarr.ndim == 2, "gix dimentation is not 2."

    logger.info('loading gix into memory.')
    gix = zarr.open(gix,mode='r')[:]
    n_pc = gix.shape[1]
    
    if isinstance(pc,str):
        assert isinstance(ras,str)
        pc_list = [pc]; ras_list = [ras]
    else:
        assert isinstance(pc,list); assert isinstance(ras,list)
        pc_list = pc; ras_list = ras

    logger.info('starting dask local cluster.')
    with LocalCluster(processes=False, threads_per_worker=2) as cluster, Client(cluster) as client:
        logger.info('dask local cluster started.')

        _ras_list = ()

        for ras_path, pc_path in zip(ras_list,pc_list):
            logger.info(f'start to work on {pc_path}')
            pc_zarr = zarr.open(pc_path,'r')
            logger.zarr_info(pc_path,pc_zarr)

            pc = da.from_zarr(pc_path)
            logger.darr_info('pc', pc)
            ras = da.empty((shape[0]*shape[1],*pc.shape[1:]),chunks = (chunks[0]*shape[1],*pc_zarr.chunks[1:]), dtype=pc.dtype)
            ras[:] = np.nan
            ras[np.ravel_multi_index((gix[0],gix[1]),dims=shape)] = pc
            ras = ras.reshape(*shape,*pc.shape[1:])
            ras.rechunk((*chunks,*pc_zarr.chunks[1:]))
            logger.info('create ras dask array')
            logger.darr_info('ras', ras)
            _ras = ras.to_zarr(ras_path,overwrite=True,compute=False)
            _ras_list += (_ras,)

        logger.info('computing graph setted. doing all the computing.')
        futures = client.persist(_ras_list)
        progress(futures,notebook=False)
        time.sleep(0.1)
        da.compute(futures)
        logger.info('computing finished.')

    logger.info('dask cluster closed.')

# %% ../../nbs/CLI/pc.ipynb 16
@mc_logger
def pc_hix(x:str, # horizonal coordinate
           y:str, # vertical coordinate
           hix:str, # output path
           bbox:list, # [x0, y0, xm, ym]
           interval:list # [x_interval, y_interval], cell size to make every cell has only one point falled in.
          ):
    '''Compute the hillbert index for point cloud data.'''
    logger = logging.getLogger(__name__)
    x_zarr = zarr.open(x,'r'); logger.zarr_info(x,x_zarr)
    y_zarr = zarr.open(y,'r'); logger.zarr_info(y,y_zarr)
    logger.info("calculating the hillbert index")
    hix_data = mr.pc_hix(x_zarr[:],y_zarr[:],bbox,interval)
    hix_zarr = zarr.open(hix, 'w', chunks=x_zarr.chunks, dtype=hix_data.dtype, shape=hix_data.shape,)
    logger.info("writing the hillbert index")
    logger.zarr_info(hix, hix_zarr)
    hix_zarr[:] = hix_data
    logger.info("done.")

# %% ../../nbs/CLI/pc.ipynb 21
@mc_logger
def pc_sort(idx_in:str, # the unsorted grid index or hillbert index of the input data
            idx:str, # output, the sorted grid index or hillbert index
            pc_in:str|list, # path (in string) or list of path for the input point cloud data
            pc:str|list, # output, path (in string) or list of path for the output point cloud data
            chunks:int=None, # chunk size in output data, same as `idx_in` by default
           ):
    '''Sort point cloud data according to the indices that sort `idx_in`.
    '''
    idx_in_path = idx_in
    logger = logging.getLogger(__name__)
    idx_in_zarr = zarr.open(idx_in_path,mode='r'); logger.zarr_info(idx_in_path,idx_in_zarr)
    logger.info('loading idx_in and calculate the sorting indices.')
    idx_in = idx_in_zarr[:]; iidx = mr.pc_sort(idx_in)
    n_pc = idx_in_zarr.shape[-1]
    if chunks is None: chunks = idx_in_zarr.chunks[-1] 
    logger.info(f'output pc chunk size is {chunks}')
    idx_chunk_size = (2, chunks) if idx_in.ndim == 2 else (chunks,)
    idx_zarr = zarr.open(idx,'w', shape=idx_in_zarr.shape, dtype=idx_in.dtype, chunks=idx_chunk_size)
    logger.info('write idx'); logger.zarr_info('idx', idx_zarr)
    idx_zarr[:] = idx_in[...,iidx]

    if isinstance(pc_in,str):
        assert isinstance(pc,str)
        pc_in_list = [pc_in]; pc_list = [pc]
    else:
        assert isinstance(pc_in,list); assert isinstance(pc,list)
        pc_in_list = pc_in; pc_list = pc

    logger.info('starting dask local cluster.')
    with LocalCluster(processes=False, threads_per_worker=2) as cluster, Client(cluster) as client:
        logger.info('dask local cluster started.')

        _pc_list = ()
        for pc_in_path, pc_path in zip(pc_in_list,pc_list):
            pc_in_zarr = zarr.open(pc_in_path,'r'); logger.zarr_info(pc_in_path, pc_in_zarr)
            pc_in = da.from_zarr(pc_in_zarr,chunks=(n_pc,*pc_in_zarr.chunks[1:]))
            logger.darr_info('pc_in', pc_in)
            logger.info('set up sorted pc data dask array.')
            pc = da.empty(pc_in.shape,chunks = pc_in.chunks, dtype=pc_in.dtype)
            logger.darr_info('pc',pc)
            pc[:] = pc_in[iidx]
            logger.info('rechunk dask array for writing.')
            pc = pc.rechunk((chunks,*pc.chunks[1:]))
            logger.darr_info('pc',pc)
            logger.info('write')
            _pc = pc.to_zarr(pc_path, overwrite=True,compute=False)
            # _pc.visualize(filename=f'_pc.svg')
            _pc_list += (_pc,)

        logger.info('computing graph setted. doing all the computing.')
        futures = client.persist(_pc_list)
        progress(futures,notebook=False)
        time.sleep(0.1)
        da.compute(futures)
        logger.info('computing finished.')
    logger.info('dask cluster closed.')

# %% ../../nbs/CLI/pc.ipynb 25
@mc_logger
def pc_union(idx1:str, # grid index or hillbert index of the first point cloud
             idx2:str, # grid index or hillbert index of the second point cloud
             idx:str, # output, grid index or hillbert index of the union point cloud
             pc1:str|list=None, # path (in string) or list of path for the first point cloud data
             pc2:str|list=None, # path (in string) or list of path for the second point cloud data
             pc:str|list=None, #output, path (in string) or list of path for the union point cloud data
             chunks:int=None, # chunk size in output data, same as `idx1` by default
            ):
    '''Get the union of two point cloud dataset.
    For points at their intersection, pc_data1 rather than pc_data2 is copied to the result pc_data.
    `pc_chunk_size` and `n_pc_chunk` are used to determine the final pc_chunk_size.
    If non of them are provided, the pc_chunk_size is setted as it in idx1.
    '''
    logger = logging.getLogger(__name__)

    idx1_zarr = zarr.open(idx1,mode='r'); logger.zarr_info(idx1,idx1_zarr)
    idx2_zarr = zarr.open(idx2,mode='r'); logger.zarr_info(idx2,idx2_zarr)
    logger.info('loading idx1 and idx2 into memory.')
    idx1 = idx1_zarr[:]; idx2 = idx2_zarr[:]

    logger.info('calculate the union')
    idx_path = idx
    idx, inv_iidx1, inv_iidx2, iidx2 = mr.pc_union(idx1,idx2)
    n_pc = idx.shape[-1]
    logger.info(f'number of points in the union: {n_pc}')
    if chunks is None: chunks = idx1_zarr.chunks[-1] 
    idx_chunk_size = (2, chunks) if idx.ndim == 2 else (chunks,)
    idx_zarr = zarr.open(idx_path,'w',shape=idx.shape,dtype=idx.dtype,chunks=idx_chunk_size)
    logger.info('write union idx')
    idx_zarr[:] = idx
    logger.info('write done')
    logger.zarr_info(idx_path, idx_zarr)
    
    if pc1 is None:
        logger.info('no point cloud data provided, exit.')
        return None

    if isinstance(pc1,str):
        assert isinstance(pc2,str); assert isinstance(pc,str)
        pc1_list = [pc1]; pc2_list = [pc2]; pc_list = [pc]
    else:
        assert isinstance(pc1,list); assert isinstance(pc2,list); assert isinstance(pc,list)
        pc1_list = pc1; pc2_list = pc2; pc_list = pc

    logger.info('starting dask local cluster.')
    with LocalCluster(processes=False, n_workers=1, threads_per_worker=2) as cluster, Client(cluster) as client:
        logger.info('dask local cluster started.')

        _pc_list = ()
        for pc1_path, pc2_path, pc_path in zip(pc1_list,pc2_list,pc_list):
            pc1_zarr = zarr.open(pc1_path,'r'); pc2_zarr = zarr.open(pc2_path,'r')
            logger.zarr_info(pc1_path, pc1_zarr); logger.zarr_info(pc2_path, pc2_zarr);
            pc1 = da.from_zarr(pc1_path,chunks=(pc1_zarr.shape[0],*pc1_zarr.chunks[1:]))
            pc2 = da.from_zarr(pc2_path,chunks=(pc2_zarr.shape[0],*pc2_zarr.chunks[1:]))
            logger.darr_info('pc1', pc1); logger.darr_info('pc2',pc2)
            logger.info('set up union pc data dask array.')
            pc = da.empty((n_pc,*pc1.shape[1:]),chunks = (n_pc,*pc1.chunks[1:]), dtype=pc1.dtype)
            logger.darr_info('pc',pc)
            pc[inv_iidx1] = pc1
            pc[inv_iidx2] = pc2[iidx2]
            logger.info('rechunk dask array for writing.')
            pc = pc.rechunk((chunks,*pc.chunks[1:]))
            logger.darr_info('pc',pc)
            logger.info('write')
            # pc.visualize(filename=f'pc.svg')
            _pc = pc.to_zarr(pc_path, overwrite=True,compute=False)
            _pc_list += (_pc,)

        logger.info('computing graph setted. doing all the computing.')
        futures = client.persist(_pc_list)
        progress(futures,notebook=False); time.sleep(0.1)
        da.compute(futures)
        logger.info('computing finished.')

    logger.info('dask cluster closed.')

# %% ../../nbs/CLI/pc.ipynb 31
@mc_logger
def pc_intersect(idx1:str, # grid index or hillbert index of the first point cloud
                 idx2:str, # grid index or hillbert index of the second point cloud
                 idx:str, # output, grid index or hillbert index of the union point cloud
                 pc1:str|list=None, # path (in string) or list of path for the first point cloud data
                 pc2:str|list=None, # path (in string) or list of path for the second point cloud data
                 pc:str|list=None, #output, path (in string) or list of path for the union point cloud data
                 chunks:int=None, # chunk size in output data, same as `idx1` by default
                 prefer_1=True, # save pc1 on intersection to output pc dataset by default `True`. Otherwise, save data from pc2
                ):
    '''Get the intersection of two point cloud dataset.
    `pc_chunk_size` and `n_pc_chunk` are used to determine the final pc_chunk_size.
    If non of them are provided, the n_pc_chunk is set to n_chunk in idx1.
    '''
    logger = logging.getLogger(__name__)

    idx1_zarr = zarr.open(idx1,mode='r'); logger.zarr_info(idx1,idx1_zarr)
    idx2_zarr = zarr.open(idx2,mode='r'); logger.zarr_info(idx2,idx2_zarr)
    logger.info('loading idx1 and idx2 into memory.')
    idx1 = idx1_zarr[:]; idx2 = idx2_zarr[:]

    logger.info('calculate the intersection')
    idx_path = idx
    idx, iidx1, iidx2 = mr.pc_intersect(idx1,idx2)
    n_pc = idx.shape[-1]
    logger.info(f'number of points in the intersection: {n_pc}')
    if chunks is None: chunks = idx1_zarr.chunks[-1] 
    idx_chunk_size = (2, chunks) if idx.ndim == 2 else (chunks,)    
    idx_zarr = zarr.open(idx_path,'w',shape=idx.shape,dtype=idx.dtype,chunks=idx_chunk_size)
    logger.info('write intersect idx')
    idx_zarr[:] = idx
    logger.info('write done')
    logger.zarr_info(idx_path, idx_zarr)

    if (pc1 is None) and (pc2 is None):
        logger.info('no point cloud data provided, exit.')
        return None

    if prefer_1:
        logger.info('select pc1 as pc_input.')
        iidx = iidx1; pc_input = pc1
    else:
        logger.info('select pc2 as pc_input.')
        iidx = iidx2; pc_input = pc2

    if isinstance(pc_input,str):
        assert isinstance(pc,str)
        pc_input_list = [pc_input]; pc_list = [pc]
    else:
        assert isinstance(pc_input,list); assert isinstance(pc,list)
        pc_input_list = pc_input; pc_list = pc

    logger.info('starting dask local cluster.')
    with LocalCluster(processes=False, threads_per_worker=2) as cluster, Client(cluster) as client:
        logger.info('dask local cluster started.')

        _pc_list = ()
        for pc_input_path, pc_path in zip(pc_input_list,pc_list):
            pc_input_zarr = zarr.open(pc_input_path,'r')
            logger.zarr_info(pc_input_path,pc_input_zarr)
            pc_input = da.from_zarr(pc_input_path)
            logger.darr_info('pc_input', pc_input)

            logger.info('set up intersect pc data dask array.')
            pc = da.empty((n_pc,*pc_input.shape[1:]),chunks = (n_pc,*pc_input.chunks[1:]), dtype=pc_input.dtype)
            logger.darr_info('pc',pc)
            pc[:] = pc_input[iidx]
            logger.info('rechunk dask array for writing.')
            pc = pc.rechunk((chunks,*pc.chunks[1:]))
            logger.darr_info('pc',pc)
            logger.info('write')
            # pc.visualize(filename=f'pc.svg')
            _pc = pc.to_zarr(pc_path, overwrite=True,compute=False)
            _pc_list += (_pc,)

        logger.info('computing graph setted. doing all the computing.')
        futures = client.persist(_pc_list)
        progress(futures,notebook=False); time.sleep(0.1)
        da.compute(futures)
        logger.info('computing finished.')
    logger.info('dask cluster closed.')

# %% ../../nbs/CLI/pc.ipynb 37
@mc_logger
def pc_diff(idx1:str, # grid index or hillbert index of the first point cloud
            idx2:str, # grid index or hillbert index of the second point cloud
            idx:str, # output, grid index or hillbert index of the union point cloud
            pc1:str|list=None, # path (in string) or list of path for the first point cloud data
            pc:str|list=None, #output, path (in string) or list of path for the union point cloud data
            chunks:int=None, # chunk size in output data,optional
           ):
    '''Get the point cloud in `idx1` that are not in `idx2`.
    `pc_chunk_size` and `n_pc_chunk` are used to determine the final pc_chunk_size.
    If non of them are provided, the n_pc_chunk is set to n_chunk in idx1.
    '''
    logger = logging.getLogger(__name__)

    idx1_zarr = zarr.open(idx1,mode='r'); logger.zarr_info(idx1,idx1_zarr)
    idx2_zarr = zarr.open(idx2,mode='r'); logger.zarr_info(idx2,idx2_zarr)
    logger.info('loading idx1 and idx2 into memory.')
    idx1 = idx1_zarr[:]; idx2 = idx2_zarr[:]

    logger.info('calculate the diff.')
    idx_path = idx
    idx, iidx1 = mr.pc_diff(idx1,idx2)
    n_pc = idx.shape[-1]
    logger.info(f'number of points in the diff: {n_pc}')
    if chunks is None: chunks = idx1_zarr.chunks[-1] 
    idx_chunk_size = (2, chunks) if idx.ndim == 2 else (chunks,)
    idx_zarr = zarr.open(idx_path,'w',shape=idx.shape,dtype=idx.dtype,chunks=idx_chunk_size)
    logger.info('write intersect idx')
    idx_zarr[:] = idx
    logger.info('write done')
    logger.zarr_info(idx_path, idx_zarr)

    if pc1 is None:
        logger.info('no point cloud data provided, exit.')
        return None

    if isinstance(pc1,str):
        assert isinstance(pc,str)
        pc1_list = [pc1]; pc_list = [pc]
    else:
        assert isinstance(pc1,list); assert isinstance(pc,list)
        pc1_list = pc1; pc_list = pc

    logger.info('starting dask local cluster.')
    with LocalCluster(processes=False, threads_per_worker=2) as cluster, Client(cluster) as client:
        logger.info('dask local cluster started.')

        _pc_list = ()
        for pc1_path, pc_path in zip(pc1_list,pc_list):
            pc1_zarr = zarr.open(pc1_path,'r'); logger.zarr_info(pc1_path, pc1_zarr)
            pc1 = da.from_zarr(pc1_path); logger.darr_info('pc1', pc1)
            logger.info('set up diff pc data dask array.')
            pc = da.empty((n_pc,*pc1.shape[1:]),chunks = (n_pc,*pc1.chunks[1:]), dtype=pc1.dtype)
            logger.darr_info('pc',pc)
            pc[:] = pc1[iidx1]
            logger.info('rechunk dask array for writing.')
            pc = pc.rechunk((chunks,*pc.chunks[1:]))
            logger.darr_info('pc',pc)
            logger.info('write')
            # pc.visualize(filename=f'pc.svg',optimize_graph=True)
            _pc = pc.to_zarr(pc_path, overwrite=True,compute=False)
            _pc_list += (_pc,)

        logger.info('computing graph setted. doing all the computing.')
        futures = client.persist(_pc_list)
        progress(futures,notebook=False); time.sleep(0.1)
        da.compute(futures)
        logger.info('computing finished.')
    logger.info('dask cluster closed.')

# %% ../../nbs/CLI/pc.ipynb 43
@mc_logger
def pc_logic_ras(ras, # the raster image used for thresholding
                 gix, # output, grid index of selected pixels
                 operation:str, # logical operation on input ras
                 chunks:int=100000, # chunk size in output data, optional
                ):
    '''generate point cloud index based on logical operation of one raster image.
    '''
    gix_path = gix
    logger = logging.getLogger(__name__)
    ras_zarr = zarr.open(ras, mode='r'); logger.zarr_info(ras,ras_zarr)

    ras = ras_zarr[:]; logger.info('loading ras into memory.')
    is_pc = ne.evaluate(operation,{'ras':ras})
    logger.info(f'select pc based on operation: {operation}')
    gix = np.stack(np.where(is_pc)).astype(np.int32)
    n_pc = gix.shape[1]
    logger.info(f'number of selected pixels: {n_pc}.')

    gix_zarr = zarr.open(gix_path,'w',dtype=gix.dtype,shape=gix.shape,chunks=(2,chunks))
    logger.info('writing gix.')
    gix_zarr[:] = gix
    logger.info('write done.')

# %% ../../nbs/CLI/pc.ipynb 46
@mc_logger
def pc_logic_pc(idx_in:str,# the grid index or hillbert index of input pc data
                pc_in:str, # the grid index or hillbert index cloud data used for thresholding
                idx:str, # output, grid index or hillbert index of selected pixels
                operation:str, # operator
                chunks:int=None, # chunk size in output data,optional
               ):
    '''generate point cloud index and data based on logical operation one point cloud data.
    '''
    idx_path = idx
    logger = logging.getLogger(__name__)
    idx_in_zarr = zarr.open(idx_in,mode='r'); logger.zarr_info(idx_in,idx_in_zarr)
    pc_in_zarr = zarr.open(pc_in, mode='r'); logger.zarr_info(pc_in,pc_in_zarr)

    idx_in = idx_in_zarr[:]; logger.info('loading idx_in into memory.')
    pc_in = pc_in_zarr[:]; logger.info('loading pc_in into memory.')

    is_pc = ne.evaluate(operation,{'pc_in':pc_in})
    logger.info(f'select pc based on operation: {operation}')
    idx = idx_in[...,is_pc]
    n_pc = idx.shape[-1]
    logger.info(f'number of selected pixels: {n_pc}.')
    if chunks is None: chunks = idx_in_zarr.chunks[-1] 
    idx_chunk_size = (2, chunks) if idx.ndim == 2 else (chunks,)
    idx_zarr = zarr.open(idx_path,'w',shape=idx.shape,dtype=idx.dtype,chunks=idx_chunk_size)
    logger.zarr_info('idx', idx_zarr)
    logger.info('writing idx.')
    idx_zarr[:] = idx
    logger.info('write done.')

# %% ../../nbs/CLI/pc.ipynb 51
@mc_logger
def pc_select_data(idx_in:str, # the grid index or hillbert index of the input data
                   idx:str, # the grid index or hillbert index of the output data
                   pc_in:str|list, # path (in string) or list of path for the input point cloud data
                   pc:str|list, # output, path (in string) or list of path for the output point cloud data
                   chunks:int=None, # chunk size in output data, same as chunks of `idx` by default
                  ):
    '''generate point cloud data based on its index and one point cloud data.
    The index of generated point cloud data must in the index of the old one.
    '''
    idx_in_path = idx_in; idx_path = idx
    logger = logging.getLogger(__name__)
    idx_in_zarr = zarr.open(idx_in_path,mode='r'); logger.zarr_info(idx_in_path,idx_in_zarr)
    idx_zarr = zarr.open(idx_path,mode='r'); logger.zarr_info(idx_path,idx_zarr)
    logger.info('loading idx_in and idx into memory.')
    idx_in = idx_in_zarr[:]; idx = idx_zarr[:]
    iidx_in, iidx = mr.pc_intersect(idx_in,idx)[1:]
    np.testing.assert_array_equal(iidx,np.arange(iidx.shape[0]),err_msg='idx have points that are not covered by idx_in.')
    n_pc = iidx_in.shape[0]
    if chunks is None: chunks = idx_zarr.chunks[-1] 

    if isinstance(pc_in,str):
        assert isinstance(pc,str)
        pc_in_list = [pc_in]; pc_list = [pc]
    else:
        assert isinstance(pc_in,list); assert isinstance(pc,list)
        pc_in_list = pc_in; pc_list = pc

    logger.info('starting dask local cluster.')
    with LocalCluster(processes=False, threads_per_worker=2) as cluster, Client(cluster) as client:
        logger.info('dask local cluster started.')

        _pc_list = ()
        for pc_in_path, pc_path in zip(pc_in_list,pc_list):
            pc_in_zarr = zarr.open(pc_in_path,'r'); logger.zarr_info(pc_in_path, pc_in_zarr)
            pc_in = da.from_zarr(pc_in_path); logger.darr_info('pc_in', pc_in)
            logger.info('set up selected pc data dask array.')
            pc = da.empty((n_pc,*pc_in.shape[1:]),chunks = (n_pc,*pc_in.chunks[1:]), dtype=pc_in.dtype)
            logger.darr_info('pc',pc)
            pc[:] = pc_in[iidx_in]
            logger.info('rechunk dask array for writing.')
            pc = pc.rechunk((chunks,*pc.chunks[1:]))
            logger.darr_info('pc',pc)
            logger.info('write')
            # pc.visualize(filename=f'pc.svg',optimize_graph=True)
            _pc = pc.to_zarr(pc_path, overwrite=True,compute=False)
            _pc_list += (_pc,)

        logger.info('computing graph setted. doing all the computing.')
        futures = client.persist(_pc_list)
        progress(futures,notebook=False); time.sleep(0.1)
        da.compute(futures)
        logger.info('computing finished.')
    logger.info('dask cluster closed.')
