aim-cu 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
aim_cu-0.2.0/LICENSE ADDED
@@ -0,0 +1,121 @@
1
+ Creative Commons Legal Code
2
+
3
+ CC0 1.0 Universal
4
+
5
+ CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
6
+ LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
7
+ ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
8
+ INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
9
+ REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
10
+ PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
11
+ THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
12
+ HEREUNDER.
13
+
14
+ Statement of Purpose
15
+
16
+ The laws of most jurisdictions throughout the world automatically confer
17
+ exclusive Copyright and Related Rights (defined below) upon the creator
18
+ and subsequent owner(s) (each and all, an "owner") of an original work of
19
+ authorship and/or a database (each, a "Work").
20
+
21
+ Certain owners wish to permanently relinquish those rights to a Work for
22
+ the purpose of contributing to a commons of creative, cultural and
23
+ scientific works ("Commons") that the public can reliably and without fear
24
+ of later claims of infringement build upon, modify, incorporate in other
25
+ works, reuse and redistribute as freely as possible in any form whatsoever
26
+ and for any purposes, including without limitation commercial purposes.
27
+ These owners may contribute to the Commons to promote the ideal of a free
28
+ culture and the further production of creative, cultural and scientific
29
+ works, or to gain reputation or greater distribution for their Work in
30
+ part through the use and efforts of others.
31
+
32
+ For these and/or other purposes and motivations, and without any
33
+ expectation of additional consideration or compensation, the person
34
+ associating CC0 with a Work (the "Affirmer"), to the extent that he or she
35
+ is an owner of Copyright and Related Rights in the Work, voluntarily
36
+ elects to apply CC0 to the Work and publicly distribute the Work under its
37
+ terms, with knowledge of his or her Copyright and Related Rights in the
38
+ Work and the meaning and intended legal effect of CC0 on those rights.
39
+
40
+ 1. Copyright and Related Rights. A Work made available under CC0 may be
41
+ protected by copyright and related or neighboring rights ("Copyright and
42
+ Related Rights"). Copyright and Related Rights include, but are not
43
+ limited to, the following:
44
+
45
+ i. the right to reproduce, adapt, distribute, perform, display,
46
+ communicate, and translate a Work;
47
+ ii. moral rights retained by the original author(s) and/or performer(s);
48
+ iii. publicity and privacy rights pertaining to a person's image or
49
+ likeness depicted in a Work;
50
+ iv. rights protecting against unfair competition in regards to a Work,
51
+ subject to the limitations in paragraph 4(a), below;
52
+ v. rights protecting the extraction, dissemination, use and reuse of data
53
+ in a Work;
54
+ vi. database rights (such as those arising under Directive 96/9/EC of the
55
+ European Parliament and of the Council of 11 March 1996 on the legal
56
+ protection of databases, and under any national implementation
57
+ thereof, including any amended or successor version of such
58
+ directive); and
59
+ vii. other similar, equivalent or corresponding rights throughout the
60
+ world based on applicable law or treaty, and any national
61
+ implementations thereof.
62
+
63
+ 2. Waiver. To the greatest extent permitted by, but not in contravention
64
+ of, applicable law, Affirmer hereby overtly, fully, permanently,
65
+ irrevocably and unconditionally waives, abandons, and surrenders all of
66
+ Affirmer's Copyright and Related Rights and associated claims and causes
67
+ of action, whether now known or unknown (including existing as well as
68
+ future claims and causes of action), in the Work (i) in all territories
69
+ worldwide, (ii) for the maximum duration provided by applicable law or
70
+ treaty (including future time extensions), (iii) in any current or future
71
+ medium and for any number of copies, and (iv) for any purpose whatsoever,
72
+ including without limitation commercial, advertising or promotional
73
+ purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
74
+ member of the public at large and to the detriment of Affirmer's heirs and
75
+ successors, fully intending that such Waiver shall not be subject to
76
+ revocation, rescission, cancellation, termination, or any other legal or
77
+ equitable action to disrupt the quiet enjoyment of the Work by the public
78
+ as contemplated by Affirmer's express Statement of Purpose.
79
+
80
+ 3. Public License Fallback. Should any part of the Waiver for any reason
81
+ be judged legally invalid or ineffective under applicable law, then the
82
+ Waiver shall be preserved to the maximum extent permitted taking into
83
+ account Affirmer's express Statement of Purpose. In addition, to the
84
+ extent the Waiver is so judged Affirmer hereby grants to each affected
85
+ person a royalty-free, non transferable, non sublicensable, non exclusive,
86
+ irrevocable and unconditional license to exercise Affirmer's Copyright and
87
+ Related Rights in the Work (i) in all territories worldwide, (ii) for the
88
+ maximum duration provided by applicable law or treaty (including future
89
+ time extensions), (iii) in any current or future medium and for any number
90
+ of copies, and (iv) for any purpose whatsoever, including without
91
+ limitation commercial, advertising or promotional purposes (the
92
+ "License"). The License shall be deemed effective as of the date CC0 was
93
+ applied by Affirmer to the Work. Should any part of the License for any
94
+ reason be judged legally invalid or ineffective under applicable law, such
95
+ partial invalidity or ineffectiveness shall not invalidate the remainder
96
+ of the License, and in such case Affirmer hereby affirms that he or she
97
+ will not (i) exercise any of his or her remaining Copyright and Related
98
+ Rights in the Work or (ii) assert any associated claims and causes of
99
+ action with respect to the Work, in either case contrary to Affirmer's
100
+ express Statement of Purpose.
101
+
102
+ 4. Limitations and Disclaimers.
103
+
104
+ a. No trademark or patent rights held by Affirmer are waived, abandoned,
105
+ surrendered, licensed or otherwise affected by this document.
106
+ b. Affirmer offers the Work as-is and makes no representations or
107
+ warranties of any kind concerning the Work, express, implied,
108
+ statutory or otherwise, including without limitation warranties of
109
+ title, merchantability, fitness for a particular purpose, non
110
+ infringement, or the absence of latent or other defects, accuracy, or
111
+ the present or absence of errors, whether or not discoverable, all to
112
+ the greatest extent permissible under applicable law.
113
+ c. Affirmer disclaims responsibility for clearing rights of other persons
114
+ that may apply to the Work or any use thereof, including without
115
+ limitation any person's Copyright and Related Rights in the Work.
116
+ Further, Affirmer disclaims responsibility for obtaining any necessary
117
+ consents, permissions or other rights required for any use of the
118
+ Work.
119
+ d. Affirmer understands and acknowledges that Creative Commons is not a
120
+ party to this document and has no duty or obligation with respect to
121
+ this CC0 or use of the Work.
aim_cu-0.2.0/PKG-INFO ADDED
@@ -0,0 +1,120 @@
1
+ Metadata-Version: 2.4
2
+ Name: aim-cu
3
+ Version: 0.2.0
4
+ Summary: AIM-CU: A CUSUM-based tool for AI Monitoring
5
+ Home-page: https://github.com/DIDSR/AIM-CU
6
+ Project-URL: User Inteface, https://huggingface.co/spaces/didsr/AIM-CU
7
+ Requires-Python: >=3.9
8
+ Description-Content-Type: text/x-rst
9
+ License-File: LICENSE
10
+ Requires-Dist: numpy>=1.23
11
+ Requires-Dist: pandas>=1.5
12
+ Requires-Dist: matplotlib>=3.6
13
+ Requires-Dist: rpy2>=3.5
14
+ Requires-Dist: tomli; python_version < "3.11"
15
+ Dynamic: description
16
+ Dynamic: description-content-type
17
+ Dynamic: home-page
18
+ Dynamic: license-file
19
+ Dynamic: project-url
20
+ Dynamic: requires-dist
21
+ Dynamic: requires-python
22
+ Dynamic: summary
23
+
24
+ AIM-CU: A CUSUM-based tool for AI Monitoring
25
+ ============================================
26
+ Monitoring a clinically deployed AI device to detect performance drift is an essential step to ensure the safety and effectiveness of AI.
27
+
28
+ AIM-CU is a statistical tool for AI monitoring based on a cumulative sum (AIM-CU) approach.
29
+
30
+ AIM-CU computes:
31
+
32
+ * The parameter choices for change-point detection based on an acceptable false alarm rate
33
+ * Detection delay estimates for a given displacement of the performance metric from the target for those parameter choices.
34
+
35
+ System setup
36
+ ------------
37
+ Make sure R is installed in the system. There is no specific version that this relies on. Here we have used the version 4.1.2 (2021-11-01).
38
+ Instructions for Linux (the below setup is only performed in Linux):
39
+
40
+ .. code-block:: shell
41
+
42
+ wget -qO- https://cloud.r-project.org/bin/linux/ubuntu/marutter_pubkey.asc | tee -a /etc/apt/trusted.gpg.d/cran_ubuntu_key.asc
43
+ add-apt-repository "deb https://cloud.r-project.org/bin/linux/ubuntu $(lsb_release -cs)-cran40/"
44
+ apt-get install -y --no-install-recommends r-base r-base-dev
45
+
46
+ # setup R configs
47
+ echo "r <- getOption('repos'); r['CRAN'] <- 'http://cran.us.r-project.org'; options(repos = r);" > ~/.Rprofile
48
+ Rscript -e "install.packages('ggplot2')"
49
+ Rscript -e "install.packages('hexbin')"
50
+ Rscript -e "install.packages('lazyeval')"
51
+ Rscript -e "install.packages('cusumcharter')"
52
+ Rscript -e "install.packages('RcppCNPy')"
53
+ Rscript -e "install.packages('spc')"
54
+
55
+ Code execution
56
+ --------------
57
+ Clone AIM-CU repository.
58
+
59
+ .. code-block:: shell
60
+
61
+ git clone https://github.com/DIDSR/AIM-CU.git
62
+
63
+ Run the following commands to install required dependencies (Python = 3.10 is used).
64
+
65
+ .. code-block:: shell
66
+
67
+ apt-get -y install python3
68
+ apt-get -y install pip
69
+ cd AIM-CU
70
+ pip install -r requirements.txt
71
+
72
+ Run AIM-CU.
73
+
74
+ .. code-block:: shell
75
+
76
+ cd src/package
77
+ python3 app.py
78
+
79
+ Open the URL http://0.0.0.0:7860 that is running the AIM-CU locally.
80
+
81
+ Example code execution
82
+ ----------------------
83
+ Example code can be run through Jupyter Notebook. Do this by entering the ``jupyter notebook`` command from the ``/src/package/`` directory. The tool is designed to be used through a GUI, not from the console.
84
+
85
+ Demo
86
+ ----
87
+ AIM-CU can also be run through the demo available at https://huggingface.co/spaces/didsr/AIM-CU. If Space is paused, click on Restart button. Note: this Space uses a custom Docker container; build may break due to latest package updates pulled by HuggingFace.
88
+
89
+ Usability
90
+ ---------
91
+ * Example AI output CSV file is available as `config/spec-60-60.csv <config/spec-60-60.csv>`_ to be uploaded in monitoring phase.
92
+
93
+ * Workflow instruction to run the tool is available at bottom-left of UI.
94
+
95
+ * Sample UI output is available at `assets/ui.png <assets/ui.png>`_.
96
+
97
+ * Setting ``control:save_figure`` to ``true`` from `config.toml <config/config.toml>`_ will save tables and plots in `figure/ <figure/>`_.
98
+
99
+ * Running AIM-CU usually only takes a few seconds, and it does not require a GPU to run.
100
+
101
+ Related References
102
+ ------------------
103
+ * Smriti Prathapan, Ravi K. Samala, Nathan Hadjiyski, Pierre‑François D’Haese, Nicholas Petrick, Jana Delfino, Fabien Maldonado, Brandon Nelson, Ghada Zamzmi, Phuong Nguyen, Yelena Yesha, and Berkman Sahiner, "Detecting performance drift in AI models for medical image analysis using CUSUM chart" (Journal in press 2026)
104
+
105
+ * Prathapan, S., Sahiner, B., Kadia, D., and Samala, R.K. 2025, "AIM-CU: A statistical tool for AI Monitoring." (Journal in-press 2026)
106
+
107
+ * Prathapan, S., Samala, R.K., Hadjiyski, N., D’Haese, P.F., Maldonado, F., Nguyen, P., Yesha, Y. and Sahiner, B., 2024, April. Quantifying input data drift in medical machine learning models by detecting change-points in time-series data. In Medical Imaging 2024: Computer-Aided Diagnosis (Vol. 12927, pp. 67-76). SPIE. https://doi.org/10.1117/12.3008771
108
+
109
+ * Smriti Prathapan, Ravi K. Samala, Nathan Hadjiyski, Pierre‑François D’Haese, Nicholas Petrick, Jana Delfino, Fabien Maldonado, Brandon Nelson, Ghada Zamzmi, Phuong Nguyen, Yelena Yesha, and Berkman Sahiner, "Post-market Monitoring of AI-enabled Medical Devices for Radiology and Healthcare Applications" (FDA-UMiami Collaboration Poster, September 2023)
110
+
111
+ Disclaimer
112
+ ---------------------
113
+
114
+ **About the Catalog of Regulatory Science Tools**
115
+
116
+ The enclosed tool is *in preparation* for the `Catalog of Regulatory Science Tools <https://cdrh-rst.fda.gov>`_. Note that this software is not (yet) a standalone RST but rather is to be included as a **reference tool** to support other Regulatory Science Tools (such as synthetic datasets) for reproducibility.
117
+
118
+ This catalog collates a variety of regulatory science tools that the FDA's Center for Devices and Radiological Health's (CDRH) Office of Science and Engineering Labs (OSEL) developed. These tools use the most innovative science to support medical device development and patient access to safe and effective medical devices. If you are considering using a tool from this catalog in your marketing submissions, note that these tools have not been qualified as `Medical Device Development Tools <https://www.fda.gov/medical-devices/medical-device-development-tools-mddt>`_ and the FDA has not evaluated the suitability of these tools within any specific context of use. You may `request feedback or meetings for medical device submissions <https://www.fda.gov/regulatory-information/search-fda-guidance-documents/requests-feedback-and-meetings-medical-device-submissions-q-submission-program>`_ as part of the Q-Submission Program.
119
+
120
+ For more information about the Catalog of Regulatory Science Tools, email RST_CDRH@fda.hhs.gov.
@@ -0,0 +1,97 @@
1
+ AIM-CU: A CUSUM-based tool for AI Monitoring
2
+ ============================================
3
+ Monitoring a clinically deployed AI device to detect performance drift is an essential step to ensure the safety and effectiveness of AI.
4
+
5
+ AIM-CU is a statistical tool for AI monitoring based on a cumulative sum (AIM-CU) approach.
6
+
7
+ AIM-CU computes:
8
+
9
+ * The parameter choices for change-point detection based on an acceptable false alarm rate
10
+ * Detection delay estimates for a given displacement of the performance metric from the target for those parameter choices.
11
+
12
+ System setup
13
+ ------------
14
+ Make sure R is installed in the system. There is no specific version that this relies on. Here we have used the version 4.1.2 (2021-11-01).
15
+ Instructions for Linux (the below setup is only performed in Linux):
16
+
17
+ .. code-block:: shell
18
+
19
+ wget -qO- https://cloud.r-project.org/bin/linux/ubuntu/marutter_pubkey.asc | tee -a /etc/apt/trusted.gpg.d/cran_ubuntu_key.asc
20
+ add-apt-repository "deb https://cloud.r-project.org/bin/linux/ubuntu $(lsb_release -cs)-cran40/"
21
+ apt-get install -y --no-install-recommends r-base r-base-dev
22
+
23
+ # setup R configs
24
+ echo "r <- getOption('repos'); r['CRAN'] <- 'http://cran.us.r-project.org'; options(repos = r);" > ~/.Rprofile
25
+ Rscript -e "install.packages('ggplot2')"
26
+ Rscript -e "install.packages('hexbin')"
27
+ Rscript -e "install.packages('lazyeval')"
28
+ Rscript -e "install.packages('cusumcharter')"
29
+ Rscript -e "install.packages('RcppCNPy')"
30
+ Rscript -e "install.packages('spc')"
31
+
32
+ Code execution
33
+ --------------
34
+ Clone AIM-CU repository.
35
+
36
+ .. code-block:: shell
37
+
38
+ git clone https://github.com/DIDSR/AIM-CU.git
39
+
40
+ Run the following commands to install required dependencies (Python = 3.10 is used).
41
+
42
+ .. code-block:: shell
43
+
44
+ apt-get -y install python3
45
+ apt-get -y install pip
46
+ cd AIM-CU
47
+ pip install -r requirements.txt
48
+
49
+ Run AIM-CU.
50
+
51
+ .. code-block:: shell
52
+
53
+ cd src/package
54
+ python3 app.py
55
+
56
+ Open the URL http://0.0.0.0:7860 that is running the AIM-CU locally.
57
+
58
+ Example code execution
59
+ ----------------------
60
+ Example code can be run through Jupyter Notebook. Do this by entering the ``jupyter notebook`` command from the ``/src/package/`` directory. The tool is designed to be used through a GUI, not from the console.
61
+
62
+ Demo
63
+ ----
64
+ AIM-CU can also be run through the demo available at https://huggingface.co/spaces/didsr/AIM-CU. If Space is paused, click on Restart button. Note: this Space uses a custom Docker container; build may break due to latest package updates pulled by HuggingFace.
65
+
66
+ Usability
67
+ ---------
68
+ * Example AI output CSV file is available as `config/spec-60-60.csv <config/spec-60-60.csv>`_ to be uploaded in monitoring phase.
69
+
70
+ * Workflow instruction to run the tool is available at bottom-left of UI.
71
+
72
+ * Sample UI output is available at `assets/ui.png <assets/ui.png>`_.
73
+
74
+ * Setting ``control:save_figure`` to ``true`` from `config.toml <config/config.toml>`_ will save tables and plots in `figure/ <figure/>`_.
75
+
76
+ * Running AIM-CU usually only takes a few seconds, and it does not require a GPU to run.
77
+
78
+ Related References
79
+ ------------------
80
+ * Smriti Prathapan, Ravi K. Samala, Nathan Hadjiyski, Pierre‑François D’Haese, Nicholas Petrick, Jana Delfino, Fabien Maldonado, Brandon Nelson, Ghada Zamzmi, Phuong Nguyen, Yelena Yesha, and Berkman Sahiner, "Detecting performance drift in AI models for medical image analysis using CUSUM chart" (Journal in press 2026)
81
+
82
+ * Prathapan, S., Sahiner, B., Kadia, D., and Samala, R.K. 2025, "AIM-CU: A statistical tool for AI Monitoring." (Journal in-press 2026)
83
+
84
+ * Prathapan, S., Samala, R.K., Hadjiyski, N., D’Haese, P.F., Maldonado, F., Nguyen, P., Yesha, Y. and Sahiner, B., 2024, April. Quantifying input data drift in medical machine learning models by detecting change-points in time-series data. In Medical Imaging 2024: Computer-Aided Diagnosis (Vol. 12927, pp. 67-76). SPIE. https://doi.org/10.1117/12.3008771
85
+
86
+ * Smriti Prathapan, Ravi K. Samala, Nathan Hadjiyski, Pierre‑François D’Haese, Nicholas Petrick, Jana Delfino, Fabien Maldonado, Brandon Nelson, Ghada Zamzmi, Phuong Nguyen, Yelena Yesha, and Berkman Sahiner, "Post-market Monitoring of AI-enabled Medical Devices for Radiology and Healthcare Applications" (FDA-UMiami Collaboration Poster, September 2023)
87
+
88
+ Disclaimer
89
+ ---------------------
90
+
91
+ **About the Catalog of Regulatory Science Tools**
92
+
93
+ The enclosed tool is *in preparation* for the `Catalog of Regulatory Science Tools <https://cdrh-rst.fda.gov>`_. Note that this software is not (yet) a standalone RST but rather is to be included as a **reference tool** to support other Regulatory Science Tools (such as synthetic datasets) for reproducibility.
94
+
95
+ This catalog collates a variety of regulatory science tools that the FDA's Center for Devices and Radiological Health's (CDRH) Office of Science and Engineering Labs (OSEL) developed. These tools use the most innovative science to support medical device development and patient access to safe and effective medical devices. If you are considering using a tool from this catalog in your marketing submissions, note that these tools have not been qualified as `Medical Device Development Tools <https://www.fda.gov/medical-devices/medical-device-development-tools-mddt>`_ and the FDA has not evaluated the suitability of these tools within any specific context of use. You may `request feedback or meetings for medical device submissions <https://www.fda.gov/regulatory-information/search-fda-guidance-documents/requests-feedback-and-meetings-medical-device-submissions-q-submission-program>`_ as part of the Q-Submission Program.
96
+
97
+ For more information about the Catalog of Regulatory Science Tools, email RST_CDRH@fda.hhs.gov.
@@ -0,0 +1,38 @@
1
+ # Security Policy
2
+
3
+ ## Reporting Security Vulnerabilities
4
+
5
+ The U.S. Food and Drug Administration (FDA) takes security vulnerabilities seriously. If you believe you have found a security vulnerability in this repository, please report it to us through coordinated disclosure.
6
+
7
+ **Please do NOT report security vulnerabilities through public GitHub issues, discussions, or pull requests.**
8
+
9
+ ---
10
+
11
+ ## How to Report
12
+
13
+ FDA follows the HHS Vulnerability Disclosure Policy. Submit your report through the official HHS reporting portal:
14
+
15
+ - **HHS Vulnerability Disclosure Policy:** [https://www.hhs.gov/vulnerability-disclosure-policy/index.html]
16
+ - **Submit a Report:** [https://hhs.responsibledisclosure.com]
17
+
18
+ ### Contact
19
+
20
+ For general security questions or concerns about FDA systems, contact:
21
+ - **Repository Maintainer:** [RST_CDRH@fda.hhs.gov]
22
+ - **Security Maintainer:** [ciocc@fda.hhs.gov]
23
+
24
+
25
+ ---
26
+ ## Security Best Practices for Contributors
27
+
28
+ When contributing to this repository:
29
+
30
+ - Follow the principle of least privilege
31
+ - Validate and sanitize all inputs
32
+ - Use parameterized queries for database access
33
+ - Keep dependencies up to date
34
+ - Never commit secrets, API keys, or credentials
35
+ - Review security advisories for dependencies
36
+
37
+ ---
38
+
@@ -0,0 +1,27 @@
1
+ # aim_cu/__init__.py
2
+
3
+ """
4
+ AIM-CU: CUSUM-based AI performance monitoring.
5
+ """
6
+
7
+ from .aim_cu import (
8
+ CUSUM,
9
+ compute_ARL1,
10
+ compute_ARL1_table,
11
+ get_ref_value,
12
+ get_ref_values,
13
+ get_threshold,
14
+ load_package_config,
15
+ shift_in_mean,
16
+ )
17
+
18
+ __all__ = [
19
+ "CUSUM",
20
+ "compute_ARL1",
21
+ "compute_ARL1_table",
22
+ "get_ref_value",
23
+ "get_ref_values",
24
+ "get_threshold",
25
+ "load_package_config",
26
+ "shift_in_mean",
27
+ ]
@@ -0,0 +1,623 @@
1
+ """
2
+ AIM-CU is a statistical tool for AI monitoring based on a cumulative sum (AIM-CU) approach.
3
+
4
+ AIM-CU computes:
5
+ - The parameter choices for change-point detection based on an acceptable false alarm rate
6
+ - Detection delay estimates for a given displacement of the performance metric from the target for those parameter choices.
7
+
8
+ Notes:
9
+ - Requires: rpy2, an R installation, and R package `spc` (plus optional ggplot2/hexbin/lazyeval/cusumcharter/RcppCNPy).
10
+ - If R packages are missing, this code will attempt to install them (CRAN mirror must be reachable).
11
+ """
12
+ #Packages
13
+ import os
14
+ import sys
15
+ import random
16
+ import warnings
17
+ from collections import OrderedDict
18
+ from typing import Tuple, List
19
+
20
+ import numpy as np
21
+ import pandas as pd
22
+ import tomli
23
+ import matplotlib
24
+ matplotlib.use("Agg")
25
+ import matplotlib.pyplot as plt
26
+ from importlib.resources import files
27
+
28
+ #Read config.toml
29
+ def load_package_config():
30
+ config_path = files("aim_cu").joinpath("config.toml")
31
+ with config_path.open("rb") as f:
32
+ return tomli.load(f)
33
+
34
+ _CONFIG = load_package_config()
35
+ #print("_CONFIG =", _CONFIG)
36
+ shift_in_mean = _CONFIG["CUSUM_params"]["shift_in_mean"]
37
+ # ---------------------------
38
+ # R / rpy2 setup (spc package)
39
+ # ---------------------------
40
+ import rpy2.robjects as ro
41
+ import rpy2.robjects.packages as rpackages
42
+
43
+ #---new imports
44
+ # import R's utility package
45
+ utils = rpackages.importr('utils')
46
+ spc = rpackages.importr('spc')
47
+
48
+ # Suppress all R warnings globally
49
+ ro.r["options"](warn=-1)
50
+
51
+ #warnings.filterwarnings("ignore")
52
+ warnings.filterwarnings("ignore", module=r"rpy2\..*")
53
+ random.seed(58)
54
+
55
+ # --------------------------------------------------------
56
+ # I Compute CUSUM parameters: h,k and average run length
57
+ # --------------------------------------------------------
58
+ def get_ref_value(h: float, ARL_0: float) -> float:
59
+ """
60
+ Compute the normalized reference value (k) for a given threshold (h) and in-control Average Run Length (ARL₀).
61
+
62
+ Parameters
63
+ ----------
64
+ h : float
65
+ Normalized decision threshold of the CUSUM chart.
66
+ ARL_0 : float
67
+ Target in-control Average Run Length (ARL₀).
68
+
69
+ Returns
70
+ -------
71
+ float
72
+ Normalized reference value (k) corresponding to the given h and ARL₀.
73
+ """
74
+ k = np.round(spc.xcusum_crit_L0h(ARL_0, h), decimals=4).tolist()[0]
75
+ return k
76
+
77
+
78
+ def get_threshold(k: float, ARL_0: float) -> float:
79
+ """
80
+ Compute the normalized threshold (h) for a given reference value (k)
81
+ and target in-control Average Run Length (ARL₀).
82
+
83
+ Parameters
84
+ ----------
85
+ k : float
86
+ Normalized reference value used in the CUSUM chart.
87
+ ARL_0 : float
88
+ Target in-control Average Run Length (ARL₀).
89
+
90
+ Returns
91
+ -------
92
+ float
93
+ Normalized threshold (h) corresponding to the given k and ARL₀.
94
+ """
95
+ h = np.round(
96
+ spc.xcusum_crit_(k, ARL_0, mu0=0, hs=0, sided="one", r=30),
97
+ decimals=4,
98
+ ).tolist()[0]
99
+ return h
100
+
101
+
102
+ def get_ref_values(h: float, list_ARL_0: List[float]) -> Tuple[pd.DataFrame, OrderedDict]:
103
+ """
104
+ Compute normalized reference values (k) for a list of target ARL₀ values
105
+ given a fixed threshold (h).
106
+
107
+ Parameters
108
+ ----------
109
+ h : float
110
+ Normalized decision threshold of the CUSUM chart.
111
+ list_ARL_0 : List[float]
112
+ List of target in-control Average Run Length (ARL₀) values.
113
+
114
+ Returns
115
+ -------
116
+ Tuple[pandas.DataFrame, collections.OrderedDict]
117
+ A tuple containing:
118
+ - DataFrame: Table with columns ['ARL_0', 'k'] listing each ARL₀ and its corresponding k.
119
+ - OrderedDict: Mapping of ARL₀ to k
120
+ """
121
+ dict_ARL0_k: OrderedDict = OrderedDict()
122
+ summary_table_df_ARL0_k = pd.DataFrame(columns=["ARL_0", "k"])
123
+
124
+ for n, ARL_0 in enumerate(list_ARL_0):
125
+ k = np.round(spc.xcusum_crit_L0h(ARL_0, h), decimals=4).tolist()[0]
126
+ summary_table_df_ARL0_k.loc[n] = [ARL_0, k]
127
+ dict_ARL0_k[ARL_0] = k
128
+
129
+ return summary_table_df_ARL0_k, dict_ARL0_k
130
+
131
+
132
+ def compute_ARL1(h: float, k: float, mu1: float) -> float:
133
+ """
134
+ Compute the out-of-control Average Run Length (ARL₁) for a given
135
+ threshold (h), reference value (k), and mean shift (μ₁).
136
+
137
+ Parameters
138
+ ----------
139
+ h : float
140
+ Normalized decision threshold.
141
+ k : float
142
+ Normalized reference value.
143
+ mu1 : float
144
+ Change in the process mean (standard deviations from the target value).
145
+
146
+ Returns
147
+ -------
148
+ float
149
+ Average Run Length (ARL₁), is the estimate of steady state ARL (expected detection delay) to detect the change in mean.
150
+ """
151
+ ARL_1 = np.round(
152
+ spc.xcusum_ad_(k=k, h=h, mu1=mu1, mu0=0, sided="two", r=20),
153
+ decimals=2,
154
+ ).tolist()[0]
155
+ return ARL_1
156
+
157
+
158
+ def compute_ARL1_table(h: float, shift_in_mean: List[float], dict_ARL0_k: OrderedDict) -> pd.DataFrame:
159
+ """
160
+ Compute a table of Average Run Length (ARL₁) values across multiple change in mean and ARL₀-reference value (k) combinations.
161
+
162
+ Parameters
163
+ ----------
164
+ h : float
165
+ Normalized decision threshold.
166
+ shift_in_mean : List[float]
167
+ List of mean shifts (μ₁), expressed in standardized units.
168
+ dict_ARL0_k : OrderedDict
169
+ Mapping of target in-control ARL₀ values to their corresponding
170
+ normalized reference values (k).
171
+
172
+ Returns
173
+ -------
174
+ pandas.DataFrame
175
+ Table of ARL₁ values for mean shifts and corresponding ARL₀-k combinations.
176
+ """
177
+ list_ARL_0 = list(dict_ARL0_k.keys())
178
+
179
+ dict_data_ARL1_k: OrderedDict = OrderedDict()
180
+ dict_data_ARL1_k["Shift in mean"] = shift_in_mean
181
+
182
+ for ARL_0 in list_ARL_0:
183
+ k = dict_ARL0_k[ARL_0]
184
+ list_ARL_1 = []
185
+ for mu1 in shift_in_mean:
186
+ ARL_1 = np.round(
187
+ spc.xcusum_ad_(k=k, h=h, mu1=mu1, mu0=0, sided="two", r=20),
188
+ decimals=2,
189
+ ).tolist()[0]
190
+ list_ARL_1.append(ARL_1)
191
+
192
+ #Format column name: "50 (0.16)"
193
+ col_name = f"{int(ARL_0)} ({k:.2f})"
194
+ dict_data_ARL1_k[col_name] = list_ARL_1
195
+
196
+ #dict_data_ARL1_k[ARL_0] = list_ARL_1
197
+
198
+ return pd.DataFrame(dict_data_ARL1_k)
199
+
200
+
201
+ # -------------------------------------------
202
+ # II Performance drift detection using CUSUM
203
+ # -------------------------------------------
204
+ class CUSUM:
205
+ """
206
+ CUSUM-based performance drift detection.
207
+
208
+ This class provides methods to initialize baseline parameters, compute
209
+ positive and negative CUSUM statistics, detect performance drifts, and
210
+ generate CUSUM charts.
211
+ """
212
+
213
+ def __init__(self):
214
+ self.df_metric = None
215
+ self.metric_type = None
216
+
217
+ self.AvgDD = None
218
+ self.data = None
219
+
220
+ self.H = None
221
+ self.in_std = None
222
+ self.in_mu = None
223
+ self.S_hi = None
224
+ self.S_lo = None
225
+
226
+ self.config = None
227
+
228
+ self.total_days = None
229
+ self.pre_change_days = None
230
+ self.post_change_days = None
231
+
232
+ self.init_days = None
233
+
234
+ def initialize(self) -> None:
235
+ """
236
+ Load and initialize configuration settings from the configuration file (`config.toml`).
237
+
238
+ This method populates the 'config' attribute with parameters such as plotting options, and default values.
239
+
240
+ Returns
241
+ -------
242
+ None
243
+ """
244
+ try:
245
+ path_file_config = os.path.abspath("config.toml")
246
+ with open(path_file_config, "rb") as file_config:
247
+ self.config = tomli.load(file_config)
248
+ except FileNotFoundError:
249
+ print("Error: config.toml not found at", path_file_config)
250
+ sys.exit(1)
251
+
252
+ def set_init_stats(self, init_days: int) -> None:
253
+ """
254
+ Compute in-control parameters from baseline observations.
255
+
256
+ Uses the first 'init_days' observations to estimate the in-control mean and standard deviation.
257
+
258
+ Parameters
259
+ ----------
260
+ init_days : int
261
+ Number of observations assumed to be in-control.
262
+
263
+ Returns
264
+ -------
265
+ None
266
+ """
267
+ self.init_days = init_days
268
+ in_control_data = self.data[: self.init_days]
269
+ self.in_std = float(np.std(in_control_data))
270
+ self.in_mu = float(np.mean(in_control_data))
271
+
272
+ def set_timeline(self, data: np.ndarray) -> None:
273
+ """
274
+ Set the timeline of the performance metric.
275
+
276
+ Determines the total number of observations in the input data.
277
+
278
+ Parameters
279
+ ----------
280
+ data : np.ndarray
281
+ 1D Array of the AI performance metric.
282
+
283
+ Returns
284
+ -------
285
+ None
286
+ """
287
+ self.total_days = int(np.shape(data)[0])
288
+
289
+ def set_df_metric_default(self) -> None:
290
+ """
291
+ Read the AI performance metric from the default CSV file and initializes the internal
292
+ data and set the timeline.
293
+
294
+ Raises
295
+ ------
296
+ SystemExit
297
+ If the CSV file is not found at the specified path.
298
+
299
+ Returns
300
+ -------
301
+ None
302
+ """
303
+ try:
304
+ path_csv = os.path.abspath(os.path.join("../../", self.config["path_input"]["path_df_metric"]))
305
+ self.df_metric = pd.read_csv(path_csv)
306
+ except FileNotFoundError:
307
+ print("Error: CSV file not found at", path_csv)
308
+ sys.exit(1)
309
+
310
+ self.data = self.df_metric[self.df_metric.columns[1]].to_numpy()
311
+ self.set_timeline(self.data)
312
+
313
+ def set_df_metric_csv(self, data_csv: pd.DataFrame) -> None:
314
+ """
315
+ Assign the performance metric data read fom the input csv file to a datafram .
316
+ Parameters
317
+ ----------
318
+ data_csv : pandas.DataFrame
319
+ DataFrame containing the performance metric data. The values are expected in the second column (index 1).
320
+
321
+ Returns
322
+ -------
323
+ None
324
+ """
325
+ self.df_metric = data_csv
326
+ self.data = self.df_metric[self.df_metric.columns[1]].to_numpy()
327
+ self.set_timeline(self.data)
328
+
329
+ def compute_cusum(
330
+ self, x: List[float], mu_0: float, ref_val: float
331
+ ) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
332
+ """
333
+ Compute CUSUM statistics for the performance metric.
334
+
335
+ Calculates the positive (S_hi) and negative (S_lo) cumulative sums,
336
+ along with the cumulative deviation from the in-control mean, to
337
+ monitor for shifts in the process.
338
+
339
+ Parameters
340
+ ----------
341
+ x : List[float]
342
+ Sequence of observed performance metric values.
343
+ mu_0 : float
344
+ In-control mean (baseline) of the performance metric.
345
+ ref_val : float
346
+ Reference value (k) that determines sensitivity to shifts.
347
+
348
+ Returns
349
+ -------
350
+ Tuple[np.ndarray, np.ndarray, np.ndarray]
351
+ A tuple containing:
352
+ - S_hi : Positive CUSUM values (detects upward shifts)
353
+ - S_lo : Negative CUSUM values (detects downward shifts)
354
+ - cusum : Cumulative sum of deviations from the mean
355
+ """
356
+ x = np.asarray(x, dtype=float)
357
+ num_rows = x.shape[0]
358
+
359
+ x_mean = np.zeros(num_rows, dtype=float)
360
+ self.S_hi = np.zeros(num_rows, dtype=float)
361
+ self.S_lo = np.zeros(num_rows, dtype=float)
362
+ cusum = np.zeros(num_rows, dtype=float)
363
+
364
+ # Starts with 0
365
+ self.S_hi[0] = 0.0
366
+ self.S_lo[0] = 0.0
367
+ cusum[0] = 0.0
368
+
369
+ mean_hi = np.zeros(num_rows, dtype=float)
370
+ mean_lo = np.zeros(num_rows, dtype=float)
371
+
372
+
373
+ x_mean[0] = x[0] - mu_0
374
+ mean_hi[0] = x[0] - mu_0 - ref_val
375
+ mean_lo[0] = mu_0 - ref_val - x[0]
376
+
377
+ for i in range(1, num_rows):
378
+ x_mean[i] = x[i] - mu_0
379
+ mean_hi[i] = x[i] - mu_0 - ref_val
380
+ self.S_hi[i] = max(0.0, self.S_hi[i - 1] + mean_hi[i])
381
+ mean_lo[i] = mu_0 - ref_val - x[i]
382
+ self.S_lo[i] = max(0.0, self.S_lo[i - 1] + mean_lo[i])
383
+ cusum[i] = cusum[i - 1] + x_mean[i]
384
+
385
+ return (
386
+ np.round(self.S_hi, 2),
387
+ np.round(self.S_lo, 2),
388
+ np.round(cusum, 2),
389
+ )
390
+
391
+ def change_detection(
392
+ self,
393
+ normalized_ref_value: float = 0.5,
394
+ normalized_threshold: float = 4,
395
+ ) -> None:
396
+ """
397
+ Detect changes in the process using CUSUM statistics.
398
+
399
+ Computes the CUSUM values and identifies the first point at which
400
+ the process deviates significantly from the in-control state based
401
+ on the specified reference value and threshold.
402
+
403
+ Parameters
404
+ ----------
405
+ normalized_ref_value : float, optional
406
+ Normalized reference value (k) that controls sensitivity to
407
+ shifts in the process mean. Default is 0.5.
408
+ normalized_threshold : float, optional
409
+ Normalized decision threshold (h) used to signal a change.
410
+ Default is 4.
411
+ Returns
412
+ -------
413
+ None
414
+ """
415
+ self.pre_change_days = None
416
+
417
+ control_limit = normalized_threshold
418
+ self.H = control_limit * self.in_std
419
+ ref_val = normalized_ref_value * self.in_std
420
+
421
+ x = np.array(self.data, dtype=float)
422
+ self.S_hi, self.S_lo, _ = self.compute_cusum(x, self.in_mu, ref_val)
423
+
424
+ # Find first occurrence where the threshold is exceeded
425
+ S_hi_exceeds = np.where(self.S_hi > self.H)[0]
426
+ S_lo_exceeds = np.where(self.S_lo > self.H)[0]
427
+
428
+ # Detect whether S_hi or S_lo exceeds threshold
429
+ if len(S_hi_exceeds) > 0 and len(S_lo_exceeds) > 0:
430
+ if S_hi_exceeds[0] < S_lo_exceeds[0]:
431
+ self.pre_change_days = int(S_hi_exceeds[0])
432
+ print(f"Detected upward drift at: {self.pre_change_days}")
433
+ else:
434
+ self.pre_change_days = int(S_lo_exceeds[0])
435
+ print(f"Detected downward drift at: {self.pre_change_days}")
436
+ elif len(S_hi_exceeds) > 0:
437
+ self.pre_change_days = int(S_hi_exceeds[0])
438
+ print(f"Detected upward drift at: {self.pre_change_days}")
439
+ elif len(S_lo_exceeds) > 0:
440
+ self.pre_change_days = int(S_lo_exceeds[0])
441
+ print(f"Detected downward drift at: {self.pre_change_days}")
442
+ else:
443
+ print("No performance drift detected")
444
+
445
+ def plot_input_data(self):
446
+ """
447
+ Plot the AI performance metric with baseline region highlighted.
448
+
449
+ Generates a scatter plot of the performance metric over time
450
+
451
+ Returns
452
+ -------
453
+ matplotlib.figure.Figure
454
+ Matplotlib figure containing the scatter plot of the data.
455
+ """
456
+ x1 = np.arange(self.init_days)
457
+ y1 = self.data[: self.init_days]
458
+
459
+ x2 = np.arange(self.init_days, self.total_days, 1)
460
+ y2 = self.data[self.init_days : self.total_days]
461
+
462
+ fig, ax = plt.subplots(figsize=(10, 5))
463
+
464
+ # Scatter plots
465
+ ax.scatter(x1, y1, color="lime", s=20, alpha=0.4)
466
+ ax.scatter(x2, y2, color="lime", s=20, alpha=0.2)
467
+
468
+ # Highlight baseline
469
+ ax.axvspan(0, self.init_days, color="palegreen", alpha=0.25)
470
+
471
+ # Labels and title
472
+ ax.set_title("AI output", fontsize=16, fontweight="bold")
473
+ ax.set_xlabel("Time", fontsize=14, fontweight="bold")
474
+ ax.set_ylabel("AI model metric", fontsize=14, fontweight="bold")
475
+
476
+ # Tick spacing (similar to dtick=20)
477
+ ax.set_xticks(np.arange(0, self.total_days, 20))
478
+
479
+ # Background color (optional)
480
+ try:
481
+ ax.set_facecolor(self.config["color"]["blue_005"])
482
+ except Exception:
483
+ pass # fallback if config not set
484
+
485
+ # Remove legend
486
+ ax.legend().set_visible(False) if ax.get_legend() else None
487
+
488
+ plt.tight_layout()
489
+
490
+ return fig
491
+
492
+ def plot_changepoint(self):
493
+ """
494
+ Plot the AI performance metric with the detected change pointhighlighting in-control and out-of-control regions
495
+ and marking the detected change point.
496
+
497
+ Returns
498
+ -------
499
+ matplotlib.figure.Figure
500
+ Matplotlib figure containing the change-point plot.
501
+ """
502
+
503
+ # Determine change-point
504
+ if self.pre_change_days is None:
505
+ split = self.init_days
506
+ else:
507
+ split = self.pre_change_days
508
+
509
+ # Split data
510
+ x1 = np.arange(split)
511
+ y1 = self.data[:split]
512
+ mean_y1 = np.mean(y1) if len(y1) else 0.0
513
+
514
+ x2 = np.arange(split, self.total_days)
515
+ y2 = self.data[split:self.total_days]
516
+ mean_y2 = np.mean(y2) if len(y2) else 0.0
517
+
518
+ fig, ax = plt.subplots(figsize=(10, 5))
519
+
520
+ # Scatter plots
521
+ ax.scatter(x1, y1, color="darkturquoise", s=20, alpha=0.4, label="In-control data")
522
+ ax.scatter(x2, y2, color="coral", s=20, alpha=0.4, label="Test data")
523
+
524
+ # Mean lines
525
+ if len(x1):
526
+ ax.plot([x1.min(), x1.max()], [mean_y1, mean_y1],
527
+ linestyle="--", color="darkturquoise", label="In-control mean")
528
+
529
+ if len(x2):
530
+ ax.plot([x2.min(), x2.max()], [mean_y2, mean_y2],
531
+ linestyle="--", color="coral", label="Test mean")
532
+
533
+ # Change-point line
534
+ ax.axvline(x=split, color="grey", linestyle="--", label="Detected change-point")
535
+
536
+ # Labels and title
537
+ ax.set_title("AI model performance versus time", fontsize=16, fontweight="bold")
538
+ ax.set_xlabel("Time", fontsize=14, fontweight="bold")
539
+ ax.set_ylabel("AI model performance", fontsize=14, fontweight="bold")
540
+
541
+ # Tick spacing similar to dtick=20
542
+ ax.set_xticks(np.arange(0, self.total_days, 20))
543
+
544
+ # Background color (optional)
545
+ try:
546
+ ax.set_facecolor(self.config["color"]["blue_005"])
547
+ except Exception:
548
+ pass
549
+
550
+ # Legend (top center like Plotly)
551
+ ax.legend(loc="lower center", bbox_to_anchor=(0.5, 1.15), ncol=3, frameon=True)
552
+
553
+ plt.tight_layout()
554
+
555
+ return fig
556
+
557
+
558
+
559
+ def plot_cusum_chart(self):
560
+ """
561
+ Plot the CUSUM chart of the performance metric: the positive (S_hi) and negative (S_lo) CUSUM statistics
562
+ along with the decision threshold
563
+ used for change detection.
564
+
565
+ Returns
566
+ -------
567
+ matplotlib.figure.Figure
568
+ Matplotlib figure with the CUSUM chart.
569
+ """
570
+
571
+ fig, ax = plt.subplots(figsize=(10, 5))
572
+
573
+ # X axis
574
+ x = np.arange(len(self.S_hi))
575
+
576
+ # Plot S_hi and S_lo (normalized)
577
+ ax.plot(x, self.S_hi / self.in_std,
578
+ label="Positive changes (S_hi)",
579
+ color="cyan")
580
+
581
+ ax.plot(x, self.S_lo / self.in_std,
582
+ label="Negative changes (S_lo)",
583
+ color="darkcyan")
584
+
585
+ # Threshold line
586
+ threshold = self.H / self.in_std
587
+ ax.plot([0, len(self.S_lo)],
588
+ [threshold, threshold],
589
+ linestyle="--",
590
+ color="magenta",
591
+ label="Threshold (h)")
592
+
593
+ # Determine split point
594
+ split = self.pre_change_days if self.pre_change_days is not None else self.init_days
595
+
596
+ # Background shading (like Plotly rectangles)
597
+ try:
598
+ ax.axvspan(0, split,
599
+ color=self.config["color"]["blue_005"],
600
+ alpha=0.8)
601
+
602
+ ax.axvspan(split, len(self.S_lo),
603
+ color="rgb(253, 243, 235)",
604
+ alpha=0.8)
605
+ except Exception:
606
+ # fallback if config missing
607
+ ax.axvspan(0, split, color="lightblue", alpha=0.3)
608
+ ax.axvspan(split, len(self.S_lo), color="mistyrose", alpha=0.3)
609
+
610
+ # Labels and title
611
+ ax.set_title("CUSUM Chart", fontsize=16, fontweight="bold")
612
+ ax.set_xlabel("Time", fontsize=14, fontweight="bold")
613
+ ax.set_ylabel("CUSUM value", fontsize=14, fontweight="bold")
614
+
615
+ # Tick spacing similar to dtick=20
616
+ ax.set_xticks(np.arange(0, len(self.S_lo), 20))
617
+
618
+ # Legend (top center)
619
+ ax.legend(loc="lower center", bbox_to_anchor=(0.5, 1.15), ncol=3, frameon=True)
620
+
621
+ plt.tight_layout()
622
+
623
+ return fig
@@ -0,0 +1,21 @@
1
+ # paths to output files
2
+ [path_output]
3
+ path_figure = "."
4
+
5
+ # primary colors
6
+ [color]
7
+ blue_005 = "#F3F9FC"
8
+ blue_020 = "#D0E4F0"
9
+ blue_040 = "#A3CAE1"
10
+ blue_060 = "#77B0D2"
11
+ blue_080 = "#4F96C4"
12
+ blue_100 = "#007CBA"
13
+
14
+ # controls
15
+ [control]
16
+ save_figure = "false"
17
+
18
+ # CUSUM parameters
19
+ [CUSUM_params]
20
+ list_ARL_0 = [50, 100, 150, 200, 300, 400, 500, 1000]
21
+ shift_in_mean = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6]
@@ -0,0 +1,120 @@
1
+ Metadata-Version: 2.4
2
+ Name: aim-cu
3
+ Version: 0.2.0
4
+ Summary: AIM-CU: A CUSUM-based tool for AI Monitoring
5
+ Home-page: https://github.com/DIDSR/AIM-CU
6
+ Project-URL: User Inteface, https://huggingface.co/spaces/didsr/AIM-CU
7
+ Requires-Python: >=3.9
8
+ Description-Content-Type: text/x-rst
9
+ License-File: LICENSE
10
+ Requires-Dist: numpy>=1.23
11
+ Requires-Dist: pandas>=1.5
12
+ Requires-Dist: matplotlib>=3.6
13
+ Requires-Dist: rpy2>=3.5
14
+ Requires-Dist: tomli; python_version < "3.11"
15
+ Dynamic: description
16
+ Dynamic: description-content-type
17
+ Dynamic: home-page
18
+ Dynamic: license-file
19
+ Dynamic: project-url
20
+ Dynamic: requires-dist
21
+ Dynamic: requires-python
22
+ Dynamic: summary
23
+
24
+ AIM-CU: A CUSUM-based tool for AI Monitoring
25
+ ============================================
26
+ Monitoring a clinically deployed AI device to detect performance drift is an essential step to ensure the safety and effectiveness of AI.
27
+
28
+ AIM-CU is a statistical tool for AI monitoring based on a cumulative sum (AIM-CU) approach.
29
+
30
+ AIM-CU computes:
31
+
32
+ * The parameter choices for change-point detection based on an acceptable false alarm rate
33
+ * Detection delay estimates for a given displacement of the performance metric from the target for those parameter choices.
34
+
35
+ System setup
36
+ ------------
37
+ Make sure R is installed in the system. There is no specific version that this relies on. Here we have used the version 4.1.2 (2021-11-01).
38
+ Instructions for Linux (the below setup is only performed in Linux):
39
+
40
+ .. code-block:: shell
41
+
42
+ wget -qO- https://cloud.r-project.org/bin/linux/ubuntu/marutter_pubkey.asc | tee -a /etc/apt/trusted.gpg.d/cran_ubuntu_key.asc
43
+ add-apt-repository "deb https://cloud.r-project.org/bin/linux/ubuntu $(lsb_release -cs)-cran40/"
44
+ apt-get install -y --no-install-recommends r-base r-base-dev
45
+
46
+ # setup R configs
47
+ echo "r <- getOption('repos'); r['CRAN'] <- 'http://cran.us.r-project.org'; options(repos = r);" > ~/.Rprofile
48
+ Rscript -e "install.packages('ggplot2')"
49
+ Rscript -e "install.packages('hexbin')"
50
+ Rscript -e "install.packages('lazyeval')"
51
+ Rscript -e "install.packages('cusumcharter')"
52
+ Rscript -e "install.packages('RcppCNPy')"
53
+ Rscript -e "install.packages('spc')"
54
+
55
+ Code execution
56
+ --------------
57
+ Clone AIM-CU repository.
58
+
59
+ .. code-block:: shell
60
+
61
+ git clone https://github.com/DIDSR/AIM-CU.git
62
+
63
+ Run the following commands to install required dependencies (Python = 3.10 is used).
64
+
65
+ .. code-block:: shell
66
+
67
+ apt-get -y install python3
68
+ apt-get -y install pip
69
+ cd AIM-CU
70
+ pip install -r requirements.txt
71
+
72
+ Run AIM-CU.
73
+
74
+ .. code-block:: shell
75
+
76
+ cd src/package
77
+ python3 app.py
78
+
79
+ Open the URL http://0.0.0.0:7860 that is running the AIM-CU locally.
80
+
81
+ Example code execution
82
+ ----------------------
83
+ Example code can be run through Jupyter Notebook. Do this by entering the ``jupyter notebook`` command from the ``/src/package/`` directory. The tool is designed to be used through a GUI, not from the console.
84
+
85
+ Demo
86
+ ----
87
+ AIM-CU can also be run through the demo available at https://huggingface.co/spaces/didsr/AIM-CU. If Space is paused, click on Restart button. Note: this Space uses a custom Docker container; build may break due to latest package updates pulled by HuggingFace.
88
+
89
+ Usability
90
+ ---------
91
+ * Example AI output CSV file is available as `config/spec-60-60.csv <config/spec-60-60.csv>`_ to be uploaded in monitoring phase.
92
+
93
+ * Workflow instruction to run the tool is available at bottom-left of UI.
94
+
95
+ * Sample UI output is available at `assets/ui.png <assets/ui.png>`_.
96
+
97
+ * Setting ``control:save_figure`` to ``true`` from `config.toml <config/config.toml>`_ will save tables and plots in `figure/ <figure/>`_.
98
+
99
+ * Running AIM-CU usually only takes a few seconds, and it does not require a GPU to run.
100
+
101
+ Related References
102
+ ------------------
103
+ * Smriti Prathapan, Ravi K. Samala, Nathan Hadjiyski, Pierre‑François D’Haese, Nicholas Petrick, Jana Delfino, Fabien Maldonado, Brandon Nelson, Ghada Zamzmi, Phuong Nguyen, Yelena Yesha, and Berkman Sahiner, "Detecting performance drift in AI models for medical image analysis using CUSUM chart" (Journal in press 2026)
104
+
105
+ * Prathapan, S., Sahiner, B., Kadia, D., and Samala, R.K. 2025, "AIM-CU: A statistical tool for AI Monitoring." (Journal in-press 2026)
106
+
107
+ * Prathapan, S., Samala, R.K., Hadjiyski, N., D’Haese, P.F., Maldonado, F., Nguyen, P., Yesha, Y. and Sahiner, B., 2024, April. Quantifying input data drift in medical machine learning models by detecting change-points in time-series data. In Medical Imaging 2024: Computer-Aided Diagnosis (Vol. 12927, pp. 67-76). SPIE. https://doi.org/10.1117/12.3008771
108
+
109
+ * Smriti Prathapan, Ravi K. Samala, Nathan Hadjiyski, Pierre‑François D’Haese, Nicholas Petrick, Jana Delfino, Fabien Maldonado, Brandon Nelson, Ghada Zamzmi, Phuong Nguyen, Yelena Yesha, and Berkman Sahiner, "Post-market Monitoring of AI-enabled Medical Devices for Radiology and Healthcare Applications" (FDA-UMiami Collaboration Poster, September 2023)
110
+
111
+ Disclaimer
112
+ ---------------------
113
+
114
+ **About the Catalog of Regulatory Science Tools**
115
+
116
+ The enclosed tool is *in preparation* for the `Catalog of Regulatory Science Tools <https://cdrh-rst.fda.gov>`_. Note that this software is not (yet) a standalone RST but rather is to be included as a **reference tool** to support other Regulatory Science Tools (such as synthetic datasets) for reproducibility.
117
+
118
+ This catalog collates a variety of regulatory science tools that the FDA's Center for Devices and Radiological Health's (CDRH) Office of Science and Engineering Labs (OSEL) developed. These tools use the most innovative science to support medical device development and patient access to safe and effective medical devices. If you are considering using a tool from this catalog in your marketing submissions, note that these tools have not been qualified as `Medical Device Development Tools <https://www.fda.gov/medical-devices/medical-device-development-tools-mddt>`_ and the FDA has not evaluated the suitability of these tools within any specific context of use. You may `request feedback or meetings for medical device submissions <https://www.fda.gov/regulatory-information/search-fda-guidance-documents/requests-feedback-and-meetings-medical-device-submissions-q-submission-program>`_ as part of the Q-Submission Program.
119
+
120
+ For more information about the Catalog of Regulatory Science Tools, email RST_CDRH@fda.hhs.gov.
@@ -0,0 +1,13 @@
1
+ LICENSE
2
+ setup.py
3
+ aim_cu/README.rst
4
+ aim_cu/SECURITY.md
5
+ aim_cu/__init__.py
6
+ aim_cu/aim_cu.py
7
+ aim_cu/config.toml
8
+ aim_cu.egg-info/PKG-INFO
9
+ aim_cu.egg-info/SOURCES.txt
10
+ aim_cu.egg-info/dependency_links.txt
11
+ aim_cu.egg-info/requires.txt
12
+ aim_cu.egg-info/top_level.txt
13
+ test/test_aim_cu.py
@@ -0,0 +1,7 @@
1
+ numpy>=1.23
2
+ pandas>=1.5
3
+ matplotlib>=3.6
4
+ rpy2>=3.5
5
+
6
+ [:python_version < "3.11"]
7
+ tomli
@@ -0,0 +1 @@
1
+ aim_cu
aim_cu-0.2.0/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
aim_cu-0.2.0/setup.py ADDED
@@ -0,0 +1,31 @@
1
+ from setuptools import setup, find_packages
2
+ from pathlib import Path
3
+
4
+ this_dir = Path(__file__).parent
5
+ long_description = (this_dir / "aim_cu" / "README.rst").read_text(encoding="utf-8")
6
+
7
+ setup(
8
+ name="aim-cu",
9
+ version="0.2.0",
10
+ description="AIM-CU: A CUSUM-based tool for AI Monitoring",
11
+ long_description=long_description,
12
+ long_description_content_type="text/x-rst",
13
+ authors="Smriti Prathapan, Berkman Sahiner, Dhaval Kadia and Ravi Samala",
14
+ packages=find_packages(),
15
+ python_requires=">=3.9",
16
+ install_requires=[
17
+ "numpy>=1.23",
18
+ "pandas>=1.5",
19
+ "matplotlib>=3.6",
20
+ "rpy2>=3.5",
21
+ "tomli; python_version<'3.11'",
22
+ ],
23
+ include_package_data=True,
24
+ package_data={
25
+ "aim_cu": ["config.toml", "SECURITY.md", "README.rst"],
26
+ },
27
+ url="https://github.com/DIDSR/AIM-CU",
28
+ project_urls={
29
+ "User Inteface" : "https://huggingface.co/spaces/didsr/AIM-CU",
30
+ },
31
+ )
@@ -0,0 +1,90 @@
1
+ #Usage:
2
+ # python test_aim_cu.py --csv /scratch/smriti.prathapan/fromOHPC/note1/AIM-CU-package/spec-60-60.csv --norm_h 5
3
+
4
+ import argparse
5
+ # import numpy as np
6
+ import pandas as pd
7
+ import warnings
8
+ warnings.simplefilter("ignore")
9
+ import aim_cu
10
+ # import matplotlib
11
+ # from collections import OrderedDict
12
+ # matplotlib.use("Agg")
13
+ # import matplotlib.pyplot as plt
14
+
15
+ def main():
16
+ parser = argparse.ArgumentParser()
17
+ parser.add_argument("--csv", default=None, help="input the CSV path")
18
+ parser.add_argument("--init_days", type=int, default=30)
19
+ parser.add_argument("--norm_k", type=float, default=0.5)
20
+ parser.add_argument("--norm_h", type=float, default=4.0)
21
+ #parser.add_argument("--show_plots", action="store_true")
22
+ args = parser.parse_args()
23
+
24
+ # --------------------------------------------------------
25
+ # I Compute CUSUM parameters and average run length
26
+ # --------------------------------------------------------
27
+ h = float(args.norm_h)
28
+ print("\n=== SPC helper functions for computing CUSUM parameters ===")
29
+ k_for_arl100 = aim_cu.get_ref_value(h=h, ARL_0=100.0)
30
+ #print(f"k (for h={h}, ARL0=100): {k_for_arl100}")
31
+
32
+ h_back = aim_cu.get_threshold(k=k_for_arl100, ARL_0=100.0)
33
+ print(f"h (for k={k_for_arl100}, ARL0=100): {h_back}")
34
+
35
+ df_k, dict_k = aim_cu.get_ref_values(h=4.0, list_ARL_0=[50.0, 100.0, 200.0])
36
+ print(f"list of k values h={h} : \n")
37
+ print(df_k)
38
+
39
+ #print(aim_cu.shift_in_mean)
40
+ #print(aim_cu.dict_ARL0_k)
41
+
42
+ ARL_1 = aim_cu.compute_ARL1(h=h_back,k=k_for_arl100, mu1=0.65)
43
+ print(f"ARL_1 : {ARL_1}")
44
+
45
+ df_arl_1 = aim_cu.compute_ARL1_table(h=4, shift_in_mean=[0.1, 0.2, 0.3], dict_ARL0_k=dict_k)
46
+ print(f"ARL_1 table : {df_arl_1}")
47
+
48
+ # -------------------------------
49
+ # II Performance drift detection
50
+ # -------------------------------
51
+ print("\n=== Performance drift detection using CUSUM ===")
52
+ cusum = aim_cu.CUSUM()
53
+
54
+
55
+ # If config.toml is not found, initialize() will sys.exit(1)
56
+ try:
57
+ cusum.initialize()
58
+ except SystemExit:
59
+ print("WARNING: config.toml not found. Using fallback config.")
60
+ cusum.config = {
61
+ "color": {"blue_005": "white"},
62
+ "control": {"save_figure": "false"},
63
+ "path_output": {"path_figure": "."},
64
+ }
65
+
66
+ if args.csv:
67
+ df = pd.read_csv(args.csv)
68
+ cusum.set_df_metric_csv(df)
69
+ else:
70
+ cusum.set_df_metric_default()
71
+
72
+ cusum.set_init_stats(args.init_days)
73
+ print(f"Total observations: {cusum.total_days}")
74
+ print(f"Baseline observations: {cusum.init_days}")
75
+
76
+ cusum.change_detection(normalized_ref_value=args.norm_k, normalized_threshold=args.norm_h)
77
+
78
+ fig1 = cusum.plot_input_data()
79
+ #fig1.write_image("input_data.png")
80
+ fig1.savefig("input_data.png", dpi=200, bbox_inches="tight")
81
+
82
+ fig2 = cusum.plot_changepoint()
83
+ fig2.savefig("changepoint.png", dpi=200, bbox_inches="tight")
84
+
85
+ fig3 = cusum.plot_cusum_chart()
86
+ fig3.savefig("cusum_chart.png", dpi=200, bbox_inches="tight")
87
+
88
+
89
+ if __name__ == "__main__":
90
+ main()