hedicim.Patient.java Source code

Java tutorial

Introduction

Here is the source code for hedicim.Patient.java

Source

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */

package hedicim;

import static hedicim.Const.stat_cells;
import static hedicim.HEDICIM.dic_scorers;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.*;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;

/**
 *
 * @author kuryfs
 */
public class Patient {
    static int hedicim_id_count = 1;
    static Map<String, Patient> map = new HashMap<>();
    static Map<Integer, List<Patient>> subject_id_map = new HashMap<>();
    static Map<Integer, Patient> icustay_map = new HashMap<>();
    String stat_cells_content[];
    boolean eligible = false;
    long eligibility_begin = -1;
    EnumMap<DICSS, DICScore> has_1_day_dic;
    EnumMap<DICSS, DICScore> has_4_day_dic;
    EVENT evt_t0;
    EVENT sofa_1_day, sofa_4_day;
    EVENT saps_1_day, saps_4_day;
    EVENT fibrinogen_1_day, fibrinogen_4_day;
    EVENT procalcitonin_1_day, procalcitonin_4_day;

    EnumMap<DICSS, Map<Integer, List<DICPeriod>>> dic_periods;
    List<EVENT> events = new ArrayList<>();
    String origin_id = "";
    DATA_SOURCE data_source = DATA_SOURCE.UNKNOWN;

    int subject_id = 0;
    List<Float> icd9s = new ArrayList<>();
    List<Long[]> heparin_periods = new ArrayList<>();
    Set<ICUStay> icustays = new HashSet<>();
    long icustay_total_los = 0;
    long icustay_subject_total_num;
    long hospital_los = 0;
    boolean has_dic = false;
    boolean age_group = Const.AGE_GROUP_NEONATE;
    boolean received_heparin = false;
    boolean received_lmwh = false;
    boolean died_early = false;
    String hospital_expire = null;
    int sapsi_min = 0, sapsi_max = 0;
    int sofa_min = 0, sofa_max = 0;
    Date admit_dt, disch_dt;
    long dod;

    long ts_first_adm_of_hep_or_lmwh = 0;
    long ts_first_dic_event = 0;
    long ts_first_medication_is_relevant_event; // This is used to trim medication events to the proximity of DIC or near-DIC events
    long ts_last_medication_is_relevant_event; // This is used to trim medication events to the proximity of DIC or near-DIC events

    String gender = "X";
    int age_on_admit = 0;

    Patient(DATA_SOURCE _data_source, String _origin_id) {
        origin_id = _origin_id;
        map.put(origin_id, this);
        data_source = _data_source;
        dic_periods = new EnumMap<>(DICSS.class);
        has_1_day_dic = new EnumMap<>(DICSS.class);
        has_4_day_dic = new EnumMap<>(DICSS.class);
        stat_cells_content = new String[stat_cells.length];
    }

    static void mapSubjectID(Patient patient) {
        if (!subject_id_map.containsKey(patient.subject_id))
            subject_id_map.put(patient.subject_id, new ArrayList<>());
        ((List<Patient>) subject_id_map.get(patient.subject_id)).add(patient);
    }

    void addICUStay(ICUStay icustay) {
        icustays.add(icustay);
        icustay_map.put(icustay.id, this);
    }

    EVENT findEventByTimeDistance(long ts, ITEMID itemid) {
        return findEventByTimeWindowCenter(
                ts > Const.STANDARD_DISTANCE_WINDOW ? (ts - Const.STANDARD_DISTANCE_WINDOW) : 0,
                ts + Const.STANDARD_DISTANCE_WINDOW, itemid);
    }

    EVENT findEventByTimeWindowCenter(long ts_start_inclusive, long ts_end_exclusive, ITEMID itemid) {
        EVENT retval = null;
        long diff = Integer.MAX_VALUE;
        long ts = (ts_end_exclusive - ts_start_inclusive) / 2 + ts_start_inclusive;

        for (int j = events.size() - 1; j >= 0; j--) {
            EVENT event = events.get(j);
            long evt_ts = event.CHARTTIME.getTime();
            if (evt_ts > ts_end_exclusive)
                continue;
            if (evt_ts > ts) {
                if (event.ITEMID == itemid.value && evt_ts - ts < diff) {
                    retval = event;
                    diff = evt_ts - ts;
                }
            } else {
                if (ts_start_inclusive > evt_ts)
                    break;
                if (event.ITEMID == itemid.value && ts - evt_ts < diff) {
                    retval = event;
                    diff = ts - evt_ts;
                }
            }
        }

        return retval;
    }

