~brenthuisman/dosia

71d96ee89fc2c31b8839f9ee53ddb0cddc044aa9 — Brent 9 months ago f14e22e
trf import
A test.trf.py => test.trf.py +11 -0
@@ 0,0 1,11 @@
import trf

fn=r"D:\postdoc\analyses\trf\19_03_27 13_28_48 Z 1_210_1_210.trf"

header,table=trf.read_trf(fn)
print(header)
print("================")
print(table)

header.to_csv(r"D:\postdoc\analyses\trf\trfhead.csv")
table.to_csv(r"D:\postdoc\analyses\trf\trftab.csv")
\ No newline at end of file

A trf/__init__.py => trf/__init__.py +15 -0
@@ 0,0 1,15 @@
# Copyright (C) 2018 Simon Biggs

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at

#     http://www.apache.org/licenses/LICENSE-2.0

# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from .trf2pandas import read_trf

A trf/config.json => trf/config.json +371 -0
@@ 0,0 1,371 @@
{
    "time_increment": 0.04,
    "linac_state_codes": {
        "39": "Move Only",
        "40": "Pause",
        "41": "Intersegment",
        "42": "Radiation On",
        "43": "Interupted",
        "44": "Interupted Ready",
        "45": "Terminated Checking",
        "46": "Terminated Ok",
        "47": "Terminated Fault"
    },
    "wedge_codes": {
        "0": "Moving",
        "1": "In",
        "2": "Out"
    },
    "column_names": [
        "Control point/Actual Value (None)",
        "Energy Cal Block/Set (None)",
        "Linac State/Actual Value (None)",
        "Actual Dose Rate/Actual Value (Mu/min)",
        "Step Dose/Actual Value (Mu)",
        "Dose/Raw value (1/64th Mu)",
        "PRF Pauses/Actual Value (None)",
        "Wedge Position/Actual Value (None)",
        "Step Gantry/Scaled Actual (deg)",
        "Step Gantry/Positional Error (deg)",
        "Step Collimator/Scaled Actual (deg)",
        "Step Collimator/Positional Error (deg)",
        "Table Isocentric/Scaled Actual (deg)",
        "Table Isocentric/Positional Error (deg)",
        "Table Longitudinal/Scaled Actual (deg)",
        "Table Longitudinal/Positional Error (deg)",
        "Table Lateral/Scaled Actual (deg)",
        "Table Lateral/Positional Error (deg)",
        "Table Height/Scaled Actual (deg)",
        "Table Height/Positional Error (deg)",
        "X1 Diaphragm/Scaled Actual (mm)",
        "X2 Diaphragm/Scaled Actual (mm)",
        "X1 Diaphragm/Positional Error (mm)",
        "X2 Diaphragm/Positional Error (mm)",
        "Dlg Y2/Scaled Actual (mm)",
        "Dlg Y1/Scaled Actual (mm)",
        "Dlg Y2/Positional Error (mm)",
        "Dlg Y1/Positional Error (mm)",
        "Y2 Leaf 40/Actual Tolerance (mm)",
        "Y1 Leaf 40/Actual Tolerance (mm)",
        "Y2 Leaf 1/Scaled Actual (mm)",
        "Y2 Leaf 2/Scaled Actual (mm)",
        "Y2 Leaf 3/Scaled Actual (mm)",
        "Y2 Leaf 4/Scaled Actual (mm)",
        "Y2 Leaf 5/Scaled Actual (mm)",
        "Y2 Leaf 6/Scaled Actual (mm)",
        "Y2 Leaf 7/Scaled Actual (mm)",
        "Y2 Leaf 8/Scaled Actual (mm)",
        "Y2 Leaf 9/Scaled Actual (mm)",
        "Y2 Leaf 10/Scaled Actual (mm)",
        "Y2 Leaf 11/Scaled Actual (mm)",
        "Y2 Leaf 12/Scaled Actual (mm)",
        "Y2 Leaf 13/Scaled Actual (mm)",
        "Y2 Leaf 14/Scaled Actual (mm)",
        "Y2 Leaf 15/Scaled Actual (mm)",
        "Y2 Leaf 16/Scaled Actual (mm)",
        "Y2 Leaf 17/Scaled Actual (mm)",
        "Y2 Leaf 18/Scaled Actual (mm)",
        "Y2 Leaf 19/Scaled Actual (mm)",
        "Y2 Leaf 20/Scaled Actual (mm)",
        "Y2 Leaf 21/Scaled Actual (mm)",
        "Y2 Leaf 22/Scaled Actual (mm)",
        "Y2 Leaf 23/Scaled Actual (mm)",
        "Y2 Leaf 24/Scaled Actual (mm)",
        "Y2 Leaf 25/Scaled Actual (mm)",
        "Y2 Leaf 26/Scaled Actual (mm)",
        "Y2 Leaf 27/Scaled Actual (mm)",
        "Y2 Leaf 28/Scaled Actual (mm)",
        "Y2 Leaf 29/Scaled Actual (mm)",
        "Y2 Leaf 30/Scaled Actual (mm)",
        "Y2 Leaf 31/Scaled Actual (mm)",
        "Y2 Leaf 32/Scaled Actual (mm)",
        "Y2 Leaf 33/Scaled Actual (mm)",
        "Y2 Leaf 34/Scaled Actual (mm)",
        "Y2 Leaf 35/Scaled Actual (mm)",
        "Y2 Leaf 36/Scaled Actual (mm)",
        "Y2 Leaf 37/Scaled Actual (mm)",
        "Y2 Leaf 38/Scaled Actual (mm)",
        "Y2 Leaf 39/Scaled Actual (mm)",
        "Y2 Leaf 40/Scaled Actual (mm)",
        "Y2 Leaf 41/Scaled Actual (mm)",
        "Y2 Leaf 42/Scaled Actual (mm)",
        "Y2 Leaf 43/Scaled Actual (mm)",
        "Y2 Leaf 44/Scaled Actual (mm)",
        "Y2 Leaf 45/Scaled Actual (mm)",
        "Y2 Leaf 46/Scaled Actual (mm)",
        "Y2 Leaf 47/Scaled Actual (mm)",
        "Y2 Leaf 48/Scaled Actual (mm)",
        "Y2 Leaf 49/Scaled Actual (mm)",
        "Y2 Leaf 50/Scaled Actual (mm)",
        "Y2 Leaf 51/Scaled Actual (mm)",
        "Y2 Leaf 52/Scaled Actual (mm)",
        "Y2 Leaf 53/Scaled Actual (mm)",
        "Y2 Leaf 54/Scaled Actual (mm)",
        "Y2 Leaf 55/Scaled Actual (mm)",
        "Y2 Leaf 56/Scaled Actual (mm)",
        "Y2 Leaf 57/Scaled Actual (mm)",
        "Y2 Leaf 58/Scaled Actual (mm)",
        "Y2 Leaf 59/Scaled Actual (mm)",
        "Y2 Leaf 60/Scaled Actual (mm)",
        "Y2 Leaf 61/Scaled Actual (mm)",
        "Y2 Leaf 62/Scaled Actual (mm)",
        "Y2 Leaf 63/Scaled Actual (mm)",
        "Y2 Leaf 64/Scaled Actual (mm)",
        "Y2 Leaf 65/Scaled Actual (mm)",
        "Y2 Leaf 66/Scaled Actual (mm)",
        "Y2 Leaf 67/Scaled Actual (mm)",
        "Y2 Leaf 68/Scaled Actual (mm)",
        "Y2 Leaf 69/Scaled Actual (mm)",
        "Y2 Leaf 70/Scaled Actual (mm)",
        "Y2 Leaf 71/Scaled Actual (mm)",
        "Y2 Leaf 72/Scaled Actual (mm)",
        "Y2 Leaf 73/Scaled Actual (mm)",
        "Y2 Leaf 74/Scaled Actual (mm)",
        "Y2 Leaf 75/Scaled Actual (mm)",
        "Y2 Leaf 76/Scaled Actual (mm)",
        "Y2 Leaf 77/Scaled Actual (mm)",
        "Y2 Leaf 78/Scaled Actual (mm)",
        "Y2 Leaf 79/Scaled Actual (mm)",
        "Y2 Leaf 80/Scaled Actual (mm)",
        "Y1 Leaf 1/Scaled Actual (mm)",
        "Y1 Leaf 2/Scaled Actual (mm)",
        "Y1 Leaf 3/Scaled Actual (mm)",
        "Y1 Leaf 4/Scaled Actual (mm)",
        "Y1 Leaf 5/Scaled Actual (mm)",
        "Y1 Leaf 6/Scaled Actual (mm)",
        "Y1 Leaf 7/Scaled Actual (mm)",
        "Y1 Leaf 8/Scaled Actual (mm)",
        "Y1 Leaf 9/Scaled Actual (mm)",
        "Y1 Leaf 10/Scaled Actual (mm)",
        "Y1 Leaf 11/Scaled Actual (mm)",
        "Y1 Leaf 12/Scaled Actual (mm)",
        "Y1 Leaf 13/Scaled Actual (mm)",
        "Y1 Leaf 14/Scaled Actual (mm)",
        "Y1 Leaf 15/Scaled Actual (mm)",
        "Y1 Leaf 16/Scaled Actual (mm)",
        "Y1 Leaf 17/Scaled Actual (mm)",
        "Y1 Leaf 18/Scaled Actual (mm)",
        "Y1 Leaf 19/Scaled Actual (mm)",
        "Y1 Leaf 20/Scaled Actual (mm)",
        "Y1 Leaf 21/Scaled Actual (mm)",
        "Y1 Leaf 22/Scaled Actual (mm)",
        "Y1 Leaf 23/Scaled Actual (mm)",
        "Y1 Leaf 24/Scaled Actual (mm)",
        "Y1 Leaf 25/Scaled Actual (mm)",
        "Y1 Leaf 26/Scaled Actual (mm)",
        "Y1 Leaf 27/Scaled Actual (mm)",
        "Y1 Leaf 28/Scaled Actual (mm)",
        "Y1 Leaf 29/Scaled Actual (mm)",
        "Y1 Leaf 30/Scaled Actual (mm)",
        "Y1 Leaf 31/Scaled Actual (mm)",
        "Y1 Leaf 32/Scaled Actual (mm)",
        "Y1 Leaf 33/Scaled Actual (mm)",
        "Y1 Leaf 34/Scaled Actual (mm)",
        "Y1 Leaf 35/Scaled Actual (mm)",
        "Y1 Leaf 36/Scaled Actual (mm)",
        "Y1 Leaf 37/Scaled Actual (mm)",
        "Y1 Leaf 38/Scaled Actual (mm)",
        "Y1 Leaf 39/Scaled Actual (mm)",
        "Y1 Leaf 40/Scaled Actual (mm)",
        "Y1 Leaf 41/Scaled Actual (mm)",
        "Y1 Leaf 42/Scaled Actual (mm)",
        "Y1 Leaf 43/Scaled Actual (mm)",
        "Y1 Leaf 44/Scaled Actual (mm)",
        "Y1 Leaf 45/Scaled Actual (mm)",
        "Y1 Leaf 46/Scaled Actual (mm)",
        "Y1 Leaf 47/Scaled Actual (mm)",
        "Y1 Leaf 48/Scaled Actual (mm)",
        "Y1 Leaf 49/Scaled Actual (mm)",
        "Y1 Leaf 50/Scaled Actual (mm)",
        "Y1 Leaf 51/Scaled Actual (mm)",
        "Y1 Leaf 52/Scaled Actual (mm)",
        "Y1 Leaf 53/Scaled Actual (mm)",
        "Y1 Leaf 54/Scaled Actual (mm)",
        "Y1 Leaf 55/Scaled Actual (mm)",
        "Y1 Leaf 56/Scaled Actual (mm)",
        "Y1 Leaf 57/Scaled Actual (mm)",
        "Y1 Leaf 58/Scaled Actual (mm)",
        "Y1 Leaf 59/Scaled Actual (mm)",
        "Y1 Leaf 60/Scaled Actual (mm)",
        "Y1 Leaf 61/Scaled Actual (mm)",
        "Y1 Leaf 62/Scaled Actual (mm)",
        "Y1 Leaf 63/Scaled Actual (mm)",
        "Y1 Leaf 64/Scaled Actual (mm)",
        "Y1 Leaf 65/Scaled Actual (mm)",
        "Y1 Leaf 66/Scaled Actual (mm)",
        "Y1 Leaf 67/Scaled Actual (mm)",
        "Y1 Leaf 68/Scaled Actual (mm)",
        "Y1 Leaf 69/Scaled Actual (mm)",
        "Y1 Leaf 70/Scaled Actual (mm)",
        "Y1 Leaf 71/Scaled Actual (mm)",
        "Y1 Leaf 72/Scaled Actual (mm)",
        "Y1 Leaf 73/Scaled Actual (mm)",
        "Y1 Leaf 74/Scaled Actual (mm)",
        "Y1 Leaf 75/Scaled Actual (mm)",
        "Y1 Leaf 76/Scaled Actual (mm)",
        "Y1 Leaf 77/Scaled Actual (mm)",
        "Y1 Leaf 78/Scaled Actual (mm)",
        "Y1 Leaf 79/Scaled Actual (mm)",
        "Y1 Leaf 80/Scaled Actual (mm)",
        "Y2 Leaf 1/Positional Error (mm)",
        "Y2 Leaf 2/Positional Error (mm)",
        "Y2 Leaf 3/Positional Error (mm)",
        "Y2 Leaf 4/Positional Error (mm)",
        "Y2 Leaf 5/Positional Error (mm)",
        "Y2 Leaf 6/Positional Error (mm)",
        "Y2 Leaf 7/Positional Error (mm)",
        "Y2 Leaf 8/Positional Error (mm)",
        "Y2 Leaf 9/Positional Error (mm)",
        "Y2 Leaf 10/Positional Error (mm)",
        "Y2 Leaf 11/Positional Error (mm)",
        "Y2 Leaf 12/Positional Error (mm)",
        "Y2 Leaf 13/Positional Error (mm)",
        "Y2 Leaf 14/Positional Error (mm)",
        "Y2 Leaf 15/Positional Error (mm)",
        "Y2 Leaf 16/Positional Error (mm)",
        "Y2 Leaf 17/Positional Error (mm)",
        "Y2 Leaf 18/Positional Error (mm)",
        "Y2 Leaf 19/Positional Error (mm)",
        "Y2 Leaf 20/Positional Error (mm)",
        "Y2 Leaf 21/Positional Error (mm)",
        "Y2 Leaf 22/Positional Error (mm)",
        "Y2 Leaf 23/Positional Error (mm)",
        "Y2 Leaf 24/Positional Error (mm)",
        "Y2 Leaf 25/Positional Error (mm)",
        "Y2 Leaf 26/Positional Error (mm)",
        "Y2 Leaf 27/Positional Error (mm)",
        "Y2 Leaf 28/Positional Error (mm)",
        "Y2 Leaf 29/Positional Error (mm)",
        "Y2 Leaf 30/Positional Error (mm)",
        "Y2 Leaf 31/Positional Error (mm)",
        "Y2 Leaf 32/Positional Error (mm)",
        "Y2 Leaf 33/Positional Error (mm)",
        "Y2 Leaf 34/Positional Error (mm)",
        "Y2 Leaf 35/Positional Error (mm)",
        "Y2 Leaf 36/Positional Error (mm)",
        "Y2 Leaf 37/Positional Error (mm)",
        "Y2 Leaf 38/Positional Error (mm)",
        "Y2 Leaf 39/Positional Error (mm)",
        "Y2 Leaf 40/Positional Error (mm)",
        "Y2 Leaf 41/Positional Error (mm)",
        "Y2 Leaf 42/Positional Error (mm)",
        "Y2 Leaf 43/Positional Error (mm)",
        "Y2 Leaf 44/Positional Error (mm)",
        "Y2 Leaf 45/Positional Error (mm)",
        "Y2 Leaf 46/Positional Error (mm)",
        "Y2 Leaf 47/Positional Error (mm)",
        "Y2 Leaf 48/Positional Error (mm)",
        "Y2 Leaf 49/Positional Error (mm)",
        "Y2 Leaf 50/Positional Error (mm)",
        "Y2 Leaf 51/Positional Error (mm)",
        "Y2 Leaf 52/Positional Error (mm)",
        "Y2 Leaf 53/Positional Error (mm)",
        "Y2 Leaf 54/Positional Error (mm)",
        "Y2 Leaf 55/Positional Error (mm)",
        "Y2 Leaf 56/Positional Error (mm)",
        "Y2 Leaf 57/Positional Error (mm)",
        "Y2 Leaf 58/Positional Error (mm)",
        "Y2 Leaf 59/Positional Error (mm)",
        "Y2 Leaf 60/Positional Error (mm)",
        "Y2 Leaf 61/Positional Error (mm)",
        "Y2 Leaf 62/Positional Error (mm)",
        "Y2 Leaf 63/Positional Error (mm)",
        "Y2 Leaf 64/Positional Error (mm)",
        "Y2 Leaf 65/Positional Error (mm)",
        "Y2 Leaf 66/Positional Error (mm)",
        "Y2 Leaf 67/Positional Error (mm)",
        "Y2 Leaf 68/Positional Error (mm)",
        "Y2 Leaf 69/Positional Error (mm)",
        "Y2 Leaf 70/Positional Error (mm)",
        "Y2 Leaf 71/Positional Error (mm)",
        "Y2 Leaf 72/Positional Error (mm)",
        "Y2 Leaf 73/Positional Error (mm)",
        "Y2 Leaf 74/Positional Error (mm)",
        "Y2 Leaf 75/Positional Error (mm)",
        "Y2 Leaf 76/Positional Error (mm)",
        "Y2 Leaf 77/Positional Error (mm)",
        "Y2 Leaf 78/Positional Error (mm)",
        "Y2 Leaf 79/Positional Error (mm)",
        "Y2 Leaf 80/Positional Error (mm)",
        "Y1 Leaf 1/Positional Error (mm)",
        "Y1 Leaf 2/Positional Error (mm)",
        "Y1 Leaf 3/Positional Error (mm)",
        "Y1 Leaf 4/Positional Error (mm)",
        "Y1 Leaf 5/Positional Error (mm)",
        "Y1 Leaf 6/Positional Error (mm)",
        "Y1 Leaf 7/Positional Error (mm)",
        "Y1 Leaf 8/Positional Error (mm)",
        "Y1 Leaf 9/Positional Error (mm)",
        "Y1 Leaf 10/Positional Error (mm)",
        "Y1 Leaf 11/Positional Error (mm)",
        "Y1 Leaf 12/Positional Error (mm)",
        "Y1 Leaf 13/Positional Error (mm)",
        "Y1 Leaf 14/Positional Error (mm)",
        "Y1 Leaf 15/Positional Error (mm)",
        "Y1 Leaf 16/Positional Error (mm)",
        "Y1 Leaf 17/Positional Error (mm)",
        "Y1 Leaf 18/Positional Error (mm)",
        "Y1 Leaf 19/Positional Error (mm)",
        "Y1 Leaf 20/Positional Error (mm)",
        "Y1 Leaf 21/Positional Error (mm)",
        "Y1 Leaf 22/Positional Error (mm)",
        "Y1 Leaf 23/Positional Error (mm)",
        "Y1 Leaf 24/Positional Error (mm)",
        "Y1 Leaf 25/Positional Error (mm)",
        "Y1 Leaf 26/Positional Error (mm)",
        "Y1 Leaf 27/Positional Error (mm)",
        "Y1 Leaf 28/Positional Error (mm)",
        "Y1 Leaf 29/Positional Error (mm)",
        "Y1 Leaf 30/Positional Error (mm)",
        "Y1 Leaf 31/Positional Error (mm)",
        "Y1 Leaf 32/Positional Error (mm)",
        "Y1 Leaf 33/Positional Error (mm)",
        "Y1 Leaf 34/Positional Error (mm)",
        "Y1 Leaf 35/Positional Error (mm)",
        "Y1 Leaf 36/Positional Error (mm)",
        "Y1 Leaf 37/Positional Error (mm)",
        "Y1 Leaf 38/Positional Error (mm)",
        "Y1 Leaf 39/Positional Error (mm)",
        "Y1 Leaf 40/Positional Error (mm)",
        "Y1 Leaf 41/Positional Error (mm)",
        "Y1 Leaf 42/Positional Error (mm)",
        "Y1 Leaf 43/Positional Error (mm)",
        "Y1 Leaf 44/Positional Error (mm)",
        "Y1 Leaf 45/Positional Error (mm)",
        "Y1 Leaf 46/Positional Error (mm)",
        "Y1 Leaf 47/Positional Error (mm)",
        "Y1 Leaf 48/Positional Error (mm)",
        "Y1 Leaf 49/Positional Error (mm)",
        "Y1 Leaf 50/Positional Error (mm)",
        "Y1 Leaf 51/Positional Error (mm)",
        "Y1 Leaf 52/Positional Error (mm)",
        "Y1 Leaf 53/Positional Error (mm)",
        "Y1 Leaf 54/Positional Error (mm)",
        "Y1 Leaf 55/Positional Error (mm)",
        "Y1 Leaf 56/Positional Error (mm)",
        "Y1 Leaf 57/Positional Error (mm)",
        "Y1 Leaf 58/Positional Error (mm)",
        "Y1 Leaf 59/Positional Error (mm)",
        "Y1 Leaf 60/Positional Error (mm)",
        "Y1 Leaf 61/Positional Error (mm)",
        "Y1 Leaf 62/Positional Error (mm)",
        "Y1 Leaf 63/Positional Error (mm)",
        "Y1 Leaf 64/Positional Error (mm)",
        "Y1 Leaf 65/Positional Error (mm)",
        "Y1 Leaf 66/Positional Error (mm)",
        "Y1 Leaf 67/Positional Error (mm)",
        "Y1 Leaf 68/Positional Error (mm)",
        "Y1 Leaf 69/Positional Error (mm)",
        "Y1 Leaf 70/Positional Error (mm)",
        "Y1 Leaf 71/Positional Error (mm)",
        "Y1 Leaf 72/Positional Error (mm)",
        "Y1 Leaf 73/Positional Error (mm)",
        "Y1 Leaf 74/Positional Error (mm)",
        "Y1 Leaf 75/Positional Error (mm)",
        "Y1 Leaf 76/Positional Error (mm)",
        "Y1 Leaf 77/Positional Error (mm)",
        "Y1 Leaf 78/Positional Error (mm)",
        "Y1 Leaf 79/Positional Error (mm)",
        "Y1 Leaf 80/Positional Error (mm)"
    ]
}

