Next / Previous / Contents / TCC Help System / NM Tech homepage

12. class FormPhotoSet: All photos for one name

Each instance of a FormPhotoSet contains all the images that have the same inverted English name.

catweb
# - - - - -   c l a s s   F o r m P h o t o S e t   - - - - -

class FormPhotoSet:
    """Container for photos that have birds with a given English name.

      Exports:
        FormPhotoSet ( birdId ):
          [ birdId is a bird form code as an abbr.BirdID ->
              return a new, empty FormPhotoSet for birdId ]
        .birdId:     [ as passed to constructor; read-only ]
        .addArchImage ( archImage ):
          [ archImage is a photo catalog entry as an
            archindex.ArchImage instance ->
              self  :=  self with archImage added ]
        .genArchImages():
          [ generate the ArchImage instances in self, in
            descending order by image size, with catalog number
            as a secondary key ]
        .pathName():
          [ returns the relative path name to the page that
            displays this form ]
        .buildPage():
          [ form page for self  :=  content displaying
                thumbnails and catalog data for self ]

One internal data structure suffices to hold all the ArchImage instances of the set. One of the author's standard Python tricks is to keep a set of values in a dictionary, and then structure the key so that, when sorted, it produces the values in the desired order.

As discussed in Section 3.2, “The form page”, the principal sort order of images is in descending order by image area. In case two images happen to have the same area, their catalog number is used as a tie-breaker. Hence, the dictionary key is a tuple of two values:

    (-area,catNo)

where -area is the image's area in square millimeters as a float, negated, and catNo is the image's catalog number.

catweb
      State/Invariants:
        .__sizeMap:
          [ a dictionary whose values are the ArchImage instances
            contained in self, and each key is a 2-tuple whose
            first element is the image's area in square mm as a
            float, negated, and the second element is the
            image's catalog number ]
    """

12.1. FormPhotoSet.__init__(): Constructor

All the constructor does is to record its arguments and initialize the .__sizeMap dictionary.

catweb
# - - -   F o r m P h o t o S e t . _ _ i n i t _ _   - - -

    def __init__ ( self, birdId ):
        """Constructor."""
        self.birdId  =  birdId
        self.__sizeMap  =  {}

12.2. FormPhotoSet.addArchImage(): Add one photo

To add a new ArchImage object to this FormPhotoSet instance, we construct the 2-tuple used as a key to self.__sizeMap, then store the ArchImage under that key. There shouldn't be any duplications, but let's check anyway.

catweb
# - - -   F o r m P h o t o S e t . a d d A r c h I m a g e   - - -

    def addArchImage ( self, archImage ):
        """Add a new archived, cataloged image to self.
        """
        #-- 1 --
        # [ area  :=  number of pixels in archImage ]
        area  =  archImage.wide * archImage.high

        #-- 2 --
        # [ key  :=  ( -area, catalog number from archImage ) ]
        key  =  ( -area, archImage.original.catNo )

        #-- 3 --
        if  self.__sizeMap.has_key ( key ):
            raise KeyError, ( "Duplicate form photo: area %dpx, "
                "catalog number %s" %
                (area, archImage.original.catNo) )
        else:
            self.__sizeMap [ key ]  =  archImage

12.3. FormPhotoSet.genArchImages(): Generate contained photos

This method generates all our contained ArchImage instances. It uses the standard Python trick of setting up a dictionary key so that it sorts in the desired order. In our case, the order is descending order by area, with catalog number as a tiebreaker.

catweb
# - - -   F o r m P h o t o S e t . g e n A r c h I m a g e s   - - -

    def genArchImages ( self ):
        """Generate contained images.
        """
        keyList  =  self.__sizeMap.keys()
        keyList.sort()
        for  key in keyList:
            yield  self.__sizeMap[key]
        raise StopIteration

12.4. FormPhotoSet.pathName(): Form page relative path name

This method returns the path name of this form's form page, relative to the index page. Python's os.path.join() function is used to mate the directory and file names. The file name is based on the bird code from self.birdId, but we must sanitize it by translating the relationship codes. The class variable relMap maps those codes onto strings acceptable for use in file names.

