Code archeology

digging in my attic I found stash of old CDRs from my student times. Back then I freelanced for small medical technology company and developed video archiving for cardiac catheterisation procedures

Up to the 90s golden standard was 35mm chemical film (black and white of course) - and doctorl liked those for crisp image quality. Unfortunately they too up a lot of place - one propcedure produced kind of tuna can with film - and there was a lot of procedures ( busy doc could do some 1500-2000 in a year). And thy had to be stored for a long time… And they got lost. Add costs for chemical processing, dark room, dedicated personell - and you will understand that nobody vas really happy with this solution.

In 90ties DICOM was introduced, and suddently everybody needed everything digital. And new x-Ray machine was pretty expensive - so there was market for retrofitting existing systems with digital imaging technology. We used UltraSparks (Ultra 1, 256 MB RAM, 4 GB hard disks), viedograbber boards from Parallax Graphics (boy, were they expesive!), 1x CDR Burner (one of the first), some RS323 fixtures to start recordig where footswitch was pressed, some programming in C under Motif - here is your brand new image acquisition workstation (it makes DM 150000 please)

We had decent hardware for acquisition - and got realime recording - but we ran into close time constraints. Patient stays at the office for half an hour before transfer, and it is absolutely necessary to give recorded CD with him. Burning it at 1x Speed takes about 20 minutes - but we still had to prepare data in DICOM format. It uses loseless JPEG as compression method - and there was absolutely no hardware support in doing this (well, maybe there was but not in our budget constraints). Solaris provided libraries, but execution times were some 150ms for 512x512x8 picture - absolutely inacceptable

Assembly to the Rescure! After 3 works of hard work I finally produced this (and now try to figure out what it does;):

! pointer to the source pixel
#define pixPtr %i0
! width of scan
#define scanWidth %i1
! height of scan
#define scanHeight %i2
!destination pointer 
#define outP %i3
! saved destination pointer to be able to determine how many bytes we have written
#define outPsave %l0
! pointer to the chewed huffman table
#define huffP %i4
! predictor value
#define pred %l1
! predictor value for the first pixel in the row ( next )
#define predFirst %l3
! nice work variables to know how muchwork we have to do ...
! nothe , that we can use amount of rows in place
#define pix2do %l2
#define row2do %i2
! work place
#define work %l4
! pixel
#define pixel %l5
! difference value
#define diff %l6
! huffman code
#define huffCode %o0
!and size in bits
#define huffSize %o1
! huffman buffer
#define huffBits %l7
#define huffBitsFree %i5

    .section    ".text",#alloc,#execinstr
    .align    8
    .skip    16

    ! block 0

    .global    encodeScan
    .type    encodeScan,2
    save    %sp,-104,%sp

    ! here we start with initializing
    mov outP , outPsave
    !init huffman coder
    add 32 , %g0 , huffBitsFree
    ! not forget to init huff bits ...
    ! since new register sets are not always init to 0 ...
    mov %g0 , huffBits
    ! init amount of pixels in the row
    ! predictor for the first row is always 0 ,
    ! and for the others we will init on the ond of the row

    ! we are ready for the first pixel in the row ,
    ! predictor for this is in the predFirst ( for the very first row , it's 0 )

    ! load the very first pixel            
    ldub [pixPtr] , pixel
    ! init predictor for this pixel
    add %g0 , 0x80 , pred
    ! setup predictor for the first pixel of the next row
    mov pixel , predFirst
    ! setup amount of pixels to do in this row
    mov scanWidth , pix2do
    ba predict
    inc pixPtr

    ! load the pixel in question
    ldub [pixPtr] , pixel
    ! and walk a step ...
    inc pixPtr
    ! calculate difference value
    sub pixel , pred , diff
    ! move used pixel to the next predictor
    mov pixel , pred
    ! clamp difference value to 9 bits , since we use 8 bit pixel values
    and 0x1ff , diff , diff
    ! and shift it 3 bits to left , to be able to use
    ! this as table index
    sll diff , 3 , diff

    ! now we can encode this difference as huffman code
    ! load descriptor for this difference
    ldd [huffP + diff] , huffCode
    ! reduce amount of free bits by actual size of coded symbol
    sub huffBitsFree , huffSize , huffBitsFree
    ! shift code to left to fit the place
    sll huffCode , huffBitsFree , huffCode
    ! and or this  ...
    or huffBits , huffCode , huffBits

    ! ready to emit coded data
    ! we do this always 16 bitwise
    cmp huffBitsFree , 16
    bg end_pixel
    ! get 8 higher bits from ...
    srl huffBits , 24 , work
    ! stuff 00 after 0xff
    cmp work , 0xff
    bne after_stuff
    stb work , [outP]
    inc outP
    stb %g0 , [outP]
    ! and following 8 bits
    srl huffBits , 16 , work
    and work , 0xff , work
    cmp work , 0xff
    bne after_stuff2
    stb work , [outP + 1]
    stb %g0 , [outP + 2]
    inc outP
    inc 2 , outP
    sll huffBits , 16 , huffBits
    inc 16 , huffBitsFree
    ! we are ready with this pixel
    ! decrement pixel in row counter
    deccc pix2do
    bne loadPix
    ! use delay slot to load pixel , if we do not take the branch
    ! we are on the end of row , but we have to load first pixel of the nex row ,
    ! haven't we ?
    ldub [pixPtr] , pixel
    deccc row2do
    bne restartRow
    ! init predictor for this pixel
    mov predFirst , pred    

    ! flush huffman coder
    cmp huffBitsFree , 24
    bg flush_last
    srl huffBits , 24 , work
    sll huffBits , 8 , huffBits
    stb work , [outP]
    inc outP
    ! fake 0x1ff to be a code
    set 0x1ff , huffCode
    sub huffBitsFree , 9 , huffBitsFree
    sll huffCode , huffBitsFree , huffCode
    ! and encode this
    or huffBits , huffCode , huffBits
    ! and emit 8 resulting bits
    srl huffBits , 24 , work
    sll huffBits , 8 , huffBits
    stb work , [outP]
    ! and ond of image marker ...
    or %g0 , 0xff , work
    stb work , [outP + 1]
    or %g0 , 0xd9 , work
    stb work , [outP + 2]
    inc 3 , outP    

    ! return amount of bytes written so far
    sub outP , outPsave , %i0    
    jmp    %i7+8
    .size    encodeScan,(.-encodeScan)
    .align    8

    .file    "encodeScan.c"
    .xstabs    ".stab.index","Xs ; V=3.1 ; R=SC4.0 18 Oct 1995 C 4.0",60,0,0,0
    .xstabs    ".stab.index","/export/home0/kostik/source/compress; /opt/SUNWspro/SC4.0/bin/acc -Xs -YP,:/usr/ucblib:/opt/SUNWspro/SC4.0/bin/../lib:/opt/SUNWspro/SC4.0/bin:/usr/ccs/lib:/usr/lib -S -I/usr/ucbinclude  -c encodeScan.c -Qoption acomp -xp",52,0,0,0
    .ident    "@(#) encodeScan.s 1.2@(#) Copyright � by Konstantin Priblouda 1997 all rigths reserved"

Execution times went down to 38 / 28 ms (on micro / ultraspark at 170 MHz) - and suddently we had almost realtime conversion of video sequences. After couple of years we moved to Silicon Graphics hardware ( pretty expesive, but it had better video grabbing boards) - but no hardware support for loseless JPEG. So I had to port all the assenlby routunes to MIPS

See full source code on github

Have fun