A trf/constants.py => trf/constants.py +35 -0
@@ 0,0 1,35 @@
# Copyright (C) 2018 Cancer Care Associates

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at

#     http://www.apache.org/licenses/LICENSE-2.0

# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


import json
import os

CONFIG_FILEPATH = os.path.join(os.path.dirname(__file__), "config.json")

with open(CONFIG_FILEPATH) as json_data_file:
    CONFIG = json.load(json_data_file)

Y1_LEAF_BANK_NAMES = [
    "Y1 Leaf {}/Scaled Actual (mm)".format(item) for item in range(1, 81)
]

Y2_LEAF_BANK_NAMES = [
    "Y2 Leaf {}/Scaled Actual (mm)".format(item) for item in range(1, 81)
]

JAW_NAMES = ["X1 Diaphragm/Scaled Actual (mm)", "X2 Diaphragm/Scaled Actual (mm)"]

GANTRY_NAME = "Step Gantry/Scaled Actual (deg)"
COLLIMATOR_NAME = "Step Collimator/Scaled Actual (deg)"

A trf/delivery.py => trf/delivery.py +60 -0
@@ 0,0 1,60 @@
# Copyright (C) 2018 Cancer Care Associates

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at

#     http://www.apache.org/licenses/LICENSE-2.0

# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from typing import Type

