{ "cells": [ { "cell_type": "markdown", "id": "7ce6eb8c-8ae0-4c9e-af12-bb45b8fcbbbf", "metadata": {}, "source": [ "# FlowIO Tutorial\n", "\n", "https://flowio.readthedocs.io/en/latest/?badge=latest\n", "\n", "FlowIO is a Python library for reading and writing flow cytometry standard (FCS) files. It is intended as a lightweight library, suitable for parsing FCS data sets (e.g. as a web server backend, for simple metadata extraction, etc.). It is **highly recommended** that one be familiar with the various FCS file standards (2.0, 3.0, 3,1) before using FlowIO for downstream analysis. For higher level cytometry analysis, please see the related [FlowKit](https://github.com/whitews/FlowKit) library which offers a much wider set of analysis options such as compensation, transformation, and gating support (including support for importing FlowJo 10 workspaces).\n", "\n", "If you have any questions about FlowIO, find any bugs, or feel something is missing from the documentation [please submit an issue to the GitHub repository here](https://github.com/whitews/FlowIO/issues/new/).\n", "\n", "## Table of Contents\n", "\n", "* [A Primer on FCS File Sections](#A-Primer-on-FCS-File-Sections)\n", "* [FlowData Class](#FlowData-Class)\n", " * [Create a FlowData Instance](#Create-a-FlowData-Instance)\n", " * [Metadata and Channel Information](#Metadata-and-Channel-Information)\n", " * [Channel Metadata](#Channel-Metadata)\n", " * [Event Data](#Event-Data)\n", " * [Export as FCS](#Export-as-FCS)\n", "* [Other FlowIO Features](#Other-FlowIO-Features)\n", " * [List of FCS Keywords](#List-of-FCS-Keywords)\n", " * [Reading FCS Files with Multiple Datasets](#Reading-FCS-Files-with-Multiple-Datasets)\n", " * [Creating FCS Files from Numerical Arrays](#Creating-FCS-Files-from-Numerical-Arrays)\n", " * [Custom Exceptions](#Custom-Exceptions)" ] }, { "cell_type": "code", "id": "145c25a0-61fe-4da4-8cbd-96b4a77f1852", "metadata": { "ExecuteTime": { "end_time": "2025-05-08T13:19:34.292686Z", "start_time": "2025-05-08T13:19:34.195485Z" } }, "source": [ "import flowio" ], "outputs": [], "execution_count": 1 }, { "cell_type": "code", "id": "761bf024-9927-4f52-87ea-f71ebabed54b", "metadata": { "ExecuteTime": { "end_time": "2025-05-08T13:19:34.386089Z", "start_time": "2025-05-08T13:19:34.382643Z" } }, "source": [ "flowio.__version__" ], "outputs": [ { "data": { "text/plain": [ "'1.4.0'" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "execution_count": 2 }, { "cell_type": "markdown", "id": "6533931f-3f38-4e80-a26a-9ae7a1d11d4e", "metadata": {}, "source": [ "## A Primer on FCS File Sections\n", "\n", "Before getting into the details on the FlowIO API, let's go over some nomenclature used by the FCS specification for defining the various sections found in an FCS file. We'll use the FCS 3.1 specification here, as this version is most commonly encountered these days. While there are differences between the FCS versions these basic section definitions are generally the same for other versions.\n", "\n", "The FCS specification lists the following file sections and references their names using all capital letters. The FlowIO documentation uses these same conventions (all caps) when referencing these sections.\n", "\n", "| Segment Name | Description |\n", "| --- | --- |\n", "| HEADER | Identifies the FCS version and describes the byte locations for the other segments in the data set. |\n", "| TEXT | Contains a series of ASCII encoded keyword-value pairs that describe various aspects of the data set (a.k.a. metadata). |\n", "| DATA | Contains the raw event data in one of three modes (list, correlated or uncorrelated) described by the `$MODE` keyword value. The data are stored in one of four allowed formats (binary, floating point, double precision floating point or ASCII) described by the `$DATATYPE` keyword value. |\n", "| ANALYSIS | An optional segment that, when present, contains the results of data processing. The ANALYSIS segment has the same structure as the TEXT segment; i.e., it consists of a series of keyword-value pairs. There are no required keywords for the ANALYSIS segment. |\n", "\n", "### Notes on FCS Metadata\n", "\n", "As mentioned above, the TEXT segment contains metadata stored as keyword-value pairs. Some keywords are FCS defined and these contain the character prefix `$`, and only FCS-defined keywords are allowed to begin with the `$` character. Keyword names are case-insensitive and can be any mixture of case, though FCS readers are instructed to ignore case. Some FCS-defined keywords are required, while others are optional." ] }, { "cell_type": "markdown", "id": "d28a3a88-ef58-4169-bf7f-87d0b222b97b", "metadata": {}, "source": [ "## FlowData Class\n", "\n", "A FlowData instance represents a single FCS file and is created from a local file name, file path, filehandle, or a pathlib Path object. FlowIO currently supports reading FCS 2.0, 3.0, and 3.1 files.\n", "\n", "Let's take a look at the FlowData constructor method:\n", "\n", " FlowData(\n", " fcs_file,\n", " ignore_offset_error=False,\n", " ignore_offset_discrepancy=False,\n", " use_header_offsets=False,\n", " only_text=False,\n", " nextdata_offset=None,\n", " null_channel_list=None,\n", " )\n", "\n", "* **fcs_file**: a file path string, Path instance, or file handle to an FCS file\n", "* **ignore_offset_error**: option to ignore data offset error (see note below), default is False\n", "* **ignore_offset_discrepancy**: option to ignore discrepancy between the HEADER\n", " and TEXT values for the DATA byte offset location, default is False\n", "* **use_header_offsets**: use the HEADER section for the data offset locations, default is False.\n", " Setting this option to True also suppresses an error in cases of an offset discrepancy.\n", "* **only_text**: option to only read the TEXT segment of the FCS file without loading event data,\n", " default is False\n", "* **nextdata_offset**: an integer indicating the byte offset for a data set, used for reading\n", " a data set from FCS file contain multiple data sets\n", "* **null_channel_list**: list of PnN labels corresponding to null channels. Null channels are\n", " omitted from fluoro_indices, scatter_indices, and time_index.\n", "\n", "**Note about FCS files with a data offset error:** \n", "\n", "Some FCS files incorrectly report the location of the last data byte as the last byte **exclusive** of the data section rather than the last byte **inclusive** of the data section. In short, the reported location of the last byte is off by one byte. Technically, these are invalid FCS files but are not corrupted data files. To attempt to read in these files, set the `ignore_offset_error` option to True.\n", "\n", "**Note on `ignore_offset_discrepancy` and `use_header_offset`:**\n", "\n", "The byte offset location for the DATA segment is defined in 2 places in an FCS file: the HEADER and the TEXT segments. By default, FlowIO uses the offset values found in the TEXT segment. If the HEADER values differ from the TEXT values, a DataOffsetDiscrepancyError will be raised. This option allows overriding this error to force loading of the FCS file. The related `use_header_offset` can be used to force loading the file using the data offset locations found in the HEADER section rather than the TEXT section. Setting `use_header_offset` to True is equivalent to setting both options to True, meaning no error will be raised for an offset discrepancy." ] }, { "cell_type": "markdown", "id": "c634b8fb-2ff3-46a0-b56a-e092e146917a", "metadata": {}, "source": [ "### Create a FlowData Instance\n", "\n", "Let's create a FlowData instance from an FCS file path string." ] }, { "cell_type": "code", "id": "b16a6d45-76e9-4037-adf9-2180351c2c46", "metadata": { "ExecuteTime": { "end_time": "2025-05-08T13:19:34.447559Z", "start_time": "2025-05-08T13:19:34.442356Z" } }, "source": [ "fcs_path = '../../data/fcs_files/data1.fcs'" ], "outputs": [], "execution_count": 3 }, { "cell_type": "code", "id": "f1a55751-c4f6-4d18-9765-ffb43e96bdad", "metadata": { "ExecuteTime": { "end_time": "2025-05-08T13:19:34.506328Z", "start_time": "2025-05-08T13:19:34.491225Z" } }, "source": [ "fd = flowio.FlowData(fcs_path)" ], "outputs": [], "execution_count": 4 }, { "cell_type": "code", "id": "1ac9a896-2e64-4189-a2af-56933f50cced", "metadata": { "ExecuteTime": { "end_time": "2025-05-08T13:19:34.541524Z", "start_time": "2025-05-08T13:19:34.538421Z" } }, "source": [ "fd" ], "outputs": [ { "data": { "text/plain": [ "FlowData(data1.fcs)" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "execution_count": 5 }, { "cell_type": "markdown", "id": "64019e36-0cf9-4864-8eec-a3c807417228", "metadata": {}, "source": [ "### Metadata and Channel Information" ] }, { "cell_type": "markdown", "id": "f4ffbe5e-27df-4471-9917-e4f6d4ec443c", "metadata": {}, "source": [ "Get the FCS version of the file:" ] }, { "cell_type": "code", "id": "d6cde367-52a3-4b9f-b492-a5cd5a08ee7a", "metadata": { "ExecuteTime": { "end_time": "2025-05-08T13:19:34.588941Z", "start_time": "2025-05-08T13:19:34.586030Z" } }, "source": [ "fd.version" ], "outputs": [ { "data": { "text/plain": [ "'2.0'" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "execution_count": 6 }, { "cell_type": "markdown", "id": "0a405b83-53fd-4708-bcd9-fb9e64bee06a", "metadata": {}, "source": [ "All the keyword-value pairs in the TEXT segment are available via the `text` attribute. A few of these values are also available as dedicated attributes for convenience, we'll get to those in a bit.\n", "\n", "**NOTE: The FlowData class stores TEXT keywords in lowercase regardless of how they were stored in the FCS file. Additionally, any FCS-defined keywords are stripped of their `$` character prefix. This is intentionally done for more convenient lookup so the user doesn't have to remember which keywords are FCS-defined or worry about the case.**" ] }, { "cell_type": "code", "id": "3c90b3f0-ce98-45d3-a37a-400f49f0bc22", "metadata": { "scrolled": true, "ExecuteTime": { "end_time": "2025-05-08T13:19:34.641055Z", "start_time": "2025-05-08T13:19:34.635278Z" } }, "source": [ "fd.text" ], "outputs": [ { "data": { "text/plain": [ "{'byteord': '4,3,2,1',\n", " 'datatype': 'I',\n", " 'nextdata': '0',\n", " 'sys': 'Macintosh System Software 9.0.4',\n", " 'creator': 'CELLQuestª 3.3',\n", " 'tot': '13367',\n", " 'mode': 'L',\n", " 'par': '8',\n", " 'p1n': 'FSC-H',\n", " 'p1r': '1024',\n", " 'p1b': '16',\n", " 'p1e': '0,0',\n", " 'p1g': '3.67',\n", " 'p2n': 'SSC-H',\n", " 'p2r': '1024',\n", " 'p2b': '16',\n", " 'p2e': '0,0',\n", " 'p2g': '8',\n", " 'p3n': 'FL1-H',\n", " 'p3r': '1024',\n", " 'p3b': '16',\n", " 'p3e': '4,0',\n", " 'p4n': 'FL2-H',\n", " 'p4r': '1024',\n", " 'p4b': '16',\n", " 'p4e': '4,0',\n", " 'p5n': 'FL3-H',\n", " 'p5r': '1024',\n", " 'p5b': '16',\n", " 'p5e': '4,0',\n", " 'p1s': 'FSC-Height',\n", " 'p2s': 'SSC-Height',\n", " 'p3s': 'CD4 FITC',\n", " 'p4s': 'CD8 B PE',\n", " 'p5s': 'CD3 PerCP',\n", " 'p6n': 'FL2-A',\n", " 'p6r': '1024',\n", " 'p6b': '16',\n", " 'p6e': '0,0',\n", " 'timeticks': '100',\n", " 'p7n': 'FL4-H',\n", " 'p7r': '1024',\n", " 'p7e': '4,0',\n", " 'p7b': '16',\n", " 'p7s': 'CD8 APC',\n", " 'p8n': 'Time',\n", " 'p8r': '1024',\n", " 'p8e': '0,0',\n", " 'p8b': '16',\n", " 'p8s': 'Time (102.40 sec.)',\n", " 'sample id': 'Default Patient ID',\n", " 'src': 'Default',\n", " 'case number': 'Default Case Number',\n", " 'cyt': 'FACSCalibur',\n", " 'cytnum': 'E3820',\n", " 'btim': '16:31:33',\n", " 'etim': '16:31:52',\n", " 'bdacqlibversion': '3.1',\n", " 'bdnpar': '7',\n", " 'bdp1n': 'FSC-H',\n", " 'bdp2n': 'SSC-H',\n", " 'bdp3n': 'FL1-H',\n", " 'bdp4n': 'FL2-H',\n", " 'bdp5n': 'FL3-H',\n", " 'bdp6n': 'FL2-A',\n", " 'bdp7n': 'FL4-H',\n", " 'bdword0': '24',\n", " 'bdword1': '394',\n", " 'bdword2': '492',\n", " 'bdword3': '477',\n", " 'bdword4': '566',\n", " 'bdword5': '397',\n", " 'bdword6': '397',\n", " 'bdword7': '397',\n", " 'bdword8': '398',\n", " 'bdword9': '397',\n", " 'bdword10': '300',\n", " 'bdword11': '299',\n", " 'bdword12': '551',\n", " 'bdword13': '4',\n", " 'bdword14': '397',\n", " 'bdword15': '501',\n", " 'bdword16': '481',\n", " 'bdword17': '586',\n", " 'bdword18': '574',\n", " 'bdword19': '100',\n", " 'bdword20': '100',\n", " 'bdword21': '100',\n", " 'bdword22': '100',\n", " 'bdword23': '1',\n", " 'bdword24': '1',\n", " 'bdword25': '0',\n", " 'bdword26': '0',\n", " 'bdword27': '0',\n", " 'bdword28': '136',\n", " 'bdword29': '52',\n", " 'bdword30': '52',\n", " 'bdword31': '52',\n", " 'bdword32': '52',\n", " 'bdword33': '52',\n", " 'bdword34': '12',\n", " 'bdword35': '201',\n", " 'bdword36': '6',\n", " 'bdword37': '138',\n", " 'bdword38': '280',\n", " 'bdword39': '3',\n", " 'bdword40': '3',\n", " 'bdword41': '100',\n", " 'bdword42': '100',\n", " 'bdword43': '0',\n", " 'bdword44': '1023',\n", " 'bdword45': '1023',\n", " 'bdword46': '1023',\n", " 'bdword47': '53',\n", " 'bdword48': '550',\n", " 'bdword49': '56',\n", " 'bdword50': '72',\n", " 'bdword51': '52',\n", " 'bdword52': '0',\n", " 'bdword53': '0',\n", " 'bdword54': '0',\n", " 'bdword55': '0',\n", " 'bdword56': '0',\n", " 'bdword57': '0',\n", " 'bdword58': '0',\n", " 'bdword59': '0',\n", " 'bdword60': '0',\n", " 'bdword61': '0',\n", " 'bdword62': '0',\n", " 'bdword63': '0',\n", " 'bdlasermode': '1',\n", " 'calibfile': 'FALSE',\n", " 'p7thresvol': '52',\n", " 'fil': 'B07',\n", " 'date': '23-Aug-02',\n", " 'number well info keywords': '3',\n", " '&1sample': '200',\n", " '&2number of washes': '1',\n", " '&3mixing vol': '100',\n", " '&4number of mixes': '2',\n", " '&5data file prefix part #1\\\\\\\\&6data file prefix part #2\\\\\\\\&7data file prefix part #3\\\\\\\\&8acquisition doc.': 'LYMPH SUBSET ACQ',\n", " '&9instr. sett. file': 'E#7 Settings #1',\n", " '&10patient id': ' FJ#192659',\n", " '&11day': '35d',\n", " '&12sample id': 'T-cells',\n", " '&13analysis doc.': ''}" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "execution_count": 7 }, { "cell_type": "markdown", "id": "7816d8a4-9f4f-4501-b3df-ac5a4aad0f04", "metadata": {}, "source": "As mentioned above, certain metadata in the TEXT segment is available in other FlowData attributes. Most of these relate to event and channel metadata. For example, `event_count` gives the number of events in the FCS file:" }, { "cell_type": "code", "id": "4ed20bf1-e8d2-47b6-8056-df5591b0b52f", "metadata": { "ExecuteTime": { "end_time": "2025-05-08T13:19:34.703132Z", "start_time": "2025-05-08T13:19:34.698546Z" } }, "source": [ "fd.event_count" ], "outputs": [ { "data": { "text/plain": [ "13367" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "execution_count": 8 }, { "cell_type": "markdown", "id": "bf038964-8d49-4751-9106-c8c164d50db8", "metadata": {}, "source": [ "The data type for the event data is available via the `data_type` attribute, storing the single character data type code. The four\n", "allowed values are 'I' for unsigned binary integer, 'F' for single precision IEEE floating point, 'D' for double precision IEEE floating points, or 'A' for ASCII." ] }, { "cell_type": "code", "id": "81f643bc-41e8-4a6a-9d14-366a7012a0fa", "metadata": { "ExecuteTime": { "end_time": "2025-05-08T13:19:34.763526Z", "start_time": "2025-05-08T13:19:34.758984Z" } }, "source": [ "fd.data_type" ], "outputs": [ { "data": { "text/plain": [ "'I'" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "execution_count": 9 }, { "cell_type": "markdown", "id": "ca5e1328-4c81-418b-a634-edb64ff9b489", "metadata": {}, "source": [ "The FCS file size (in bytes):" ] }, { "cell_type": "code", "id": "2bd65e79-26f8-488b-895d-fd62ad338b2a", "metadata": { "ExecuteTime": { "end_time": "2025-05-08T13:19:34.822620Z", "start_time": "2025-05-08T13:19:34.818898Z" } }, "source": [ "fd.file_size" ], "outputs": [ { "data": { "text/plain": [ "216432" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "execution_count": 10 }, { "cell_type": "markdown", "id": "52197846-0e92-49d5-b1b5-f45c48381adc", "metadata": {}, "source": [ "The number of channels of event data:" ] }, { "cell_type": "code", "id": "6860aa69-91b4-476c-baf3-c25c0062c371", "metadata": { "ExecuteTime": { "end_time": "2025-05-08T13:19:34.884221Z", "start_time": "2025-05-08T13:19:34.879249Z" } }, "source": [ "fd.channel_count" ], "outputs": [ { "data": { "text/plain": [ "8" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "execution_count": 11 }, { "cell_type": "markdown", "id": "e4c7066d-e256-4032-8c92-2cc1338ae685", "metadata": {}, "source": [ "#### Channel Metadata\n", "\n", "FCS defines several keyword sets for channel metadata. These numbered parameter keywords begin with the letter 'P' followed by a channel number and a third character denoting the type of channel metadata. For example, keywords of the for 'PnN' correspond to the required parameter names, where 'n' is a channel number (e.g. 'P1N' for the first channel's name). We'll go over several of these channel metadata sets." ] }, { "cell_type": "markdown", "id": "62a8ff88-1a5a-47a9-b696-339cb719dcc3", "metadata": {}, "source": [ "The 'PnN' channel labels are found in the `pnn_labels` attribute. The order of these correspond to the channel order found in the event data. " ] }, { "cell_type": "code", "id": "9f207899-7129-4b75-b37c-6702dc3590b0", "metadata": { "ExecuteTime": { "end_time": "2025-05-08T13:19:34.943771Z", "start_time": "2025-05-08T13:19:34.938818Z" } }, "source": [ "fd.pnn_labels" ], "outputs": [ { "data": { "text/plain": [ "['FSC-H', 'SSC-H', 'FL1-H', 'FL2-H', 'FL3-H', 'FL2-A', 'FL4-H', 'Time']" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "execution_count": 12 }, { "cell_type": "markdown", "id": "4a0196ec-5998-4d27-b2bd-72882c58b256", "metadata": {}, "source": [ "The 'PnS' labels are not required by FCS, but often contain useful channel information. The `pns_labels` attribute is guaranteed to have a matching length to that of the required `pnn_labels` list. Any missing PnS fields will contain an empty string. This is useful for retrieving channel metadata for the same channel index." ] }, { "cell_type": "code", "id": "4ae6ee64-8683-4ef5-8c2b-feb9e1f38cac", "metadata": { "ExecuteTime": { "end_time": "2025-05-08T13:19:34.992213Z", "start_time": "2025-05-08T13:19:34.988368Z" } }, "source": [ "fd.pns_labels" ], "outputs": [ { "data": { "text/plain": [ "['FSC-Height',\n", " 'SSC-Height',\n", " 'CD4 FITC',\n", " 'CD8 B PE',\n", " 'CD3 PerCP',\n", " '',\n", " 'CD8 APC',\n", " 'Time (102.40 sec.)']" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "execution_count": 13 }, { "cell_type": "markdown", "id": "df310dba-a271-4076-ae54-30dc0bc95019", "metadata": {}, "source": "Additionally, there are the 'PnR' values containing the data range for each channel." }, { "cell_type": "code", "id": "6cfb2fce-3959-4e31-bd43-e699e0b0380e", "metadata": { "ExecuteTime": { "end_time": "2025-05-08T13:19:35.050642Z", "start_time": "2025-05-08T13:19:35.046268Z" } }, "source": [ "fd.pnr_values" ], "outputs": [ { "data": { "text/plain": [ "[1024.0, 1024.0, 1024.0, 1024.0, 1024.0, 1024.0, 1024.0, 1024.0]" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "execution_count": 14 }, { "cell_type": "markdown", "id": "27f0257b-77c0-4780-a233-042464b955ee", "metadata": {}, "source": [ "All the channel metadata needed for correct interpretation of the raw event data is summarized in an additional `channel` attribute, stored as a dictionary. The keys are channel numbers (not channel indices) and the values are dictionaries where the keywords are the channel metadata class types: 'pnn', 'pns', 'pne', 'png', and 'pnr'." ] }, { "cell_type": "code", "id": "6e9b7ac6-e111-47b6-b34f-c9088bd0527d", "metadata": { "ExecuteTime": { "end_time": "2025-05-08T13:19:35.118622Z", "start_time": "2025-05-08T13:19:35.107979Z" } }, "source": [ "fd.channels" ], "outputs": [ { "data": { "text/plain": [ "{1: {'pnn': 'FSC-H',\n", " 'pns': 'FSC-Height',\n", " 'pne': (0.0, 0.0),\n", " 'png': 3.67,\n", " 'pnr': 1024.0},\n", " 2: {'pnn': 'SSC-H',\n", " 'pns': 'SSC-Height',\n", " 'pne': (0.0, 0.0),\n", " 'png': 8.0,\n", " 'pnr': 1024.0},\n", " 3: {'pnn': 'FL1-H',\n", " 'pns': 'CD4 FITC',\n", " 'pne': (4.0, 1.0),\n", " 'png': 1.0,\n", " 'pnr': 1024.0},\n", " 4: {'pnn': 'FL2-H',\n", " 'pns': 'CD8 B PE',\n", " 'pne': (4.0, 1.0),\n", " 'png': 1.0,\n", " 'pnr': 1024.0},\n", " 5: {'pnn': 'FL3-H',\n", " 'pns': 'CD3 PerCP',\n", " 'pne': (4.0, 1.0),\n", " 'png': 1.0,\n", " 'pnr': 1024.0},\n", " 6: {'pnn': 'FL2-A', 'pns': '', 'pne': (0.0, 0.0), 'png': 1.0, 'pnr': 1024.0},\n", " 7: {'pnn': 'FL4-H',\n", " 'pns': 'CD8 APC',\n", " 'pne': (4.0, 1.0),\n", " 'png': 1.0,\n", " 'pnr': 1024.0},\n", " 8: {'pnn': 'Time',\n", " 'pns': 'Time (102.40 sec.)',\n", " 'pne': (0.0, 0.0),\n", " 'png': 1.0,\n", " 'pnr': 1024.0}}" ] }, "execution_count": 15, "metadata": {}, "output_type": "execute_result" } ], "execution_count": 15 }, { "cell_type": "markdown", "id": "884bbd3b-288a-4898-a8ea-16aaba8c60bb", "metadata": {}, "source": [ "Finally, we have a few attributes to serve as helpers for distinguishing common parameter types found in cytometry data (scatter channels, fluorescence channels, and the time channel). These attributes are `scatter_indices`, `fluoro_indices`, and `time_index`. Note, these are indices (zero-indexed) and not channel numbers." ] }, { "cell_type": "code", "id": "81b9672e-dd19-4083-8605-3edfe499b8cb", "metadata": { "ExecuteTime": { "end_time": "2025-05-08T13:19:35.186837Z", "start_time": "2025-05-08T13:19:35.183596Z" } }, "source": [ "fd.scatter_indices" ], "outputs": [ { "data": { "text/plain": [ "[0, 1]" ] }, "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "execution_count": 16 }, { "cell_type": "code", "id": "cfe24d51-a946-4106-8efe-665670bfdb84", "metadata": { "ExecuteTime": { "end_time": "2025-05-08T13:19:35.251531Z", "start_time": "2025-05-08T13:19:35.243677Z" } }, "source": [ "fd.fluoro_indices" ], "outputs": [ { "data": { "text/plain": [ "[2, 3, 4, 5, 6]" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "execution_count": 17 }, { "cell_type": "code", "id": "b154a1ec-4d02-4053-a728-3a51130bb127", "metadata": { "ExecuteTime": { "end_time": "2025-05-08T13:19:35.302832Z", "start_time": "2025-05-08T13:19:35.296864Z" } }, "source": [ "fd.time_index" ], "outputs": [ { "data": { "text/plain": [ "7" ] }, "execution_count": 18, "metadata": {}, "output_type": "execute_result" } ], "execution_count": 18 }, { "cell_type": "markdown", "id": "c2114e58-3e3b-4a4d-95fa-2f9ef260dc83", "metadata": {}, "source": [ "### Event Data\n", "\n", "The FlowData class stores event data in the same unprocessed list mode as found in the FCS file. In general, this unprocessed data is not suitable for downstream analysis as preprocessing steps are needed for proper interpretation of the channel data. However, the preprocessed event data is available as a 2-D NumPy array via the `as_array` method. This is done intentionally to minimize the memory usage of FlowData instances." ] }, { "cell_type": "markdown", "id": "a4d22146-1f24-4535-9509-5c5c80b7f424", "metadata": {}, "source": [ "Get unprocessed event data as 1-D array from the `events` attribute:" ] }, { "cell_type": "code", "id": "698fffa7-81cd-4e2d-90c4-6043a94e696f", "metadata": { "scrolled": true, "ExecuteTime": { "end_time": "2025-05-08T13:19:35.349992Z", "start_time": "2025-05-08T13:19:35.347330Z" } }, "source": [ "# only selecting the first few for demonstration\n", "fd.events[:10]" ], "outputs": [ { "data": { "text/plain": [ "array('H', [323, 218, 220, 394, 267, 5, 183, 0, 70, 43])" ] }, "execution_count": 19, "metadata": {}, "output_type": "execute_result" } ], "execution_count": 19 }, { "cell_type": "markdown", "id": "1d7c2244-ef29-40f2-935e-e2d57ed4bf4b", "metadata": {}, "source": [ "Get the processed data as a 2-D NumPy array using the `as_array` method. First, let's read the docstring." ] }, { "cell_type": "code", "id": "3fc84071-8998-4893-8d68-715e34e47676", "metadata": { "ExecuteTime": { "end_time": "2025-05-08T13:19:35.397895Z", "start_time": "2025-05-08T13:19:35.395021Z" } }, "source": "help(fd.as_array)", "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Help on method as_array in module flowio.flowdata:\n", "\n", "as_array(preprocess=True) method of flowio.flowdata.FlowData instance\n", " Retrieve the event data list as a 2-D NumPy array. Pre-processing is\n", " applied if requested and includes applying gain, log, and time scaling\n", " as necessary.\n", " \n", " :param preprocess: Boolean for whether to apply gain, log, and time\n", " scaling as necessary according the FCS metadata (default is True).\n", " \n", " :return: NumPy array of 2-D event data\n", "\n" ] } ], "execution_count": 20 }, { "cell_type": "code", "id": "eb7db286-97f5-4363-b488-66f1b5b81001", "metadata": { "ExecuteTime": { "end_time": "2025-05-08T13:19:35.449242Z", "start_time": "2025-05-08T13:19:35.445188Z" } }, "source": [ "# by default, it returns the preprocessed data\n", "fd.as_array()" ], "outputs": [ { "data": { "text/plain": [ "array([[ 88.01089918, 27.25 , 7.23394163, ..., 5. ,\n", " 5.18613419, 0. ],\n", " [ 19.07356948, 5.375 , 36.51741273, ..., 0. ,\n", " 4.29351021, 0. ],\n", " [ 70.57220708, 26. , 2.48045441, ..., 0. ,\n", " 8.58210354, 0. ],\n", " ...,\n", " [ 62.1253406 , 27.625 , 11.75743266, ..., 0. ,\n", " 1.77827941, 174. ],\n", " [ 36.23978202, 64.5 , 5.42469094, ..., 0. ,\n", " 4.95806824, 174. ],\n", " [ 66.48501362, 8.75 , 1.43301257, ..., 0. ,\n", " 6.0429639 , 174. ]])" ] }, "execution_count": 21, "metadata": {}, "output_type": "execute_result" } ], "execution_count": 21 }, { "cell_type": "code", "id": "d244beab-aee7-4d25-83df-9f922730245b", "metadata": { "ExecuteTime": { "end_time": "2025-05-08T13:19:35.644309Z", "start_time": "2025-05-08T13:19:35.641020Z" } }, "source": [ "# set 'preprocess=False' to get a 2-D NumPy array of unprocessed data\n", "fd.as_array(preprocess=False)" ], "outputs": [ { "data": { "text/plain": [ "array([[323., 218., 220., ..., 5., 183., 0.],\n", " [ 70., 43., 400., ..., 0., 162., 0.],\n", " [259., 208., 101., ..., 0., 239., 0.],\n", " ...,\n", " [228., 221., 274., ..., 0., 64., 174.],\n", " [133., 516., 188., ..., 0., 178., 174.],\n", " [244., 70., 40., ..., 0., 200., 174.]])" ] }, "execution_count": 22, "metadata": {}, "output_type": "execute_result" } ], "execution_count": 22 }, { "cell_type": "markdown", "id": "88850483-9a18-48e7-8f68-22365801739a", "metadata": {}, "source": [ "### Export as FCS\n", "\n", "The FlowData class can also export the instance as a new FCS file using the `write_fcs` method. This is useful for modifying or removing certain metadata. Note, FlowIO only exports FCS files with $MODE 'F' (single precision floating point). If non-floating point data was loaded, the event data will be preprocessed and stored as 'F'. " ] }, { "cell_type": "code", "id": "ca322348-0c33-48e3-9495-546dd8a6d0f5", "metadata": { "ExecuteTime": { "end_time": "2025-05-08T13:19:35.733280Z", "start_time": "2025-05-08T13:19:35.731129Z" } }, "source": "help(fd.write_fcs)", "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Help on method write_fcs in module flowio.flowdata:\n", "\n", "write_fcs(filename, metadata=None) method of flowio.flowdata.FlowData instance\n", " Export FlowData instance as a new FCS file.\n", " \n", " By default, the output FCS file will include the $cyt, $date, and $spill\n", " keywords (and values) from the FlowData instance. To exclude these keys,\n", " specify a custom `metadata` dictionary (including an empty dictionary for\n", " the bare minimum metadata). Note: Any critical keywords related to the\n", " interpretation of the event data are defined and set internally,\n", " overriding those in the provided `metadata` dictionary. These keywords\n", " include: PnB, PnE, and PnG.\n", " \n", " :param filename: name of exported FCS file\n", " :param metadata: an optional dictionary for adding metadata keywords/values\n", " :return: None\n", "\n" ] } ], "execution_count": 23 }, { "cell_type": "markdown", "id": "89cacc23-f3a1-479f-aea7-9129f7db2219", "metadata": {}, "source": [ "## Other FlowIO Features\n", "\n", "The FlowData class is the main feature of FlowIO, however there are a few other useful features." ] }, { "cell_type": "markdown", "id": "6deccd94-fb08-4c06-a84d-61d91c5f8e90", "metadata": {}, "source": [ "### List of FCS Keywords\n", "\n", "As mentioned in the section on FCS metadata, there are keywords that are predefined in the FCS specification. FlowIO includes a lookup list of these reserved keywords. There are 3 variables for all the reserved keywords, just the required keywords, and just the optional keywords. All 3 are found in the `fcs_keywords` module. " ] }, { "cell_type": "code", "id": "dc959ce2-e080-4a61-bbee-82428877387e", "metadata": { "ExecuteTime": { "end_time": "2025-05-08T13:19:35.812896Z", "start_time": "2025-05-08T13:19:35.808664Z" } }, "source": [ "flowio.fcs_keywords.FCS_STANDARD_KEYWORDS" ], "outputs": [ { "data": { "text/plain": [ "['beginanalysis',\n", " 'begindata',\n", " 'beginstext',\n", " 'byteord',\n", " 'datatype',\n", " 'endanalysis',\n", " 'enddata',\n", " 'endstext',\n", " 'mode',\n", " 'nextdata',\n", " 'par',\n", " 'tot',\n", " 'abrt',\n", " 'btim',\n", " 'cells',\n", " 'com',\n", " 'csmode',\n", " 'csvbits',\n", " 'cyt',\n", " 'cytsn',\n", " 'date',\n", " 'etim',\n", " 'exp',\n", " 'fil',\n", " 'gate',\n", " 'inst',\n", " 'last_modified',\n", " 'last_modifier',\n", " 'lost',\n", " 'op',\n", " 'originality',\n", " 'plateid',\n", " 'platename',\n", " 'proj',\n", " 'smno',\n", " 'spillover',\n", " 'src',\n", " 'sys',\n", " 'timestep',\n", " 'tr',\n", " 'vol',\n", " 'wellid']" ] }, "execution_count": 24, "metadata": {}, "output_type": "execute_result" } ], "execution_count": 24 }, { "cell_type": "code", "id": "6e679d0a-96e4-468e-acc5-179a138fb9b3", "metadata": { "ExecuteTime": { "end_time": "2025-05-08T13:19:35.870047Z", "start_time": "2025-05-08T13:19:35.866945Z" } }, "source": [ "flowio.fcs_keywords.FCS_STANDARD_REQUIRED_KEYWORDS" ], "outputs": [ { "data": { "text/plain": [ "['beginanalysis',\n", " 'begindata',\n", " 'beginstext',\n", " 'byteord',\n", " 'datatype',\n", " 'endanalysis',\n", " 'enddata',\n", " 'endstext',\n", " 'mode',\n", " 'nextdata',\n", " 'par',\n", " 'tot']" ] }, "execution_count": 25, "metadata": {}, "output_type": "execute_result" } ], "execution_count": 25 }, { "cell_type": "code", "id": "54237b48-df20-42f8-80c8-88f162c46283", "metadata": { "ExecuteTime": { "end_time": "2025-05-08T13:19:35.921865Z", "start_time": "2025-05-08T13:19:35.917713Z" } }, "source": [ "flowio.fcs_keywords.FCS_STANDARD_OPTIONAL_KEYWORDS" ], "outputs": [ { "data": { "text/plain": [ "['abrt',\n", " 'btim',\n", " 'cells',\n", " 'com',\n", " 'csmode',\n", " 'csvbits',\n", " 'cyt',\n", " 'cytsn',\n", " 'date',\n", " 'etim',\n", " 'exp',\n", " 'fil',\n", " 'gate',\n", " 'inst',\n", " 'last_modified',\n", " 'last_modifier',\n", " 'lost',\n", " 'op',\n", " 'originality',\n", " 'plateid',\n", " 'platename',\n", " 'proj',\n", " 'smno',\n", " 'spillover',\n", " 'src',\n", " 'sys',\n", " 'timestep',\n", " 'tr',\n", " 'vol',\n", " 'wellid']" ] }, "execution_count": 26, "metadata": {}, "output_type": "execute_result" } ], "execution_count": 26 }, { "cell_type": "markdown", "id": "42f0b978-9b94-454c-95da-48c5c151c1ce", "metadata": {}, "source": [ "### Reading FCS Files with Multiple Datasets\n", "\n", "Some FCS files contain multiple data sets within the same file. FlowIO supports reading in these files via the standalone `read_multiple_data_sets` function which returns a list of FlowData instances. Let's review the docstring and then use the function to extract the data sets from an example file." ] }, { "cell_type": "code", "id": "946dad42-5185-4cfe-975b-95fad2e5c961", "metadata": { "ExecuteTime": { "end_time": "2025-05-08T13:19:35.971096Z", "start_time": "2025-05-08T13:19:35.968335Z" } }, "source": "help(flowio.read_multiple_data_sets)", "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Help on function read_multiple_data_sets in module flowio.utils:\n", "\n", "read_multiple_data_sets(filename_or_handle, ignore_offset_error=False, ignore_offset_discrepancy=False, use_header_offsets=False, only_text=False)\n", " Utility function for reading all data sets contained in an FCS file.\n", " \n", " :param filename_or_handle: a path string or a file handle for an FCS file\n", " :param ignore_offset_error: option to ignore data offset error (see above note), default is False\n", " :param ignore_offset_discrepancy: option to ignore discrepancy between the HEADER\n", " and TEXT values for the DATA byte offset location, default is False\n", " :param use_header_offsets: use the HEADER section for the data offset locations, default is False.\n", " Setting this option to True also suppresses an error in cases of an offset discrepancy.\n", " :param only_text: option to only read the \"text\" segment of the FCS file without loading event data,\n", " default is False\n", " \n", " :return: List of FlowData instances for each found data set\n", "\n" ] } ], "execution_count": 27 }, { "metadata": {}, "cell_type": "markdown", "source": [ "The following example file has the \"off by one\" issue of incorrectly reporting the last byte location.\n", "We must set `ignore_offset_error=True` to open the file without throwing an error. Note,\n", "FlowIO will still emit a UserWarning indicating that the file should be reviewed." ], "id": "aee60bcfc39276c9" }, { "cell_type": "code", "id": "e8ce133c-f8a0-4bd8-afed-2cb8034da85a", "metadata": { "ExecuteTime": { "end_time": "2025-05-08T13:19:36.075098Z", "start_time": "2025-05-08T13:19:36.018807Z" } }, "source": "fd_list = flowio.read_multiple_data_sets(\"../../data/fcs_files/coulter.lmd\", ignore_offset_error=True)", "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "/home/swhite@dhe.duke.edu/git/flowio/src/flowio/flowdata.py:451: UserWarning: FCS file coulter.lmd reported incorrect data offset. Attempting to parse data section, but event data should be reviewed before trusting this file.\n", " warn(warn_msg)\n" ] } ], "execution_count": 28 }, { "cell_type": "code", "id": "30a8a162-35bf-47fb-9fa3-4cc89d1eef66", "metadata": { "ExecuteTime": { "end_time": "2025-05-08T13:19:36.084904Z", "start_time": "2025-05-08T13:19:36.082505Z" } }, "source": [ "len(fd_list)" ], "outputs": [ { "data": { "text/plain": [ "2" ] }, "execution_count": 29, "metadata": {}, "output_type": "execute_result" } ], "execution_count": 29 }, { "cell_type": "code", "id": "251db741-ee5a-4c6a-85e0-9875d359a8de", "metadata": { "ExecuteTime": { "end_time": "2025-05-08T13:19:36.148123Z", "start_time": "2025-05-08T13:19:36.142106Z" } }, "source": [ "fd_list" ], "outputs": [ { "data": { "text/plain": [ "[FlowData(coulter.lmd), FlowData(coulter.lmd)]" ] }, "execution_count": 30, "metadata": {}, "output_type": "execute_result" } ], "execution_count": 30 }, { "cell_type": "markdown", "id": "a5cad4dc-f761-4878-9681-b2fe260bb696", "metadata": {}, "source": [ "### Creating FCS Files from Numerical Arrays\n", "\n", "The standalone `create_fcs` function allows for the creation of new FCS file from numerical arrays. This can be useful for creating FCS files for test cases, saving processed events, or a subset of extracted event data. Let's review the docstring and see an example of creating an FCS file from a 2-D array of randomly generated data." ] }, { "cell_type": "code", "id": "78b48188-3bc0-451f-b97a-3bf3affd4be9", "metadata": { "ExecuteTime": { "end_time": "2025-05-08T13:19:36.198873Z", "start_time": "2025-05-08T13:19:36.196279Z" } }, "source": "help(flowio.create_fcs)", "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Help on function create_fcs in module flowio.create_fcs:\n", "\n", "create_fcs(file_handle, event_data, channel_names, opt_channel_names=None, metadata_dict=None)\n", " Create a new FCS file from a list of event data.\n", " \n", " Note:\n", " A proper spillover matrix shall have the first value corresponding to the\n", " number of compensated fluorescence channels followed by the $PnN names\n", " which should match the given channel_names argument. All values in the\n", " spill text string should be comma-delimited with no newline characters.\n", " \n", " :param file_handle: file handle for new FCS file\n", " :param event_data: list of event data (flattened 1-D list)\n", " :param channel_names: list of channel labels to use for PnN fields\n", " :param opt_channel_names: optional list of channel labels to use for PnS fields\n", " :param metadata_dict: an optional dictionary for adding extra metadata keywords/values\n", " \n", " :return:\n", "\n" ] } ], "execution_count": 31 }, { "cell_type": "markdown", "id": "a9aae98d-b5c0-49c5-9bb3-2f78854e3eea", "metadata": {}, "source": [ "#### Generate a synthetic data set of 2 separated clusters in 4 dimensions. \n", "\n", "**Note, we flatten the 2-D array to a list for input to the `create_fcs` function.**" ] }, { "cell_type": "code", "id": "7063d94c-b439-41cb-a0b0-871ca1ddfb4a", "metadata": { "ExecuteTime": { "end_time": "2025-05-08T13:19:36.263612Z", "start_time": "2025-05-08T13:19:36.257819Z" } }, "source": [ "import numpy as np" ], "outputs": [], "execution_count": 32 }, { "cell_type": "code", "id": "6a5287f0-c268-4437-8569-f66a58b6229e", "metadata": { "ExecuteTime": { "end_time": "2025-05-08T13:19:36.330095Z", "start_time": "2025-05-08T13:19:36.315240Z" } }, "source": [ "# these clusters are clearly separated, each containing 2000 points\n", "cluster1 = np.random.multivariate_normal(\n", " [6000.0, 6000.0, 0.0, 3000.0],\n", " np.array(\n", " [\n", " [600000, 300, 0, 0],\n", " [300, 1000, 0, 0],\n", " [0, 0, 1, 10],\n", " [0, 0, 10, 1000]\n", " ]\n", " ),\n", " (2000,)\n", ")\n", "cluster2 = np.random.multivariate_normal(\n", " [-10.0, 0.0, 0.0, 0.0],\n", " np.array(\n", " [\n", " [10000, 100, 0, 0],\n", " [100, 10000, 0, 0],\n", " [0, 0, 100000, 0],\n", " [0, 0, 0, 1000]\n", " ]\n", " ),\n", " (2000,)\n", ")\n", "\n", "data_set_points = np.vstack(\n", " [\n", " cluster1,\n", " cluster2,\n", " ]\n", ").flatten().tolist()" ], "outputs": [], "execution_count": 33 }, { "cell_type": "code", "id": "c88d2793-a468-461f-8bcf-2a846dd5ae6b", "metadata": { "ExecuteTime": { "end_time": "2025-05-08T13:19:36.391237Z", "start_time": "2025-05-08T13:19:36.386852Z" } }, "source": [ "# create required labels for the 4 channels\n", "channel_names = [\n", " 'channel_A',\n", " 'channel_B',\n", " 'channel_C',\n", " 'channel_D'\n", "]\n", "\n", "# create a filehandle and save the data to a new FCS file\n", "fh = open('create_fcs_example.fcs', 'wb')\n", "flowio.create_fcs(fh, data_set_points, channel_names)\n", "fh.close()" ], "outputs": [], "execution_count": 34 }, { "cell_type": "markdown", "id": "478faab4-36b2-4bd7-94a5-e561527fcdcf", "metadata": {}, "source": [ "#### Open the newly created FCS file using the FlowData class" ] }, { "cell_type": "code", "id": "f176a950-d083-4dfc-93a2-684263e22c33", "metadata": { "ExecuteTime": { "end_time": "2025-05-08T13:19:36.460638Z", "start_time": "2025-05-08T13:19:36.457835Z" } }, "source": [ "fd_we_created = flowio.FlowData('create_fcs_example.fcs')" ], "outputs": [], "execution_count": 35 }, { "cell_type": "code", "id": "95a12f06-c68d-4f38-8bc6-9f2fb3bdc939", "metadata": { "ExecuteTime": { "end_time": "2025-05-08T13:19:36.512122Z", "start_time": "2025-05-08T13:19:36.509351Z" } }, "source": [ "fd_we_created.channel_count" ], "outputs": [ { "data": { "text/plain": [ "4" ] }, "execution_count": 36, "metadata": {}, "output_type": "execute_result" } ], "execution_count": 36 }, { "cell_type": "code", "id": "853ddda5-5a6a-4533-9803-ac6a09e7934b", "metadata": { "ExecuteTime": { "end_time": "2025-05-08T13:19:36.566028Z", "start_time": "2025-05-08T13:19:36.563621Z" } }, "source": [ "fd_we_created.event_count" ], "outputs": [ { "data": { "text/plain": [ "4000" ] }, "execution_count": 37, "metadata": {}, "output_type": "execute_result" } ], "execution_count": 37 }, { "cell_type": "code", "id": "c443d9f8-f1d7-426d-884e-281ee95572e1", "metadata": { "ExecuteTime": { "end_time": "2025-05-08T13:19:36.631503Z", "start_time": "2025-05-08T13:19:36.626111Z" } }, "source": [ "fd_we_created.channels" ], "outputs": [ { "data": { "text/plain": [ "{1: {'pnn': 'channel_A',\n", " 'pns': '',\n", " 'pne': (0.0, 0.0),\n", " 'png': 1.0,\n", " 'pnr': 262144.0},\n", " 2: {'pnn': 'channel_B',\n", " 'pns': '',\n", " 'pne': (0.0, 0.0),\n", " 'png': 1.0,\n", " 'pnr': 262144.0},\n", " 3: {'pnn': 'channel_C',\n", " 'pns': '',\n", " 'pne': (0.0, 0.0),\n", " 'png': 1.0,\n", " 'pnr': 262144.0},\n", " 4: {'pnn': 'channel_D',\n", " 'pns': '',\n", " 'pne': (0.0, 0.0),\n", " 'png': 1.0,\n", " 'pnr': 262144.0}}" ] }, "execution_count": 38, "metadata": {}, "output_type": "execute_result" } ], "execution_count": 38 }, { "cell_type": "markdown", "id": "9d2a717d-a2ba-4869-b0d2-de6ba5857e56", "metadata": {}, "source": [ "### Custom Exceptions\n", "\n", "FlowIO includes a few custom exception and warning classes, useful for catches FlowIO specific errors. All FlowIO defined warnings derive from the generic `FlowIOWarning` class. All FlowIO defined exceptions derive from the `FlowIOException` class." ] }, { "cell_type": "markdown", "id": "7e63f395-2f8e-46e2-91de-a89e9af31c1f", "metadata": {}, "source": [ "`PnEWarning`\n", "\n", "Warning for invalid PnE values when creating FCS files" ] }, { "cell_type": "markdown", "id": "de414483-2499-40fc-b65e-24c482cede15", "metadata": {}, "source": [ "`FCSParsingError`\n", "\n", "Errors relating to parsing an FCS file" ] }, { "cell_type": "markdown", "id": "5dbbc65f-e113-4d60-8d55-2e96cd17e612", "metadata": {}, "source": [ "`DataOffsetDiscrepancyError`\n", "\n", "Raised when an FCS file's HEADER & TEXT section provide different byte\n", "offsets for the DATA section." ] }, { "cell_type": "markdown", "id": "eb6b0c2c-881c-4f41-ac46-b218f9c8a77e", "metadata": {}, "source": [ "`MultipleDataSetsError`\n", "\n", "Raised for errors related to FCS files containing more than one dataset, indicated by\n", "the 'nextdata' keyword." ] }, { "cell_type": "code", "id": "1f27c07e-9841-4e4d-80e9-706c3c8ccb10", "metadata": { "ExecuteTime": { "end_time": "2025-05-08T13:19:36.677715Z", "start_time": "2025-05-08T13:19:36.675840Z" } }, "source": [], "outputs": [], "execution_count": null } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.10.12" } }, "nbformat": 4, "nbformat_minor": 5 }