#############################################################################
# CDMS: Interface to CDMS-simulator
#   (c) 1997/2000: Rolf Schrödter, DLR, Institut für Planetenerkundung
#############################################################################
# $Id: BrowseUncompress.tcl,v 1.4 2006-11-22 13:51:29+01 rolf Exp rolf $
#############################################################################
# BrowseUncompress.tcl : Uncompress ROLIS images (frame type = 58xx)
#############################################################################

package require Decoder

namespace eval uc {
    variable UC
    variable Prev
    variable Frame
}

proc uc::errorMsg { msg {code 0} } {
    puts "Uncompress - $msg"
    if { $code } {
        exit $code
    }
}
proc uc::frameMsg { msg } {
    variable UC
    uc::errorMsg "frame#$UC(FrameNo) / $msg"
}

proc uc::init {} {
    variable UC
    variable Frame

    # Compression rates = NT tiles / NF frames
    set UC(Rate.0)      {1 16}
    set UC(Rate.1)      {1 12}
    set UC(Rate.2)      {1  8}
    set UC(Rate.3)      {1  6}
    set UC(Rate.4)      {1  4}
    set UC(Rate.5)      {1  3}
    set UC(Rate.6)      {2  5}
    set UC(Rate.7)      {1  2}
    set UC(Rate.8)      {3  5}
    set UC(Rate.9)      {2  3}
    set UC(Rate.10)     {3  4}
    set UC(Rate.11)     {1  1}
    set UC(Rate.12)     {5  4}
    set UC(Rate.13)     {3  2}
    set UC(Rate.14)     {2  1}
    set UC(Rate.15)     {3  1}

    set UC(CFrameNo)    0       ;# Compressed frame counter
    set UC(FrameNo)     0       ;# Total frame counter
    set UC(ImgCount)    0       ;# Total image counter
    set UC(ImgFrames)   0       ;# Total frames in image

    set UC(ImgNo)       -1      ;# Rolis image buffer number
    set UC(ImgRate)     -1      ;# Rolis image compression rate
    set UC(ImgOBT)      ?       ;# CDMS string onboard time of first frame of this image
    set UC(ImgOBT.Num)  0       ;# CDMS numeric onboard time of first frame of this image

    set Frame(OBT)      ?       ;# CDMS header: Onboard time string
    set Frame(OBT.Num)  0       ;# CDMS header: Numeric onboard time (seconds)

    set Frame(Type)     0       ;# Frame type, should be = 0x58
    set Frame(Rate)     -1      ;# Rate of frame
    set Frame(Count)    -1      ;# Counter of  frame
    set Frame(TCount)   -1      ;# Type dependent frame counter of frame
                                ;# Calculated fields)

    set Frame(ImgNo)    -1      ;# Image number from frame data
    set Frame(TileNo)   -1      ;# Tile number from frame data

    set Frame(NT)       0       ;# Number of Tiles
    set Frame(NF)       0       ;# Number of Frames
    set Frame(TS)       0       ;# Tile size in bytes

    uc::reset
}

proc uc::reset {} {
    variable UC
    variable Frame
    variable Prev
    variable Tile

    set Tile(Data)  {}          ;# Tile data, binary
    set Tile(Id)    0           ;# Tile identifier 0..255
    set Tile(MSB)   0           ;# MSB of tile data
    set Tile(Img)   0           ;# Tile image

    set UC(EOB)         1       ;# End-of-coder-block

    array set Prev [array get Frame]    ;# Prev frame data = Frame
}


#########################################################
# Little helpers for debugging
#
proc uc::dump { data {size 16} } {
    binary scan $data S$size int_list
    foreach val $int_list {
        append result [format "%04X " [expr {($val+0x10000)&0xFFFF}] ]
    }
    return $result
}
proc uc::dump1 {data {size 16}} {
    if { $size > [string length $data] } {
        set size [string length $data]
    }
    binary scan $data c$size byte_list
    foreach byte $byte_list {
        append result [format "%02X " [expr {($byte +0x100)&0xFF}]]
    }
    return $result
}