catweb
# - - -   F o r m P h o t o S e t . p a t h N a m e   - - -

    relMap  =  { abbrModule.REL_HYBRID: "-x-",
                 abbrModule.REL_PAIR:   "-o-" }

    def pathName ( self ):
        """Returns the relative path name of self's form page.
        """

        #-- 1 --
        # [ if self.birdId.rel is None ->
        #     basename  :=  self.birdId.abbr
        #   else ->
        #     basename  :=  self.birdId.abbr +
        #         (self.birdId.rel, translated using self.relMap) +
        #         self.birdId.abbr2 ]
        if  self.birdId.rel is None:
            basename  =  self.birdId.abbr.rstrip().lower()
        else:
            cleanRel  =  self.relMap [ self.birdId.rel ]
            basename  =  ( "%s%s%s" %
                           (self.birdId.abbr.rstrip().lower(), cleanRel,
                            self.birdId.abbr2.rstrip().lower()) )

To distinguish questionable forms, we add "-q" to the end in that case.

catweb
        #-- 2 --
        if  self.birdId.q:  suffix = "-q"
        else:               suffix = ""

For the name of the forms subdirectory, see Section 9.2, “FORM_SUBDIR: Form subdirectory name”.

catweb
        #-- 2 --
        # [ return FORM_SUBDIR + basename + ".html" ]
        path  =  os.path.join ( FORM_SUBDIR, basename )
        return  "%s%s.html" % (path, suffix)

12.5. FormPhotoSet.buildPage(): Build the XHTML page for one form

This method is the driver for the construction of the XHTML page representing the forms in this FormPhotoSet.

catweb
# - - -   F o r m P h o t o S e t . b u i l d P a g e   - - -

    def buildPage ( self ):
        """Builds the XHTML page for self.
        """

For the URL of the stylesheet, see Section 9.1, “CSS_STYLESHEET: Stylesheet name”.

catweb
        #-- 1 --
        # [ page  :=  a new, generic XHTML page with self's title
        #   body  :=  the body element of that page ]
        titleText  =  ( "Shipman's bird photo index: %s" %
                        self.birdId.engComma() )
        page, body  =  webPage ( titleText, CSS_STYLESHEET )

The body of the page is mainly a table. See Section 12.6, “formSet.__buildTable(): Build the image table”.

catweb
        #-- 2 --
        # [ body  :=  body with a table added containing
        #       thumbnails and catalog data from self ]
        self.__buildTable ( body )

The page is now complete. Create the file and serialize the page to it.

catweb
        #-- 3 --
        # [ if self's page can be created new ->
        #     pagePath  :=  path to self's file
        #     pageFile  :=  a new, empty file ]
        pagePath  =  self.pathName()
        pageFile  =  open ( pagePath, "w" )

        #-- 4 --
        # [ pageFile  +:=  page, serialized ]
        page.write ( pageFile )
        pageFile.close()

12.6. formSet.__buildTable(): Build the image table

This method builds the table that holds the thumbnails and image data. For the XHTML, see Section 4.3, “XHTML for the form page”.

catweb
# - - -   F o r m P h o t o S e t . _ _ b u i l d T a b l e

    def __buildTable ( self, body ):
        """Build the XHTML table.

          [ body  :=  body with a table added containing
                thumbnails and catalog data from self ]
        """

        #-- 1 --
        # [ body  :=  body with a new table element
        #   table  :=  that table element ]
        table  =  xc.Element ( body, "table", border="4",
                               cellspacing="4" )

        #-- 2 --
        # [ table  :=  table with a new colgroup added defining
        #              the table columns ]
        colgroup  =  xc.Element ( table, "colgroup" )
        xc.Element ( colgroup, "col", align="center" )
        xc.Element ( colgroup, "col", align="right", valign="top" )
        xc.Element ( colgroup, "col", align="left", valign="top" )

        #-- 3 --
        # [ table  :=  table with a thead added containing the
        #              column headings ]
        thead  =  xc.Element ( table, "thead" )
        headRow  =  xc.Element ( thead, "tr" )
        head1  =  xc.Element ( headRow, "th", align="center" )
        xc.Text ( head1, "Thumbnail" )
        head2  =  xc.Element ( headRow, "th", align="center" )
        xc.Text ( head2, "Size" )
        head3  =  xc.Element ( headRow, "th", align="center" )
        xc.Text ( head3, "Data" )

        #-- 4 --
        # [ table  :=  table with a tbody element added
        #   tbody  :=  that tbody element ]
        tbody  =  xc.Element ( table, "tbody" )