from pymedphys._imports import numpy as np

from pymedphys._base.delivery import DeliveryBase, DeliveryGeneric

from .constants import (
    COLLIMATOR_NAME,
    GANTRY_NAME,
    JAW_NAMES,
    Y1_LEAF_BANK_NAMES,
    Y2_LEAF_BANK_NAMES,
)
from .trf2pandas import read_trf


class DeliveryLogfile(DeliveryBase):
    @classmethod
    def from_logfile(cls, filepath):
        _, dataframe = read_trf(filepath)

        return cls._from_pandas(dataframe)

    @classmethod
    def _from_pandas(cls: Type[DeliveryGeneric], table) -> DeliveryGeneric:
        raw_monitor_units = table["Step Dose/Actual Value (Mu)"]

        diff = np.append([0], np.diff(raw_monitor_units))
        diff[diff < 0] = 0

        monitor_units = np.cumsum(diff)

        gantry = table[GANTRY_NAME]
        collimator = table[COLLIMATOR_NAME]

        y1_bank = [table[name] for name in Y1_LEAF_BANK_NAMES]

        y2_bank = [table[name] for name in Y2_LEAF_BANK_NAMES]

        mlc = [y1_bank, y2_bank]
        mlc = np.swapaxes(mlc, 0, 2)

        jaw = [table[name] for name in JAW_NAMES]
        jaw = np.swapaxes(jaw, 0, 1)

        return cls(monitor_units, gantry, collimator, mlc, jaw)