#########################################################
# Extract frame parameters Frame(*)
#   Rate    - Compression rate = 0..15  --> NT,NF,TS
#   NT      - Number of tiles per coder block
#   NF      - Number of frames per coder block
#   TS      - Tile size in bytes
#   Count   - Frame counter for current coder block = 0..NF-1
#   TCount  - Global frame counter for all compressed frames, type=58xx
#
proc uc::frameParams { data } {
    variable UC
    variable Frame

    binary scan $data S1S1S1 x1 x2 x3
    set Frame(Type)     [expr {($x1 >> 8) & 0x00FF}]        ;# Should be = 0x58

    if { $Frame(Type) == 0x53 } {       ;# ISB frame found

        # Remember this ISB & search next ISB
        #
        browse::isb::found $data yes

        if { $UC(ImgFrames) > 0 } {    ;# Flush Image on ISB frame
            puts "ISB found, start new image"
            uc::newImage
            return 0
        }
    }
    if { $Frame(Type) != 0x58 } {
        return 0
    }
    set Frame(Rate)     [expr {($x1 >> 4) & 0x000F}]        ;# 0..15
    set Frame(Count)    [expr { $x1       & 0x000F}]        ;# 0..15
    set Frame(TCount)   [expr {($x2+0x10000) & 0xFFFF}]     ;# 0..65535

    if { $Frame(Count) == 0 } {                             ;# Starts with tile data
        set Frame(ImgNo)    [expr {($x3 >> 5) & 0x0007}]    ;# 0..7
        set Frame(TileNo)   [expr {($x3 >> 8) & 0x00FF}]    ;# 0..255
    }

    set n $Frame(Rate)
    foreach {Frame(NT) Frame(NF)} $UC(Rate.$n) {}

    set Frame(TS)   [expr {$Frame(NF)*252/$Frame(NT)}]

    return 1
}

#######################################################
# Get OFFSET of next starting tile for current frame
# returns:
#   -1      No tile starting in this frame
#   =0      Tile starts with frame (new frame block: count=0)
#   >0      Byte position of starting tile
#
proc uc::tileOfs {} {
    variable Frame

    if { $Frame(Count) == 0 } {
        return 0
    }
    set absPos  [expr {(252 * $Frame(Count) / $Frame(TS) + 1) *$Frame(TS) }]    ;# Position of next starting tile within NF frames
    set ofs     [expr {$absPos - $Frame(Count) * 252}]                          ;# Offset of next tile within current frame

    expr { ($ofs < 252) ? $ofs : -1 }   ;# Offset = -1 if next tile is outside current frame
}

#########################################################
# Check whether there are more tiles after POS in frame
# returns:
#   TRUE    - There are more tiles in current frame
#   FALSE   - No more tiles in current frame
#
proc uc::endOfBlock { pos } {
    variable Frame

    expr { $Frame(Count)*252 + $pos >= $Frame(NT)*$Frame(TS) }
}

#########################################################
# Check whether there are more tiles after POS in frame.
# Output of various messages.
# Returns:
#   TRUE    - Frame continues prev. frame
#   FALSE   - Missing frame or startin a new coder block
#
# Check if prev Tile is probably lossless
#
proc uc::lossless {} {
    variable Tile

    set size [string length $Tile(Data)]
    if { $size > 0 } {
        set last [expr {$size-1}]   ;# position of last byte in tile
        binary scan $Tile(Data) @${last}c1 lastByte
        return [expr {$lastByte == 0}]
    } else {
        return 1
    }
}
proc uc::checkPrev {} {
    variable UC
    variable Prev
    variable Frame

    set continued 1     ;# flag, whether frame continues tile in prev. frame

    if { $Frame(TCount) != ( $Prev(TCount) + 1 ) & 0xFFFF } {
        uc::frameMsg "Missing frame TCount=$Prev(TCount) -> $Frame(TCount)"
        set continued 0
    }
    if { $Frame(Rate) != $Prev(Rate) } {
        uc::frameMsg "Unexpected rate change: prev=$Prev(Rate) frame=$Frame(Rate)"
        set continued 0
        if { $Frame(Count) != 0 } {
            uc::frameMsg "New rate=Frame(Rate) starts with first frame=$Frame(Count)"
        }
    }
    if { $Frame(Count) == 0 } {
        set continued 0
        if { $Prev(Count) != $Prev(NF)-1 } {
            if { ! [uc::lossless] } {
                uc::frameMsg "Unexpected begin of block: NF=$Prev(NF) last frame=$Prev(Count)"
            }
        }
    } elseif { $Frame(Count) != $Prev(Count) + 1 } {
        set continued 0
        uc::frameMsg "Missing frames: count=$Prev(Count) -> $Frame(Count)"
    }
    return $continued
}