Finally, we add a row to the table for each contained ArchImage object.

catweb
        #-- 5 --
        # [ tbody  :=  tbody with rows added representing the
        #       ArchImages in self, in key order ]
        for  archImage in self.genArchImages():
            #-- 5 body --
            # [ archImage is an archx.ArchImage ->
            #     tbody  :=  tbody with a row added representing
            #                archImage ]
            self.__addRow ( tbody, archImage )

12.7. formSet.__addRow(): Generate one row of the table

This method translates one ArchImage instance into a table row.

catweb
# - - -   F o r m P h o t o S e t . _ _ a d d R o w   - - -

    def __addRow ( self, tbody, archImage ):
        """Add one row to the table.

          [ (tbody is an xc.Element) and
            (archImage is an archx.ArchImage) ->
              tbody  :=  tbody with a tr element added containing
                  the thumbnail and data from archImage ]
        """

        #-- 1 --
        # [ tbody  :=  tbody with a new, empty tr element added
        #   tr     :=  that tr element ]
        tr  =  xc.Element ( tbody, "tr" )

Just to keep things uncomplicated, we'll delegate production of each of the three cells in the row to separate methods.

catweb
        #-- 2 --
        # [ tr  :=  tr with a new td added containing the
        #           thumbnail for archImage ]
        self.__addThumbnail ( tr, archImage )

        #-- 3 --
        # [ tr  :=  tr with a new td added containing size data
        #           from archImage ]
        self.__addSize ( tr, archImage )

        #-- 4 --
        # [ tr  :=  tr with a new td added containing general
        #           data from archImage )
        self.__addData ( tr, archImage )

12.8. FormPhotoSet.__addThumbnail(): Add the thumbnail cell

This method generates the first column of the table, containing the image thumbnail; see Section 4.3, “XHTML for the form page”.

catweb
# - - -   F o r m P h o t o S e t . _ _ a d d T h u m b n a i l

    def __addThumbnail ( self, tr, archImage ):
        """Add a thumbnail image cell

          [ (tr is an xc.Element) and
            (archImage is an archx.ArchImage) ->
              tr  :=  tr with a new td added containing the
                      thumbnail for archImage ]
        """

        #-- 1 --
        # [ tr  :=  tr with a new td element added
        #   td  :=  that new td element ]
        td  =  xc.Element ( tr, "td", valign="top", align="center" )

        #-- 2 --
        # [ td  :=  td with a new img element added whose href
        #           is the thumbnail for archImage ]
        href = "../thumb/%s.jpg" % archImage.original.catNo
        xc.Element ( td, "img", src=href, alt="thumbnail" )

12.9. FormPhotoSet.__addSize(): Add the size cell

This method generates the second column of the table, displaying the image size. See Section 4.3, “XHTML for the form page”.

catweb
# - - -   F o r m P h o t o S e t . _ _ a d d S i z e   - - -

    def __addSize ( self, tr, archImage ):
        """Generate the image size cell of the table.

          [ (tr is an xc.Element) and
            (archImage is an archx.ArchImage) ->
              tr  :=  tr with a new td added containing size data
                      from archImage ]
        """

        #-- 1 --
        # [ tr  :=  tr with a new td element added
        #   td  : = that new td element ]
        td  =  xc.Element ( tr, "td", valign="top", align="right" )

The three lines in this cell are vertically stacked by placing each inside a div element.

The frame area appears only for images that have a scan attribute that tells the dots per inch of the scanner. If this attribute is missing, the original is from a digital camera, and we don't display the frame area at all.

