//  [python]
def make_image_sets(key_metadata, channel_metadata):
    """Make image sets by permuting key and channel metadata

    key_metadata: collection of 2 tuples. 2 tuple is key + collection of
                    values, for instance, [("Well", ("A01", "A02", "A03")),
                    ("Site", ("1","2","3","4"))]

    channel_metadata: collection of tuple of channel name, channel metadata key,
                        channel metadata value and image type

    returns a Groups module and a workspace with the image sets in its measurements
    """

    channel_descriptors = {channel_name: image_type
                           for channel_name, metadata_key, metadata_value, image_type in channel_metadata}


    # iscds = [
    #     cellprofiler_core.pipeline.ImageSetChannelDescriptor(channel_name, image_type)
    #     for channel_name, metadata_key, metadata_value, image_type in channel_metadata
    # ]
    image_set_key_names = [key for key, values in key_metadata]

    slices = tuple([slice(0, len(values)) for key, values in key_metadata])
    ijkl = numpy.mgrid[slices]
    ijkl = ijkl.reshape(ijkl.shape[0], reduce(lambda x, y: x * y, ijkl.shape[1:]))
    image_set_values = [values for key, values in key_metadata]
    image_set_keys = [
        tuple([image_set_values[i][ijkl[i, j]] for i in range(ijkl.shape[0])])
        for j in range(ijkl.shape[1])
    ]
    #
    # The image set dictionary is a dictionary of image set key
    # to a collection of ipds. We make up a file name that consists
    # of the image set keys strung together with the channel metadata key.
    # Likewise, the IPD metadata consists of the image set keys and
    # the channel metadata key/value.
    # Construct the dictionary by passing its constructor a collection
    # of image_set_key / ipd collection tuples.
    #
    image_sets = []
    for k in image_set_keys:
        image_set = []
        image_sets.append(image_set)
        for _, channel_metadata_key, channel_metadata_value, _ in channel_metadata:
            file_name = "_".join(list(k) + [channel_metadata_value]) + ".tif"
            metadata = dict(
                [
                    (kn, kv)
                    for kn, kv in zip(
                        image_set_key_names + [channel_metadata_key],
                        list(k) + [channel_metadata_value],
                    )
                ]
            )
            image_set.append((file_name, metadata))
    #
    # Scramble the image sets
    #
    r = numpy.random.RandomState()
    r.seed(
        numpy.frombuffer(
            "".join(["%s=%s" % kv for kv in key_metadata]).encode(), numpy.uint8
        )
    )
    image_sets = [image_sets[i] for i in r.permutation(len(image_sets))]

    m = cellprofiler_core.measurement.Measurements()
    m.set_metadata_tags(
        [
            "_".join((cellprofiler_core.constants.measurement.C_METADATA, k))
            for k in image_set_key_names
        ]
    )
    m.set_channel_descriptors(channel_descriptors)
    for i, ipds in enumerate(image_sets):
        image_number = i + 1
        for (file_name, metadata), channel_name in zip(ipds, channel_descriptors.keys()):
            for feature, value in (
                (cellprofiler_core.constants.measurement.C_FILE_NAME, file_name),
                (
                    cellprofiler_core.constants.measurement.C_PATH_NAME,
                    os.pathsep + "images",
                ),
                (
                    cellprofiler_core.constants.measurement.C_URL,
                    "file://images/" + file_name,
                ),
            ):
                m[
                    cellprofiler_core.constants.measurement.IMAGE,
                    feature + "_" + channel_name,
                    image_number,
                ] = value
            for key, value in list(metadata.items()):
                feature = "_".join(
                    (cellprofiler_core.constants.measurement.C_METADATA, key)
                )
                m[
                    cellprofiler_core.constants.measurement.IMAGE, feature, image_number
                ] = value

    pipeline = cellprofiler_core.pipeline.Pipeline()
    module = cellprofiler_core.modules.groups.Groups()
    module.set_module_num(1)
    pipeline.add_module(module)
    workspace = cellprofiler_core.workspace.Workspace(
        pipeline, module, m, None, m, None
    )
    return module, workspace