#########################################################
# Check tile if it is empty
#
proc uc::emptyTile {} {
    variable Tile

    binary scan $Tile(Data) S1 x    ;# Empty bytes are filled with zero
    expr { $x == 0 }                ;# Tile is empty if Id=Img=MSB=0
}

#########################################################
# Flush the current tile, decode it
#
proc uc::flushTile {} {
    variable Tile

    set size [string length $Tile(Data)]
    if { $size } {
        binary scan $Tile(Data) S1 x
        set Tile(Id)    [expr {($x >> 8) & 0x00FF}]
        set Tile(Img)   [expr {($x >> 5) & 0x0007}]
        set Tile(MSB)   [expr { $x       & 0x001F}]
        if {0} {
            puts "# Tile Id=$Tile(Id) Len=$size Img=$Tile(Img) MSB=$Tile(MSB)"
            update
        }

        decoder::decode_tile $Tile(Data)
    }
    set Tile(Data) {}   ;# clear tile
}

#########################################################
# Append data to the current tile
# Side effect: Sets $UC(EOB): end-of-coder-block
#
proc uc::appendTile {data ofs len} {
    variable UC
    variable Tile
    variable Frame

    set pos [expr {$ofs+4}]
    append Tile(Data) [string range $data $pos [expr {$pos+$len-1}] ]

    if { [uc::emptyTile] } {
        uc::frameMsg "Empty tile, frame not filled Count=$Frame(Count) ofs=$ofs"
        set Tile(Data) {}   ;# clear tile
        set UC(EOB) 1       ;# mark end-of-coder-block
    } else {
        incr ofs $len
        set UC(EOB) [uc::endOfBlock $ofs]
    }

    set size [string length $Tile(Data)]
    if { $size > $Frame(TS) } {         ;# Unexpected serious error
        uc::frameMsg "*** Tile too long: size=$size, expected TS=$Frame(TS) ***"
    } elseif { $size == $Frame(TS) } {
        uc::flushTile
    }

    set size [string length $Tile(Data)]
    if { $UC(EOB) && ($size > 0) }  {
        # Unexpected error: end-of-block with unflushed tile
        uc::frameMsg "*** Unexpected End-of-Block  ofs=$ofs tile=$size rate=$Frame(Rate) count=$Frame(Count) TS=$Frame(TS) ***"
        uc::flushTile
    }
}

###############################################
# Copy more tile data
# Starts with a new tile, but may left an unfinished tile.
#
proc uc::copyTiles { data pos } {
    variable UC
    variable Frame

    while { $pos < 252 } {
        if { $UC(EOB) } {
            break
        }
        if { $pos + $Frame(TS) <= 252 } {
            uc::appendTile $data $pos $Frame(TS)
            incr pos $Frame(TS)
        } else {
            uc::appendTile $data $pos [expr {252-$pos}]
            break
        }
    }
}

###############################################
# Start NEW coder BLOCK
#
proc uc::newBlock { data } {
    variable UC
    variable Frame

    set ofs [uc::tileOfs]   ;# Position of new tile in frame
    if { $ofs < 0 } {       ;# Start new block only if there is a tile
        set UC(EOB) 1       ;# Try to find tile in next frame
        uc::frameMsg "New block: Skipping frame TCount=$Frame(TCount) count=$Frame(Count)"
        return              ;# Ignore data
    }

    set UC(EOB) 0           ;# New block starts with a tile in frame
    if { $Frame(Count) != 0 } {
        uc::frameMsg "Coder block starts with count=$Frame(Count), missing frames ?"
    }
    uc::copyTiles $data $ofs     ;# start copying with next starting tile in frame
}

###############################################
# CONTINUE coder BLOCK
#
proc uc::continueBlock { data } {
    variable UC
    variable Frame
    variable Tile

    set ofs [uc::tileOfs]       ;# offset of next tile >=0 or -1
    if { $ofs < 0 } {   ;# continued tile, whole frame
        uc::appendTile $data 0 252
    } elseif { $ofs > 0 } { ;# end of continued tile
        uc::appendTile $data 0 $ofs     ;# 1. Append rest of tile
        uc::copyTiles $data $ofs        ;# 2. Continue copying with next starting tile in frame
    } else {    ;# ofs=0 should only happen for NewBlock: count=0
        uc::frameMsg "*** Continued frame should not have ofs=0 ***"
    }
}

