!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!
subroutine cubeio_get_header(cubset,cubdef,head,cub,error)
  use gkernel_interfaces
  use cubeio_interfaces, except_this=>cubeio_get_header
  use cubetools_access
  use cubetools_setup_types
  use cubeio_header
  use cubeio_types
  use cubeio_messaging
  !----------------------------------------------------------------------
  ! @ public
  ! Get the file header in the cube_header_t, derived from the file
  ! header on disk (whatever the file kind).
  ! Also set up the IO-descriptor internal structure.
  !----------------------------------------------------------------------
  type(cube_setup_t),  intent(in)    :: cubset
  type(cube_define_t), intent(in)    :: cubdef
  type(cube_header_t), intent(inout) :: head
  type(cubeio_cube_t), intent(inout) :: cub
  logical,             intent(inout) :: error
  ! Local
  character(len=*), parameter :: rname='GET>HEADER'
  character(len=file_l) :: cubeid
  !
  call cubeio_message(ioseve%trace,rname,'Welcome')
  !
  ! File name
  if (.not.cubdef%dofilename) then
    call cubeio_message(seve%e,rname,'Input cube name is not set')
    error = .true.
    return
  endif
  if (cubdef%doid) then
    cubeid = cubdef%id
  else
    cubeid = cubdef%filename
  endif
  call cubeio_read_header(cubdef%filename,cubeid,cub,error)
  if (error)  return
  !
  ! Set up descriptor
  call cubeio_set_descriptor_intrinsic(cub,error)
  if (error)  return
  cub%desc%filename = cubdef%filename
  call cubeio_set_descriptor_external(cubset,cubdef,.true.,cub,error)
  if (error)  return
  !
  ! Setup header
  select case (cub%desc%filekind)
  case (code_filekind_fits)
    call cubeio_header_get_and_derive(cub%hfits,head,error)
    if (error) return
  case (code_filekind_gdf)
    call cubeio_header_get_and_derive(cub%hgdf,head,error)
    if (error) return
  case default
    call cubeio_message(seve%e,rname,'No associated file on disk')
    error = .true.
    return
  end select
  !
end subroutine cubeio_get_header
!
subroutine cubeio_transpose_cube_desc(cubset,cubdef,in,out,error)
  use gkernel_interfaces
  use cubetools_access
  use cubeio_interfaces, except_this=>cubeio_transpose_cube_desc
  use cubeio_messaging
  use cubeio_types
  use cubeio_desc
  !---------------------------------------------------------------------
  ! @ public
  ! Memory-to-memory transposition of the input 'cubeio_cube_t' into the
  ! output one
  !---------------------------------------------------------------------
  type(cube_setup_t),   intent(in)    :: cubset
  type(cube_define_t),  intent(in)    :: cubdef
  type(cubeio_cube_t),  intent(in)    :: in
  type(cubeio_cube_t),  intent(inout) :: out
  logical,              intent(inout) :: error
  ! Local
  character(len=*), parameter :: rname='TRANSPOSE>CUBE>HEADER'
  !
  if (.not.cubdef%dotransname) then
    call cubeio_message(seve%e,rname,'Transposed cube name is not set')
    error = .true.
    return
  endif
  call cubeio_desc_transpose(in%desc,out%desc,cubdef%access,error)
  if (error) return
  out%desc%filename = cubdef%transname
  call cubeio_set_descriptor_external(cubset,cubdef,.true.,out,error)
  if (error)  return
  !
end subroutine cubeio_transpose_cube_desc
!
subroutine cubeio_get_cube_data(cubset,cubdef,cub,error)
  use cubeio_interfaces, except_this=>cubeio_get_cube_data
  use cubetools_setup_types
  use cubeio_types
  use cubeio_messaging
  !----------------------------------------------------------------------
  ! @ public
  ! Read or prepare the data of a cube
  !----------------------------------------------------------------------
  type(cube_setup_t),  intent(in)    :: cubset
  type(cube_define_t), intent(in)    :: cubdef
  type(cubeio_cube_t), intent(inout) :: cub
  logical,             intent(inout) :: error
  ! Local
  character(len=*), parameter :: rname='GET>CUBE>DATA'
  type(cubeio_range_t) :: range
  !
  if (cub%ready())  return
  !
  ! ZZZ deplacer le core
  call cubeio_get_cube_data_core(error)
  if (error)  return
  !
  cub%data%ready = cub%desc%buffered
  !
