| 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' |