    EVENT findEventByTimeWindow(long ts, long ts_start_inclusive, long ts_end_exclusive, ITEMID itemid) {
        EVENT retval = null;
        long diff = Integer.MAX_VALUE;

        for (int j = events.size() - 1; j >= 0; j--) {
            EVENT event = events.get(j);
            long evt_ts = event.CHARTTIME.getTime();
            if (evt_ts > ts_end_exclusive)
                continue;
            if (evt_ts > ts) {
                if (event.ITEMID == itemid.value && evt_ts - ts < diff) {
                    retval = event;
                    diff = evt_ts - ts;
                }
            } else {
                if (ts_start_inclusive > evt_ts)
                    break;
                if (event.ITEMID == itemid.value && ts - evt_ts < diff) {
                    retval = event;
                    diff = ts - evt_ts;
                }
            }
        }

        return retval;
    }

    void removeLabEventsNearDeath(long nearness_window) {
        if (dod == 0)
            return;

        for (Iterator it = events.iterator(); it.hasNext();) {
            EVENT labe = (EVENT) it.next();
            if (labe.CHARTTIME.getTime() > dod - nearness_window)
                // Ignore all DIC events too close to death.
                it.remove();
        }
    }

    void alignTimestampsToHospitalAdmission() {
        for (EVENT event : events)
            event.CHARTTIME = new Date(event.CHARTTIME.getTime() - admit_dt.getTime());
        //if(dod != 0)
        //    dod = dod-admit_dt.getTime();
    }

    void sortEvents() {
        Collections.sort(events, new Comparator<EVENT>() {
            @Override
            public int compare(EVENT l1, EVENT l2) {
                if (l1.CHARTTIME.getTime() > l2.CHARTTIME.getTime())
                    return 1;
                if (l1.CHARTTIME.getTime() < l2.CHARTTIME.getTime())
                    return -1;
                return 0;
            }
        });
    }

    Map<ITEMID, EVENT> mapEventsBeforePointInList(int i) {
        // Find the most recent lab of each type inside the time window
        long timestamp = events.get(i).CHARTTIME.getTime();
        Map<ITEMID, EVENT> events_map = new HashMap<>();

        // Inside the time window get the most recent lab event of each type.
        for (int j = i; j >= 0 && timestamp - events.get(j).CHARTTIME.getTime() <= Const.LAB_TIME_WINDOW; j--) {
            // In here we're restricted to the LAB_TIME_WINDOW.
            // Get the most recent EVENT of each type.                
            EVENT labe = events.get(j);
            if (!events_map.containsKey(labe.ITEMID))
                events_map.put(Const.value_map.get(labe.ITEMID), labe);
        }

        return events_map;
    }

    int fastForwardToSameTimestamp(int i) {
        for (int f = i + 1; f < events.size(); f++) {
            if (events.get(f).CHARTTIME.getTime() < events.get(i).CHARTTIME.getTime()) {
                // This never happened, because the EVENTS are sorted by CHARTTIME.
                // But, just in case something happens in the future, I've left this 'if' here.
                System.out.println("Big exception in generateSIRSEvents() at origin_id=" + origin_id + ", f=" + f
                        + ", i=" + i + ".");
            }

            if (events.get(f).CHARTTIME.getTime() != events.get(i).CHARTTIME.getTime())
                break;
            else
                i = f;
        }
        return i;
    }