contains
  subroutine cubeio_get_cube_data_core(error)
    logical, intent(inout) :: error
    !
    ! Sanity
    if (cub%desc%order.eq.code_null) then
      call cubeio_message(seve%e,rname,  &
        'Attempt to get cube data while header is not loaded')
      error = .true.
      return
    endif
    ! if (cub%io%trans%do) then
    !   call cubeio_message(seve%e,rname,'Internal error: a transposition is pending')
    !   error = .true.
    !   return
    ! endif
    !
    if (cub%desc%buffered.eq.code_buffer_none)  return  ! Data is not to be read nor transposed
    !
    select case (cub%desc%buffered)
    case (code_buffer_none)
      return
    case (code_buffer_memory)
      call cubeio_message(ioseve%others,rname,'File is buffered in memory')
      call cubeio_data_reallocate(cubset,cub%data,cub%desc%iscplx,  &
        cub%desc%nx,cub%desc%ny,cub%desc%nc,cub%desc%order,error)
      if (error) return
      range%blc(:) = 0
      range%trc(:) = 0
      if (cub%desc%iscplx) then
        call cubeio_read_cube_data(rname,cubset,cub,range,cub%data%c4,error)
      else
        call cubeio_read_cube_data(rname,cubset,cub,range,cub%data%r4,error)
      endif
      if (error)  return
    case (code_buffer_disk)
      call cubeio_message(ioseve%others,rname,'File is not buffered in memory, using disk')
      call cubeio_data_free(cub%data,error)
      if (error)  return
    case default
      call cubeio_message(seve%e,rname,'Unexpected buffering kind')
      error = .true.
      return
    end select
  end subroutine cubeio_get_cube_data_core
  !
end subroutine cubeio_get_cube_data
!
subroutine cubeio_read_header(name,cubeid,cub,error)
  use gkernel_interfaces
  use cubefitsio_image_read
  use cubefitsio_header_read
  use cubeio_interfaces, except_this=>cubeio_read_header
  use cubeio_types
  use cubeio_messaging
  !------------------------------------------------------------------
  ! @ private
  ! Read the file header from disk in the proper structure
  !------------------------------------------------------------------
  character(len=*),    intent(in)    :: name    ! File name
  character(len=*),    intent(in)    :: cubeid  ! Identifier (feedback only)
  type(cubeio_cube_t), intent(inout) :: cub
  logical,             intent(inout) :: error
  ! Local
  character(len=*), parameter :: rname='READ>HEADER'
  integer(kind=4) :: filekind
  !
  ! Read header to get the file information
  call gag_file_guess(rname,name,filekind,error)
  if (error)  return
  !
  select case (filekind)
  case (1)
    cub%desc%filekind = code_filekind_gdf
    cub%hgdf%file = name
    cub%hgdf%blc = 0
    cub%hgdf%trc = 0
    call gdf_read_header(cub%hgdf,error)
    if (error) return
  case (2)
    cub%desc%filekind = code_filekind_fits
    call cubefitsio_image_open(cub%hfits,name,cubeid,error)
    if (error)  return
    call cubefitsio_header_fill(cub%hfits,error)
    if (error)  return
  case default
    call cubeio_message(seve%e,rname,'Unsupported file kind')
    error = .true.
    return
  end select
  !
end subroutine cubeio_read_header
!
subroutine cubeio_read_cube_data_r4(rname,cubset,cub,range,data,error)
  use gkernel_interfaces
  use cubetools_nan
  use cubefitsio_image_read
  use cubeio_interfaces, except_this=>cubeio_read_cube_data_r4
  use cubeio_types
  use cubeio_messaging
  use cubeio_timing
  !---------------------------------------------------------------------
  ! @ private-generic cubeio_read_cube_data
  ! Wrapper around gdf_read_data (R*4 version)
  !---------------------------------------------------------------------
  character(len=*),     intent(in)    :: rname
  type(cube_setup_t),   intent(in)    :: cubset
  type(cubeio_cube_t),  intent(inout) :: cub   ! INOUT for cub%hgdf%blc/trc
  type(cubeio_range_t), intent(in)    :: range
  real(kind=4),         intent(out)   :: data(:,:,:)
  logical,              intent(inout) :: error
  ! Local
  type(cputime_t) :: tmp
  integer(kind=index_length) :: i2,i3
  real(kind=4) :: bval,eval
  !
  call gag_cputime_init(tmp)
  !
  ! Need to open or reopen (if not yet opened)
  call cub%open(error)
  if (error)  return
  !
  select case (cub%desc%filekind)
  case (code_filekind_fits)
    ! FITS
    call cubefitsio_image_dataread(cub%hfits,data,range%blc,range%trc,error)
    if (error)  return
    bval = 0.
    eval = -1.
  case (code_filekind_gdf)
    ! Gildas-Data-Format
    cub%hgdf%blc(:) = range%blc(:)
    cub%hgdf%trc(:) = range%trc(:)
    call gdf_read_data(cub%hgdf,data,error)
    if (error)  return
    bval = cub%hgdf%gil%bval
    eval = cub%hgdf%gil%eval
  case default
    call cubeio_message(seve%e,rname,'No associated file on disk')
    error = .true.
    return
  end select
  !
  if (cub%desc%unblank.eq.code_patchblank_otf) then
    do i3=1,ubound(data,3)
      do i2=1,ubound(data,2)
        where (abs(data(:,i2,i3)-bval).le.eval)
          data(:,i2,i3) = gr4nan
        end where
      enddo
    enddo
  endif
  !
  call gag_cputime_add(cub%time%read,tmp)
  !