To calculate the percentage frame area, we need to know the area in square millimeters of a full frame. Then we use the scanner precision, expressed in pixels per millimeter, plus the size of the image in pixels, to calculate the fraction of the frame. Here is the math:

  1. The constant FULL_FRAME_MM2 gives the area of a full frame in square millimeters: 24mm × 36mm for a 35mm film frame.

  2. Since 1 inch = 25.4mm, we can convert that value to square inches by dividing by the square of 25.4.

  3. To convert square inches to pixels, we multiply by the square of Pi, the dot pitch of the scanner in dots per inch. This gives us Pf, the number of pixels in a full frame at resolution Pi, which is available as archImage.original.scan.

For an image with x pixels, the image size as a percentage of the frame, then, is 100 × x / Pf.

catweb
        #-- 2 --
        # [ if archImage has a known dots/inch ->
        #     td  :=  td with a div element added, containing the
        #             percentage of a full frame covered by archImage ]
        if  archImage.original.scan:
            fullFramePixels  =  ( FULL_FRAME_MM2 / (MM_PER_IN**2) *
                                  (archImage.original.scan)**2 )
            thisFramePixels  =  archImage.wide * archImage.high
            divPercent  =  xc.Element ( td, "div" )
            framePercent  =  100.0 * thisFramePixels / fullFramePixels
            xc.Text ( divPercent, "%.2f%%" % framePercent )

The width and height are added in separate div elements; the latter is preceded by “x”. It would be nice to have the special Unicode character “×” (code point 0x00d7), but so far I haven't been able to figure out how to make it work.

catweb
        #-- 3 --
        # [ td  :=  td with two divs added, the first containing
        #           the image width from archImage, the second
        #           containing 'x' and the image height ]
        divW  =  xc.Element ( td, "div" )
        xc.Text ( divW, str(archImage.wide) )
        divH  =  xc.Element ( td, "div" )
        xc.Text ( divH, "x" )
        xc.Text ( divH, str(archImage.high) )

12.10. FormPhotoSet.__addData(): Add the cataloging data cell

This method adds all the remaining catalog data to the third column of the table row. First we add the cell's td element.

catweb
# - - -   F o r m P h o t o S e t . _ _ a d d D a t a   - - -

    def __addData ( self, tr, archImage ):
        """Add the catalog data cell to the table row.

          [ (tr is an xc.Element) and
            (archImage is an archx.ArchImage) ->
              tr  :=  tr with a new td added containing general
                      data from archImage )
        """
        #-- 1 --
        # [ tr  :=  tr with a new td element added
        #   td  :=  that new td element ]
        td  =  xc.Element ( tr, "td", valign="top", align="left",
                            class_="cat-info" )

The elements inside this cell are stacked vertically using div elements. The first div holds the catalog number, state, and locality. The catalog number is additionally wrapped in a span element of class cat-no.

catweb
        #-- 2 --
        # [ td  :=  td with a new div element added
        #   majorDiv  :=  that new div ]
        majorDiv  =  xc.Element ( td, "div", class_="ident" )

        #-- 3 --
        # [ majorDiv  :=  majorDiv with a new span element added
        #                 containing archImage.original.catNo ]
        catSpan  =  xc.Element ( majorDiv, "span", class_="cat-no" )
        xc.Text ( catSpan, archImage.original.catNo )

Add the state code, always present. Add the locality data (and the colon that precedes it) only if there is locality data.

catweb
        #-- 4 --
        # [ majorDiv  :=  majorDiv with archImage's state and
        #                 locality added ]
        xc.Text ( majorDiv, " " )
        xc.Text ( majorDiv, archImage.original.state.upper() )
        if  archImage.original.loc:
            xc.Text ( majorDiv, ": %s" % archImage.original.loc )

Eventually we'll add special divs for each element such as pose, beh, and so on. For now, just put up the note content.

catweb
        #-- 5 --
        # [ td  :=  td with a new div element added containing
        #           archImage.original.note ]
        if  archImage.original.note:
            noteDiv  =  xc.Element ( td, "div" )
            xc.Text ( noteDiv, archImage.original.note )