    void generateSIRSEvents() {
        // NOTICE: The constants remove the magic numbers from this function, but not the magic
        // operators. The calculator defined by the ISTH requires correct use of >, >=, < and <=.
        List<EVENT> events_to_add = new ArrayList<>();

        // Iterate through the lab tests, get the worst-case lab test of each type in case of duplicates
        for (int i = 0; i < events.size(); i++) {
            // Fast-foward to all events charted at the same time. This often is the case.
            i = fastForwardToSameTimestamp(i);
            SIRSScore sirs_score = SIRSScore.calculateSIRSScore(mapEventsBeforePointInList(i));
            sirs_score.date = events.get(i).CHARTTIME;

            if (sirs_score.score > 0d) {
                events_to_add.add(new EVENT(ITEMID.HEDICIM_SIRS.value, sirs_score.date,
                        Double.toString(sirs_score.score), (float) sirs_score.score.intValue(), null, null));

                if (!eligible)
                    if (sirs_score.score >= 2d) {
                        EVENT evt = findEventByTimeDistance(sirs_score.date.getTime(), ITEMID.Overall_SOFA_Score);
                        if (evt != null)
                            if (evt.VALUENUM >= 3) {
                                eligible = true;
                                eligibility_begin = sirs_score.date.getTime();
                            }
                    }
            }
        }

        events.addAll(events_to_add);
        sortEvents();
    }

    void generatePlateletReductionEvents() {
        boolean needs_to_add_and_resort = false;
        List<EVENT> events_to_add = new ArrayList<>();
        for (int i = 0; i < events.size(); i++) {
            if (events.get(i).ITEMID != ITEMID.PLATELET.value)
                continue;
            // Platelet count found.
            EVENT eventi = events.get(i);
            // Now look back up to 24 hours.
            boolean n30p_reduction_found = false;
            for (int h = i - 1; h >= 0 && events.get(i).CHARTTIME.getTime()
                    - events.get(h).CHARTTIME.getTime() > Const.N24H_IN_MILLISECONDS; h--) {
                if (!Objects.equals(events.get(h).ITEMID, ITEMID.PLATELET.value))
                    continue;
                // Platelet count previous to i found.
                EVENT eventh = events.get(h);
                // Check if there was a reduction from h to i.
                if (eventi.VALUENUM < eventh.VALUENUM * (1f - 0.5f)) { // 50% reduction
                    // Found 50% reduction.
                    // Create an event with the previous higher value, special ITEMID, but the time of when the reduction was noticed.
                    EVENT n50p_reduction = new EVENT(eventh);
                    n50p_reduction.ITEMID = ITEMID.HEDICIM_PLATELET_COUNT_50P_REDUCTION.value;
                    n50p_reduction.CHARTTIME = new Date(eventi.CHARTTIME.getTime());
                    events_to_add.add(n50p_reduction);
                    needs_to_add_and_resort = true;
                    break; // Stop looking for more reductions in comparison to eventi
                }
                if (!n30p_reduction_found && eventi.VALUENUM < eventh.VALUENUM * (1f - 0.3f)) { // 30% reduction
                    // Found 30% reduction.
                    // Create an event with the previous higher value, special ITEMID, but the time of when the reduction was noticed.
                    EVENT n30p_reduction = new EVENT(eventh);
                    n30p_reduction.ITEMID = ITEMID.HEDICIM_PLATELET_COUNT_30P_REDUCTION.value;
                    n30p_reduction.CHARTTIME = new Date(eventi.CHARTTIME.getTime());
                    n30p_reduction_found = true; // Stop looking for 30% reductions.
                    events_to_add.add(n30p_reduction);
                    needs_to_add_and_resort = true;
                }
            }
        }

        if (needs_to_add_and_resort) {
            events.addAll(events_to_add);
            sortEvents();
        }
    }

    void setStatCell(String stat_cell, String value) {
        stat_cells_content[Arrays.asList(stat_cells).indexOf(stat_cell)] = value;
    }

    static final float icd9s_for_sepsis[] = { 785.52f, // SEPTIC SHOCK -- 1064 HADM_IDs in MIMIC-II
    };

    // In the intervals below, beginnings are inclusive, endings are exclusive.
    static final float icd9_intervals_for_sepsis[][] = { { 995.9f, 996f }, // Sepsis: 2013 ICD-9-CM Diagnosis Code 995.91
            { 038f, 039f }, // SEPTICEMIA -- 3309 HADM_IDs in MIMIC-II.
    };