end subroutine cubeio_read_cube_data_r4
!
subroutine cubeio_read_cube_data_c4(rname,cubset,cub,range,data,error)
  use gkernel_interfaces
  use cubeio_interfaces, except_this=>cubeio_read_cube_data_c4
  use cubeio_types
  use cubeio_messaging
  use cubeio_timing
  !---------------------------------------------------------------------
  ! @ private-generic cubeio_read_cube_data
  ! Wrapper around gdf_read_data (C*4 version)
  !---------------------------------------------------------------------
  character(len=*),     intent(in)    :: rname
  type(cube_setup_t),   intent(in)    :: cubset
  type(cubeio_cube_t),  intent(inout) :: cub   ! INOUT for cub%hgdf%blc/trc
  type(cubeio_range_t), intent(in)    :: range
  complex(kind=4),      intent(out)   :: data(:,:,:)
  logical,              intent(inout) :: error
  ! Local
  type(cputime_t) :: tmp
  !
  call gag_cputime_init(tmp)
  !
  ! Need to open or reopen (if not yet opened)
  call cub%open(error)
  if (error)  return
  !
  select case (cub%desc%filekind)
  case (code_filekind_fits)
    call cubeio_message(seve%e,rname,'Reading data from a C*4 FITS is not implemented')
    error = .true.
    return
  case (code_filekind_gdf)
    cub%hgdf%blc(:) = range%blc(:)
    cub%hgdf%trc(:) = range%trc(:)
    call gdf_read_data(cub%hgdf,data,error)
    if (error)  return
  case default
    call cubeio_message(seve%e,rname,'No associated file on disk')
    error = .true.
    return
  end select
  !
  if (cub%desc%unblank.eq.code_patchblank_otf) then
    ! Blank complex*4 values in GDF is an undocumented feature, if such feature
    ! ever existed.
  endif
  !
  call gag_cputime_add(cub%time%read,tmp)
