Repository 'ashlar'
hg clone https://toolshed.g2.bx.psu.edu/repos/perssond/ashlar

Changeset 0:b3054f3d42b2 (2021-03-12)
Next changeset 1:f183d9de4622 (2022-08-24)
Commit message:
"planemo upload for repository https://github.com/ohsu-comp-bio/ashlar commit 27f0c9be58e9e5aecc69067d0e60b5cb945de4b2-dirty"
added:
ashlar.xml
macros.xml
pyramid_upgrade.py
b
diff -r 000000000000 -r b3054f3d42b2 ashlar.xml
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/ashlar.xml Fri Mar 12 00:14:49 2021 +0000
[
@@ -0,0 +1,180 @@
+<tool id="ashlar" name="ASHLAR" version="@VERSION@.6" profile="17.09">
+    <description>Alignment by Simultaneous Harmonization of Layer/Adjacency Registration</description>
+    <macros>
+        <import>macros.xml</import>
+    </macros>

+    <expand macro="requirements"/>
+    @VERSION_CMD@
+
+    <command detect_errors="exit_code"><![CDATA[
+
+        #def clean(file,type)
+          #set name_clean = str($file.element_identifier).replace('.ome.tiff','').replace('.tiff','').replace('.tiff.','')
+
+          #if $type == "raw"
+            #set file_clean = $name_clean + ".ome.tiff"
+          #elif $type == "ffp"
+            #set file_clean = $name_clean + "_ffp.ome.tiff"
+          #elif $type == "dfp"
+            #set file_clean = $name_clean + "_dfp.ome.tiff"
+          #end if
+
+          #return $file_clean
+        #end def
+
+        ## Link the illumination files to appropriate file extension
+        #for $dfp in $ldfp:
+            ln -s "$dfp" "$clean($dfp,"dfp")" &&
+        #end for
+        #for $ffp in $lffp:
+            ln -s "$ffp" "$clean($ffp,"ffp")" &&
+        #end for
+
+        @CMD_BEGIN@
+
+        ## Supply the raw images
+        #for $raw in $lraw:
+            "$raw"
+        #end for
+
+        ## Additional arguments
+        -m $max_shift
+
+        #if $flip_x
+        --flip-x
+        #end if
+
+        #if $flip_y
+        --flip-y
+        #end if
+
+        -c $adv.align_channel
+
+        #if $adv.filter_sigma
+        --filter-sigma $adv.filter_sigma
+        #end if
+
+        #if $adv.tile_size
+        --tile-size $adv.tile_size
+        #end if
+
+        --ffp
+        #for $ffp in $lffp:
+            "$clean($ffp,"ffp")"
+        #end for
+        --dfp
+        #for $dfp in $ldfp:
+            "$clean($dfp,"dfp")"
+        #end for
+        --pyramid
+        -f registered.ome.tif;
+
+        #if $upgrade.decide == "do_upgrade"
+        python ${__tool_directory__}/pyramid_upgrade.py
+        registered.ome.tif
+
+        #if $upgrade.markers_file
+        -n `python "$get_markers" "${upgrade.markers_file}"`
+        #end if
+        #end if
+    ]]></command>
+
+    <configfiles>
+        <configfile name="get_markers">
+import pandas as pd
+import sys
+
+marker_file = sys.argv[1]
+df = pd.read_csv(marker_file)
+print(' '.join(df['marker_name'].array)) 
+        </configfile>
+    </configfiles>
+
+
+
+    <inputs>
+        <param name="lraw" type="data_collection" format="tiff" collection_type="list" label="Raw Images"/>
+        <param name="ldfp" type="data_collection" format="tiff" collection_type="list" label="Deep Field Profile Images"/>
+        <param name="lffp" type="data_collection" format="tiff" collection_type="list" label="Flat Field Profile Images"/>
+
+        <param name="flip_x" type="boolean" value="false" label="Flip X-axis"/>
+        <param name="flip_y" type="boolean" value="false" label="Flip Y-axis"/>
+
+        <param name="max_shift" type="integer" value="30" label="Maximum allowed per-tile corrective shift" help="In micros"/>
+
+        <conditional name="upgrade">
+            <param name="decide" type="select" label="Upgrade to BF6-Compliant OME-TIFF Pyramid">
+                <option value="do_upgrade">Upgrade Pyramid</option>
+                <option value="dont_upgrade">Leave Legacy Pyramid</option>
+            </param>
+            <when value="do_upgrade">
+                <param name="markers_file" type="data" format="csv,tabular" optional="true" label="Markers File (optional)"/>
+            </when>
+            <when value="dont_upgrade">
+            </when>
+        </conditional>
+        <section name="adv" title="Advanced Options" expanded="false">
+            <param name="align_channel" type="integer" value="0" label="Align Channel Number"/>
+            <param name="filter_sigma" type="float" optional="true" label="Sigma"/>
+            <param name="tile_size" type="integer"  optional="true" label="Cyto Mask Channel"/>
+        </section>
+    </inputs>
+
+    <outputs>
+        <data format="tiff" name="output" from_work_dir="registered.ome.tif" label="${tool.name} on ${on_string}"/>
+    </outputs>
+    <help><![CDATA[
+Ashlar python package for microscopy registration, developed by HMS (repo: https://github.com/labsyspharm/ashlar)
+        ashlar [-h] [-o DIR] [-c [CHANNEL]]
+       [--output-channels [CHANNEL [CHANNEL ...]]] [-m SHIFT]
+       [--filter-sigma SIGMA] [-f FORMAT] [--pyramid]
+       [--tile-size PIXELS] [--ffp [FILE [FILE ...]]]
+       [--dfp [FILE [FILE ...]]] [--plates] [-q] [--version]
+       [FILE [FILE ...]]
+
+Stitch and align one or more multi-series images
+
+positional arguments:
+  FILE                  an image file to be processed (one file per cycle)
+
+optional arguments:
+  -h, --help            show this help message and exit
+  -o DIR, --output DIR  write output image files to DIR; default is the
+                        current directory
+  -c [CHANNEL], --align-channel [CHANNEL]
+                        align images using channel number CHANNEL; numbering
+                        starts at 0
+  --output-channels [CHANNEL [CHANNEL ...]]
+                        output only channels listed in CHANNELS; numbering
+                        starts at 0
+  -m SHIFT, --maximum-shift SHIFT
+                        maximum allowed per-tile corrective shift in microns
+  --filter-sigma SIGMA  width in pixels of Gaussian filter to apply to images
+                        before alignment; default is 0 which disables
+                        filtering
+  -f FORMAT, --filename-format FORMAT
+                        use FORMAT to generate output filenames, with {cycle}
+                        and {channel} as required placeholders for the cycle
+                        and channel numbers; default is
+                        cycle_{cycle}_channel_{channel}.tif
+  --pyramid             write output as a single pyramidal TIFF
+  --tile-size PIXELS    set tile width and height to PIXELS (pyramid output
+                        only); default is 1024
+  --ffp [FILE [FILE ...]]
+                        read flat field profile image from FILES; if specified
+                        must be one common file for all cycles or one file for
+                        each cycle
+  --dfp [FILE [FILE ...]]
+                        read dark field profile image from FILES; if specified
+                        must be one common file for all cycles or one file for
+                        each cycle
+  --plates              enable plate mode for HTS data
+  -q, --quiet           suppress progress display
+  --version             print version
+
+OHSU Wrapper Repo: https://github.com/ohsu-comp-bio/ashlar
+Conda Package Available From: https://anaconda.org/ohsu-comp-bio/ashlar
+    ]]></help>
+    <expand macro="citations" />
+</tool>
b
diff -r 000000000000 -r b3054f3d42b2 macros.xml
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/macros.xml Fri Mar 12 00:14:49 2021 +0000
b
@@ -0,0 +1,20 @@
+<?xml version="1.0"?>
+<macros>
+    <xml name="requirements">
+        <requirements>
+            <requirement type="package" version="@VERSION@">ashlar</requirement>
+            <requirement type="package" version="1.1.5">pandas</requirement>
+        </requirements>
+    </xml>
+
+    <xml name="version_cmd">
+        <version_command>echo @VERSION@</version_command>
+    </xml>
+    <xml name="citations">
+        <citations>
+        </citations>
+    </xml>
+
+    <token name="@VERSION@">1.13.0</token>
+    <token name="@CMD_BEGIN@">ashlar</token>
+</macros>
b
diff -r 000000000000 -r b3054f3d42b2 pyramid_upgrade.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/pyramid_upgrade.py Fri Mar 12 00:14:49 2021 +0000
[
b'@@ -0,0 +1,566 @@\n+import sys\n+import os\n+import argparse\n+import struct\n+import re\n+import fractions\n+import io\n+import xml.etree.ElementTree\n+import collections\n+import reprlib\n+import dataclasses\n+from typing import List, Any\n+\n+\n+datatype_formats = {\n+    1: "B", # BYTE\n+    2: "s", # ASCII\n+    3: "H", # SHORT\n+    4: "I", # LONG\n+    5: "I", # RATIONAL (pairs)\n+    6: "b", # SBYTE\n+    7: "B", # UNDEFINED\n+    8: "h", # SSHORT\n+    9: "i", # SLONG\n+    10: "i", # SRATIONAL (pairs)\n+    11: "f", # FLOAT\n+    12: "d", # DOUBLE\n+    13: "I", # IFD\n+    16: "Q", # LONG8\n+    17: "q", # SLONG8\n+    18: "Q", # IFD8\n+}\n+rational_datatypes = {5, 10}\n+\n+\n+class TiffSurgeon:\n+    """Read, manipulate and write IFDs in BigTIFF files."""\n+\n+    def __init__(self, path, *, writeable=False, encoding=None):\n+        self.path = path\n+        self.writeable = writeable\n+        self.encoding = encoding\n+        self.endian = ""\n+        self.ifds = None\n+        self.file = open(self.path, "r+b" if self.writeable else "rb")\n+        self._validate()\n+\n+    def _validate(self):\n+        signature = self.read("2s")\n+        signature = signature.decode("ascii", errors="ignore")\n+        if signature == "II":\n+            self.endian = "<"\n+        elif signature == "MM":\n+            self.endian = ">"\n+        else:\n+            raise FormatError(f"Not a TIFF file (signature is \'{signature}\').")\n+        version = self.read("H")\n+        if version == 42:\n+            raise FormatError("Cannot process classic TIFF, only BigTIFF.")\n+        offset_size, reserved, first_ifd_offset = self.read("H H Q")\n+        if version != 43 or offset_size != 8 or reserved != 0:\n+            raise FormatError("Malformed TIFF, giving up!")\n+        self.first_ifd_offset = first_ifd_offset\n+\n+    def read(self, fmt, *, file=None):\n+        if file is None:\n+            file = self.file\n+        endian = self.endian or "="\n+        size = struct.calcsize(endian + fmt)\n+        raw = file.read(size)\n+        value = self.unpack(fmt, raw)\n+        return value\n+\n+    def write(self, fmt, *values):\n+        if not self.writeable:\n+            raise ValueError("File is opened as read-only.")\n+        raw = self.pack(fmt, *values)\n+        self.file.write(raw)\n+\n+    def unpack(self, fmt, raw):\n+        assert self.endian or re.match(r"\\d+s", fmt), \\\n+            "can\'t unpack non-string before endianness is detected"\n+        fmt = self.endian + fmt\n+        size = struct.calcsize(fmt)\n+        values = struct.unpack(fmt, raw[:size])\n+        if len(values) == 1:\n+            return values[0]\n+        else:\n+            return values\n+\n+    def pack(self, fmt, *values):\n+        assert self.endian, "can\'t pack without endian set"\n+        fmt = self.endian + fmt\n+        raw = struct.pack(fmt, *values)\n+        return raw\n+\n+    def read_ifds(self):\n+        ifds = [self.read_ifd(self.first_ifd_offset)]\n+        while ifds[-1].offset_next:\n+            ifds.append(self.read_ifd(ifds[-1].offset_next))\n+        self.ifds = ifds\n+\n+    def read_ifd(self, offset):\n+        self.file.seek(offset)\n+        num_tags = self.read("Q")\n+        buf = io.BytesIO(self.file.read(num_tags * 20))\n+        offset_next = self.read("Q")\n+        try:\n+            tags = TagSet([self.read_tag(buf) for i in range(num_tags)])\n+        except FormatError as e:\n+            raise FormatError(f"IFD at offset {offset}, {e}") from None\n+        ifd = Ifd(tags, offset, offset_next)\n+        return ifd\n+\n+    def read_tag(self, buf):\n+        tag = Tag(*self.read("H H Q 8s", file=buf))\n+        value, offset_range = self.tag_value(tag)\n+        tag = dataclasses.replace(tag, value=value, offset_range=offset_range)\n+        return tag\n+\n+    def append_ifd_sequence(self, ifds):\n+        """Write list of IFDs as a chained sequence at the end of the file.\n+\n+        Returns a list of new Ifd objects with updated offsets.\n+\n+        """\n+        self.file.seek(0, os.SEEK_END)\n+        new_ifds'..b'umber of channels: {size_c}")\n+    print(f"Pyramid sub-resolutions ({num_levels - 1} total):")\n+    for dim_x, dim_y in page_dims[size_c::size_c]:\n+        print(f"    {dim_x} x {dim_y}")\n+    software = tiff.ifds[0].tags.get_value(305, "<not set>")\n+    print(f"Software: {software}")\n+    print()\n+\n+    print("Updating OME-XML metadata...")\n+    # We already verified there is nothing but Image elements under the root.\n+    for other_image in root[1:]:\n+        root.remove(other_image)\n+    for tiffdata in pixels.findall("ome:TiffData", xml_ns):\n+        pixels.remove(tiffdata)\n+    new_tiffdata = xml.etree.ElementTree.Element(\n+        f"{{{xml_ns[\'ome\']}}}TiffData",\n+        attrib={"IFD": "0", "PlaneCount": str(size_c)},\n+    )\n+    # A valid OME-XML Pixels begins with size_c Channels; then comes TiffData.\n+    pixels.insert(size_c, new_tiffdata)\n+\n+    if args.channel_names:\n+        print("Renaming channels...")\n+        channels = pixels.findall("ome:Channel", xml_ns)\n+        for channel, name in zip(channels, args.channel_names):\n+            channel.attrib["Name"] = name\n+\n+    fix_attrib_namespace(root)\n+    # ElementTree.tostring would have been simpler but it only supports\n+    # xml_declaration and default_namespace starting with Python 3.8.\n+    xml_file = io.BytesIO()\n+    tree = xml.etree.ElementTree.ElementTree(root)\n+    tree.write(\n+        xml_file,\n+        encoding="utf-8",\n+        xml_declaration=True,\n+        default_namespace=xml_ns["ome"],\n+    )\n+    new_omexml = xml_file.getvalue()\n+\n+    print("Writing new TIFF headers...")\n+    stale_ranges = [ifd.offset_range for ifd in tiff.ifds]\n+    main_ifds = tiff.ifds[:size_c]\n+    channel_sub_ifds = [tiff.ifds[c + size_c : : size_c] for c in range(size_c)]\n+    for i, (main_ifd, sub_ifds) in enumerate(zip(main_ifds, channel_sub_ifds)):\n+        for ifd in sub_ifds:\n+            if 305 in ifd.tags:\n+                stale_ranges.append(ifd.tags[305].offset_range)\n+                del ifd.tags[305]\n+            ifd.tags.insert(tiff.append_tag_data(254, 3, 1))\n+        if i == 0:\n+            stale_ranges.append(main_ifd.tags[305].offset_range)\n+            stale_ranges.append(main_ifd.tags[270].offset_range)\n+            old_software = main_ifd.tags[305].value.replace("Faas", "F*a*a*s")\n+            new_software = f"pyramid_upgrade.py (was {old_software})"\n+            main_ifd.tags.insert(tiff.append_tag_data(305, 2, new_software))\n+            main_ifd.tags.insert(tiff.append_tag_data(270, 2, new_omexml))\n+        else:\n+            if 305 in main_ifd.tags:\n+                stale_ranges.append(main_ifd.tags[305].offset_range)\n+                del main_ifd.tags[305]\n+        sub_ifds[:] = tiff.append_ifd_sequence(sub_ifds)\n+        offsets = [ifd.offset for ifd in sub_ifds]\n+        main_ifd.tags.insert(tiff.append_tag_data(330, 16, offsets))\n+    main_ifds = tiff.append_ifd_sequence(main_ifds)\n+    tiff.write_first_ifd_offset(main_ifds[0].offset)\n+\n+    print("Clearing old headers and tag values...")\n+    # We overwrite all the old IFDs and referenced data values with obvious\n+    # "filler" as a courtesy to anyone who might need to poke around in the TIFF\n+    # structure down the road. A real TIFF parser wouldn\'t see the stale data,\n+    # but a human might just scan for the first thing that looks like a run of\n+    # OME-XML and not realize it\'s been replaced with something else. The filler\n+    # content is the repeated string "unused " with square brackets at the\n+    # beginning and end of each filled IFD or data value.\n+    filler = b"unused "\n+    f_len = len(filler)\n+    for r in stale_ranges:\n+        tiff.file.seek(r.start)\n+        tiff.file.write(b"[")\n+        f_total = len(r) - 2\n+        for i in range(f_total // f_len):\n+            tiff.file.write(filler)\n+        tiff.file.write(b" " * (f_total % f_len))\n+        tiff.file.write(b"]")\n+\n+    tiff.close()\n+\n+    print()\n+    print("Success!")\n+\n+\n+if __name__ == "__main__":\n+    main()\n'