    void makeStatCell(Row row, String stat_cell) {
        int index = Arrays.asList(stat_cells).indexOf(stat_cell);
        Cell cell = row.createCell(index);
        /*if(stat_cells_content[index] != null) {
        cell.setCellValue(stat_cells_content[index]);
        return;
        }*/
        sortEvents();
        EVENT evt;
        switch (stat_cell) {
        case "eligible":
            cell.setCellValue(eligible);
            break;
        case "id":
            cell.setCellValue(origin_id);
            break;
        case "age":
            cell.setCellValue(age_on_admit);
            break;
        case "gender":
            cell.setCellValue(gender);
            break;
        case "icd9":
            if (icd9s != null && icd9s.size() > 0)
                cell.setCellValue(icd9s.get(0));
            break;
        case "SOFA peak":
            cell.setCellValue(sofa_max);
            break;
        case "SAPSI max":
            cell.setCellValue(sapsi_max);
            break;
        case "MODS day 1":
            evt = findEventByTimeDistance(eligibility_begin, ITEMID.Overall_SOFA_Score);
            if (evt != null) {
                if (evt.VALUENUM >= 12)
                    cell.setCellValue(true);
                else
                    cell.setCellValue(false);
            }
            break;
        case "MODS day 4":
            evt = findEventByTimeDistance(eligibility_begin + Const.STANDARD_DISTANCE_WINDOW * 3,
                    ITEMID.Overall_SOFA_Score);
            if (evt != null) {
                if (evt.VALUENUM >= 12)
                    cell.setCellValue(true);
                else
                    cell.setCellValue(false);
            }
            break;
        case "SOFA score day 1":
            evt = findEventByTimeDistance(eligibility_begin, ITEMID.Overall_SOFA_Score);
            if (evt != null)
                cell.setCellValue(evt.VALUENUM);
            break;
        case "SOFA score day 4":
            evt = findEventByTimeDistance(eligibility_begin + Const.STANDARD_DISTANCE_WINDOW * 3,
                    ITEMID.Overall_SOFA_Score);
            if (evt != null)
                cell.setCellValue(evt.VALUENUM);
            break;
        case "jaam score day 1":
            evt = findEventByTimeDistance(eligibility_begin, ITEMID.DIC_SCORE_JAAM);
            if (evt != null)
                cell.setCellValue(evt.VALUENUM);
            break;
        case "jaam score day 4":
            evt = findEventByTimeDistance(eligibility_begin + Const.STANDARD_DISTANCE_WINDOW * 3,
                    ITEMID.DIC_SCORE_JAAM);
            if (evt != null)
                cell.setCellValue(evt.VALUENUM);
            break;
        case "isth score day 1":
            evt = findEventByTimeDistance(eligibility_begin, ITEMID.DIC_SCORE_ISTH);
            if (evt != null)
                cell.setCellValue(evt.VALUENUM);
            break;
        case "isth score day 4":
            evt = findEventByTimeDistance(eligibility_begin + Const.STANDARD_DISTANCE_WINDOW * 3,
                    ITEMID.DIC_SCORE_ISTH);
            if (evt != null)
                cell.setCellValue(evt.VALUENUM);
            break;
        case "jmhw score day 1":
            evt = findEventByTimeDistance(eligibility_begin, ITEMID.DIC_SCORE_JMHW);
            if (evt != null)
                cell.setCellValue(evt.VALUENUM);
            break;
        case "jmhw score day 4":
            evt = findEventByTimeDistance(eligibility_begin + Const.STANDARD_DISTANCE_WINDOW * 3,
                    ITEMID.DIC_SCORE_JMHW);
            if (evt != null)
                cell.setCellValue(evt.VALUENUM);
            break;
        case "ksth score day 1":
            evt = findEventByTimeDistance(eligibility_begin, ITEMID.DIC_SCORE_KSTH);
            if (evt != null)
                cell.setCellValue(evt.VALUENUM);
            break;
        case "ksth score day 4":
            evt = findEventByTimeDistance(eligibility_begin + Const.STANDARD_DISTANCE_WINDOW * 3,
                    ITEMID.DIC_SCORE_KSTH);
            if (evt != null)
                cell.setCellValue(evt.VALUENUM);
            break;
        case "deceased within 28 days":
            if (dod != 0 && eligibility_begin != -1 && dod > eligibility_begin
                    && dod <= Const.EARLY_DEATH_TIME_WINDOW + eligibility_begin)
                cell.setCellValue(true);
            else
                cell.setCellValue(false);
            break;
        case "deceased within 1 year":
            if (dod != 0 && eligibility_begin != -1 && dod > eligibility_begin
                    && dod <= (1000L * 60L * 60L * 24L * 365L) + eligibility_begin)
                cell.setCellValue(true);
            else
                cell.setCellValue(false);
            break;
        case "received heparin":
            cell.setCellValue(received_heparin | received_lmwh);
            break;
        case "total hospital los":
            cell.setCellValue(hospital_los);
            break;
        case "total icu los":
            cell.setCellValue(icustay_total_los);
        case "fibrinogen day 1":
            evt = findEventByTimeDistance(eligibility_begin, ITEMID.FIBRINOGEN);
            if (evt != null)
                cell.setCellValue(evt.VALUE);
            // TO DO
            break;
        case "fibrinogen day 4":
            evt = findEventByTimeDistance(eligibility_begin + Const.STANDARD_DISTANCE_WINDOW * 3,
                    ITEMID.FIBRINOGEN);
            if (evt != null)
                cell.setCellValue(evt.VALUENUM);
            break;
        case "saps1 day 1":
            evt = findEventByTimeDistance(eligibility_begin, ITEMID.SAPSI);
            if (evt != null)
                cell.setCellValue(evt.VALUENUM);
            break;
        case "saps1 day 4":
            evt = findEventByTimeDistance(eligibility_begin + Const.STANDARD_DISTANCE_WINDOW * 3, ITEMID.SAPSI);
            if (evt != null)
                cell.setCellValue(evt.VALUENUM);
            break;
        case "sirs day 1":
            evt = findEventByTimeDistance(eligibility_begin, ITEMID.HEDICIM_SIRS);
            if (evt != null)
                cell.setCellValue(evt.VALUENUM);
            break;
        case "sirs day 4":
            evt = findEventByTimeDistance(eligibility_begin + Const.STANDARD_DISTANCE_WINDOW * 3,
                    ITEMID.HEDICIM_SIRS);
            if (evt != null)
                cell.setCellValue(evt.VALUENUM);
            break;
        case "T0":
            if (eligibility_begin != -1)
                cell.setCellValue(eligibility_begin / 1000d * 60d * 60d);
            break;
        case "DIC day 1": {
            boolean found_dic = false;
            for (DICScorer dics : dic_scorers.values()) {
                evt = findEventByTimeDistance(eligibility_begin, dics.getITEMID());
                if (evt != null)
                    if (evt.VALUENUM >= dics.getDiagnosticScore(this)) {
                        found_dic = true;
                        break;
                    }
            }
            cell.setCellValue(found_dic);
            break;
        }
        case "DIC day 4": {
            boolean found_dic = false;
            for (DICScorer dics : dic_scorers.values()) {
                evt = findEventByTimeDistance(eligibility_begin + Const.STANDARD_DISTANCE_WINDOW * 3,
                        dics.getITEMID());
                if (evt != null)
                    if (evt.VALUENUM >= dics.getDiagnosticScore(this)) {
                        found_dic = true;
                        break;
                    }
            }
            cell.setCellValue(found_dic);
            break;
        }
        case "has any DIC":
            cell.setCellValue(has_dic);
            break;
        //            case "lactate day 1":
        //                evt = findEventByTimeDistance(eligibility_begin, ITEMID.LACTATE);
        //                if(evt != null)
        //                    cell.setCellValue(evt.VALUE);
        //                break;
        //            case "lactate day 4":
        //                evt = findEventByTimeDistance(eligibility_begin+Const.STANDARD_DISTANCE_WINDOW*3, ITEMID.LACTATE);
        //                if(evt != null)
        //                    cell.setCellValue(evt.VALUE);
        //                break;
        case "pt day 1":
            evt = findEventByTimeDistance(eligibility_begin, ITEMID.PT);
            if (evt != null)
                cell.setCellValue(evt.VALUE);
        case "pt day 4":
            evt = findEventByTimeDistance(eligibility_begin + Const.STANDARD_DISTANCE_WINDOW * 3, ITEMID.PT);
            if (evt != null)
                cell.setCellValue(evt.VALUE);
            break;
        case "aptt day 1":
            evt = findEventByTimeDistance(eligibility_begin, ITEMID.APTT);
            if (evt != null)
                cell.setCellValue(evt.VALUE);
            break;
        case "aptt day 4":
            evt = findEventByTimeDistance(eligibility_begin + Const.STANDARD_DISTANCE_WINDOW * 3, ITEMID.APTT);
            if (evt != null)
                cell.setCellValue(evt.VALUE);
            break;
        case "fdp day 1":
            evt = findEventByTimeDistance(eligibility_begin, ITEMID.FDP);
            if (evt != null)
                cell.setCellValue(evt.VALUE);
            break;
        case "fdp day 4":
            evt = findEventByTimeDistance(eligibility_begin + Const.STANDARD_DISTANCE_WINDOW * 3, ITEMID.FDP);
            if (evt != null)
                cell.setCellValue(evt.VALUE);
            break;
        case "platelets day 1":
            evt = findEventByTimeDistance(eligibility_begin, ITEMID.PLATELET);
            if (evt != null)
                cell.setCellValue(evt.VALUE);
            break;
        case "platelets day 4":
            evt = findEventByTimeDistance(eligibility_begin + Const.STANDARD_DISTANCE_WINDOW * 3, ITEMID.PLATELET);
            if (evt != null)
                cell.setCellValue(evt.VALUE);
            break;
        case "pt-inr day 1":
            evt = findEventByTimeDistance(eligibility_begin, ITEMID.PT_INR);
            if (evt != null)
                cell.setCellValue(evt.VALUE);
            break;
        case "pt-inr day 4":
            evt = findEventByTimeDistance(eligibility_begin + Const.STANDARD_DISTANCE_WINDOW * 3, ITEMID.PT_INR);
            if (evt != null)
                cell.setCellValue(evt.VALUE);
            break;
        case "d-dimer day 1":
            evt = findEventByTimeDistance(eligibility_begin, ITEMID.D_DIMER);
            if (evt != null)
                cell.setCellValue(evt.VALUE);
            break;
        case "d-dimer day 4":
            evt = findEventByTimeDistance(eligibility_begin + Const.STANDARD_DISTANCE_WINDOW * 3, ITEMID.D_DIMER);
            if (evt != null)
                cell.setCellValue(evt.VALUE);
            break;
        case "has malignancy":
            break;
        case "has ICD9 for DIC":
            cell.setCellValue(ISTHDICScorer.hasConditionAssociatedWithDIC(this));
            break;
        case "has ICD9 for sepsis":
            cell.setCellValue(false);
            for (float icd9 : icd9s) {
                for (float icd9_for_sepsis : icd9s_for_sepsis)
                    if (icd9 == icd9_for_sepsis) {
                        cell.setCellValue(true);
                        break;
                    }
                for (float[] interval : icd9_intervals_for_sepsis)
                    if (icd9 >= interval[0] && icd9 < interval[1]) {
                        cell.setCellValue(true);
                        break;
                    }
            }
            break;
        case "hospital expire":
            if ("Y".equals(hospital_expire))
                cell.setCellValue(true);
            else if ("N".equals(hospital_expire))
                cell.setCellValue(false);
            break;
        case "Needed platelet reduction event":
            break;
        case "dod":
            if (dod > 0)
                cell.setCellValue(dod);
            break;
        }
    }

    static void makeStatFile(String filename) throws FileNotFoundException, IOException {
        try (FileOutputStream fileOut = new FileOutputStream(filename)) {
            Workbook wb = new XSSFWorkbook();
            Sheet sheet = wb.createSheet("HEDICIM AMIA 2015");
            int r = 0;
            Row header_row = sheet.createRow(r++);
            for (String stat_cell : stat_cells)
                header_row.createCell(Arrays.asList(stat_cells).indexOf(stat_cell)).setCellValue(stat_cell);
            for (Patient patient : map.values()) {
                if (!patient.eligible)
                    continue;
                Row row = sheet.createRow(r++);
                for (String stat_cell : stat_cells)
                    patient.makeStatCell(row, stat_cell);
            }
            wb.write(fileOut);
            fileOut.close();
            System.out.println("Statistics file " + filename + " succesfully written.");
        } catch (Exception ex) {
            System.err.println("Exception at makeStatFile(\"" + filename + "\"): " + ex.getLocalizedMessage());
        }
    }
}