end subroutine cubeio_read_cube_data_c4
!
subroutine cubeio_check_input_chan_block(cubset,head,cub,first,last,error)
  use cubeio_messaging
  use cubetools_setup_types
  use cubeio_interfaces, except_this=>cubeio_check_input_chan_block
  use cubeio_chan
  use cubeio_types
  !---------------------------------------------------------------------
  ! @ private
  ! Check if the current block buffer provides the fichan:lichan channel
  ! range. If not, flush the previous contents and load proper one.
  !---------------------------------------------------------------------
  type(cube_setup_t),   intent(in)    :: cubset
  type(cube_header_t),  intent(in)    :: head
  type(cubeio_cube_t),  intent(inout) :: cub
  integer(kind=chan_k), intent(in)    :: first
  integer(kind=chan_k), intent(in)    :: last
  logical,              intent(inout) :: error
  ! Local
  character(len=*), parameter :: rname='CHECK>INPUT>CHAN>BLOCK'
  character(len=message_length) :: mess
  integer(kind=chan_k) :: nchanperblock,fichan,lichan
  !
  fichan = cubeio_chan_number(cub,first)
  lichan = cubeio_chan_number(cub,last)
  !
  ! ZZZ Check sanity of cub%block => should be LMV from previous calls
  ! ZZZ Assume fichan.le.lichan
  if (fichan.ge.cub%block%first .and. lichan.le.cub%block%last)  return
  !
  ! Before flushing the current buffer, ensure all the tasks are finished:
  !$OMP TASKWAIT
  !
  if (fichan.ne.cub%block%last+1) then
    ! This means the channel buffers are not read contiguously, in order.
    ! One of the risk is a possible overlap of the new buffer with
    ! data already read from disk.
    if (cub%block%last.ne.0)  &  ! No warning if block was not yet used
      call cubeio_message(seve%w,rname,'Non-contiguous input buffer might be unefficient')
  endif
  !
  ! Need to buffer another region. Flush current contents first if it has
  ! been modified
  call cubeio_flush_any_block(cubset,head,cub,cub%block,error)
  if (error)  return
  !
  call cubeio_max_chan_block(cubset,cub,cubset%buff%block,'SET\BUFFER /BLOCK',  &
    nchanperblock,error)
  if (error)  return
  if (nchanperblock.lt.lichan-fichan+1) then
    call cubeio_message(seve%e,rname,  &
      'SET\BUFFERING /PARALLEL must be smaller than SET\BUFFERING /BLOCK')
    ! It should even be MUCH larger than the buffer block, so that several ranges
    ! can be processed in parallel by several parallelisation tasks.
    error = .true.
    return
  endif
  call cubeio_block_reallocate(cubset,cub%block,cub%desc%iscplx,  &
    cub%desc%nx,cub%desc%ny,nchanperblock,code_cube_imaset,error)
  if (error)  return
  cub%block%first = fichan
  cub%block%last  = min(fichan+nchanperblock-1,cub%desc%nc)
  write(mess,'(2(A,I0))')  &
    'Buffering input channel block from ',cub%block%first,' to ',cub%block%last
  call cubeio_message(ioseve%others,rname,mess)
  call cubeio_read_chan_block(cubset,cub,cub%block,error)
  if (error)  return
  ! At start the buffer is read-only. Note that this may change in a sequence
  ! read_chan(1), write_chan(1), read_chan(2), write_chan(2), ... Do not reset
  ! to .false. afterwards
  cub%block%readwrite = .false.
  !
end subroutine cubeio_check_input_chan_block
!
subroutine cubeio_check_input_pix_block(cubset,head,cub,first,last,error)
  use cubeio_messaging
  use cubetools_setup_types
  use cubeio_interfaces, except_this=>cubeio_check_input_pix_block
  use cubeio_pix
  use cubeio_types
  !---------------------------------------------------------------------
  ! @ private
  ! Check if the current block buffer provides the fypix:lypix Y pixel
  ! row range. If not, flush the previous contents and load proper one.
  !---------------------------------------------------------------------
  type(cube_setup_t),   intent(in)    :: cubset
  type(cube_header_t),  intent(in)    :: head
  type(cubeio_cube_t),  intent(inout) :: cub
  integer(kind=pixe_k), intent(in)    :: first
  integer(kind=pixe_k), intent(in)    :: last
  logical,              intent(inout) :: error
  ! Local
  character(len=*), parameter :: rname='CHECK>INPUT>PIX>BLOCK'
  integer(kind=pixe_k) :: nyperblock,fypix,lypix
  character(len=message_length) :: mess
  !
  fypix = cubeio_ypix_number(cub,first)
  lypix = cubeio_ypix_number(cub,last)
  !
  ! ZZZ Check sanity of cub%block => should be VLM from previous calls
  ! ZZZ Assume fypix.le.lypix
  if (fypix.ge.cub%block%first .and. lypix.le.cub%block%last)  return
  !
  ! Before flushing the current buffer, ensure all the tasks are finished:
  !$OMP TASKWAIT
  !
  if (fypix.ne.cub%block%last+1) then
    ! This means the Y row buffers are not read contiguously, in order.
    ! One of the risk is a possible overlap of the new buffer with
    ! data already read from disk.
    if (cub%block%last.ne.0)  &  ! No warning if block was not yet used
      call cubeio_message(seve%w,rname,'Non-contiguous input buffer might be unefficient')
  endif
  !
  ! Need to buffer another region. Flush current contents first if it has
  ! been modified
  call cubeio_flush_any_block(cubset,head,cub,cub%block,error)
  if (error)  return
  !
  call cubeio_max_y_block(cubset,cub,cubset%buff%block,'SET\BUFFER /BLOCK',  &
    nyperblock,error)
  if (error)  return
  if (nyperblock.lt.lypix-fypix+1) then
    call cubeio_message(seve%e,rname,  &
      'SET\BUFFERING /PARALLEL must be smaller than SET\BUFFERING /BLOCK')
    ! It should even be MUCH larger than the buffer block, so that several ranges
    ! can be processed in parallel by several parallelisation tasks.
    error = .true.
    return
  endif
  call cubeio_block_reallocate(cubset,cub%block,cub%desc%iscplx,  &
    cub%desc%nx,nyperblock,cub%desc%nc,code_cube_speset,error)
  if (error)  return
  cub%block%first = fypix
  cub%block%last  = min(fypix+nyperblock-1,cub%desc%ny)
  write(mess,'(2(A,I0))')  &
    'Buffering input Y row block from ',cub%block%first,' to ',cub%block%last
  call cubeio_message(ioseve%others,rname,mess)
  call cubeio_read_y_block(cubset,cub,cub%block,error)
  if (error)  return
  ! At start the buffer is read-only. Note that this may change in a sequence
  ! read_pix(1), write_pix(1), read_pix(2), write_pix(2), ... Do not reset
  ! to .false. afterwards
  cub%block%readwrite = .false.
  !