###############################################
# Process next data frame
#
proc uc::nextFrame { data } {
    variable UC
    variable Prev
    variable Frame

    if { ! [uc::frameParams $data] } {  ;# ignore other than Compressed frames
        return
    }
    #
    # Start of a new image, if
    # (A) The image number changes
    # (B) If we have no current image, e.g. after ISB found
    #
    if { ($Frame(ImgNo) != $Prev(ImgNo)) || ($UC(ImgNo) < 0) } {  ;
        uc::newImage $Frame(ImgNo) $Frame(Rate)
    }

    if { $UC(EOB) } {   ;# normal end-of-coding-block
        uc::newBlock $data
    } else {
        set continued [uc::checkPrev]
        if { $continued } {     ;# normal case, no missing frames
            continueBlock $data
        } else {
            uc::flushTile       ;# missing frames
            uc::newBlock $data
        }
    }

    array set Prev [array get Frame]    ;# copy Prev=Frame
    incr UC(CFrameNo)
    incr UC(ImgFrames)
}

###############################################
# Swap low,hi bytes
#
proc uc::swap { data } {
    binary scan $data s* intList    ;# read 16-bit little endian
    binary format S* $intList       ;# format 16-bit big-endian
}

###############################################
# Public astro_header
# subcommands:
#   astro_header format sizeX sizeY - return formatted astro header
#   astro_header scan header        - return [list sizeX sizeY]
#   astro_header size               - return size of astro header
#
proc uc::astro_header { subcmd args } {
    if { [string compare $subcmd "format"] == 0 } {
        foreach {sizeX sizeY} $args {
            return [binary format a3x117s4x30 "PC" [list $sizeX $sizeY 1 1]]
        }
    } elseif {[string compare $subcmd "scan"] == 0} {
        foreach data $args {}
        binary scan $data @120s2 size
        return $size
    } elseif {[string compare $subcmd "size"] == 0} {
        return 158
    } else {
        error "uc::astro_header invalid subcmd"
    }
}

###############################################
# Public fits_header & data
# subcommands:
#   fits_header format sizeX sizeY - return formatted FITS header
#
proc uc::fits_header { subcmd args } {
    if { [string compare $subcmd "format"] == 0 } {
        foreach {sizeX sizeY rate isb} $args {
            array set isbX $isb

            append result [format "%-80s" [format "%-8s= %20s / %s"  "SIMPLE"   "T"     "file conforms to FITS standard" ]]
            append result [format "%-80s" [format "%-8s= %20d / %s"  "BITPIX"   16      "number of bits per data pixel" ]]
            append result [format "%-80s" [format "%-8s= %20d / %s"  "NAXIS"    2       "number of data axes" ]]
            append result [format "%-80s" [format "%-8s= %20d / %s"  "NAXIS1"   $sizeX  "length of data axis 1" ]]
            append result [format "%-80s" [format "%-8s= %20d / %s"  "NAXIS2"   $sizeY  "length of data axis 1" ]]
            append result [format "%-80s" [format "%-8s= '%18s' /"   "ORIGIN"   "DLR, Institute of Planetary Research" ]]
            append result [format "%-80s" [format "%-8s= '%18s' /"   "INSTRUME" "Rosetta Lander Rolis" ]]
            append result [format "%-80s" [format "%-8s  %s"         "COMMENT"  "Raw unprocessed image data" ]]
            append result [format "%-80s" [format "%-8s= %20d / %s"  "COMPRESS" $rate   "Rolis compression rate code" ]]
            if {[llength $isb]} {
                append result [format "%-80s" [format "%-8s= '%18s' / %s"  "ROL-ISB"  "Rolis Image Status Block information" "--- treat with care ---" ]]
                append result [format "%-80s" [format "%-8s= %20d / %s"    "BUFNO"    $isbX(BufNo)    "Rolis-ISB image buffer number"]]
                append result [format "%-80s" [format "%-8s= '%18s' / %s"  "FLAGS"    $isbX(Flags)    "Rolis-ISB imaging flags" ]]
                append result [format "%-80s" [format "%-8s= '%18s' / %s"  "RTIME"    $isbX(RTime)    "Rolis-ISB instrument time" ]]
                append result [format "%-80s" [format "%-8s= '%18s' / %s"  "LOBT"     $isbX(LOBT)     "Rolis-ISB Lander onboard time" ]]
                append result [format "%-80s" [format "%-8s= '%18s' / %s"  "EXPTIME"  $isbX(ExpTime)  "Rolis-ISB exposure time" ]]
                append result [format "%-80s" [format "%-8s= '%18s' / %s"  "LED"      $isbX(LED)      "Rolis-ISB LED status" ]]
                append result [format "%-80s" [format "%-8s= '%18s' / %s"  "IFL-POS"  $isbX(IFL-Pos)  "Rolis-ISB infinity lens position" ]]
                append result [format "%-80s" [format "%-8s= %20d / %s"    "AVRG"     $isbX(Avrg)     "Rolis-ISB image average" ]]
                append result [format "%-80s" [format "%-8s= %20d / %s"    "DMA-ERR"  $isbX(DMA-Err)  "Rolis-ISB DMA error counter (missing rows)" ]]
                append result [format "%-80s" [format "%-8s= '%18s' / %s"  "CH-TEMP1" $isbX(CH-Temp1) "ROLIS-ISB Camera temperature before exposure" ]]
                append result [format "%-80s" [format "%-8s= '%18s' / %s"  "CH-TEMP2" $isbX(CH-Temp2) "ROLIS-ISB Camera temperature after exposure" ]]
                append result [format "%-80s" [format "%-8s= %20d / %s"    "FILTER"   $isbX(Filter)   "Rolis-ISB Image filter (unused=0)" ]]
                append result [format "%-80s" [format "%-8s= %20d / %s"    "ERR-CODE" $isbX(ErrCode)  "Rolis-ISB Error code (unused=0)" ]]
            }
            append result [format "%-80s" [format "%-8s"                   "END" ]]

            return [format "%-2880s" $result]
        }
    } else {
        error "uc::fits_header invalid subcmd"
    }
}