A trf/detect.py => trf/detect.py +47 -0
@@ 0,0 1,47 @@
from .partition import split_into_header_table
from .table import decode_rows
from .trf2pandas import header_as_dataframe


def detect_cli(args):
    detect_file_encoding(args.filepath)


def detect_file_encoding(filepath):
    with open(filepath, "rb") as file:
        trf_contents = file.read()

    trf_header_contents, trf_table_contents = split_into_header_table(trf_contents)

    header_dataframe = header_as_dataframe(trf_header_contents)
    print(header_dataframe)

    possible_groupings = search_for_possible_decoding_options(trf_table_contents)

    return possible_groupings


def search_for_possible_decoding_options(trf_table_contents):
    line_grouping_range = range(600, 800)
    linac_state_codes_column_range = range(0, 50)
    reference_state_code_keys = None

    possible_groupings = []

    for line_grouping in line_grouping_range:
        for linac_state_codes_column in linac_state_codes_column_range:
            try:
                decode_rows(
                    trf_table_contents,
                    input_line_grouping=line_grouping,
                    input_linac_state_codes_column=linac_state_codes_column,
                    reference_state_code_keys=reference_state_code_keys,
                )
                possible_groupings.append([line_grouping, linac_state_codes_column])
                print(
                    f"Line Grouping: {line_grouping}, Linac State Codes Column: {linac_state_codes_column}"
                )
            except ValueError:
                pass

    return possible_groupings