end subroutine cubeio_check_input_pix_block
!
subroutine cubeio_check_input_any_block(cubset,head,cub,first,last,error)
  use cubeio_messaging
  use cubetools_setup_types
  use cubeio_interfaces, except_this=>cubeio_check_input_any_block
  use cubeio_subcube
  use cubeio_types
  !---------------------------------------------------------------------
  ! @ private
  ! Check if the current block buffer provides the fi3:li3 data
  ! range. If not, flush the previous contents and load proper one.
  !---------------------------------------------------------------------
  type(cube_setup_t),   intent(in)    :: cubset
  type(cube_header_t),  intent(in)    :: head
  type(cubeio_cube_t),  intent(inout) :: cub
  integer(kind=data_k), intent(in)    :: first
  integer(kind=data_k), intent(in)    :: last
  logical,              intent(inout) :: error
  ! Local
  character(len=*), parameter :: rname='CHECK>INPUT>ANY>BLOCK'
  character(len=message_length) :: mess
  integer(kind=data_k) :: n3perblock,fi3,li3,n3
  !
  fi3 = cubeio_plane_number(cub,first)
  li3 = cubeio_plane_number(cub,last)
  ! ZZZ Assume fi3.le.li3
  !
  ! During the iteration, it is valid to request planes beyond
  ! the cube (e.g. surset extraction with EXTRACT). Deal with this:
  n3 = cub%desc%n3
  if (fi3.gt.n3 .or. li3.lt.1) then
    ! The range is fully off the cube. Free the block buffer and assume
    ! the caller will not use it.
    call cubeio_flush_any_block(cubset,head,cub,cub%block,error)
    if (error)  return
    call cubeio_block_free(cub%block,error)
    if (error)  return
    return
  endif
  if ((fi3.lt.1  .and. li3.ge.1) .or. &
      (fi3.le.n3 .and. li3.gt.n3)) then
    ! The range overlaps the cube boundaries. Solution?
    ! 1) Build a buffer with expected number of planes, this
    !   requires allocating a dedicated data array (instead of
    !   usual pointer, hence inefficient), and put NaN or valid
    !   values where relevant => too complicated.
    ! 2) Return a buffer with less planes (only the valid ones
    !    from the input cube => this breaks the rule to provide
    !    the requested range
    ! => Rejected!
    call cubeio_message(seve%e,rname,  &
      'Internal error: the input range overlaps the cube boundaries')
    error = .true.
    return
  endif
  !
  if (fi3.ge.cub%block%first .and. li3.le.cub%block%last)  return
  !
  ! Before flushing the current buffer, ensure all the tasks are finished:
  !$OMP TASKWAIT
  !
  if (fi3.ne.cub%block%last+1) then
    ! This means the channel buffers are not read contiguously, in order.
    ! One of the risk is a possible overlap of the new buffer with
    ! data already read from disk.
    if (cub%block%last.ne.0)  &  ! No warning if block was not yet used
      call cubeio_message(seve%w,rname,'Non-contiguous input buffer might be unefficient')
  endif
  !
  ! Need to buffer another region. Flush current contents first if it has
  ! been modified
  call cubeio_flush_any_block(cubset,head,cub,cub%block,error)
  if (error)  return
  !
  call cubeio_max_any_block(cubset,cub,cubset%buff%block,'SET\BUFFER /BLOCK',  &
    n3perblock,error)
  if (error)  return
  if (n3perblock.lt.li3-fi3+1) then
    call cubeio_message(seve%e,rname,  &
      'SET\BUFFERING /PARALLEL must be smaller than SET\BUFFERING /BLOCK')
    ! It should even be MUCH larger than the buffer block, so that several ranges
    ! can be processed in parallel by several parallelisation tasks.
    error = .true.
    return
  endif
  n3perblock = li3-fi3+1  ! Allocate only stricly needed, to ensure satisfying the
                          ! contiguity tested above at next call. This is specific
                          ! to the subcube access, and might degrade performance.
                          ! Should definitely not be done for spectrum/image access
                          ! else we would allocate planes one by one.
  call cubeio_anyblock_reallocate(cubset,cub%block,cub%desc%iscplx,  &
    cub%desc%n1,cub%desc%n2,n3perblock,code_cube_unkset,error)
  if (error)  return
  cub%block%first = fi3
  cub%block%last  = min(fi3+n3perblock-1,cub%desc%n3)
  write(mess,'(2(A,I0))')  &
    'Buffering input channel block from ',cub%block%first,' to ',cub%block%last
  call cubeio_message(ioseve%others,rname,mess)
  call cubeio_read_any_block(cubset,cub,cub%block,error)
  if (error)  return
  ! At start the buffer is read-only. Note that this may change in a sequence
  ! read_any(1), write_any(1), read_any(2), write_any(2), ... Do not reset
  ! to .false. afterwards
  cub%block%readwrite = .false.
  !
end subroutine cubeio_check_input_any_block
!
subroutine cubeio_read_chan_block(cubset,cub,lmvblock,error)
  use cubeio_messaging
  use gkernel_interfaces
  use cubetools_setup_types
  use cubeio_interfaces, except_this=>cubeio_read_chan_block
  use cubeio_types
  !---------------------------------------------------------------------
  ! @ private
  ! Read a LMV block of channels from LMV or VLM file on disk
  !---------------------------------------------------------------------
  type(cube_setup_t),   intent(in)    :: cubset
  type(cubeio_cube_t),  intent(inout) :: cub
  type(cubeio_block_t), intent(inout) :: lmvblock
  logical,              intent(inout) :: error
  ! Local
  character(len=*), parameter :: rname='READ>CHAN>BLOCK'
  type(cubeio_block_t) :: vlmblock
  integer(kind=pixe_k) :: iy,lmvy,vlmy,nyperblock
  integer(kind=chan_k) :: nchan,lmvchan,vlmchan
  character(len=message_length) :: mess
  type(cubeio_range_t) :: range
  integer(kind=4), parameter :: downfactor=10
  real(kind=4) :: maxsize
  !
  ! Sanity
  if (lmvblock%order.ne.code_cube_imaset) then
    call cubeio_message(seve%e,rname,'Internal error: block is not LMV')
    error = .true.
    return
  endif
  if (cub%desc%iscplx.neqv.lmvblock%iscplx) then
    call cubeio_message(seve%e,rname,'Channel block and output cube mismatch type (R*4/C*4)')
    error = .true.
    return
  endif
  if (lmvblock%first.lt.1 .or. lmvblock%last.gt.cub%desc%nc) then
    call cubeio_message(seve%e,rname,'Internal error: invalid range')
    error = .true.
    return
  endif
  nchan = lmvblock%last-lmvblock%first+1  ! Useful size of the block
  !
  select case (cub%desc%order)
  case (code_cube_imaset)
    ! Easy: just a contiguous piece to be extracted
    range%blc(:) = 0
    range%trc(:) = 0
    range%blc(3) = lmvblock%first
    range%trc(3) = lmvblock%last
    if (lmvblock%iscplx) then
      call cubeio_read_cube_data(rname,cubset,cub,range,lmvblock%c4,error)
    else
      call cubeio_read_cube_data(rname,cubset,cub,range,lmvblock%r4,error)
    endif
    if (error)  return
    !
  case (code_cube_speset)
    ! Transposition needed: this means traversing the whole file with
    ! an intermediate buffer to collect the elements
    write(mess,'(3(A,I0))')  'Collecting LMV block ',lmvblock%first,' to ',  &
      lmvblock%last,' from VLM file'
    call cubeio_message(ioseve%trans,rname,mess)
    !
    maxsize = cubset%buff%block/downfactor
    call cubeio_max_y_block(cubset,cub,maxsize,'SET\BUFFER /BLOCK',  &
      nyperblock,error)
    if (error)  return
    call cubeio_block_reallocate(cubset,vlmblock,cub%desc%iscplx,cub%desc%nx,&
         nyperblock,cub%desc%nc,code_cube_speset,error)
    if (error)  return
    vlmblock%readwrite = .false.
    vlmblock%last = 0
    do iy=1,cub%desc%ny  ! For ALL Y pixel rows in VLM file
      if (iy.gt.vlmblock%last) then
        vlmblock%first = iy
        vlmblock%last = min(iy+nyperblock-1,cub%desc%ny)
        ! Note 1: we read here ALL Y pixel rows but a SUBSET of channels
        write(mess,'(2(A,I0))')  'Reading Y pixels block from ',vlmblock%first,' to ',vlmblock%last
        call cubeio_message(ioseve%trans,rname,mess)
        call cubeio_read_y_block(cubset,cub,vlmblock,error)
        if (error)  return
      endif
      ! Note 2: we write here a SUBSET of channels for current Y pixel row
      lmvy =  iy                    ! Position of first pixel of this Y row in lmvblock
      vlmy = (iy-vlmblock%first+1)  ! Position of first pixel of this Y row in vlmblock
      if (lmvblock%iscplx) then
        do lmvchan=1,nchan  ! For the desired SUBSET of channels
          vlmchan = lmvchan+lmvblock%first-1
          lmvblock%c4(:,lmvy,lmvchan) = vlmblock%c4(vlmchan,:,vlmy)
        enddo
      else
        do lmvchan=1,nchan  ! For the desired SUBSET of channels
          vlmchan = lmvchan+lmvblock%first-1
          lmvblock%r4(:,lmvy,lmvchan) = vlmblock%r4(vlmchan,:,vlmy)
        enddo
      endif
    enddo
    !
    call cubeio_block_free(vlmblock,error)
    !
  case default
    call cubeio_message(seve%e,rname,'Unsupported cube order')
    error = .true.
    return
  end select
end subroutine cubeio_read_chan_block
!
subroutine cubeio_read_y_block(cubset,cub,vlmblock,error)
  use cubeio_messaging
  use gkernel_interfaces
  use cubetools_setup_types
  use cubeio_interfaces, except_this=>cubeio_read_y_block
  use cubeio_types
  !---------------------------------------------------------------------
  ! @ private
  ! Read a VLM block of channels from VLM or LMV file on disk
  !---------------------------------------------------------------------
  type(cube_setup_t),   intent(in)    :: cubset
  type(cubeio_cube_t),  intent(inout) :: cub
  type(cubeio_block_t), intent(inout) :: vlmblock
  logical,              intent(inout) :: error
  ! Local
  character(len=*), parameter :: rname='READ>Y>BLOCK'
  type(cubeio_block_t) :: lmvblock
  integer(kind=chan_k) :: ichan,lmvchan,vlmchan,nchanperblock
  integer(kind=pixe_k) :: ny,lmvy,vlmy
  character(len=message_length) :: mess
  type(cubeio_range_t) :: range
  integer(kind=4), parameter :: downfactor=10
  real(kind=4) :: maxsize
  !
  ! Sanity
  if (vlmblock%order.ne.code_cube_speset) then
    call cubeio_message(seve%e,rname,'Internal error: block is LMV')
    error = .true.
    return
  endif
  if (cub%desc%iscplx.neqv.vlmblock%iscplx) then
    call cubeio_message(seve%e,rname,'Y rows block and output cube mismatch type (R*4/C*4)')
    error = .true.
    return
  endif
  if (vlmblock%first.lt.1 .or. vlmblock%last.gt.cub%desc%ny) then
    call cubeio_message(seve%e,rname,'Internal error: invalid range')
    error = .true.
    return
  endif
  ny = vlmblock%last-vlmblock%first+1  ! Useful size of the block
  !
  select case (cub%desc%order)
  case (code_cube_speset)
    ! Easy: just a contiguous piece to be extracted
    range%blc(:) = 0
    range%trc(:) = 0
    range%blc(3) = vlmblock%first
    range%trc(3) = vlmblock%last
    if (lmvblock%iscplx) then
      call cubeio_read_cube_data(rname,cubset,cub,range,vlmblock%c4,error)
    else
      call cubeio_read_cube_data(rname,cubset,cub,range,vlmblock%r4,error)
    endif
    if (error)  return
    !
  case (code_cube_imaset)
    ! Transposition needed: this means traversing the whole file with
    ! an intermediate buffer to collect the elements
    write(mess,'(3(A,I0))')  'Collecting VLM block ',vlmblock%first,' to ',  &
      vlmblock%last,' from LMV file'
    call cubeio_message(ioseve%trans,rname,mess)
    !
    maxsize = cubset%buff%block/downfactor
    call cubeio_max_chan_block(cubset,cub,maxsize,'SET\BUFFER /BLOCK',  &
      nchanperblock,error)
    if (error)  return
    call cubeio_block_reallocate(cubset,lmvblock,cub%desc%iscplx,&
         cub%desc%nx,cub%desc%ny,nchanperblock,code_cube_imaset,error)
    if (error)  return
    lmvblock%readwrite = .false.
    lmvblock%last = 0
    do ichan=1,cub%desc%nc  ! For ALL channels in LMV file
      if (ichan.gt.lmvblock%last) then
        lmvblock%first = ichan
        lmvblock%last = min(ichan+nchanperblock-1,cub%desc%nc)
        ! Note 1: we read here ALL channels but a SUBSET of Y rows
        write(mess,'(2(A,I0))')  'Reading channel block from ',lmvblock%first,' to ',lmvblock%last
        call cubeio_message(ioseve%trans,rname,mess)
        call cubeio_read_chan_block(cubset,cub,lmvblock,error)
        if (error)  return
      endif
      ! Note 2: we write here a SUBSET of Y rows for current channel
      vlmchan =  ichan                    ! Position of first pixel of this channel in vlmblock
      lmvchan = (ichan-lmvblock%first+1)  ! Position of first pixel of this channel in lmvblock
      if (vlmblock%iscplx) then
        do vlmy=1,ny  ! For the desired SUBSET of Y rows
          lmvy = vlmy+vlmblock%first-1
          vlmblock%c4(vlmchan,:,vlmy) = lmvblock%c4(:,lmvy,lmvchan)
        enddo
      else
        do vlmy=1,ny  ! For the desired SUBSET of Y rows
          lmvy = vlmy+vlmblock%first-1
          vlmblock%r4(vlmchan,:,vlmy) = lmvblock%r4(:,lmvy,lmvchan)
        enddo
      endif
    enddo
    !
    call cubeio_block_free(lmvblock,error)
    !
  case default
    call cubeio_message(seve%e,rname,'Unsupported cube order')
    error = .true.
    return
  end select
end subroutine cubeio_read_y_block
!
subroutine cubeio_read_any_block(cubset,cub,anyblock,error)
  use cubeio_messaging
  use gkernel_interfaces
  use cubetools_setup_types
  use cubeio_interfaces, except_this=>cubeio_read_any_block
  use cubeio_types
  !---------------------------------------------------------------------
  ! @ private
  ! Read a block of planes from file on disk
  !---------------------------------------------------------------------
  type(cube_setup_t),   intent(in)    :: cubset
  type(cubeio_cube_t),  intent(inout) :: cub
  type(cubeio_block_t), intent(inout) :: anyblock
  logical,              intent(inout) :: error
  ! Local
  character(len=*), parameter :: rname='READ>ANY>BLOCK'
  integer(kind=data_k) :: n3
  type(cubeio_range_t) :: range
  !
  ! Sanity
  if (anyblock%order.ne.code_cube_unkset) then  ! ZZZ Is this test relevant?
    call cubeio_message(seve%e,rname,'Internal error: block has not ANY order')
    error = .true.
    return
  endif
  if (cub%desc%iscplx.neqv.anyblock%iscplx) then
    call cubeio_message(seve%e,rname,'Block and output cube mismatch type (R*4/C*4)')
    error = .true.
    return
  endif
  if (anyblock%first.lt.1 .or. anyblock%last.gt.cub%desc%n3) then
    call cubeio_message(seve%e,rname,'Internal error: invalid range')
    error = .true.
    return
  endif
  n3 = anyblock%last-anyblock%first+1  ! Useful size of the block
  !
  select case (cub%desc%order)
  case (code_cube_imaset,code_cube_speset)
    ! Easy: just a contiguous piece to be extracted
    range%blc(:) = 0
    range%trc(:) = 0
    range%blc(3) = anyblock%first
    range%trc(3) = anyblock%last
    if (anyblock%iscplx) then
      call cubeio_read_cube_data(rname,cubset,cub,range,anyblock%c4,error)
    else
      call cubeio_read_cube_data(rname,cubset,cub,range,anyblock%r4,error)
    endif
    if (error)  return
    !
  case default
    call cubeio_message(seve%e,rname,'Unsupported cube order')
    error = .true.
    return
  end select
end subroutine cubeio_read_any_block
!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
