class_docstr: Dual KPConv block (usually strided + non strided)
  
  Arguments: Accepted kwargs
      block_names: Name of the blocks to be used as part of this dual block
      down_conv_nn: Size of the convs e.g. [64,128],
      grid_size: Size of the grid for each block,
      prev_grid_size: Size of the grid in the previous KPConv
      has_bottleneck: Wether a block should implement the bottleneck
      max_num_neighbors: Max number of neighboors for the radius search,
      deformable: Is deformable,
      add_one: Add one as a feature,
class_docstr_tok: ["Dual","KPConv","block","(","usually","strided","+","non","strided",")","Arguments",":","Accepted","kwargs","block_names",":","Name","of","the","blocks","to","be","used","as","part","of","this","dual","block","down_conv_nn",":","Size","of","the","convs","e.g",".","[","64,128","]",",","grid_size",":","Size","of","the","grid","for","each","block",",","prev_grid_size",":","Size","of","the","grid","in","the","previous","KPConv","has_bottleneck",":","Wether","a","block","should","implement","the","bottleneck","max_num_neighbors",":","Max","number","of","neighboors","for","the","radius","search",",","deformable",":","Is","deformable",",","add_one",":","Add","one","as","a","feature",","]
code: def __init__(
      self,
      block_names=None,
      down_conv_nn=None,
      grid_size=None,
      prev_grid_size=None,
      has_bottleneck=None,
      max_num_neighbors=None,
      deformable=False,
      add_one=False,
      **kwargs,
  ):
      super(KPDualBlock, self).__init__()
  
      assert len(block_names) == len(down_conv_nn)
      self.blocks = torch.nn.ModuleList()
      for i, class_name in enumerate(block_names):
          # Constructing extra keyword arguments
          block_kwargs = {}
          for key, arg in kwargs.items():
              block_kwargs[key] = arg[i] if is_list(arg) else arg
  
          # Building the block
          kpcls = getattr(sys.modules[__name__], class_name)
          block = kpcls(
              down_conv_nn=down_conv_nn[i],
              grid_size=grid_size[i],
              prev_grid_size=prev_grid_size[i],
              has_bottleneck=has_bottleneck[i],
              max_num_neighbors=max_num_neighbors[i],
              deformable=deformable[i]
              if is_list(deformable)
              else deformable,
              add_one=add_one[i] if is_list(add_one) else add_one,
              **block_kwargs,
          )
          self.blocks.append(block)
  
imports: import torch
  import sys
  from torch.nn import Linear
  from .kernels import KPConvLayer, KPConvDeformableLayer
  from torch_points3d.core.common_modules.base_modules import BaseModule, FastBatchNorm1d
  from torch_points3d.core.spatial_ops import RadiusNeighbourFinder
  from torch_points3d.core.data_transform import GridSampling3D
  from torch_points3d.utils.enums import ConvolutionFormat
  from torch_points3d.core.base_conv.message_passing import GlobalBaseModule
  from torch_points3d.core.common_modules.base_modules import Identity
  from torch_points3d.utils.config import is_list
label_desc: Use image node_id 1 to create a new KPDualBlock object from inherited base classes: BaseModule with example: obj = KPDualBlock(block_names, down_conv_nn, grid_size, prev_grid_size, has_bottleneck, max_num_neighbors, deformable, add_one)