A trf/header.py => trf/header.py +85 -0
@@ 0,0 1,85 @@
# Copyright (C) 2018 Cancer Care Associates

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at

#     http://www.apache.org/licenses/LICENSE-2.0

# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


import re
from collections import namedtuple

Header = namedtuple(
    "Header", ["machine", "date", "timezone", "field_label", "field_name"]
)


def determine_header_length(trf_contents):
    test = trf_contents.split(b"\t")
    row_skips = 6
    i = next(i for i, item in enumerate(test[row_skips::]) if len(item) > 3) + row_skips
    header_length = len(b"\t".join(test[0:i])) + 3

    return header_length


def decode_header(trf_header_contents):
    match = re.match(
        br"[\x00-\x19]"  # start bit
        br"(\d\d/\d\d/\d\d \d\d:\d\d:\d\d Z)"  # date
        br"[\x00-\x19]"  # divider bit
        br"((\+|\-)\d\d:\d\d)"  # time zone
        br"[\x00-\x25]"  # divider bit
        br"([\x20-\xFF]*)"  # field label and name
        br"[\x00-\x19]"  # divider bit
        br"([\x20-\xFF]+)"  # machine name
        br"[\x00-\x19]",  # divider bit
        trf_header_contents,
    )

    if match is None:
        print(trf_header_contents)
        raise ValueError("Logfile header not of an expected form.")

    groups = match.groups()
    date = groups[0].decode("ascii")
    timezone = groups[1].decode("ascii")
    field = groups[3].decode("ascii")
    machine = groups[4].decode("ascii")

    split_field = field.split("/")
    if len(split_field) == 2:
        field_label, field_name = split_field
    else:
        field_label, field_name = "", field

    header = Header(machine, date, timezone, field_label, field_name)

    return header