###############################################
# Public pgm_header & data
# subcommands:
#   pgm_header format sizeX sizeY max comment - return formatted PGM header
#
proc uc::pgm_header { subcmd args } {
    if { [string compare $subcmd "format"] == 0 } {
        foreach {sizeX sizeY max comment rate isb} $args {
            append result [format "P2\n# %s\n" $comment]
            append result [format "# DLR Institute of Planetary Research\n# Rosetta Lander Rolis\n" ]
            append result [format "# ROL-ISB=Rolis Image Status Block information --- treat with care ---\n" ]
            foreach {name value} $isb {
                append result [format "# %s=%s\n" $name $value]
            }
            append result [format "# Compress=%s\n" $rate]
            append result [format "%d %d\n%d\n" $sizeX $sizeY $max]
            return $result
        }
    } else {
        error "uc::pgm_header invalid subcmd"
    }
}
proc uc::pgm_data { subcmd data} {
    if { [string compare $subcmd "format"] == 0 } {
if 0 {
        if {[string compare $::tcl_platform(byteOrder) "bigEndian"] == 0} {
            set data [uc::swap $data]
        }
}
        binary scan $data s* intList
        set i 0
        foreach x $intList {
            incr i
            append result [format "%-5d%s" $x [expr {($i % 10) ? " ":"\n"}] ]
        }
    } else {
        error "uc::pgm_data invalid subcmd"
    }
    set result
}

#######################################################
# Write BINARY data to file
proc uc::write_bin {filename data} {
    set fout [browse::open_outfile $filename]
    puts -nonewline $fout $data
    close $fout
}

#######################################################
proc uc::write_file { data filename format {rate -1} {isb {}} } {
    switch -- [string tolower $format] {
        raw {   ;# Raw data, w/o header
            set ext raw
            puts "$filename.$ext"
            uc::write_bin $filename.$ext $data
        }
        astro {
            set ext afl
            puts "$filename.$ext"
            set hdr [uc::astro_header format 1024 1024]
            uc::write_bin $filename.$ext $hdr$data
        }
        pgm {
            set ext pgm
            puts "$filename.$ext   1024 x 1024\nISB=$isb"
            set hdr [uc::pgm_header format 1024 1024 16383 $filename.$ext $rate $isb]
            set ascii [uc::pgm_data format $data]
            uc::write_bin $filename.$ext $hdr$ascii
        }
        fits {
            set ext fits
            puts "$filename.$ext  1024 x 1024\nISB=$isb"
            set hdr [uc::fits_header format 1024 1024 $rate $isb]
            if {[string compare $::tcl_platform(byteOrder) "littleEndian"] == 0} {
                set data [uc::swap $data]
            }
            uc::write_bin $filename.$ext $hdr$data
        }
        default { uc::errorMsg "Invalid -format specified: format" 1 }
    }
}