def convert_header_section_to_string(section):
    return "".join([chr(item) for item in section[1::]])


def raw_header_from_file(filepath):
    with open(filepath, "rb") as file:
        trf_contents = file.read()

    header_length = determine_header_length(trf_contents)
    trf_header_contents = trf_contents[0:header_length]

    return trf_header_contents


def decode_header_from_file(filepath):
    trf_header_contents = raw_header_from_file(filepath)

    return decode_header(trf_header_contents)

A trf/partition.py => trf/partition.py +10 -0
@@ 0,0 1,10 @@
from .header import determine_header_length


def split_into_header_table(trf_contents):
    header_length = determine_header_length(trf_contents)

    trf_header_contents = trf_contents[0:header_length]
    trf_table_contents = trf_contents[header_length::]

    return trf_header_contents, trf_table_contents

A trf/table.py => trf/table.py +298 -0
@@ 0,0 1,298 @@
# Copyright (C) 2018 Cancer Care Associates

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at

#     http://www.apache.org/licenses/LICENSE-2.0

# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from typing import List

import numpy as np
import pandas as pd

from .constants import CONFIG
from .header import determine_header_length

GROUPING_OPTIONS = {
    "integrityv3": {"line_grouping": 700, "linac_state_codes_column": 2},
    "integrityv4": {"line_grouping": 708, "linac_state_codes_column": 6},
    "unity_experimental": {"line_grouping": 700, "linac_state_codes_column": 6},
}

# LINE_GROUPING_OPTIONS = {
#     item["line_grouping"]: item["linac_state_codes_column"]
#     for _, item in GROUPING_OPTIONS.items()
# }


def decode_rows(
    trf_table_contents,
    input_line_grouping=None,
    input_linac_state_codes_column=None,
    reference_state_code_keys=None,
):
    table_byte_length = len(trf_table_contents)

    if input_line_grouping is not None or input_linac_state_codes_column is not None:
        if input_line_grouping is None or input_linac_state_codes_column is None:
            raise ValueError(
                "If customising line grouping, need to provide both "
                "`input_line_grouping` and `input_linac_state_codes_column`"
            )

        grouping_options = {
            "custom": {
                "line_grouping": input_line_grouping,
                "linac_state_codes_column": input_linac_state_codes_column,
            }
        }
    else:
        grouping_options = GROUPING_OPTIONS

    possible_groupings = {}

    for key, item in grouping_options.items():
        grouping_option = item["line_grouping"]
        linac_state_codes_column = item["linac_state_codes_column"]

        if table_byte_length / grouping_option == table_byte_length // grouping_option:
            possible_groupings[key] = (grouping_option, linac_state_codes_column)

    if not possible_groupings:
        raise ValueError("Unexpected number of bytes within file.")

    if reference_state_code_keys is None:
        reference_state_codes = set(
            np.array(list(CONFIG["linac_state_codes"].keys())).astype(int)
        )
    else:
        reference_state_codes = set(reference_state_code_keys)

    decoded_results = []
    possible_column_adjustment_key = []
    for key, (line_grouping, linac_state_codes_column) in possible_groupings.items():
        rows = [
            trf_table_contents[i : i + line_grouping]
            for i in range(0, len(trf_table_contents), line_grouping)
        ]

        result = decode_table_data(rows, line_grouping)
        tentative_state_codes = result[  # pylint: disable = unsubscriptable-object
            :, linac_state_codes_column
        ]

        if set(tentative_state_codes).issubset(reference_state_codes):
            decoded_results.append(decode_table_data(rows, line_grouping))
            possible_column_adjustment_key.append(key)

    if not decoded_results:
        raise ValueError("Decoded table didn't pass shape test")

    if len(decoded_results) > 1:
        raise ValueError("Can't determine version of trf file from table shape")

    decoded_rows = decoded_results[0]
    column_adjustment_key = possible_column_adjustment_key[0]

    return decoded_rows, column_adjustment_key


def decode_rows_from_file(filepath):
    with open(filepath, "rb") as file:
        trf_contents = file.read()

    header_length = determine_header_length(trf_contents)
    trf_table_contents = trf_contents[header_length::]

    decoded_rows, _ = decode_rows(trf_table_contents)

    return decoded_rows


def get_column_names(column_adjustment_key):
    column_names = CONFIG["column_names"]

    if column_adjustment_key == "integrityv3":
        return column_names

    filler_columns = [f"unknown{item}" for item in range(1, 5)]

    column_names = filler_columns + column_names

    if column_adjustment_key == "integrityv4":
        return column_names

    if column_adjustment_key != "unity_experimental":
        raise ValueError("Unexpected `column_adjustment_key`")

    column_names = [item for item in column_names if "Dlg" not in item]

    return column_names


def decode_trf_table(trf_table_contents):
    decoded_rows, column_adjustment_key = decode_rows(trf_table_contents)

    column_names = get_column_names(column_adjustment_key)

    number_of_columns = len(decoded_rows[0, :])

    if len(column_names) != number_of_columns:
        raise ValueError("Columns names don't agree with number of columns")

    table_dataframe = create_dataframe(
        decoded_rows, column_names, CONFIG["time_increment"]
    )

    convert_data_table(
        table_dataframe, CONFIG["linac_state_codes"], CONFIG["wedge_codes"]
    )

    return table_dataframe


def decode_data_item(row, group, byteorder) -> int:
    """Converts the bytes of data items into an integer."""
    return int.from_bytes(row[group], byteorder=byteorder)


def decode_column(raw_table_rows: List[str], column_number: int):
    """Decode all of the items in a given column."""
    grouping = 2
    i = column_number * grouping
    group = slice(i, i + grouping)
    byteorder = "little"

    column = np.array(
        [decode_data_item(row, group, byteorder) for row in raw_table_rows]
    )

    return column