#############################################################################
# Uncompress image and write it to file
#
proc uc::saveImage {} {
    variable ARG
    variable UC

    if { ! $UC(ImgFrames) } {
        return
    }
    #
    # Find the best matching ISB and save it globally
    # This variable is read by the pgm/fits header functions
    #
    if {($UC(ImgNo) >= 0) && ($UC(ImgNo) <= 7) } {
        set isb [browse::isb::getBestISB $UC(ImgNo) $UC(ImgOBT.Num)]
    } else {
        set isb {}
    }

    uc::flushTile
    decoder::missing_tiles
    decoder::wavelet_rev

    set img [decoder::get_img]
    if { [string length $ARG(Out)] != 0 } {
        set fname $ARG(Out)_$UC(ImgCount)
        puts [string repeat "-" 80]
        foreach fmt [split $ARG(Format) ","] {
            uc::write_file $img $fname $fmt $UC(ImgRate) $isb
            puts [string repeat "-" 80]
        }
    } else {
        uc::errorMsg "--- Dump image number $UC(ImgCount) ---"
        puts [uc::dump $img 64]
    }

    incr UC(ImgCount)
}

#############################################################################
# Start new image
#
proc uc::newImage { {no -1} {rate -1}} {
    variable UC
    variable Frame

    uc::saveImage

    if { $no >= 0 } {
#        puts "# Found compressed image=$no"
        puts "# Found compressed image=$no OBT=$Frame(OBT) ($Frame(OBT.Num))"
    }
    decoder::new_img 1024 1024 64
    set UC(ImgFrames)  0
    set UC(ImgNo)      $no
    set UC(ImgRate)    $rate
    set UC(ImgOBT)     $Frame(OBT)
    set UC(ImgOBT.Num) $Frame(OBT.Num)

    uc::reset
}

#############################################################################
# Uncompress image data from file
# Optionally multiple images
#############################################################################
proc uc::uncompress { args } {
    variable ARG
    variable UC
    variable Frame

    if { [llength $args] == 0 } {
        ::browse::errorMsg "Uncompress: Valid options are:
    -file filename  - filename to extract
    -from num       - extract from record number (def=0)
    -to num         - extract to record number (def=end)
    -out filename   - name of output file (def=stdout)
    -format format  - output file format = raw|astro|pgm|fits (def=raw)"
        return
    }

    # default parameters
    set ARG(From)       0
    set ARG(To)         [expr {0x7FFFFFFF}]
    set ARG(File)       {}
    set ARG(Out)        {}
    set ARG(Format)     raw

    # parse command parameters
    foreach {opt val} $args {
        set opt [string tolower $opt]
        switch -glob -- $opt {
            -from       { set ARG(From)     $val }
            -to         { set ARG(To)       $val }
            -file       { set ARG(File)     $val }
            -out        { set ARG(Out)      $val }
            -format     { set ARG(Format)   $val }
            default     { errorMsg "Extract: invalid option $opt" }
        }
    }
    if { [string length $ARG(File)] } {
        set fin [browse::open_infile $ARG(File)]
    } else {
        uc::errorMsg "no data file specified (-file filename)" 1
    }

    # Detect CDMS frame size & notify ISB module
    #
    set FrameSize [browse::detect_frame_size $fin]
    browse::isb::init $fin $FrameSize

    seek $fin [expr {$ARG(From)*$FrameSize}]
    set UC(FrameNo) $ARG(From)

    uc::init

    while { 1 } {
        set frame [read $fin $FrameSize]
        if { [string length $frame] != $FrameSize } {
            break
        }
        if { [catch {browse::cdms_frame $frame X} err ] } {
            puts stderr [format "*** %s ***" $err]
        }
        set Frame(OBT)     $X(Hdr.OBT)
        set Frame(OBT.Num) $X(Hdr.OBT.Num)

        decoder::swap X(Data)
        uc::nextFrame $X(Data)

        incr UC(FrameNo)
        if { $UC(FrameNo) > $ARG(To) } {
            break
        }
    }
    uc::saveImage

    if { $UC(CFrameNo) == 0 } { ;# no data
        uc::errorMsg "No compressed image data in file $ARG(File)" 1
    }

    close $fin
}