def decode_table_data(raw_table_rows: List[str], line_grouping):
    """Decode the table into integer values."""

    result = []
    for column_number in range(0, line_grouping // 2):
        result.append(decode_column(raw_table_rows, column_number))

    return np.array(result).T


def create_dataframe(data, column_names, time_increment):
    """Converts the provided data into a pandas dataframe."""
    dataframe = pd.DataFrame(data=data, columns=column_names)
    dataframe.index = np.round(dataframe.index * time_increment, 2)

    return dataframe


def convert_numbers_to_string(name, lookup, column):
    dtype = np.array([item for _, item in lookup.items()]).dtype
    result = np.empty_like(column).astype(dtype)
    result[:] = ""

    for i, item in lookup.items():
        result[column.values == int(i)] = item

    if np.any(result == ""):
        print(lookup)
        print(np.where(result == ""))
        print(column[result == ""].values)
        unconverted_entries = np.unique(column[result == ""])
        raise Exception(
            "The conversion lookup list for converting {} is incomplete. "
            "The following data numbers were not converted:\n"
            "{}\n"
            "Please update the trf2csv conversion script to include these "
            "in its definitions.".format(name, unconverted_entries)
        )

    return result


def convert_linac_state_codes(dataframe, linac_state_codes):
    name = "linac state"
    key = "Linac State/Actual Value (None)"
    dataframe[key] = convert_numbers_to_string(name, linac_state_codes, dataframe[key])


def convert_wedge_codes(dataframe, wedge_codes):
    name = "wedge"
    key = "Wedge Position/Actual Value (None)"
    dataframe[key] = convert_numbers_to_string(name, wedge_codes, dataframe[key])


def apply_negative(column):
    result = np.ones_like(column).astype(np.float64) * np.nan
    negative_values = column.values > 2 ** 15

    result[negative_values] = column[negative_values] - 2 ** 16
    result[np.invert(negative_values)] = column[np.invert(negative_values)]

    if np.any(np.isnan(result)):
        raise Exception("Not all column values were converted")

    return result


def convert_applying_negative(dataframe):
    keys = [
        "Control point/Actual Value (None)",
        "Table Isocentric/Scaled Actual (deg)",
        "Table Isocentric/Positional Error (deg)",
    ]

    for key in keys:
        dataframe[key] = apply_negative(dataframe[key])


def negative_and_divide_by_10(column):
    result = apply_negative(column)
    result = result / 10

    return result


def convert_negative_and_divide_by_10(dataframe):
    keys = [
        "Step Dose/Actual Value (Mu)",
        "Step Gantry/Scaled Actual (deg)",
        "Step Gantry/Positional Error (deg)",
        "Step Collimator/Scaled Actual (deg)",
        "Step Collimator/Positional Error (deg)",
    ]

    for key in keys:
        dataframe[key] = negative_and_divide_by_10(dataframe[key])


def convert_remaining(dataframe):
    column_names = dataframe.columns

    for key in column_names[14:30]:
        dataframe[key] = negative_and_divide_by_10(dataframe[key])

    # Y2 leaves need to be multiplied by -1
    for key in column_names[30:110]:
        dataframe[key] = -negative_and_divide_by_10(dataframe[key])

    for key in column_names[110::]:
        dataframe[key] = negative_and_divide_by_10(dataframe[key])


def convert_data_table(dataframe, linac_state_codes, wedge_codes):
    convert_linac_state_codes(dataframe, linac_state_codes)
    convert_wedge_codes(dataframe, wedge_codes)

    convert_applying_negative(dataframe)
    convert_negative_and_divide_by_10(dataframe)
    convert_remaining(dataframe)

A trf/trf2csv.py => trf/trf2csv.py +77 -0
@@ 0,0 1,77 @@
# Copyright (C) 2018 Cancer Care Associates

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at

#     http://www.apache.org/licenses/LICENSE-2.0

# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


"""Converts a trf file into a csv file.
"""


import os
from glob import glob

from .trf2pandas import trf2pandas


def trf2csv_by_directory(input_directory, output_directory):
    filepaths = glob(os.path.join(input_directory, "*.trf"))

    for filepath in filepaths:
        filename = os.path.basename(filepath)
        new_filename = os.path.join(output_directory, filename)

        header_csv_filepath = "{}.header.csv".format(new_filename)
        table_csv_filepath = "{}.table.csv".format(new_filename)

        print("Converting {}".format(filepath))

        header, table = trf2pandas(filepath)
        header.to_csv(header_csv_filepath)
        table.to_csv(table_csv_filepath)


def trf2csv(trf_filepath, skip_if_exists=False):
    if not os.path.exists(trf_filepath):
        raise Exception("The provided trf filepath cannot be found.")

    extension_removed = os.path.splitext(trf_filepath)[0]
    header_csv_filepath = "{}_header.csv".format(extension_removed)
    table_csv_filepath = "{}_table.csv".format(extension_removed)

    # Skip if conversion has already occured
    if not skip_if_exists or not os.path.exists(table_csv_filepath):
        print("Converting {}".format(trf_filepath))
        header, table = trf2pandas(trf_filepath)

        header.to_csv(header_csv_filepath)
        table.to_csv(table_csv_filepath)
    # else:
    #     print("Skipping {}".format(trf_filepath))

    return header_csv_filepath, table_csv_filepath


def trf2csv_cli(args):

    for glob_string in args.filepaths:
        glob_string = glob_string.replace("[", "<[>")
        glob_string = glob_string.replace("]", "<]>")
        glob_string = glob_string.replace("?", "[?]")

        glob_string = glob_string.replace("<[>", "[[]")
        glob_string = glob_string.replace("<]>", "[]]")

        filepaths = glob(glob_string)

        for filepath in filepaths:
            trf2csv(filepath)

A trf/trf2pandas.py => trf/trf2pandas.py +53 -0
@@ 0,0 1,53 @@
# Copyright (C) 2018 Cancer Care Associates

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at

#     http://www.apache.org/licenses/LICENSE-2.0

# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


"""Decodes trf file.
"""

import pandas as pd

from .header import Header, decode_header
from .partition import split_into_header_table
from .table import decode_trf_table


def trf2pandas(filepath):
    with open(filepath, "rb") as file:
        trf_contents = file.read()

    trf_header_contents, trf_table_contents = split_into_header_table(trf_contents)

    header_dataframe = header_as_dataframe(trf_header_contents)

    table_dataframe = decode_trf_table(trf_table_contents)

    return header_dataframe, table_dataframe


read_trf = trf2pandas


def header_as_dataframe(trf_header_contents):
    header = decode_header(trf_header_contents)

    return pd.DataFrame([header], columns=Header._fields)


def decode_trf(filepath):
    """DEPRECATED
    """
    _, table_dataframe = trf2pandas(filepath)

    return table_dataframe