From e12c117c8b000e2f1c9850d80b4330f6cbf30910 Mon Sep 17 00:00:00 2001 From: Burhan Khan Date: Mon, 30 Sep 2024 16:07:41 -0500 Subject: [PATCH 001/143] Created a Python file for Flask, attempted to use HTMX, and tried to read in the JSON files. --- annotationskull.json | 6 ++++++ app.py | 37 ++++++++++++++++++++++++++++++++++ bonedata.json | 7 +++++++ templates/index.html | 47 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 97 insertions(+) create mode 100644 annotationskull.json create mode 100644 app.py create mode 100644 bonedata.json create mode 100644 templates/index.html diff --git a/annotationskull.json b/annotationskull.json new file mode 100644 index 00000000..68c6fff0 --- /dev/null +++ b/annotationskull.json @@ -0,0 +1,6 @@ +{ + "id": "11", + "bone_id": "1", + "type": "text", + "text": "some annotation to display for skull image?" +} \ No newline at end of file diff --git a/app.py b/app.py new file mode 100644 index 00000000..30122604 --- /dev/null +++ b/app.py @@ -0,0 +1,37 @@ +from flask import Flask, jsonify, render_template, send_from_directory +import json + +app = Flask(__name__) + +# Route to serve the HTMX page +@app.route('/') +def index(): + return render_template('index.html') + +# Route to load the bone JSON data +@app.route('/load-bone') +def load_bone(): + # Load the bone data + with open('bonedata.json') as f: + bone_data = json.load(f) + + # Load annotation data based on the bone annotations + annotations = [] + for annotation_id in bone_data['annotations']: + with open(f'annotationskull.json') as f: + annotation = json.load(f) + if annotation['bone_id'] == bone_data['id']: + annotations.append(annotation) + + # Add annotations to bone data + bone_data['annotations_list'] = annotations + + return jsonify(bone_data) + +# Serve the image from the directory +@app.route('/images/') +def serve_image(filename): + return send_from_directory('images', filename) + +if __name__ == '__main__': + app.run(debug=True) diff --git a/bonedata.json b/bonedata.json new file mode 100644 index 00000000..1b9fc195 --- /dev/null +++ b/bonedata.json @@ -0,0 +1,7 @@ +{ + "id": "1", + "name": "Bone_name_example skull", + "description": "Skull is hard and protects brain", + "image_path": " path_to_skull_in_project_directry", + "annotations": ["11"] +} diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 00000000..292769d3 --- /dev/null +++ b/templates/index.html @@ -0,0 +1,47 @@ + + + + + + Bone Information Viewer + + + + + +
+

Bone Viewer

+ + + + + +
+

Bone Name:

+

+
+ Bone Image +
+
+

Annotations

+
    +
    +
    +
    + + + From b004ffaf536b74fb3085090f180a849f3bdeb5ef Mon Sep 17 00:00:00 2001 From: tluke900 Date: Sun, 6 Oct 2024 12:32:39 -0500 Subject: [PATCH 002/143] Basic Text display for bone set --- templates/index.html | 59 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 templates/index.html diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 00000000..3758da25 --- /dev/null +++ b/templates/index.html @@ -0,0 +1,59 @@ + + + + + + Bone Information Viewer + + + + +
    +

    Upper Limb Set

    + + Upper limb bones include the Humerus, Radius, + + + +
    +

    Humerus:

    +

    +
    +
  • Only bone of the upper arm.
  • +
  • The longest bone in the body that is not present in the leg.
  • + + +

    Radius:

    +

    +
    +
  • One of the two lower arm bones.
  • +
  • Though these bones twist around each other, the radius is on the side of the thumb.
  • + + +

    Ulna:

    +

    +
    +
  • The other lower arm bone.
  • +
  • Slightly longer than the radius, always ends on the side of the pinky.
  • + + +
    +
    +
    + + + \ No newline at end of file From 6b0392b184c57582d46316c20ed12f969c760508 Mon Sep 17 00:00:00 2001 From: Burhan Khan Date: Mon, 7 Oct 2024 14:59:03 -0500 Subject: [PATCH 003/143] Addresses PR for Issue 16. Deleted Flask python file, organized files into correct directories, edited the html file to match figma design (without image) --- ...fbde-5f60-4ea7-a3f4-3f7d3a5fd069_large.jpg | Bin 0 -> 210937 bytes .../JSON_Directory/annotationskull.json | 0 .../JSON_Directory/bonedata.json | 0 Editor_Directory/index.html | 105 ++++++++++++++++++ app.py | 37 ------ templates/index.html | 47 -------- 6 files changed, 105 insertions(+), 84 deletions(-) create mode 100644 Data_Directory/Images_Directory/9a81fbde-5f60-4ea7-a3f4-3f7d3a5fd069_large.jpg rename annotationskull.json => Data_Directory/JSON_Directory/annotationskull.json (100%) rename bonedata.json => Data_Directory/JSON_Directory/bonedata.json (100%) create mode 100644 Editor_Directory/index.html delete mode 100644 app.py delete mode 100644 templates/index.html diff --git a/Data_Directory/Images_Directory/9a81fbde-5f60-4ea7-a3f4-3f7d3a5fd069_large.jpg b/Data_Directory/Images_Directory/9a81fbde-5f60-4ea7-a3f4-3f7d3a5fd069_large.jpg new file mode 100644 index 0000000000000000000000000000000000000000..1318b3b48de5ea7796759727cd0b801589122d0a GIT binary patch literal 210937 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 + + + + + Bone Information Viewer + + + + + +
    +

    Femur

    +

    + A description of bone, something like: The femur, or thighbone, is the longest bone in the human body. +

    +
    +

    Annotations

    +
      +
    • List of annotations to be displayed here
    • +
    +
    +
    +

    Image

    + Bone Image +
    +
    + + \ No newline at end of file diff --git a/app.py b/app.py deleted file mode 100644 index 30122604..00000000 --- a/app.py +++ /dev/null @@ -1,37 +0,0 @@ -from flask import Flask, jsonify, render_template, send_from_directory -import json - -app = Flask(__name__) - -# Route to serve the HTMX page -@app.route('/') -def index(): - return render_template('index.html') - -# Route to load the bone JSON data -@app.route('/load-bone') -def load_bone(): - # Load the bone data - with open('bonedata.json') as f: - bone_data = json.load(f) - - # Load annotation data based on the bone annotations - annotations = [] - for annotation_id in bone_data['annotations']: - with open(f'annotationskull.json') as f: - annotation = json.load(f) - if annotation['bone_id'] == bone_data['id']: - annotations.append(annotation) - - # Add annotations to bone data - bone_data['annotations_list'] = annotations - - return jsonify(bone_data) - -# Serve the image from the directory -@app.route('/images/') -def serve_image(filename): - return send_from_directory('images', filename) - -if __name__ == '__main__': - app.run(debug=True) diff --git a/templates/index.html b/templates/index.html deleted file mode 100644 index 292769d3..00000000 --- a/templates/index.html +++ /dev/null @@ -1,47 +0,0 @@ - - - - - - Bone Information Viewer - - - - - -
    -

    Bone Viewer

    - - - - - -
    -

    Bone Name:

    -

    -
    - Bone Image -
    -
    -

    Annotations

    -
      -
      -
      -
      - - - From 2a7043895cf043064c3f4585838e8ae2904bdc48 Mon Sep 17 00:00:00 2001 From: tluke900 Date: Mon, 7 Oct 2024 15:58:11 -0500 Subject: [PATCH 004/143] This verison loads from given files, does not have any formatting yet. --- Data_Directory/boneSet.JSON | 8 ++++++ templates/index.html | 55 +++++++++++++++++++++++++++++-------- 2 files changed, 51 insertions(+), 12 deletions(-) create mode 100644 Data_Directory/boneSet.JSON diff --git a/Data_Directory/boneSet.JSON b/Data_Directory/boneSet.JSON new file mode 100644 index 00000000..4f821ba2 --- /dev/null +++ b/Data_Directory/boneSet.JSON @@ -0,0 +1,8 @@ +{ + "name": "Upper Limb Bones", + "bones": [ + "Humerus", + "Ulna", + "Radius" + ] + } \ No newline at end of file diff --git a/templates/index.html b/templates/index.html index 3758da25..b35d9208 100644 --- a/templates/index.html +++ b/templates/index.html @@ -23,37 +23,68 @@
      -

      Upper Limb Set

      + -
      -

      Humerus:

      +
      + + + + + -
      -
      + +
      + + + + \ No newline at end of file From f5445bd00d488a33e7264574b85f92f89026649a Mon Sep 17 00:00:00 2001 From: tluke900 Date: Tue, 8 Oct 2024 11:30:45 -0500 Subject: [PATCH 005/143] Commit before rebase --- .../Annotation_Directory/AnnotationSkull2 | 7 +++++++ .../Bone_Directory/boneExample.JSON | 19 +++++++++++++++++++ templates/index.html | 6 +++--- 3 files changed, 29 insertions(+), 3 deletions(-) create mode 100644 Data_Directory/Annotation_Directory/AnnotationSkull2 create mode 100644 Data_Directory/Bone_Directory/boneExample.JSON diff --git a/Data_Directory/Annotation_Directory/AnnotationSkull2 b/Data_Directory/Annotation_Directory/AnnotationSkull2 new file mode 100644 index 00000000..1a3e1cbc --- /dev/null +++ b/Data_Directory/Annotation_Directory/AnnotationSkull2 @@ -0,0 +1,7 @@ +{ + "Annotation Id": 2, + "Bone Id": 1, + "Annotation Type": "text", + "Annotation Handle": "Face Elements", + "Annotation Body": "14 bones constitute the face. They are the Vomer, Mandible (Jawbone), two Maxilla, two Nasal Bones, two Palatine bones, two Lacrimal Bones, two Zygomatic bones, and two interior nasal concha." +} \ No newline at end of file diff --git a/Data_Directory/Bone_Directory/boneExample.JSON b/Data_Directory/Bone_Directory/boneExample.JSON new file mode 100644 index 00000000..4257366e --- /dev/null +++ b/Data_Directory/Bone_Directory/boneExample.JSON @@ -0,0 +1,19 @@ +{ + "Bones": [ + { + "Bone Id": 1, + "Bone Name": "Cranium", + "Bone Description": "Commonly called the skull, the cranium is composed of 22 bones and is divided into two regions: the neurocranium (which protects the brain) and the viscerocranium (which forms the face). The skull also supports tendinous muscle attachments and allows neurovascular passage between intracranial and extracranial anatomy.", + "Bone Image Path": "https://example.com/images/Skull.png", + "Annotations": [1,2] + }, + { + "Bone Id": 3, + "Bone Name": "Humerus", + "Bone Description": "Upper Arm Bone", + "Bone Image Path": "https://example.com/images/Humerus.png", + "Annotations": [3,4] + } + + ] +} \ No newline at end of file diff --git a/templates/index.html b/templates/index.html index b35d9208..0cc3cf35 100644 --- a/templates/index.html +++ b/templates/index.html @@ -3,7 +3,7 @@ - Bone Information Viewer + Bone Set Viewer -
      - - -
      +

      Bone Information Viewer

      + + - - + +
      +

      Bonesets

      +
        +
        - + const boneSetList = document.getElementById('bone-set-list'); + boneSetList.innerHTML = ''; // Clear any previous data + // Iterate through each boneset and display the name and bone IDs + data.boneset.forEach(set => { + // Create a list item for the boneset name + const li = document.createElement('li'); + li.textContent = set.name; + boneSetList.appendChild(li); - -
        -
        - - + // Append the sublist under the boneset name + boneSetList.appendChild(subList); + }); + }) + .catch(error => console.error('Error loading bonesets:', error)); + } + // Add event listener to the button to load the bone sets on click + document.getElementById('load-sets-button').addEventListener('click', loadBoneSets); + - - - \ No newline at end of file + From b30a04e65dcbf2fcecf53799447c5e427b2f2d3c Mon Sep 17 00:00:00 2001 From: Burhan Khan Date: Wed, 9 Oct 2024 22:19:17 -0500 Subject: [PATCH 007/143] Displays image from the data branch of the remote repo --- ...1fbde-5f60-4ea7-a3f4-3f7d3a5fd069_large.jpg | Bin 210937 -> 0 bytes Editor_Directory/index.html | 8 ++++---- 2 files changed, 4 insertions(+), 4 deletions(-) delete mode 100644 Data_Directory/Images_Directory/9a81fbde-5f60-4ea7-a3f4-3f7d3a5fd069_large.jpg diff --git a/Data_Directory/Images_Directory/9a81fbde-5f60-4ea7-a3f4-3f7d3a5fd069_large.jpg b/Data_Directory/Images_Directory/9a81fbde-5f60-4ea7-a3f4-3f7d3a5fd069_large.jpg deleted file mode 100644 index 1318b3b48de5ea7796759727cd0b801589122d0a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 210937 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 - - - - -
        -

        Femur

        -

        - A description of bone, something like: The femur, or thighbone, is the longest bone in the human body. -

        -
        -

        Annotations

        -
          -
        • List of annotations to be displayed here
        • -
        -
        -
        -

        Image

        - Bone Image -
        -
        - - diff --git a/editor/index.html b/editor/index.html new file mode 100644 index 00000000..5f8c046a --- /dev/null +++ b/editor/index.html @@ -0,0 +1,79 @@ + + + + + + Bone Editor + + + +
        +

        Bone Editor

        + +
        +
        + + + + \ No newline at end of file From c4c7180f6e7012d966dd92c9b72fd28c7d92dfae Mon Sep 17 00:00:00 2001 From: jacksayshi Date: Thu, 10 Oct 2024 20:05:10 -0500 Subject: [PATCH 010/143] push button for editor --- editor/push.html | 24 + package-lock.json | 779 +++++++++++++++++++++++ package.json | 18 + server.js | 55 ++ editor/index.html => templates/bone.html | 0 5 files changed, 876 insertions(+) create mode 100644 editor/push.html create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 server.js rename editor/index.html => templates/bone.html (100%) diff --git a/editor/push.html b/editor/push.html new file mode 100644 index 00000000..e55d541c --- /dev/null +++ b/editor/push.html @@ -0,0 +1,24 @@ + + + + + + Automate Git Push + + + +

        Automate Git Push

        +
        + + +

        + + +

        + +
        +
        + + diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..15859049 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,779 @@ +{ + "name": "digitalbonesbox", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "digitalbonesbox", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "body-parser": "^1.20.3", + "express": "^4.21.1", + "simple-git": "^3.27.0" + } + }, + "node_modules/@kwsites/file-exists": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@kwsites/file-exists/-/file-exists-1.1.1.tgz", + "integrity": "sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw==", + "dependencies": { + "debug": "^4.1.1" + } + }, + "node_modules/@kwsites/file-exists/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@kwsites/file-exists/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/@kwsites/promise-deferred": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@kwsites/promise-deferred/-/promise-deferred-1.1.1.tgz", + "integrity": "sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==" + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + }, + "node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "4.21.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", + "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.10", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/object-inspect": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", + "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", + "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==" + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "node_modules/side-channel": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/simple-git": { + "version": "3.27.0", + "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-3.27.0.tgz", + "integrity": "sha512-ivHoFS9Yi9GY49ogc6/YAi3Fl9ROnF4VyubNylgCkA+RVqLaKWnDSzXOVzya8csELIaWaYNutsEuAhZrtOjozA==", + "dependencies": { + "@kwsites/file-exists": "^1.1.1", + "@kwsites/promise-deferred": "^1.1.1", + "debug": "^4.3.5" + }, + "funding": { + "type": "github", + "url": "https://github.com/steveukx/git-js?sponsor=1" + } + }, + "node_modules/simple-git/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/simple-git/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "engines": { + "node": ">= 0.8" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 00000000..cfc5dc45 --- /dev/null +++ b/package.json @@ -0,0 +1,18 @@ +{ + "name": "digitalbonesbox", + "version": "1.0.0", + "description": "This project is a Web App project aims to convert an existing PowerPoint-based educational tool into an interactive, mobile-friendly web application.", + "main": "server.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "start": "node server.js" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "body-parser": "^1.20.3", + "express": "^4.21.1", + "simple-git": "^3.27.0" + } +} diff --git a/server.js b/server.js new file mode 100644 index 00000000..fb879ba4 --- /dev/null +++ b/server.js @@ -0,0 +1,55 @@ +const express = require('express'); +const bodyParser = require('body-parser'); +const simpleGit = require('simple-git'); +const fs = require('fs'); +const path = require('path'); + +const app = express(); +const git = simpleGit('C:/DigitalBonesBox'); // Local repo path + +app.use(bodyParser.urlencoded({ extended: false })); +app.use(bodyParser.json()); + +// Serve the HTMX page +app.get('/', (req, res) => { + res.sendFile(path.join(__dirname, 'editor', 'push.html')); +}); + +// Endpoint for the publish action +app.post('/publish', async (req, res) => { + const { fileName, jsonData } = req.body; + const filePath = path.join('C:/DigitalBonesBox', 'databones', fileName); + + try { + // Checkout to the data branch + await git.checkout('data'); + + // Save the JSON data to a file + fs.writeFileSync(filePath, jsonData); + + // Git operations: add, commit, and push to the data branch + await git.add(filePath); + const commitMessage = `Automated publish at ${new Date().toISOString()}`; + await git.commit(commitMessage); + await git.push('origin', 'data'); + + // Switch back to the main branch + await git.checkout('main'); + + res.send('Publish successful! Changes pushed to the data branch.'); + } catch (error) { + console.error('Publish failed:', error); + // Attempt to switch back to main in case of error + try { + await git.checkout('main'); + } catch (checkoutError) { + console.error('Failed to switch back to the main branch:', checkoutError); + } + res.status(500).send('Publish failed. Check server logs for details.'); + } +}); + +const PORT = 3001; +app.listen(PORT, () => { + console.log(`Server running on http://localhost:${PORT}`); +}); diff --git a/editor/index.html b/templates/bone.html similarity index 100% rename from editor/index.html rename to templates/bone.html From 39413d29f8613accb5ea3a69555ac05510202647 Mon Sep 17 00:00:00 2001 From: tluke900 Date: Mon, 14 Oct 2024 13:12:42 -0500 Subject: [PATCH 011/143] Added drop down menu and first steps for automatic population. --- templates/boneset.html | 52 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 48 insertions(+), 4 deletions(-) diff --git a/templates/boneset.html b/templates/boneset.html index dea2d41a..692febd7 100644 --- a/templates/boneset.html +++ b/templates/boneset.html @@ -24,18 +24,62 @@

        Bone Information Viewer

        - +
        + + +
        + + + + + + + +
        + +
        -

        Bonesets

        +

        Boneset

          + + + } catch (error) { + console.error('Error fetching bone data:', error); + } +}); +
          From 07ee0bb67a3c651a25bbedfea0b3a5f8602dc2dd Mon Sep 17 00:00:00 2001 From: tluke900 Date: Mon, 14 Oct 2024 13:57:35 -0500 Subject: [PATCH 013/143] Added Dynamic Loading --- templates/boneset.html | 89 +++++++++++++++++++++--------------------- 1 file changed, 45 insertions(+), 44 deletions(-) diff --git a/templates/boneset.html b/templates/boneset.html index 692febd7..3036d957 100644 --- a/templates/boneset.html +++ b/templates/boneset.html @@ -26,16 +26,15 @@

          Bone Information Viewer

          -
          - + + - +
          @@ -75,51 +74,53 @@

          Boneset

            - - From 5a2fc676320f19dae80e3db1d84cb3615b1b85ef Mon Sep 17 00:00:00 2001 From: tluke900 Date: Wed, 16 Oct 2024 13:56:02 -0500 Subject: [PATCH 014/143] Added Instant Dynamic Loading, Removed dead buttons. --- templates/boneset.html | 91 ++++++++++++++++++++---------------------- 1 file changed, 44 insertions(+), 47 deletions(-) diff --git a/templates/boneset.html b/templates/boneset.html index 3036d957..94473022 100644 --- a/templates/boneset.html +++ b/templates/boneset.html @@ -24,9 +24,6 @@

            Bone Information Viewer

            -
            - -
            @@ -61,12 +58,10 @@

            Bone Information Viewer

            } // Add event listener for the button - document.getElementById('load-button').addEventListener('click', populateDropdown); + + document.addEventListener('DOMContentLoaded', populateDropdown); -
            - -
            @@ -75,54 +70,56 @@

            Boneset

            + From 588dc1f2e0499c6538762a3d23ecae9fcaad9792 Mon Sep 17 00:00:00 2001 From: JoshBudzynski <157167337+JoshBudzynski@users.noreply.github.com> Date: Mon, 21 Oct 2024 14:31:28 -0500 Subject: [PATCH 015/143] final_#47 --- templates/boneset.html | 60 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 58 insertions(+), 2 deletions(-) diff --git a/templates/boneset.html b/templates/boneset.html index dea2d41a..18e4b1cf 100644 --- a/templates/boneset.html +++ b/templates/boneset.html @@ -31,12 +31,20 @@

            Bone Information Viewer

            Bonesets

              + + - \ No newline at end of file + From d3465d42e84ad48ea4d506005686d75b6d0330ad Mon Sep 17 00:00:00 2001 From: Burhan Khan Date: Mon, 21 Oct 2024 15:53:38 -0500 Subject: [PATCH 017/143] Addresses Issue 49. Changed the overall layout that was pulled from main and displayed the image from the data branch correctly. Also had to delete the directories we weren't using anymore. --- Data_Directory/Images_Directory/.DS_Store | Bin 6148 -> 0 bytes Editor_Directory/index.html | 115 ---------------------- 2 files changed, 115 deletions(-) delete mode 100644 Data_Directory/Images_Directory/.DS_Store delete mode 100644 Editor_Directory/index.html diff --git a/Data_Directory/Images_Directory/.DS_Store b/Data_Directory/Images_Directory/.DS_Store deleted file mode 100644 index 5008ddfcf53c02e82d7eee2e57c38e5672ef89f6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0 - - - - - Bone Information Viewer - - - - - -
              -

              Femur

              -

              - A description of bone, something like: The femur, or thighbone, is the longest bone in the human body. -

              -
              -

              Annotations

              -
                -
              • List of annotations to be displayed here
              • -
              -
              -
              -

              Image

              - Bone Image -
              -
              - - - From fd861510b15f3ba9731b729b4483499cba03c7a4 Mon Sep 17 00:00:00 2001 From: Burhan Khan Date: Mon, 21 Oct 2024 15:59:02 -0500 Subject: [PATCH 018/143] Addresses Issue 49. Changed the overall layout that was pulled from main and displayed the image from the data branch correctly. Also had to delete the directories we weren't using anymore. Made it more dynamic in order to have it be less hardcoded for the future. --- templates/bone.html | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/templates/bone.html b/templates/bone.html index ed1b1c1e..510a9cd2 100644 --- a/templates/bone.html +++ b/templates/bone.html @@ -59,19 +59,10 @@

              Bone Editor

              const description = document.createElement('p'); description.textContent = bone.description; - if (bone.name.toLowerCase() === 'femur') { - const image = document.createElement('img'); - image.src = GITHUB_RAW_URL + IMAGES_PATH + 'femur.jpg'; - image.alt = 'Image of the Femur'; - boneElement.appendChild(image); - } - - if (bone.name.toLowerCase() === 'skull') { - const image = document.createElement('img'); - image.src = GITHUB_RAW_URL + IMAGES_PATH + 'skull.jpg'; - image.alt = 'Image of the Skull'; - boneElement.appendChild(image); - } + const image = document.createElement('img'); + image.src = GITHUB_RAW_URL + IMAGES_PATH + bone.name.toLowerCase() + '.jpg'; + image.alt = 'Image of the ' + bone.name; + boneElement.appendChild(image); const annotationsList = document.createElement('ul'); bone.annotations.forEach(annotationId => { From 98ddf0f12cab722ca82dedb385596318ba6bd960 Mon Sep 17 00:00:00 2001 From: JoshBudzynski <157167337+JoshBudzynski@users.noreply.github.com> Date: Sun, 27 Oct 2024 22:58:56 -0500 Subject: [PATCH 019/143] Issue_55 --- templates/boneset.html | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/templates/boneset.html b/templates/boneset.html index f22bafcb..e6b28bcf 100644 --- a/templates/boneset.html +++ b/templates/boneset.html @@ -54,21 +54,18 @@

              Bonesets

              const response = await fetch(GITHUB_RAW_URL + path); return response.json(); } - - function displayBoneSet(index) { + function renderBoneSet(boneset) { const boneSetList = document.getElementById('bone-set-list'); boneSetList.innerHTML = ''; // Clear any previous data - const set = boneSets[index]; - // Create a list item for the boneset name const li = document.createElement('li'); - li.textContent = set.name; + li.textContent = boneset.name; boneSetList.appendChild(li); // Create a sublist for the bone IDs const subList = document.createElement('ul'); - set.bones.forEach(bone => { + boneset.bones.forEach(bone => { const subLi = document.createElement('li'); subLi.textContent = `ID: ${bone}`; subList.appendChild(subLi); @@ -76,6 +73,11 @@

              Bonesets

              // Append the sublist under the boneset name boneSetList.appendChild(subList); + } + + function displayBoneSet(index) { + const set = boneSets[index]; + renderBoneSet(set); // Call the reusable function // Update navigation button states document.getElementById('prev-button').disabled = (index === 0); @@ -86,16 +88,12 @@

              Bonesets

              function loadBoneSets() { fetchJSON(BONESET_JSON_PATH) .then(data => { - console.log(data); // Check the fetched data boneSets = data.boneset; currentSetIndex = 0; // Start at the first set displayBoneSet(currentSetIndex); // Display the first set document.getElementById('prev-button').disabled = false; document.getElementById('next-button').disabled = false; - - //the following lines displayed all the sets fetched, so I changed it to display one - // Iterate through each boneset and display the name and bone IDs }) .catch(error => console.error('Error loading bonesets:', error)); } From 4393cf3c0ef40e955cac9377ae05c608b9fb2441 Mon Sep 17 00:00:00 2001 From: tluke900 Date: Mon, 28 Oct 2024 14:38:13 -0500 Subject: [PATCH 020/143] Update boneset.html --- templates/boneset.html | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/templates/boneset.html b/templates/boneset.html index f22bafcb..64f5b1f1 100644 --- a/templates/boneset.html +++ b/templates/boneset.html @@ -23,12 +23,16 @@

              Bone Information Viewer

              + + + +
              - - +
              From 839424acb3b5e67a205036316aff2b4f6fe3c30d Mon Sep 17 00:00:00 2001 From: Burhan Khan Date: Mon, 28 Oct 2024 15:18:28 -0500 Subject: [PATCH 021/143] Added error message of image is not found in directory. --- templates/bone.html | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/templates/bone.html b/templates/bone.html index 510a9cd2..949146d7 100644 --- a/templates/bone.html +++ b/templates/bone.html @@ -62,6 +62,12 @@

              Bone Editor

              const image = document.createElement('img'); image.src = GITHUB_RAW_URL + IMAGES_PATH + bone.name.toLowerCase() + '.jpg'; image.alt = 'Image of the ' + bone.name; + image.onerror = () => { + image.style.display = 'none'; + const errorMessage = document.createElement('p'); + errorMessage.textContent = 'Image of ' + bone.name + ' not found.'; + boneElement.appendChild(errorMessage); + }; boneElement.appendChild(image); const annotationsList = document.createElement('ul'); From 029d7525efead0ee68d0f151d6c34dd0611ff602 Mon Sep 17 00:00:00 2001 From: tluke900 Date: Sun, 3 Nov 2024 13:15:27 -0600 Subject: [PATCH 022/143] Added Full search bar functionality --- templates/boneset.html | 195 +++++++++++++++-------------------------- 1 file changed, 73 insertions(+), 122 deletions(-) diff --git a/templates/boneset.html b/templates/boneset.html index 64f5b1f1..020c4e8a 100644 --- a/templates/boneset.html +++ b/templates/boneset.html @@ -17,6 +17,10 @@ ul { list-style-type: disc; } + #search-results li { + cursor: pointer; + margin-top: 5px; + } @@ -24,17 +28,16 @@

              Bone Information Viewer

              - + +
                -
                - - +
                + +
                - -
                @@ -46,151 +49,99 @@

                Bonesets

                - + - From 4dd935ac028f87f07e003f7e6a1f4e345db346e6 Mon Sep 17 00:00:00 2001 From: JoshBudzynski <157167337+JoshBudzynski@users.noreply.github.com> Date: Mon, 4 Nov 2024 08:51:17 -0600 Subject: [PATCH 023/143] comming updates --- templates/bone.html | 1 + templates/boneset.html | 25 ++----------------------- 2 files changed, 3 insertions(+), 23 deletions(-) diff --git a/templates/bone.html b/templates/bone.html index 510a9cd2..4c9f8050 100644 --- a/templates/bone.html +++ b/templates/bone.html @@ -62,6 +62,7 @@

                Bone Editor

                const image = document.createElement('img'); image.src = GITHUB_RAW_URL + IMAGES_PATH + bone.name.toLowerCase() + '.jpg'; image.alt = 'Image of the ' + bone.name; + boneElement.appendChild(image); const annotationsList = document.createElement('ul'); diff --git a/templates/boneset.html b/templates/boneset.html index e6b28bcf..2a98671c 100644 --- a/templates/boneset.html +++ b/templates/boneset.html @@ -126,35 +126,14 @@

                Bonesets

                const dropdown = document.getElementById('boneset-select'); const selectedOption = dropdown.options[dropdown.selectedIndex]; const selectedBoneName = selectedOption.textContent; - try { const bonesets = await fetchBonesetData(); - console.log(bonesets); // Check the fetched data - - const boneSetList = document.getElementById('bone-set-list'); - boneSetList.innerHTML = ''; // Clear any previous data - // Find the selected boneset based on the dropdown selection const selectedSet = bonesets.find(set => set.name === selectedBoneName); - if (selectedSet) { - // Create a list item for the selected boneset name - const li = document.createElement('li'); - li.textContent = selectedSet.name; - boneSetList.appendChild(li); - - // Create a sublist for the bone IDs - const subList = document.createElement('ul'); - selectedSet.bones.forEach(bone => { - const subLi = document.createElement('li'); - subLi.textContent = `ID: ${bone}`; - subList.appendChild(subLi); - }); - - // Append the sublist under the boneset name - boneSetList.appendChild(subList); + renderBoneSet(selectedSet); // Use the reusable function here } else { - boneSetList.textContent = 'No bone set found for the selected option.'; + document.getElementById('bone-set-list').textContent = 'No bone set found for the selected option.'; } } catch (error) { console.error('Error loading bonesets:', error); From e8d3f56527e4812fdcb29f95a0a0b72746fa5238 Mon Sep 17 00:00:00 2001 From: jacksayshi Date: Mon, 4 Nov 2024 22:33:18 -0600 Subject: [PATCH 024/143] Removed load bones button. They will load on page loading --- templates/boneset.html | 51 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 42 insertions(+), 9 deletions(-) diff --git a/templates/boneset.html b/templates/boneset.html index 020c4e8a..16917994 100644 --- a/templates/boneset.html +++ b/templates/boneset.html @@ -62,12 +62,8 @@

                Bonesets

                return response.json(); } - // Populate dropdown menu + // Populate dropdown menu with boneset names async function populateDropdown() { - if (boneSets.length === 0) { - const data = await fetchJSON(BONESET_JSON_PATH); - boneSets = data.boneset; // Store the data globally - } const dropdown = document.getElementById('boneset-select'); dropdown.innerHTML = ''; @@ -79,7 +75,7 @@

                Bonesets

                }); } - // Display boneset details in main content area + // Display boneset details in the main content area function displayBoneSet(boneSet) { const boneSetList = document.getElementById('bone-set-list'); boneSetList.innerHTML = ''; @@ -107,7 +103,7 @@

                Bonesets

                }, 300); } - // Display search results based on search term + // Display search results based on the search term async function displaySearchResults(searchTerm) { if (boneSets.length === 0) { const data = await fetchJSON(BONESET_JSON_PATH); @@ -140,8 +136,45 @@

                Bonesets

                if (selectedSet) displayBoneSet(selectedSet); }); - // Populate dropdown on page load - document.addEventListener('DOMContentLoaded', populateDropdown); + // Load the boneset data as soon as the page loads + document.addEventListener('DOMContentLoaded', async () => { + const data = await fetchJSON(BONESET_JSON_PATH); + boneSets = data.boneset; + currentSetIndex = 0; // Reset index to 0 on load + + // Populate the dropdown menu with bonesets + populateDropdown(); + + // Display the first boneset in the list + displayBoneSet(boneSets[currentSetIndex]); + + // Enable the navigation buttons + document.getElementById('prev-button').disabled = false; + document.getElementById('next-button').disabled = boneSets.length <= 1; + }); + + // Event listeners for "Previous" and "Next" buttons + document.getElementById('prev-button').addEventListener('click', () => { + if (currentSetIndex > 0) { + currentSetIndex--; + displayBoneSet(boneSets[currentSetIndex]); + document.getElementById('next-button').disabled = false; + if (currentSetIndex === 0) { + document.getElementById('prev-button').disabled = true; + } + } + }); + + document.getElementById('next-button').addEventListener('click', () => { + if (currentSetIndex < boneSets.length - 1) { + currentSetIndex++; + displayBoneSet(boneSets[currentSetIndex]); + document.getElementById('prev-button').disabled = false; + if (currentSetIndex === boneSets.length - 1) { + document.getElementById('next-button').disabled = true; + } + } + }); From 99538103cdbd5078806b7030c3e3036cb702bf7d Mon Sep 17 00:00:00 2001 From: Burhan Khan Date: Wed, 6 Nov 2024 16:37:11 -0600 Subject: [PATCH 025/143] Created a simple python file in templates directory. Branched off main for new issue. Gave a basic foundation for working with XML files. --- templates/script.py | 51 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 templates/script.py diff --git a/templates/script.py b/templates/script.py new file mode 100644 index 00000000..f3232cd0 --- /dev/null +++ b/templates/script.py @@ -0,0 +1,51 @@ +import xml.etree.ElementTree as ET + +# Load and parse the XML file +def load_xml(file_path): + try: + tree = ET.parse(file_path) + root = tree.getroot() + print(f"XML loaded successfully from {file_path}") + return tree, root + except ET.ParseError as e: + print(f"Failed to parse XML: {e}") + return None, None + +# Access elements in the XML +def get_element_data(root, element_tag): + elements = root.findall(element_tag) + for elem in elements: + print(ET.tostring(elem, encoding="utf-8").decode("utf-8")) + +# Modify elements in the XML +def modify_element(root, element_tag, new_text): + elements = root.findall(element_tag) + for elem in elements: + elem.text = new_text + print(f"Updated text of '{element_tag}' to '{new_text}'") + +# Save the modified XML to a new file +def save_xml(tree, output_path): + try: + tree.write(output_path, encoding="utf-8", xml_declaration=True) + print(f"XML saved successfully to {output_path}") + except Exception as e: + print(f"Failed to save XML: {e}") + +# Example usage +if __name__ == "__main__": + file_path = "input.xml" + output_path = "output.xml" + + # Load XML + tree, root = load_xml(file_path) + + if root is not None: + # Access specific elements + get_element_data(root, "element_tag") # Replace 'element_tag' with actual XML tag + + # Modify elements + modify_element(root, "element_tag", "New Text") # Replace 'element_tag' with actual XML tag + + # Save modified XML + save_xml(tree, output_path) From efc0b96a11c456780e6d639fa9273335a2c06e0b Mon Sep 17 00:00:00 2001 From: JoshBudzynski <157167337+JoshBudzynski@users.noreply.github.com> Date: Mon, 11 Nov 2024 14:42:42 -0600 Subject: [PATCH 026/143] Issue_61 --- templates/boneset.html | 127 ++++++++++++++++++++++++++++++++--------- 1 file changed, 100 insertions(+), 27 deletions(-) diff --git a/templates/boneset.html b/templates/boneset.html index f22bafcb..1a8da77e 100644 --- a/templates/boneset.html +++ b/templates/boneset.html @@ -5,43 +5,117 @@ Bone Information Viewer - - + +
                +

                Bone Information Viewer

                -

                Bone Information Viewer

                + + - - - + + - - +
                +

                this coudl provide the description of the bone. Or insructions to use

                + +
                - -
                -

                Bonesets

                -
                  -
                  - - + Bone Image + + +
                  + + + + + + +
                  +

                  Bonesets

                  +
                    +
                    + +
                    -

                    Bone Editor

                    +

                    Bones Viewer

                    diff --git a/templates/boneset.html b/templates/boneset.html index 3452e9c0..ba0cbafe 100644 --- a/templates/boneset.html +++ b/templates/boneset.html @@ -3,7 +3,7 @@ - Bone Information Viewer + Bone Set Viewer diff --git a/templates/sidebar.js b/templates/sidebar.js new file mode 100644 index 00000000..cca3f315 --- /dev/null +++ b/templates/sidebar.js @@ -0,0 +1,42 @@ +async function loadSidebar() { + try { + const response = await fetch('sidebar.html'); + if (!response.ok) { + throw new Error('Failed to load sidebar'); + } + + const sidebarHTML = await response.text(); + document.getElementById('sidebar-container').innerHTML = sidebarHTML; + + const sidebar = document.getElementById('sidebar'); + const closeButton = document.getElementById('close-sidebar'); + + window.openSidebar = function() { + sidebar.style.display = 'block'; + sidebar.style.left = '0'; + }; + + window.closeSidebar = function() { + sidebar.style.left = '-250px'; + setTimeout(() => { + sidebar.style.display = 'none'; + }, 300); + }; + + + if (closeButton) { + closeButton.addEventListener('click', closeSidebar); + } + } catch (error) { + console.error('Error loading sidebar:', error); + } +} + +document.addEventListener('DOMContentLoaded', () => { + const toggleButton = document.getElementById('toggle-sidebar'); + if (toggleButton) { + toggleButton.addEventListener('click', () => { + loadSidebar().then(() => openSidebar()); + }); + } +}); From 885524a6e47a0c127e67178b81dca6437804db19 Mon Sep 17 00:00:00 2001 From: tluke900 Date: Mon, 25 Nov 2024 14:25:54 -0600 Subject: [PATCH 034/143] Basic Framework set up --- templates/XML boneset Reader.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 templates/XML boneset Reader.py diff --git a/templates/XML boneset Reader.py b/templates/XML boneset Reader.py new file mode 100644 index 00000000..75b238ef --- /dev/null +++ b/templates/XML boneset Reader.py @@ -0,0 +1,33 @@ +import xml.etree.ElementTree as ET +import os + +def process_slide_without_images(slide_xml_path, rels_file_path, output_dir): + + try: + slide_tree = ET.parse(slide_xml_path) + slide_root = slide_tree.getroot() + print("Slide XML parsed successfully.") + + except Exception as e: + print (f"Error parsing slide XML: {e}") + + rels_ns = {'rel': 'http://schemas.openxmlformats.org/package/2006/relationships'} + try: + rels_tree = ET.parse(rels_file_path) + rels_root = rels_tree.getroot() + + # Extract relationships + rels_map = { + rel.attrib["Id"]: rel.attrib["Target"] + for rel in rels_root.findall("rel:Relationship", rels_ns) + } + print(f"Relationships mapped: {rels_map}") + except Exception as e: + print(f"Error parsing .rels file: {e}") + return + + # Ensure the output directory exists + os.makedirs(output_dir, exist_ok=True) + print(f"Output directory checked/created: {output_dir}") + + From 2ca606eba35fa06695fdeba045ddd12fc2c1c65f Mon Sep 17 00:00:00 2001 From: Burhan Khan Date: Mon, 25 Nov 2024 16:00:36 -0600 Subject: [PATCH 035/143] Addresses Issue #62. Worked on making the script more flexible and less hard coded to my machine. Command line arguments are now used. Still working on correctly parsing and extracting the correct information from the tags in the XML file. --- templates/script.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/templates/script.py b/templates/script.py index e57492e4..06cfe1a2 100644 --- a/templates/script.py +++ b/templates/script.py @@ -1,6 +1,7 @@ import xml.etree.ElementTree as ET import os import shutil +import argparse def extract_images_from_slide(slide_xml_path, rels_file_path, media_folder, output_dir): print(f"Starting extraction for slide: {slide_xml_path}") @@ -35,7 +36,7 @@ def extract_images_from_slide(slide_xml_path, rels_file_path, media_folder, outp # Ensure the output directory exists os.makedirs(output_dir, exist_ok=True) - print(f"Output directory checked/created: {output_dir}") + print(f"Output directory created: {output_dir}") # Find all picture elements and extract image references image_found = False @@ -67,10 +68,15 @@ def extract_images_from_slide(slide_xml_path, rels_file_path, media_folder, outp if not image_found: print("No images were extracted. Check your slide XML, .rels file, and media folder paths.") -# Example usage -slide_xml_path = '/Users/burhankhan/Desktop/CSCI-4961-01/ppt/slides/slide2.xml' -rels_file_path = "/Users/burhankhan/Desktop/CSCI-4961-01/ppt/slides/_rels/slide2.xml.rels" -media_folder = '/Users/burhankhan/Desktop/CSCI-4961-01/ppt/media' -output_dir = '/Users/burhankhan/Desktop/images' +if __name__ == "__main__": + # Set up argument parsing + parser = argparse.ArgumentParser(description="Extract images from a PowerPoint slide XML and relationships files.") + parser.add_argument("--slide", type=str, help="Path to the slide XML file.", required=True) + parser.add_argument("--rels", type=str, help="Path to the relationships (.rels) file.", required=True) + parser.add_argument("--media", type=str, help="Path to the media folder.", required=True) + parser.add_argument("--output", type=str, help="Path to the output directory to save images.", required=True) -extract_images_from_slide(slide_xml_path, rels_file_path, media_folder, output_dir) + args = parser.parse_args() + + # Extract images based on provided arguments + extract_images_from_slide(args.slide, args.rels, args.media, args.output) From 5025897ff2a2934b544676a20913de2f41a1b0ca Mon Sep 17 00:00:00 2001 From: Burhan Khan Date: Tue, 26 Nov 2024 07:38:25 -0600 Subject: [PATCH 036/143] Addresses Issue #62. Worked on making the script more flexible and less hard coded to my machine. Command line arguments are now used. Still working on correctly parsing and extracting the correct information from the tags in the XML file. --- templates/Hardcoded.py | 83 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 templates/Hardcoded.py diff --git a/templates/Hardcoded.py b/templates/Hardcoded.py new file mode 100644 index 00000000..eaa02835 --- /dev/null +++ b/templates/Hardcoded.py @@ -0,0 +1,83 @@ +import xml.etree.ElementTree as ET +import os +import shutil +import argparse + +def extract_images_from_slide(slide_xml_path, rels_file_path, media_folder, output_dir): + print(f"Starting extraction for slide: {slide_xml_path}") + print(f"Using relationships file: {rels_file_path}") + print(f"Media folder: {media_folder}") + print(f"Output directory: {output_dir}") + + # Parse the slide XML file + try: + slide_tree = ET.parse(slide_xml_path) + slide_root = slide_tree.getroot() + print("Slide XML parsed successfully.") + except Exception as e: + print(f"Error parsing slide XML: {e}") + return + + # Parse the .rels file + rels_ns = {'rel': 'http://schemas.openxmlformats.org/package/2006/relationships'} + try: + rels_tree = ET.parse(rels_file_path) + rels_root = rels_tree.getroot() + + # Extract relationships + rels_map = { + rel.attrib["Id"]: rel.attrib["Target"] + for rel in rels_root.findall("rel:Relationship", rels_ns) + } + print(f"Relationships mapped: {rels_map}") + except Exception as e: + print(f"Error parsing .rels file: {e}") + return + + # Ensure the output directory exists + os.makedirs(output_dir, exist_ok=True) + print(f"Output directory checked/created: {output_dir}") + + # Debugging the structure of elements + pic_namespace = {'p': 'http://schemas.openxmlformats.org/presentationml/2006/main', + 'a': 'http://schemas.openxmlformats.org/drawingml/2006/main', + 'r': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'} + + # Find all picture elements + image_found = False + for pic in slide_root.findall(".//p:pic", pic_namespace): + try: + print(f"Processing : {ET.tostring(pic, encoding='unicode')}") + blip = pic.find(".//a:blip", pic_namespace) + if blip is not None and "embed" in blip.attrib: + r_id = blip.attrib["{http://schemas.openxmlformats.org/officeDocument/2006/relationships}embed"] + print(f"Found r:embed ID: {r_id}") + + if r_id in rels_map: + # Locate the image file + image_path = os.path.join(media_folder, os.path.basename(rels_map[r_id])) + if os.path.exists(image_path): + # Save the image to the output directory + output_path = os.path.join(output_dir, os.path.basename(image_path)) + shutil.copy(image_path, output_path) + print(f"Image saved: {output_path}") + image_found = True + else: + print(f"Image file not found in media folder: {image_path}") + else: + print(f"r:embed ID {r_id} not found in .rels map.") + else: + print(f"No or missing 'embed' attribute in : {ET.tostring(pic, encoding='unicode')}") + except Exception as e: + print(f"Error processing element: {e}") + + if not image_found: + print("No images were extracted. Check your slide XML, .rels file, and media folder paths.") + +# Example usage +slide_xml_path = '/Users/burhankhan/Desktop/CSCI-4961-01/ppt/slides/slide2.xml' +rels_file_path = "/Users/burhankhan/Desktop/CSCI-4961-01/ppt/slides/_rels/slide2.xml.rels" +media_folder = '/Users/burhankhan/Desktop/CSCI-4961-01/ppt/media' +output_dir = '/Users/burhankhan/Desktop/images' + +extract_images_from_slide(slide_xml_path, rels_file_path, media_folder, output_dir) From ecd6c6d77f99a9c4e2e7fcbda75b106483149420 Mon Sep 17 00:00:00 2001 From: Vamshi nandala <146904018+vnandala@users.noreply.github.com> Date: Thu, 28 Nov 2024 01:48:45 +0000 Subject: [PATCH 037/143] added unit testing for the expected functionality of toggle bar --- package-lock.json | 4944 ++++++++++++++++++++++++++++++++++++++++++--- package.json | 9 +- templates/test.js | 107 + 3 files changed, 4759 insertions(+), 301 deletions(-) create mode 100644 templates/test.js diff --git a/package-lock.json b/package-lock.json index 15859049..fe19ddac 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,20 +12,88 @@ "body-parser": "^1.20.3", "express": "^4.21.1", "simple-git": "^3.27.0" + }, + "devDependencies": { + "jest": "^29.7.0", + "jest-environment-jsdom": "^29.7.0" } }, - "node_modules/@kwsites/file-exists": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@kwsites/file-exists/-/file-exists-1.1.1.tgz", - "integrity": "sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw==", + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "debug": "^4.1.1" + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" } }, - "node_modules/@kwsites/file-exists/node_modules/debug": { + "node_modules/@babel/code-frame": { + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", + "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.25.9", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.2.tgz", + "integrity": "sha512-Z0WgzSEa+aUcdiJuCIqgujCshpMWgUpgOxXotrYPSA53hA3qopNaqcJpyr0hVb1FeWdnqFA35/fUtXgBK8srQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.0.tgz", + "integrity": "sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.26.0", + "@babel/generator": "^7.26.0", + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helpers": "^7.26.0", + "@babel/parser": "^7.26.0", + "@babel/template": "^7.25.9", + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.26.0", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/debug": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dev": true, + "license": "MIT", "dependencies": { "ms": "^2.1.3" }, @@ -38,280 +106,2266 @@ } } }, - "node_modules/@kwsites/file-exists/node_modules/ms": { + "node_modules/@babel/core/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, - "node_modules/@kwsites/promise-deferred": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@kwsites/promise-deferred/-/promise-deferred-1.1.1.tgz", - "integrity": "sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==" + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" }, - "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "node_modules/@babel/generator": { + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.2.tgz", + "integrity": "sha512-zevQbhbau95nkoxSq3f/DC/SC+EEOUZd3DYqfSkMhY2/wfSeaHV1Ew4vk8e+x8lja31IbyuUa2uQ3JONqKbysw==", + "dev": true, + "license": "MIT", "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" + "@babel/parser": "^7.26.2", + "@babel/types": "^7.26.0", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" }, "engines": { - "node": ">= 0.6" + "node": ">=6.9.0" } }, - "node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" - }, - "node_modules/body-parser": { - "version": "1.20.3", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", - "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "node_modules/@babel/helper-compilation-targets": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.9.tgz", + "integrity": "sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ==", + "dev": true, + "license": "MIT", "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.5", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.13.0", - "raw-body": "2.5.2", - "type-is": "~1.6.18", - "unpipe": "1.0.0" + "@babel/compat-data": "^7.25.9", + "@babel/helper-validator-option": "^7.25.9", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" }, "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" + "node": ">=6.9.0" } }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "node_modules/@babel/helper-module-imports": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", + "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" + }, "engines": { - "node": ">= 0.8" + "node": ">=6.9.0" } }, - "node_modules/call-bind": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", - "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "node_modules/@babel/helper-module-transforms": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz", + "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==", + "dev": true, + "license": "MIT", "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.1" + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9", + "@babel/traverse": "^7.25.9" }, "engines": { - "node": ">= 0.4" + "node": ">=6.9.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "dependencies": { - "safe-buffer": "5.2.1" - }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.9.tgz", + "integrity": "sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw==", + "dev": true, + "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">=6.9.0" } }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "node_modules/@babel/helper-string-parser": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "dev": true, + "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">=6.9.0" } }, - "node_modules/cookie": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", - "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "node_modules/@babel/helper-validator-identifier": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "dev": true, + "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">=6.9.0" } }, - "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" - }, - "node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" + "node_modules/@babel/helper-validator-option": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz", + "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" } }, - "node_modules/define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "node_modules/@babel/helpers": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.0.tgz", + "integrity": "sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==", + "dev": true, + "license": "MIT", "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" + "@babel/template": "^7.25.9", + "@babel/types": "^7.26.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=6.9.0" } }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "node_modules/@babel/parser": { + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.2.tgz", + "integrity": "sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.26.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, "engines": { - "node": ">= 0.8" + "node": ">=6.0.0" } }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } }, - "node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "engines": { - "node": ">= 0.8" + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/es-define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", - "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "license": "MIT", "dependencies": { - "get-intrinsic": "^1.2.4" + "@babel/helper-plugin-utils": "^7.14.5" }, "engines": { - "node": ">= 0.4" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.26.0.tgz", + "integrity": "sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, "engines": { - "node": ">= 0.4" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "engines": { - "node": ">= 0.6" + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/express": { - "version": "4.21.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", - "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "license": "MIT", "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.3", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.7.1", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.3.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.3", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.10", - "proxy-addr": "~2.0.7", - "qs": "6.13.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.19.0", - "serve-static": "1.16.2", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" + "@babel/helper-plugin-utils": "^7.8.0" }, - "engines": { - "node": ">= 0.10.0" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/finalhandler": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", - "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.25.9.tgz", + "integrity": "sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==", + "dev": true, + "license": "MIT", "dependencies": { - "debug": "2.6.9", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { - "node": ">= 0.8" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "engines": { - "node": ">= 0.6" + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "engines": { - "node": ">= 0.6" + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.9.tgz", + "integrity": "sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz", + "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.25.9", + "@babel/parser": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.9.tgz", + "integrity": "sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.25.9", + "@babel/generator": "^7.25.9", + "@babel/parser": "^7.25.9", + "@babel/template": "^7.25.9", + "@babel/types": "^7.25.9", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@babel/traverse/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@babel/types": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.0.tgz", + "integrity": "sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/core": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", + "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/reporters": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.7.0", + "jest-config": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-resolve-dependencies": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "jest-watcher": "^29.7.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/environment": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-get-type": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", + "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/types": "^29.6.3", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", + "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", + "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.18", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", + "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", + "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@kwsites/file-exists": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@kwsites/file-exists/-/file-exists-1.1.1.tgz", + "integrity": "sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw==", + "dependencies": { + "debug": "^4.1.1" + } + }, + "node_modules/@kwsites/file-exists/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@kwsites/file-exists/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/@kwsites/promise-deferred": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@kwsites/promise-deferred/-/promise-deferred-1.1.1.tgz", + "integrity": "sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==" + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.8", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", + "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.6", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.6.tgz", + "integrity": "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jsdom": { + "version": "20.0.1", + "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-20.0.1.tgz", + "integrity": "sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/tough-cookie": "*", + "parse5": "^7.0.0" + } + }, + "node_modules/@types/node": { + "version": "22.10.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.0.tgz", + "integrity": "sha512-XC70cRZVElFHfIUB40FgZOBbgJYFKKMa5nb9lxcwYstFG/Mi+/Y0bGS+rs6Dmhmkpq4pnNiLiuZAbc02YCOnmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.20.0" + } + }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/tough-cookie": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", + "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/yargs": { + "version": "17.0.33", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", + "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/abab": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", + "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", + "deprecated": "Use your platform's native atob() and btoa() methods instead", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-globals": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-7.0.1.tgz", + "integrity": "sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.1.0", + "acorn-walk": "^8.0.2" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/agent-base/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/agent-base/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/babel-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/transform": "^29.7.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.6.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", + "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz", + "integrity": "sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/babel-preset-jest": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", + "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "dev": true, + "license": "MIT", + "dependencies": { + "babel-plugin-jest-hoist": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.24.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz", + "integrity": "sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001669", + "electron-to-chromium": "^1.5.41", + "node-releases": "^2.0.18", + "update-browserslist-db": "^1.1.1" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001684", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001684.tgz", + "integrity": "sha512-G1LRwLIQjBQoyq0ZJGqGIJUXzJ8irpbjHLpVRXDvBEScFJ9b17sgK6vlx0GAJFE21okD7zXl08rRRUfq6HdoEQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.1.tgz", + "integrity": "sha512-cuSVIHi9/9E/+821Qjdvngor+xpnlwnuwIyZOaLmHBVdXL+gP+I6QQB9VkO7RI77YIcTV+S1W9AreJ5eN63JBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", + "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + }, + "node_modules/create-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", + "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "prompts": "^2.0.1" + }, + "bin": { + "create-jest": "bin/create-jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cssom": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", + "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==", + "dev": true, + "license": "MIT" + }, + "node_modules/cssstyle": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", + "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssom": "~0.3.6" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cssstyle/node_modules/cssom": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", + "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", + "dev": true, + "license": "MIT" + }, + "node_modules/data-urls": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz", + "integrity": "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "abab": "^2.0.6", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^11.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/decimal.js": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", + "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==", + "dev": true, + "license": "MIT" + }, + "node_modules/dedent": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz", + "integrity": "sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/domexception": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", + "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==", + "deprecated": "Use your platform's native DOMException instead", + "dev": true, + "license": "MIT", + "dependencies": { + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.66", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.66.tgz", + "integrity": "sha512-pI2QF6+i+zjPbqRzJwkMvtvkdI7MjVbSh2g8dlMguDJIXEPw+kwasS1Jl+YGPEBfGVxsVgGUratAKymPdPo2vQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/express": { + "version": "4.21.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", + "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.10", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/form-data": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", + "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" } }, "node_modules/get-intrinsic": { @@ -319,111 +2373,1336 @@ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", + "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-encoding": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/http-proxy-agent/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/http-proxy-agent/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/https-proxy-agent/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/https-proxy-agent/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-core-module": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", + "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" }, "engines": { - "node": ">= 0.4" + "node": ">=10" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/istanbul-lib-source-maps/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/istanbul-reports": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", + "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/types": "^29.6.3", + "import-local": "^3.0.2", + "jest-cli": "^29.7.0" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", + "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "execa": "^5.0.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", + "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^1.0.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.7.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-cli": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", + "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "create-jest": "^29.7.0", + "exit": "^0.1.2", + "import-local": "^3.0.2", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "yargs": "^17.3.1" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-config": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", + "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-jest": "^29.7.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-docblock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", + "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", + "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "jest-util": "^29.7.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-environment-jsdom": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-29.7.0.tgz", + "integrity": "sha512-k9iQbsf9OyOfdzWH8HDmrRT0gSIcX+FLNW7IQq94tFX0gynPwqDTW0Ho6iMVNjGz/nb+l/vW3dWM2bbLLpkbXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/jsdom": "^20.0.0", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0", + "jsdom": "^20.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "canvas": "^2.5.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jest-environment-node": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/jest-leak-detector": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", + "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-mock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", + "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", + "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-regex-util": "^29.6.3", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", + "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/environment": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-leak-detector": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-resolve": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-util": "^29.7.0", + "jest-watcher": "^29.7.0", + "jest-worker": "^29.7.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runtime": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", + "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/globals": "^29.7.0", + "@jest/source-map": "^29.6.3", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", + "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.7.0", + "semver": "^7.5.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "leven": "^3.1.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watcher": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", + "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.7.0", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsdom": { + "version": "20.0.3", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.3.tgz", + "integrity": "sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "abab": "^2.0.6", + "acorn": "^8.8.1", + "acorn-globals": "^7.0.0", + "cssom": "^0.5.0", + "cssstyle": "^2.3.0", + "data-urls": "^3.0.2", + "decimal.js": "^10.4.2", + "domexception": "^4.0.0", + "escodegen": "^2.0.0", + "form-data": "^4.0.0", + "html-encoding-sniffer": "^3.0.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.1", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.2", + "parse5": "^7.1.1", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^4.1.2", + "w3c-xmlserializer": "^4.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^2.0.0", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^11.0.0", + "ws": "^8.11.0", + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "canvas": "^2.5.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } } }, - "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dependencies": { - "get-intrinsic": "^1.1.3" + "node_modules/jsesc": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", + "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">=6" } }, - "node_modules/has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "dependencies": { - "es-define-property": "^1.0.0" + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">=6" } }, - "node_modules/has-proto": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", - "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "license": "MIT", "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=6" } }, - "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "license": "MIT", "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=6" } }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", "dependencies": { - "function-bind": "^1.1.2" + "p-locate": "^4.1.0" }, "engines": { - "node": ">= 0.4" + "node": ">=8" } }, - "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" + "yallist": "^3.0.2" } }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" + "semver": "^7.5.3" }, "engines": { - "node": ">=0.10.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "node_modules/make-dir/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, "engines": { - "node": ">= 0.10" + "node": ">=10" + } + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tmpl": "1.0.5" } }, "node_modules/media-typer": { @@ -442,6 +3721,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, "node_modules/methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", @@ -450,6 +3736,20 @@ "node": ">= 0.6" } }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, "node_modules/mime": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", @@ -480,11 +3780,41 @@ "node": ">= 0.6" } }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, "node_modules/negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -493,6 +3823,50 @@ "node": ">= 0.6" } }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", + "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nwsapi": { + "version": "2.2.13", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.13.tgz", + "integrity": "sha512-cTGB9ptp9dY9A5VbMSe7fQBcl/tt22Vcqdq8+eN93rblOuE0aCFu4aZ2vMwct/2t+lFnosm8RkQW1I0Omb1UtQ==", + "dev": true, + "license": "MIT" + }, "node_modules/object-inspect": { "version": "1.13.2", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", @@ -515,6 +3889,119 @@ "node": ">= 0.8" } }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-locate/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse5": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz", + "integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^4.5.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -523,11 +4010,133 @@ "node": ">= 0.8" } }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, "node_modules/path-to-regexp": { "version": "0.1.10", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==" }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pirates": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -540,6 +4149,43 @@ "node": ">= 0.10" } }, + "node_modules/psl": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.13.0.tgz", + "integrity": "sha512-BFwmFXiJoFqlUpZ5Qssolv15DMyc84gTBds1BjsV1BfXEo1UyyD7GsmN67n7J77uRhoSNW1AXtXKPLcBFQn9Aw==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/pure-rand": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" + }, "node_modules/qs": { "version": "6.13.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", @@ -554,26 +4200,108 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-from": "^5.0.0" + }, "engines": { - "node": ">= 0.6" + "node": ">=8" } }, - "node_modules/raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", "engines": { - "node": ">= 0.8" + "node": ">=8" + } + }, + "node_modules/resolve.exports": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", + "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" } }, "node_modules/safe-buffer": { @@ -600,6 +4328,29 @@ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/send": { "version": "0.19.0", "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", @@ -671,6 +4422,29 @@ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/side-channel": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", @@ -688,6 +4462,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, "node_modules/simple-git": { "version": "3.27.0", "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-3.27.0.tgz", @@ -723,6 +4504,64 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -731,6 +4570,149 @@ "node": ">= 0.8" } }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true, + "license": "MIT" + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -739,6 +4721,58 @@ "node": ">=0.6" } }, + "node_modules/tough-cookie": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", + "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tr46": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", + "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -751,6 +4785,23 @@ "node": ">= 0.6" } }, + "node_modules/undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "dev": true, + "license": "MIT" + }, + "node_modules/universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -759,6 +4810,48 @@ "node": ">= 0.8" } }, + "node_modules/update-browserslist-db": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", + "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, "node_modules/utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", @@ -767,6 +4860,21 @@ "node": ">= 0.4.0" } }, + "node_modules/v8-to-istanbul": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -774,6 +4882,242 @@ "engines": { "node": ">= 0.8" } + }, + "node_modules/w3c-xmlserializer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz", + "integrity": "sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-encoding": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", + "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-encoding/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/whatwg-mimetype": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", + "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-url": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", + "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml-name-validator": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", + "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true, + "license": "MIT" + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } } } } diff --git a/package.json b/package.json index cfc5dc45..a6b1e4b6 100644 --- a/package.json +++ b/package.json @@ -4,9 +4,12 @@ "description": "This project is a Web App project aims to convert an existing PowerPoint-based educational tool into an interactive, mobile-friendly web application.", "main": "server.js", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1", + "test": "jest", "start": "node server.js" }, + "jest": { + "testEnvironment": "jsdom" + }, "keywords": [], "author": "", "license": "ISC", @@ -14,5 +17,9 @@ "body-parser": "^1.20.3", "express": "^4.21.1", "simple-git": "^3.27.0" + }, + "devDependencies": { + "jest": "^29.7.0", + "jest-environment-jsdom": "^29.7.0" } } diff --git a/templates/test.js b/templates/test.js new file mode 100644 index 00000000..33810e9f --- /dev/null +++ b/templates/test.js @@ -0,0 +1,107 @@ +const html = ` +
                    + +
                    + + +`; +document.body.innerHTML = html; + +require('./sidebar.js'); // Import the JavaScript code + +beforeEach(() => { + global.fetch = jest.fn(() => + Promise.resolve({ + text: () => + Promise.resolve(` + + `), + }) + ); + + const sidebarContainer = document.getElementById('sidebar-container'); + sidebarContainer.innerHTML = ''; // Reset sidebar container + + const sidebarElement = document.getElementById('sidebar'); + if (sidebarElement) { + sidebarElement.style.left = '-250px'; // Explicitly set the initial state + } +}); + +beforeAll(() => { + document.dispatchEvent(new Event('DOMContentLoaded')); // Simulate DOMContentLoaded +}); + +afterEach(() => { + // Reset sidebar state after each test + const sidebarElement = document.getElementById('sidebar'); + if (sidebarElement) { + sidebarElement.style.left = '-250px'; // Ensure sidebar is closed + } + jest.restoreAllMocks(); // Clean up mocks +}); + +describe('Sidebar Toggle Functionality', () => { + test('loads sidebar content when the button is clicked', async () => { + const toggleButton = document.getElementById('toggle-sidebar'); + toggleButton.click(); + await new Promise(process.nextTick); + + const sidebarElement = document.getElementById('sidebar'); + expect(sidebarElement).toBeTruthy(); + expect(sidebarElement.innerHTML).toContain('Sidebar Content'); + }); + + test('toggles sidebar open and closed', async () => { + const toggleButton = document.getElementById('toggle-sidebar'); + const sidebarElement = document.getElementById('sidebar'); + + // Ensure the sidebar starts hidden + let computedStyle = window.getComputedStyle(sidebarElement); + expect(computedStyle.left).toBe('-250px'); // Sidebar is initially hidden + + // Simulate opening the sidebar + toggleButton.click(); + await new Promise((resolve) => setTimeout(resolve, 50)); // Wait for event propagation + computedStyle = window.getComputedStyle(sidebarElement); + expect(computedStyle.left).toBe('0px'); // Sidebar is open + + // Simulate closing the sidebar + toggleButton.click(); + await new Promise((resolve) => setTimeout(resolve, 50)); // Wait for event propagation + computedStyle = window.getComputedStyle(sidebarElement); + expect(computedStyle.left).toBe('-250px'); // Sidebar is closed + }); +}); + +describe('Badge Button', () => { + test('badge button is present and clickable', () => { + const badgeButton = document.getElementById('toggle-sidebar'); + expect(badgeButton).toBeTruthy(); + expect(badgeButton.tagName).toBe('BUTTON'); + }); + + test('badge button has correct initial text', () => { + const badgeButton = document.getElementById('toggle-sidebar'); + expect(badgeButton.textContent).toBe('☰'); + }); +}); + +describe('Sidebar Styling', () => { + test('sidebar starts hidden by default', () => { + const sidebar = document.getElementById('sidebar'); + expect(sidebar).toBeTruthy(); + + const computedStyle = window.getComputedStyle(sidebar); + expect(computedStyle.left).toBe('-250px'); // Sidebar is hidden initially + }); + + test('sidebar transitions smoothly', () => { + const sidebar = document.getElementById('sidebar'); + expect(sidebar.style.transition).toContain('left 0.3s ease'); + }); +}); From eb7aa4ce21017ab3c8eab0530b378472d3f2608d Mon Sep 17 00:00:00 2001 From: Vamshi nandala <146904018+vnandala@users.noreply.github.com> Date: Thu, 28 Nov 2024 02:33:14 +0000 Subject: [PATCH 038/143] Updated README with instructions for the toggle sidebar feature --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index e1cd0475..d942fcad 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,12 @@ CSS: For styling and layout of the web pages to ensure a clean and responsive de Interactive and accessible web application for students to study human anatomy. Quick revision tool with annotated images of bones. Designed for use on iPads, laptops, or other devices commonly used by students. +- **Toggle Sidebar**: + - A collapsible sidebar that enhances navigation within the app. + - Includes **placeholder options** such as **Search**, **Contact**, **Recent**, and **Help**, which are non-functional and reserved for future implementation. + - Users can open and close the sidebar using the ☰ button on the top-left corner of the page. + + ## Setup Clone the repository: From f154362ac55764a9fb530d5fd8a7ee54ed6e3a96 Mon Sep 17 00:00:00 2001 From: Burhan Khan Date: Thu, 28 Nov 2024 23:05:52 -0600 Subject: [PATCH 039/143] Addresses Issue #62. Extracted images 1-4 and put them in a separate directory. Extracted images from slide 2 using the XML file and the correlating relationship file. --- templates/Hardcoded.py | 142 ++++++++++++++++++++++------------------- templates/script.py | 33 +++++----- 2 files changed, 92 insertions(+), 83 deletions(-) diff --git a/templates/Hardcoded.py b/templates/Hardcoded.py index eaa02835..dee005c9 100644 --- a/templates/Hardcoded.py +++ b/templates/Hardcoded.py @@ -1,83 +1,91 @@ -import xml.etree.ElementTree as ET import os -import shutil -import argparse - -def extract_images_from_slide(slide_xml_path, rels_file_path, media_folder, output_dir): - print(f"Starting extraction for slide: {slide_xml_path}") - print(f"Using relationships file: {rels_file_path}") - print(f"Media folder: {media_folder}") - print(f"Output directory: {output_dir}") +import xml.etree.ElementTree as ET - # Parse the slide XML file +def extract_images_from_slide_xml(slide_xml_path, rels_xml_path, media_folder, output_folder): + # Step 1: Parse the slide XML to find image relationships try: - slide_tree = ET.parse(slide_xml_path) - slide_root = slide_tree.getroot() - print("Slide XML parsed successfully.") - except Exception as e: - print(f"Error parsing slide XML: {e}") + print(f"Parsing slide XML: {slide_xml_path}") + tree = ET.parse(slide_xml_path) + root = tree.getroot() + except ET.ParseError: + print(f"Error parsing {slide_xml_path}") + return + + # Namespace handling for slide XML + ns = {'a': 'http://schemas.openxmlformats.org/drawingml/2006/main', + 'r': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'} + + # Step 2: Find all tags which reference images + print("Finding tags in the slide XML...") + blip_found = False + embed_ids = [] # List to store all found embed IDs + for blip in root.findall(".//a:blip", ns): + blip_found = True + embed = blip.attrib.get(f"{{{ns['r']}}}embed") # Get r:embed value (e.g., rId3) + if embed: + embed = embed.strip() # Strip any whitespace, just in case + embed_ids.append(embed) + print(f"Found with embed ID: '{embed}'") + + if not blip_found: + print("No tags found in the slide XML.") return - # Parse the .rels file - rels_ns = {'rel': 'http://schemas.openxmlformats.org/package/2006/relationships'} + # Step 3: Parse the relationship file to find the image reference try: - rels_tree = ET.parse(rels_file_path) + print(f"Parsing relationships XML: {rels_xml_path}") + rels_tree = ET.parse(rels_xml_path) rels_root = rels_tree.getroot() - - # Extract relationships - rels_map = { - rel.attrib["Id"]: rel.attrib["Target"] - for rel in rels_root.findall("rel:Relationship", rels_ns) - } - print(f"Relationships mapped: {rels_map}") - except Exception as e: - print(f"Error parsing .rels file: {e}") + except ET.ParseError: + print(f"Error parsing {rels_xml_path}") return - # Ensure the output directory exists - os.makedirs(output_dir, exist_ok=True) - print(f"Output directory checked/created: {output_dir}") + # Step 4: Handle namespaces in relationships XML + # Extract the namespace if it exists + rels_ns = "" + if '}' in rels_root.tag: + rels_ns = rels_root.tag.split('}')[0] + '}' + + # Step 5: Print all relationships for debugging + print("Listing all relationships from the relationships XML:") + relationships = {} + for rel in rels_root.findall(f".//{rels_ns}Relationship"): + rel_id = rel.attrib.get('Id', '').strip() + target = rel.attrib.get('Target', '').strip() + relationships[rel_id] = target + print(f"Relationship ID: '{rel_id}', Target: '{target}'") - # Debugging the structure of elements - pic_namespace = {'p': 'http://schemas.openxmlformats.org/presentationml/2006/main', - 'a': 'http://schemas.openxmlformats.org/drawingml/2006/main', - 'r': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'} + # Step 6: Cross-check embed IDs against relationships and extract images + for embed_id in embed_ids: + if embed_id in relationships: + target = relationships[embed_id] + print(f"Found relationship for embed ID '{embed_id}': Target -> {target}") - # Find all picture elements - image_found = False - for pic in slide_root.findall(".//p:pic", pic_namespace): - try: - print(f"Processing : {ET.tostring(pic, encoding='unicode')}") - blip = pic.find(".//a:blip", pic_namespace) - if blip is not None and "embed" in blip.attrib: - r_id = blip.attrib["{http://schemas.openxmlformats.org/officeDocument/2006/relationships}embed"] - print(f"Found r:embed ID: {r_id}") + # The target should point to an image in the media folder + image_path = os.path.join(media_folder, os.path.basename(target)) + print(f"Resolved image path: {image_path}") - if r_id in rels_map: - # Locate the image file - image_path = os.path.join(media_folder, os.path.basename(rels_map[r_id])) - if os.path.exists(image_path): - # Save the image to the output directory - output_path = os.path.join(output_dir, os.path.basename(image_path)) - shutil.copy(image_path, output_path) - print(f"Image saved: {output_path}") - image_found = True - else: - print(f"Image file not found in media folder: {image_path}") - else: - print(f"r:embed ID {r_id} not found in .rels map.") + # Step 7: Copy the image to the output folder + if os.path.exists(image_path): + image_output_path = os.path.join(output_folder, os.path.basename(target)) + with open(image_path, 'rb') as img_in, open(image_output_path, 'wb') as img_out: + img_out.write(img_in.read()) + print(f"Extracted: {image_output_path}") else: - print(f"No or missing 'embed' attribute in : {ET.tostring(pic, encoding='unicode')}") - except Exception as e: - print(f"Error processing element: {e}") + print(f"Image not found: {image_path}") + else: + print(f"No relationship found for embed ID: '{embed_id}'") - if not image_found: - print("No images were extracted. Check your slide XML, .rels file, and media folder paths.") +if __name__ == "__main__": + # Define your file paths + slide_xml_path = '/Users/burhankhan/Desktop/CSCI-4961-01/ppt/slides/slide2.xml' # Replace with the path to your slide XML file + rels_xml_path = '/Users/burhankhan/Desktop/CSCI-4961-01/ppt/slides/_rels/slide2.xml.rels' # Replace with the path to the corresponding relationships file + media_folder = '/Users/burhankhan/Desktop/CSCI-4961-01/ppt/media' # Replace with the path to the media folder + output_folder = '/Users/burhankhan/Desktop/images' # Replace with your desired output directory -# Example usage -slide_xml_path = '/Users/burhankhan/Desktop/CSCI-4961-01/ppt/slides/slide2.xml' -rels_file_path = "/Users/burhankhan/Desktop/CSCI-4961-01/ppt/slides/_rels/slide2.xml.rels" -media_folder = '/Users/burhankhan/Desktop/CSCI-4961-01/ppt/media' -output_dir = '/Users/burhankhan/Desktop/images' + # Ensure output folder exists + if not os.path.exists(output_folder): + os.makedirs(output_folder) -extract_images_from_slide(slide_xml_path, rels_file_path, media_folder, output_dir) + # Run the extraction + extract_images_from_slide_xml(slide_xml_path, rels_xml_path, media_folder, output_folder) diff --git a/templates/script.py b/templates/script.py index 06cfe1a2..eaa02835 100644 --- a/templates/script.py +++ b/templates/script.py @@ -36,13 +36,19 @@ def extract_images_from_slide(slide_xml_path, rels_file_path, media_folder, outp # Ensure the output directory exists os.makedirs(output_dir, exist_ok=True) - print(f"Output directory created: {output_dir}") + print(f"Output directory checked/created: {output_dir}") - # Find all picture elements and extract image references + # Debugging the structure of elements + pic_namespace = {'p': 'http://schemas.openxmlformats.org/presentationml/2006/main', + 'a': 'http://schemas.openxmlformats.org/drawingml/2006/main', + 'r': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'} + + # Find all picture elements image_found = False - for pic in slide_root.findall(".//{http://schemas.openxmlformats.org/presentationml/2006/main}pic"): + for pic in slide_root.findall(".//p:pic", pic_namespace): try: - blip = pic.find(".//{http://schemas.openxmlformats.org/drawingml/2006/main}blip") + print(f"Processing : {ET.tostring(pic, encoding='unicode')}") + blip = pic.find(".//a:blip", pic_namespace) if blip is not None and "embed" in blip.attrib: r_id = blip.attrib["{http://schemas.openxmlformats.org/officeDocument/2006/relationships}embed"] print(f"Found r:embed ID: {r_id}") @@ -61,22 +67,17 @@ def extract_images_from_slide(slide_xml_path, rels_file_path, media_folder, outp else: print(f"r:embed ID {r_id} not found in .rels map.") else: - print("No valid found in element.") + print(f"No or missing 'embed' attribute in : {ET.tostring(pic, encoding='unicode')}") except Exception as e: print(f"Error processing element: {e}") if not image_found: print("No images were extracted. Check your slide XML, .rels file, and media folder paths.") -if __name__ == "__main__": - # Set up argument parsing - parser = argparse.ArgumentParser(description="Extract images from a PowerPoint slide XML and relationships files.") - parser.add_argument("--slide", type=str, help="Path to the slide XML file.", required=True) - parser.add_argument("--rels", type=str, help="Path to the relationships (.rels) file.", required=True) - parser.add_argument("--media", type=str, help="Path to the media folder.", required=True) - parser.add_argument("--output", type=str, help="Path to the output directory to save images.", required=True) - - args = parser.parse_args() +# Example usage +slide_xml_path = '/Users/burhankhan/Desktop/CSCI-4961-01/ppt/slides/slide2.xml' +rels_file_path = "/Users/burhankhan/Desktop/CSCI-4961-01/ppt/slides/_rels/slide2.xml.rels" +media_folder = '/Users/burhankhan/Desktop/CSCI-4961-01/ppt/media' +output_dir = '/Users/burhankhan/Desktop/images' - # Extract images based on provided arguments - extract_images_from_slide(args.slide, args.rels, args.media, args.output) +extract_images_from_slide(slide_xml_path, rels_file_path, media_folder, output_dir) From afc2d55547282b8c958da61f5dc76be8cf931054 Mon Sep 17 00:00:00 2001 From: tluke900 Date: Sun, 1 Dec 2024 19:50:42 -0600 Subject: [PATCH 040/143] Updated to better match given slide.xml --- templates/XML boneset Reader.py | 91 +++++++++++++++++++++++---------- templates/output.json | 13 +++++ templates/slide2.xml | 2 + 3 files changed, 80 insertions(+), 26 deletions(-) create mode 100644 templates/output.json create mode 100644 templates/slide2.xml diff --git a/templates/XML boneset Reader.py b/templates/XML boneset Reader.py index 75b238ef..edcee8f1 100644 --- a/templates/XML boneset Reader.py +++ b/templates/XML boneset Reader.py @@ -1,33 +1,72 @@ -import xml.etree.ElementTree as ET import os +import xml.etree.ElementTree as ET +import json + +def extract_bones_from_xml_to_json(xml_path, output_json_path): + # Step 1: Parse the XML file + try: + print(f"Parsing XML: {xml_path}") + tree = ET.parse(xml_path) + root = tree.getroot() + except ET.ParseError as e: + print(f"Error parsing {xml_path}: {e}") + return + + # Namespace handling for XML + ns = { + 'p': 'http://schemas.openxmlformats.org/presentationml/2006/main', + 'a': 'http://schemas.openxmlformats.org/drawingml/2006/main', + 'r': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships' + } + + # Step 2: Extract bones and use a set to prevent duplicates + unique_bones = set() -def process_slide_without_images(slide_xml_path, rels_file_path, output_dir): - - try: - slide_tree = ET.parse(slide_xml_path) - slide_root = slide_tree.getroot() - print("Slide XML parsed successfully.") + for boneset in root.findall(".//p:sp", ns): # Adjust this path as necessary for your XML structure + for bone in boneset.findall(".//p:txBody//a:r/a:t", ns): + bone_name = bone.text.strip() if bone.text else None + if bone_name and is_valid_bone_name(bone_name): + unique_bones.add(bone_name) - except Exception as e: - print (f"Error parsing slide XML: {e}") + # Step 3: Create a single boneset with all unique bones + output_data = { + 'boneset': 'Upper Limb', # Replace with the appropriate boneset name if needed + 'bones': sorted(unique_bones) # Convert the set to a sorted list for consistent output + } - rels_ns = {'rel': 'http://schemas.openxmlformats.org/package/2006/relationships'} + # Step 4: Write the JSON data to a file try: - rels_tree = ET.parse(rels_file_path) - rels_root = rels_tree.getroot() - - # Extract relationships - rels_map = { - rel.attrib["Id"]: rel.attrib["Target"] - for rel in rels_root.findall("rel:Relationship", rels_ns) - } - print(f"Relationships mapped: {rels_map}") - except Exception as e: - print(f"Error parsing .rels file: {e}") - return - - # Ensure the output directory exists - os.makedirs(output_dir, exist_ok=True) - print(f"Output directory checked/created: {output_dir}") + with open(output_json_path, 'w') as json_file: + json.dump(output_data, json_file, indent=4) + print(f"JSON file saved: {output_json_path}") + except IOError as e: + print(f"Error writing to {output_json_path}: {e}") + +def is_valid_bone_name(name): + """ + Determines if a given name is a valid bone name. + """ + if not name: # Ignore empty strings + return False + + if len(name) > 50: # Ignore overly long strings + return False + + if "(" in name or ")" in name: # Ignore text with parentheses + return False + + if any(char.isdigit() for char in name): # Ignore names with digits + return False + + if name.islower(): # Ignore entirely lowercase words + return False + + return True +if __name__ == "__main__": + # Define your XML file path and output JSON file path + xml_file_path = "/Users/tluke/OneDrive/Documents/GitHub/DigitalBoneBox/templates/slide2.xml" + json_file_path = "/Users/tluke/OneDrive/Documents/GitHub/DigitalBoneBox/templates/output.json" + # Run the extraction and save as JSON + extract_bones_from_xml_to_json(xml_file_path, json_file_path) diff --git a/templates/output.json b/templates/output.json new file mode 100644 index 00000000..29eb33ad --- /dev/null +++ b/templates/output.json @@ -0,0 +1,13 @@ +{ + "boneset": "Upper Limb", + "bones": [ + "Hand", + "Home", + "Humerus", + "Radius", + "Right upper limb", + "Ulna", + "Upper Limb", + "Upper limb" + ] +} \ No newline at end of file diff --git a/templates/slide2.xml b/templates/slide2.xml new file mode 100644 index 00000000..547015cf --- /dev/null +++ b/templates/slide2.xml @@ -0,0 +1,2 @@ + +Upper LimbHomeUpper limb Humerus Ulna Radius Hand Right upper limb (posterior aspect)Right upper limb (anterior aspect)HumerusUlnaRadiusRadiusHand \ No newline at end of file From 615a89980d9c979f9e3f616a5078de21e8183625 Mon Sep 17 00:00:00 2001 From: JoshBudzynski <157167337+JoshBudzynski@users.noreply.github.com> Date: Mon, 2 Dec 2024 08:47:46 -0600 Subject: [PATCH 041/143] Issue_67 --- extract_ppt_annotations.py | 81 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 extract_ppt_annotations.py diff --git a/extract_ppt_annotations.py b/extract_ppt_annotations.py new file mode 100644 index 00000000..fd9ecc9b --- /dev/null +++ b/extract_ppt_annotations.py @@ -0,0 +1,81 @@ +import xml.etree.ElementTree as ET +import json +# this is the code for one slide at a time +def parse_slide_xml(xml_file, output_json_path): + # Load the XML file + tree = ET.parse(xml_file) + root = tree.getroot() + + # Define namespaces used in the XML Not sure if this is correct + ns = { + 'p': 'http://schemas.openxmlformats.org/presentationml/2006/main', + 'a': 'http://schemas.openxmlformats.org/drawingml/2006/main' + } + + annotations = [] + + # Parse shapes (`p:sp` elements for text and shapes) + for sp in root.findall(".//p:sp", ns): + annotation = {} + + # Extract text, if present + text_elements = sp.findall(".//a:t", ns) + text = ''.join([t.text for t in text_elements if t.text]) + if text: + annotation["text"] = text + + # Extract fill color, if present + fill_color = sp.find(".//a:solidFill/a:srgbClr", ns) + if fill_color is not None: + annotation["color"] = fill_color.attrib.get("val") + + # Extract position and size + xfrm = sp.find(".//a:xfrm", ns) + if xfrm is not None: + pos = xfrm.find(".//a:off", ns) + size = xfrm.find(".//a:ext", ns) + if pos is not None and size is not None: + annotation["position"] = { + "x": pos.attrib.get("x"), + "y": pos.attrib.get("y"), + "width": size.attrib.get("cx"), + "height": size.attrib.get("cy") + } + + if annotation: + annotations.append(annotation) + + # Parse lines (`p:cxnSp` elements for line annotations) + for ln in root.findall(".//p:cxnSp", ns): + annotation = {"shape": "line"} + + # Extract line color + line_color = ln.find(".//a:ln/a:solidFill/a:srgbClr", ns) + if line_color is not None: + annotation["color"] = line_color.attrib.get("val") + + # Extract position and size + xfrm = ln.find(".//a:xfrm", ns) + if xfrm is not None: + pos = xfrm.find(".//a:off", ns) + size = xfrm.find(".//a:ext", ns) + if pos is not None and size is not None: + annotation["position"] = { + "x": pos.attrib.get("x"), + "y": pos.attrib.get("y"), + "width": size.attrib.get("cx"), + "height": size.attrib.get("cy") + } + + annotations.append(annotation) + + # Save annotations to a JSON file + with open(output_json_path, 'w') as f: + json.dump(annotations, f, indent=4) + + print(f"Annotations saved to {output_json_path}") + +# Example usage +xml_file = "slide1.xml" # Replace with your slide XML file path +output_json = "slide1_annotations.json" # Output JSON file +parse_slide_xml(xml_file, output_json) From 07ab260ed2da4d4f758bc6e51aac23b0192d148f Mon Sep 17 00:00:00 2001 From: JoshBudzynski <157167337+JoshBudzynski@users.noreply.github.com> Date: Mon, 2 Dec 2024 09:47:17 -0600 Subject: [PATCH 042/143] Issue_67 --- extract_ppt_annotations.py | 8 +- slide3_annotations.json | 237 +++++++++++++++++++++++++++++++++++++ 2 files changed, 242 insertions(+), 3 deletions(-) create mode 100644 slide3_annotations.json diff --git a/extract_ppt_annotations.py b/extract_ppt_annotations.py index fd9ecc9b..58373bcf 100644 --- a/extract_ppt_annotations.py +++ b/extract_ppt_annotations.py @@ -1,6 +1,7 @@ import xml.etree.ElementTree as ET import json -# this is the code for one slide at a time +# this is the code for one slide at a time. + def parse_slide_xml(xml_file, output_json_path): # Load the XML file tree = ET.parse(xml_file) @@ -10,6 +11,7 @@ def parse_slide_xml(xml_file, output_json_path): ns = { 'p': 'http://schemas.openxmlformats.org/presentationml/2006/main', 'a': 'http://schemas.openxmlformats.org/drawingml/2006/main' + } annotations = [] @@ -76,6 +78,6 @@ def parse_slide_xml(xml_file, output_json_path): print(f"Annotations saved to {output_json_path}") # Example usage -xml_file = "slide1.xml" # Replace with your slide XML file path -output_json = "slide1_annotations.json" # Output JSON file +xml_file = "/Users/joshbudzynski/Downloads/example_folder/ppt/slides/slide3.xml" # path to XML file +output_json = "slide3_annotations.json" # Output JSON file parse_slide_xml(xml_file, output_json) diff --git a/slide3_annotations.json b/slide3_annotations.json new file mode 100644 index 00000000..5d58b1d1 --- /dev/null +++ b/slide3_annotations.json @@ -0,0 +1,237 @@ +[ + { + "position": { + "x": "2743200", + "y": "1299410", + "width": "5943600", + "height": "4952999" + } + }, + { + "text": "Bony Pelvis" + }, + { + "text": "Home", + "position": { + "x": "1752600", + "y": "6400801", + "width": "723900", + "height": "307777" + } + }, + { + "position": { + "x": "1524000", + "y": "6248400", + "width": "9144000", + "height": "0" + } + }, + { + "position": { + "x": "8686800", + "y": "1311440", + "width": "1981200", + "height": "4936961" + } + }, + { + "position": { + "x": "1524000", + "y": "1311439", + "width": "1219200", + "height": "4936961" + } + }, + { + "text": "Right Pelvis(medial aspect)", + "position": { + "x": "6477427", + "y": "6248399", + "width": "1316687", + "height": "215444" + } + }, + { + "text": "Right Pelvis (lateral aspect)", + "position": { + "x": "3579863", + "y": "6251729", + "width": "1323299", + "height": "215444" + } + }, + { + "text": "Iliac CrestAnterior Iliac spinesPosterior Iliac spinesAuricular surface", + "position": { + "x": "8823068", + "y": "1322155", + "width": "1148008", + "height": "805605" + } + }, + { + "text": "IliumThe Ilium forms the superior part of the bony pelvis.It articulates with the sacrum to form the sacroiliac joint.", + "position": { + "x": "8757818", + "y": "3505200", + "width": "1828800", + "height": "1231106" + } + }, + { + "text": "Bony Pelvis Ilium Ischium Pubis ", + "color": "000000", + "position": { + "x": "1529348", + "y": "1330154", + "width": "1213853", + "height": "1015663" + } + }, + { + "text": "Iliac Crest", + "position": { + "x": "5431116", + "y": "1912315", + "width": "595035", + "height": "215444" + } + }, + { + "text": "Anterior Superior Iliac Spine", + "position": { + "x": "5407752", + "y": "2590801", + "width": "766897", + "height": "461665" + } + }, + { + "text": "Anterior Inferior Iliac Spine", + "position": { + "x": "5271485", + "y": "3229154", + "width": "766897", + "height": "461665" + } + }, + { + "text": "Posterior Inferior Iliac Spine", + "position": { + "x": "7887969", + "y": "3816698", + "width": "766897", + "height": "461665" + } + }, + { + "text": "Posterior Superior Iliac Spine", + "position": { + "x": "7881241", + "y": "1445569", + "width": "766897", + "height": "461665" + } + }, + { + "text": "Auricular surface", + "position": { + "x": "7169365", + "y": "1417638", + "width": "624749", + "height": "338554" + } + }, + { + "text": "No labels", + "position": { + "x": "9189990", + "y": "6400801", + "width": "974820", + "height": "307777" + } + }, + { + "shape": "line", + "color": "000000", + "position": { + "x": "1524000", + "y": "1295401", + "width": "9144000", + "height": "4009" + } + }, + { + "shape": "line", + "position": { + "x": "8686800", + "y": "3459986", + "width": "1965420", + "height": "0" + } + }, + { + "shape": "line", + "position": { + "x": "4212167", + "y": "2020037", + "width": "1218948", + "height": "0" + } + }, + { + "shape": "line", + "position": { + "x": "6026150", + "y": "2020037", + "width": "1212850", + "height": "0" + } + }, + { + "shape": "line", + "position": { + "x": "5008205", + "y": "2821633", + "width": "399547", + "height": "0" + } + }, + { + "shape": "line", + "position": { + "x": "4915392", + "y": "3459986", + "width": "356092", + "height": "0" + } + }, + { + "shape": "line", + "position": { + "x": "8153401", + "y": "3505201", + "width": "118017", + "height": "311497" + } + }, + { + "shape": "line", + "position": { + "x": "8264690", + "y": "1907233", + "width": "117311", + "height": "1145232" + } + }, + { + "shape": "line", + "position": { + "x": "7481740", + "y": "1756192", + "width": "290661", + "height": "1368008" + } + } +] \ No newline at end of file From 88d83d82bac4c1c82717c220bdb1296088b5c2fa Mon Sep 17 00:00:00 2001 From: tluke900 Date: Mon, 2 Dec 2024 10:24:04 -0600 Subject: [PATCH 043/143] Added second xml for testing, updated for less hardcoding. --- templates/XML boneset Reader.py | 51 ++++++++++++++----- templates/output.json | 6 +-- templates/slide2Pelvis.xml | 2 + templates/{slide2.xml => slide2UpperLimb.xml} | 0 4 files changed, 41 insertions(+), 18 deletions(-) create mode 100644 templates/slide2Pelvis.xml rename templates/{slide2.xml => slide2UpperLimb.xml} (100%) diff --git a/templates/XML boneset Reader.py b/templates/XML boneset Reader.py index edcee8f1..4689b069 100644 --- a/templates/XML boneset Reader.py +++ b/templates/XML boneset Reader.py @@ -19,22 +19,34 @@ def extract_bones_from_xml_to_json(xml_path, output_json_path): 'r': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships' } - # Step 2: Extract bones and use a set to prevent duplicates + # Step 2: Extract boneset name dynamically + boneset_name = None + boneset_element = root.find(".//p:sp/p:txBody//a:t", ns) + if boneset_element is not None: + boneset_name = boneset_element.text.strip() + print(f"Found boneset name: {boneset_name}") + else: + print("No boneset name found.") + boneset_name = "Unknown Boneset" + + # Step 3: Extract bones and use a set to prevent duplicates unique_bones = set() for boneset in root.findall(".//p:sp", ns): # Adjust this path as necessary for your XML structure for bone in boneset.findall(".//p:txBody//a:r/a:t", ns): bone_name = bone.text.strip() if bone.text else None - if bone_name and is_valid_bone_name(bone_name): - unique_bones.add(bone_name) + if bone_name and is_valid_bone_name(bone_name, boneset_name): + # Normalize the bone name to lowercase to handle case insensitivity + unique_bones.add(bone_name.lower()) - # Step 3: Create a single boneset with all unique bones + # Step 4: Create a single boneset with all unique bones + # Capitalize the first letter of each bone for consistency in JSON output output_data = { - 'boneset': 'Upper Limb', # Replace with the appropriate boneset name if needed - 'bones': sorted(unique_bones) # Convert the set to a sorted list for consistent output + 'boneset': boneset_name, + 'bones': sorted(bone.capitalize() for bone in unique_bones) # Convert the set to a sorted list } - # Step 4: Write the JSON data to a file + # Step 5: Write the JSON data to a file try: with open(output_json_path, 'w') as json_file: json.dump(output_data, json_file, indent=4) @@ -42,9 +54,9 @@ def extract_bones_from_xml_to_json(xml_path, output_json_path): except IOError as e: print(f"Error writing to {output_json_path}: {e}") -def is_valid_bone_name(name): +def is_valid_bone_name(name, boneset_name): """ - Determines if a given name is a valid bone name. + Determines if a given name is a valid bone name, excluding those that contain the boneset name. """ if not name: # Ignore empty strings return False @@ -61,12 +73,25 @@ def is_valid_bone_name(name): if name.islower(): # Ignore entirely lowercase words return False + # Exclude names that contain the boneset name (case-insensitive) + if boneset_name and boneset_name.lower() in name.lower(): + return False + + if "Home" in name: + return False + + if "The" in name: + return False + return True if __name__ == "__main__": - # Define your XML file path and output JSON file path - xml_file_path = "/Users/tluke/OneDrive/Documents/GitHub/DigitalBoneBox/templates/slide2.xml" - json_file_path = "/Users/tluke/OneDrive/Documents/GitHub/DigitalBoneBox/templates/output.json" + # Get the directory of the current script + current_dir = os.path.dirname(os.path.abspath(__file__)) + + # Define the XML and JSON file paths relative to the script's directory + xml_file_path = os.path.join(current_dir, "slide2UpperLimb.xml") + json_file_path = os.path.join(current_dir, "output.json") # Run the extraction and save as JSON - extract_bones_from_xml_to_json(xml_file_path, json_file_path) + extract_bones_from_xml_to_json(xml_file_path, json_file_path) \ No newline at end of file diff --git a/templates/output.json b/templates/output.json index 29eb33ad..a8cbea24 100644 --- a/templates/output.json +++ b/templates/output.json @@ -2,12 +2,8 @@ "boneset": "Upper Limb", "bones": [ "Hand", - "Home", "Humerus", "Radius", - "Right upper limb", - "Ulna", - "Upper Limb", - "Upper limb" + "Ulna" ] } \ No newline at end of file diff --git a/templates/slide2Pelvis.xml b/templates/slide2Pelvis.xml new file mode 100644 index 00000000..c8757aae --- /dev/null +++ b/templates/slide2Pelvis.xml @@ -0,0 +1,2 @@ + +Bony PelvisHomeRight Pelvis(medial aspect)Right Pelvis (lateral aspect)Bony Pelvis Ilium Ischium Pubis IliumPubisIschiumAcetabulumObturator foramenThe bony pelvis is made up of tow hip bones, sacrum, and coccyx.Hip bones - 3 fused bones:IliumIschiumPubisThe acetabulum is a cup shaped depression that articulates with the head of the femur to from the hip joint, a ball-and-socket type synovial joint.The obturator foramen is an opening created by the inferior ramus and body of the ischium, and superior and inferior pubic rami. This foramen is partially covered by the obturator membrane. The inner and outer surfaces of the obturator membrane provide the origin for the obturator internus and externus muscles, respectively. \ No newline at end of file diff --git a/templates/slide2.xml b/templates/slide2UpperLimb.xml similarity index 100% rename from templates/slide2.xml rename to templates/slide2UpperLimb.xml From e0006e646ce248e485d029802582aec958e2f095 Mon Sep 17 00:00:00 2001 From: Burhan Khan Date: Mon, 2 Dec 2024 14:05:43 -0600 Subject: [PATCH 044/143] Addresses Issue #62. Made the script less hard coded to my machine, requires command line arguements with the paths to the XML file, the relationship file, the media folder, and the path to the output directory. Allows for anyone to run the program from their own machine. --- templates/Hardcoded.py | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/templates/Hardcoded.py b/templates/Hardcoded.py index dee005c9..daa061d9 100644 --- a/templates/Hardcoded.py +++ b/templates/Hardcoded.py @@ -1,4 +1,5 @@ import os +import argparse import xml.etree.ElementTree as ET def extract_images_from_slide_xml(slide_xml_path, rels_xml_path, media_folder, output_folder): @@ -77,15 +78,19 @@ def extract_images_from_slide_xml(slide_xml_path, rels_xml_path, media_folder, o print(f"No relationship found for embed ID: '{embed_id}'") if __name__ == "__main__": - # Define your file paths - slide_xml_path = '/Users/burhankhan/Desktop/CSCI-4961-01/ppt/slides/slide2.xml' # Replace with the path to your slide XML file - rels_xml_path = '/Users/burhankhan/Desktop/CSCI-4961-01/ppt/slides/_rels/slide2.xml.rels' # Replace with the path to the corresponding relationships file - media_folder = '/Users/burhankhan/Desktop/CSCI-4961-01/ppt/media' # Replace with the path to the media folder - output_folder = '/Users/burhankhan/Desktop/images' # Replace with your desired output directory + # Create argument parser + parser = argparse.ArgumentParser(description="Extract images from a PowerPoint slide XML.") + parser.add_argument("--slide_xml_path", required=True, help="Path to the slide XML file.") + parser.add_argument("--rels_xml_path", required=True, help="Path to the relationships XML file.") + parser.add_argument("--media_folder", required=True, help="Path to the media folder containing images.") + parser.add_argument("--output_folder", required=True, help="Path to the output folder for extracted images.") + + # Parse arguments + args = parser.parse_args() # Ensure output folder exists - if not os.path.exists(output_folder): - os.makedirs(output_folder) + if not os.path.exists(args.output_folder): + os.makedirs(args.output_folder) # Run the extraction - extract_images_from_slide_xml(slide_xml_path, rels_xml_path, media_folder, output_folder) + extract_images_from_slide_xml(args.slide_xml_path, args.rels_xml_path, args.media_folder, args.output_folder) From f7e302665fc7d19ba9b3239d74eb0ec43c3c3026 Mon Sep 17 00:00:00 2001 From: tluke900 Date: Sun, 8 Dec 2024 13:00:55 -0600 Subject: [PATCH 045/143] Added Header Options --- templates/boneset.html | 15 ++++++++++++ templates/style.css | 54 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 68 insertions(+), 1 deletion(-) diff --git a/templates/boneset.html b/templates/boneset.html index 0860b27a..60d8375e 100644 --- a/templates/boneset.html +++ b/templates/boneset.html @@ -12,6 +12,21 @@
                    + Home + Tutor + Study + Help + +
                    + Login + Sign Up +
                    + + + + + +
                    diff --git a/templates/style.css b/templates/style.css index 6f036740..8e8b2059 100644 --- a/templates/style.css +++ b/templates/style.css @@ -117,4 +117,56 @@ ul li { #editor-view { margin-top: 40px; /* Add margin to move the content below the badge */ padding: 20px; /* Keep consistent padding for readability */ -} \ No newline at end of file +} + + +#text-button-Home { + padding: 20px; + cursor: pointer; +} + + +#text-button-Tutor { + padding: 20px; + cursor: pointer; +} + + +#text-button-Study { + padding: 20px; + cursor: pointer; +} +#text-button-Help { + padding: 20px; + cursor: pointer; +} + + +#text-button-Login { + padding: 20px; + cursor: pointer; + align-items: right; + +} + +#text-button-SignUp { + padding: 20px; + cursor: pointer; + align-items: right; + + +} + + +#innerbadge { + display: flex; /* Enable Flexbox for the container */ + justify-content: flex-end; /* Align items to the far right */ + align-items: center; /* Optional: Vertically center the items */ + width: 100%; /* Ensure the container spans the full width of the screen */ + padding-right: 20px; /* Adds space between the buttons and the right edge */ + box-sizing: border-box; + width: 75% + + +} + From 5c3c16dc4ea981ed3f398cc28c6c362fc43a9d75 Mon Sep 17 00:00:00 2001 From: jacksayshi Date: Wed, 11 Dec 2024 14:35:36 -0600 Subject: [PATCH 046/143] pulling changes from main and moving py script to seperate folder --- {templates => data_extraction}/Hardcoded.py | 0 {templates => data_extraction}/script.py | 0 package.json | 3 +++ 3 files changed, 3 insertions(+) rename {templates => data_extraction}/Hardcoded.py (100%) rename {templates => data_extraction}/script.py (100%) diff --git a/templates/Hardcoded.py b/data_extraction/Hardcoded.py similarity index 100% rename from templates/Hardcoded.py rename to data_extraction/Hardcoded.py diff --git a/templates/script.py b/data_extraction/script.py similarity index 100% rename from templates/script.py rename to data_extraction/script.py diff --git a/package.json b/package.json index a6b1e4b6..09523754 100644 --- a/package.json +++ b/package.json @@ -21,5 +21,8 @@ "devDependencies": { "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0" + }, + "directories": { + "test": "tests" } } From e4c4bbd79d72ee375919e8f84eecf9e542e29d90 Mon Sep 17 00:00:00 2001 From: jacksayshi Date: Wed, 11 Dec 2024 14:43:24 -0600 Subject: [PATCH 047/143] pulling from main and moving py script to destined folder --- .../extract_ppt_annotations.py | 0 .../slide3_annotations.json | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename extract_ppt_annotations.py => data_extraction/extract_ppt_annotations.py (100%) rename slide3_annotations.json => data_extraction/slide3_annotations.json (100%) diff --git a/extract_ppt_annotations.py b/data_extraction/extract_ppt_annotations.py similarity index 100% rename from extract_ppt_annotations.py rename to data_extraction/extract_ppt_annotations.py diff --git a/slide3_annotations.json b/data_extraction/slide3_annotations.json similarity index 100% rename from slide3_annotations.json rename to data_extraction/slide3_annotations.json From 70ae2b4889683fec6054e98789f1f622e45b3504 Mon Sep 17 00:00:00 2001 From: jacksayshi Date: Wed, 11 Dec 2024 14:47:59 -0600 Subject: [PATCH 048/143] recent changes pulled and good to merge as feature flag --- {templates => data_extraction}/XML boneset Reader.py | 0 {templates => data_extraction}/output.json | 0 {templates => data_extraction}/slide2Pelvis.xml | 0 {templates => data_extraction}/slide2UpperLimb.xml | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename {templates => data_extraction}/XML boneset Reader.py (100%) rename {templates => data_extraction}/output.json (100%) rename {templates => data_extraction}/slide2Pelvis.xml (100%) rename {templates => data_extraction}/slide2UpperLimb.xml (100%) diff --git a/templates/XML boneset Reader.py b/data_extraction/XML boneset Reader.py similarity index 100% rename from templates/XML boneset Reader.py rename to data_extraction/XML boneset Reader.py diff --git a/templates/output.json b/data_extraction/output.json similarity index 100% rename from templates/output.json rename to data_extraction/output.json diff --git a/templates/slide2Pelvis.xml b/data_extraction/slide2Pelvis.xml similarity index 100% rename from templates/slide2Pelvis.xml rename to data_extraction/slide2Pelvis.xml diff --git a/templates/slide2UpperLimb.xml b/data_extraction/slide2UpperLimb.xml similarity index 100% rename from templates/slide2UpperLimb.xml rename to data_extraction/slide2UpperLimb.xml From 8a08c0f892dd57b4cac70d8a45c6364f88d03cc2 Mon Sep 17 00:00:00 2001 From: tluke900 Date: Sun, 2 Feb 2025 13:49:05 -0600 Subject: [PATCH 049/143] Currently making too many sets and not populating bones within them. --- data_extraction/XML boneset Reader.py | 98 ++++++++++++++++----------- data_extraction/output.json | 50 +++++++++++--- 2 files changed, 101 insertions(+), 47 deletions(-) diff --git a/data_extraction/XML boneset Reader.py b/data_extraction/XML boneset Reader.py index 4689b069..f661b111 100644 --- a/data_extraction/XML boneset Reader.py +++ b/data_extraction/XML boneset Reader.py @@ -2,15 +2,18 @@ import xml.etree.ElementTree as ET import json -def extract_bones_from_xml_to_json(xml_path, output_json_path): - # Step 1: Parse the XML file +def extract_bones_from_xml(xml_path): + """ + Parses the XML file and extracts bonesets and their associated bones. + Returns a dictionary with boneset names as keys and lists of bones as values. + """ try: print(f"Parsing XML: {xml_path}") tree = ET.parse(xml_path) root = tree.getroot() except ET.ParseError as e: print(f"Error parsing {xml_path}: {e}") - return + return {} # Namespace handling for XML ns = { @@ -19,37 +22,53 @@ def extract_bones_from_xml_to_json(xml_path, output_json_path): 'r': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships' } - # Step 2: Extract boneset name dynamically - boneset_name = None - boneset_element = root.find(".//p:sp/p:txBody//a:t", ns) - if boneset_element is not None: - boneset_name = boneset_element.text.strip() - print(f"Found boneset name: {boneset_name}") - else: - print("No boneset name found.") - boneset_name = "Unknown Boneset" - - # Step 3: Extract bones and use a set to prevent duplicates - unique_bones = set() - - for boneset in root.findall(".//p:sp", ns): # Adjust this path as necessary for your XML structure - for bone in boneset.findall(".//p:txBody//a:r/a:t", ns): - bone_name = bone.text.strip() if bone.text else None - if bone_name and is_valid_bone_name(bone_name, boneset_name): - # Normalize the bone name to lowercase to handle case insensitivity - unique_bones.add(bone_name.lower()) - - # Step 4: Create a single boneset with all unique bones - # Capitalize the first letter of each bone for consistency in JSON output - output_data = { - 'boneset': boneset_name, - 'bones': sorted(bone.capitalize() for bone in unique_bones) # Convert the set to a sorted list - } + bonesets = {} # Dictionary to store bonesets + + INVALID_BONESETS = {"Home", "The", "Title", "Slide", "Overview"} + + + # Extract bonesets dynamically + for boneset_element in root.findall(".//p:sp", ns): + boneset_name_element = boneset_element.find(".//p:txBody//a:t", ns) + + if boneset_name_element is not None: + + boneset_name = boneset_name_element.text.strip() + + boneset_name_words = boneset_name.split() + if not len(boneset_name_words) < 4: + continue + if not boneset_name or boneset_name in INVALID_BONESETS: + continue + + if boneset_name and boneset_name not in bonesets: + bonesets[boneset_name] = set() + + # Extract bones within this boneset + for bone_element in boneset_element.findall(".//p:txBody//a:r/a:t", ns): + bone_name = bone_element.text.strip() if bone_element.text else None + if bone_name and is_valid_bone_name(bone_name, boneset_name): + bonesets[boneset_name].add(bone_name.capitalize()) - # Step 5: Write the JSON data to a file + return bonesets + +def generate_json_output(bonesets, output_json_path): + """ + Converts bonesets dictionary into a structured JSON format and writes it to a file. + """ + structured_data = [] + + for boneset_name, bones in bonesets.items(): + structured_data.append({ + "boneset": boneset_name, + "bones": sorted(bones), + "has_sub_bonesets": any(bone in bonesets for bone in bones) # Check if any bone is also a boneset + }) + + # Save to JSON file try: with open(output_json_path, 'w') as json_file: - json.dump(output_data, json_file, indent=4) + json.dump(structured_data, json_file, indent=4) print(f"JSON file saved: {output_json_path}") except IOError as e: print(f"Error writing to {output_json_path}: {e}") @@ -73,14 +92,14 @@ def is_valid_bone_name(name, boneset_name): if name.islower(): # Ignore entirely lowercase words return False - # Exclude names that contain the boneset name (case-insensitive) - if boneset_name and boneset_name.lower() in name.lower(): + if len(name.split()) > 3: return False - if "Home" in name: + # Exclude names that contain the boneset name (case-insensitive) + if boneset_name and boneset_name.lower() in name.lower(): return False - if "The" in name: + if "Home" in name or "The" in name: return False return True @@ -90,8 +109,11 @@ def is_valid_bone_name(name, boneset_name): current_dir = os.path.dirname(os.path.abspath(__file__)) # Define the XML and JSON file paths relative to the script's directory - xml_file_path = os.path.join(current_dir, "slide2UpperLimb.xml") + xml_file_path = os.path.join(current_dir, "slide2Pelvis.xml") json_file_path = os.path.join(current_dir, "output.json") - # Run the extraction and save as JSON - extract_bones_from_xml_to_json(xml_file_path, json_file_path) \ No newline at end of file + # Extract bonesets and their bones + bonesets = extract_bones_from_xml(xml_file_path) + + # Generate and save JSON output + generate_json_output(bonesets, json_file_path) diff --git a/data_extraction/output.json b/data_extraction/output.json index a8cbea24..9f5d516a 100644 --- a/data_extraction/output.json +++ b/data_extraction/output.json @@ -1,9 +1,41 @@ -{ - "boneset": "Upper Limb", - "bones": [ - "Hand", - "Humerus", - "Radius", - "Ulna" - ] -} \ No newline at end of file +[ + { + "boneset": "Bony Pelvis", + "bones": [ + "Ilium", + "Ischium", + "Pubis" + ], + "has_sub_bonesets": true + }, + { + "boneset": "Right Pelvis", + "bones": [], + "has_sub_bonesets": false + }, + { + "boneset": "Ilium", + "bones": [], + "has_sub_bonesets": false + }, + { + "boneset": "Pubis", + "bones": [], + "has_sub_bonesets": false + }, + { + "boneset": "Ischium", + "bones": [], + "has_sub_bonesets": false + }, + { + "boneset": "Acetabulum", + "bones": [], + "has_sub_bonesets": false + }, + { + "boneset": "Obturator foramen", + "bones": [], + "has_sub_bonesets": false + } +] \ No newline at end of file From 834c0c38dda18505247077f219e80ebd70adb996 Mon Sep 17 00:00:00 2001 From: JoshBudzynski <157167337+JoshBudzynski@users.noreply.github.com> Date: Mon, 3 Feb 2025 09:57:26 -0600 Subject: [PATCH 050/143] Issue_79 --- .vscode/launch.json | 15 ++++++ data_extraction/extract_ppt_annotations.py | 63 +++++----------------- 2 files changed, 28 insertions(+), 50 deletions(-) create mode 100644 .vscode/launch.json diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..2ba986f6 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,15 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "chrome", + "request": "launch", + "name": "Launch Chrome against localhost", + "url": "http://localhost:8080", + "webRoot": "${workspaceFolder}" + } + ] +} \ No newline at end of file diff --git a/data_extraction/extract_ppt_annotations.py b/data_extraction/extract_ppt_annotations.py index 58373bcf..ee1a1bbe 100644 --- a/data_extraction/extract_ppt_annotations.py +++ b/data_extraction/extract_ppt_annotations.py @@ -13,71 +13,34 @@ def parse_slide_xml(xml_file, output_json_path): 'a': 'http://schemas.openxmlformats.org/drawingml/2006/main' } - annotations = [] + middle_x_min, middle_x_max = 2000000, 6000000 # Define X range for middle + middle_y_min, middle_y_max = 1000000, 5000000 # Define Y range for middle - # Parse shapes (`p:sp` elements for text and shapes) for sp in root.findall(".//p:sp", ns): annotation = {} - - # Extract text, if present + text_elements = sp.findall(".//a:t", ns) text = ''.join([t.text for t in text_elements if t.text]) - if text: - annotation["text"] = text - - # Extract fill color, if present - fill_color = sp.find(".//a:solidFill/a:srgbClr", ns) - if fill_color is not None: - annotation["color"] = fill_color.attrib.get("val") - # Extract position and size xfrm = sp.find(".//a:xfrm", ns) if xfrm is not None: pos = xfrm.find(".//a:off", ns) size = xfrm.find(".//a:ext", ns) if pos is not None and size is not None: - annotation["position"] = { - "x": pos.attrib.get("x"), - "y": pos.attrib.get("y"), - "width": size.attrib.get("cx"), - "height": size.attrib.get("cy") - } - - if annotation: - annotations.append(annotation) - - # Parse lines (`p:cxnSp` elements for line annotations) - for ln in root.findall(".//p:cxnSp", ns): - annotation = {"shape": "line"} - - # Extract line color - line_color = ln.find(".//a:ln/a:solidFill/a:srgbClr", ns) - if line_color is not None: - annotation["color"] = line_color.attrib.get("val") - - # Extract position and size - xfrm = ln.find(".//a:xfrm", ns) - if xfrm is not None: - pos = xfrm.find(".//a:off", ns) - size = xfrm.find(".//a:ext", ns) - if pos is not None and size is not None: - annotation["position"] = { - "x": pos.attrib.get("x"), - "y": pos.attrib.get("y"), - "width": size.attrib.get("cx"), - "height": size.attrib.get("cy") - } - - annotations.append(annotation) + x, y = int(pos.attrib.get("x", 0)), int(pos.attrib.get("y", 0)) + width, height = int(size.attrib.get("cx", 0)), int(size.attrib.get("cy", 0)) + + if middle_x_min <= x <= middle_x_max and middle_y_min <= y <= middle_y_max: + annotation["text"] = text + annotation["position"] = {"x": x, "y": y, "width": width, "height": height} + annotations.append(annotation) - # Save annotations to a JSON file with open(output_json_path, 'w') as f: json.dump(annotations, f, indent=4) print(f"Annotations saved to {output_json_path}") -# Example usage -xml_file = "/Users/joshbudzynski/Downloads/example_folder/ppt/slides/slide3.xml" # path to XML file -output_json = "slide3_annotations.json" # Output JSON file -parse_slide_xml(xml_file, output_json) +xml_file = "/Users/joshbudzynski/Downloads/example_folder/ppt/slides/slide3.xml" +output_json = "slide3_annotations.json" +parse_slide_xml(xml_file, output_json) \ No newline at end of file From fac1943acfe9ce2e58b0b3ee495a3329e4b52e7c Mon Sep 17 00:00:00 2001 From: tluke900 Date: Mon, 3 Feb 2025 14:32:06 -0600 Subject: [PATCH 051/143] Removed pointless distinction --- data_extraction/XML boneset Reader.py | 1 - 1 file changed, 1 deletion(-) diff --git a/data_extraction/XML boneset Reader.py b/data_extraction/XML boneset Reader.py index f661b111..6affbccd 100644 --- a/data_extraction/XML boneset Reader.py +++ b/data_extraction/XML boneset Reader.py @@ -62,7 +62,6 @@ def generate_json_output(bonesets, output_json_path): structured_data.append({ "boneset": boneset_name, "bones": sorted(bones), - "has_sub_bonesets": any(bone in bonesets for bone in bones) # Check if any bone is also a boneset }) # Save to JSON file From ee76a7873284630d4640c89baf5bd73b2c5c2714 Mon Sep 17 00:00:00 2001 From: Burhan Khan Date: Mon, 3 Feb 2025 14:43:04 -0600 Subject: [PATCH 052/143] Images are being extracted along with the long arguement needed to run the script. Working on changing the names of the images extracted. --- .vscode/settings.json | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..6f3a2913 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "liveServer.settings.port": 5501 +} \ No newline at end of file From a3fa3d374ea0b0e548f1457f650b504c42a82393 Mon Sep 17 00:00:00 2001 From: tluke900 Date: Mon, 3 Feb 2025 15:33:18 -0600 Subject: [PATCH 053/143] Removing point i dont want --- data_extraction/output.json | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/data_extraction/output.json b/data_extraction/output.json index 9f5d516a..c91cf78f 100644 --- a/data_extraction/output.json +++ b/data_extraction/output.json @@ -5,37 +5,30 @@ "Ilium", "Ischium", "Pubis" - ], - "has_sub_bonesets": true + ] }, { "boneset": "Right Pelvis", - "bones": [], - "has_sub_bonesets": false + "bones": [] }, { "boneset": "Ilium", - "bones": [], - "has_sub_bonesets": false + "bones": [] }, { "boneset": "Pubis", - "bones": [], - "has_sub_bonesets": false + "bones": [] }, { "boneset": "Ischium", - "bones": [], - "has_sub_bonesets": false + "bones": [] }, { "boneset": "Acetabulum", - "bones": [], - "has_sub_bonesets": false + "bones": [] }, { "boneset": "Obturator foramen", - "bones": [], - "has_sub_bonesets": false + "bones": [] } ] \ No newline at end of file From f6e43b900507c7b3dbd5b200f29db146340ec989 Mon Sep 17 00:00:00 2001 From: Burhan Khan Date: Mon, 3 Feb 2025 15:52:16 -0600 Subject: [PATCH 054/143] Addresses Issue Seventy Two. Fully automated the extraction of several images from the pptx slides.Each slide gets its respective folder along with the images used in that slide. Renaming of images was not addressed as it was not necessary. --- data_extraction/Hardcoded.py | 89 +++++++++++------------------------- 1 file changed, 26 insertions(+), 63 deletions(-) diff --git a/data_extraction/Hardcoded.py b/data_extraction/Hardcoded.py index daa061d9..a76da560 100644 --- a/data_extraction/Hardcoded.py +++ b/data_extraction/Hardcoded.py @@ -1,96 +1,59 @@ import os -import argparse import xml.etree.ElementTree as ET def extract_images_from_slide_xml(slide_xml_path, rels_xml_path, media_folder, output_folder): - # Step 1: Parse the slide XML to find image relationships try: - print(f"Parsing slide XML: {slide_xml_path}") tree = ET.parse(slide_xml_path) root = tree.getroot() except ET.ParseError: print(f"Error parsing {slide_xml_path}") return - # Namespace handling for slide XML ns = {'a': 'http://schemas.openxmlformats.org/drawingml/2006/main', 'r': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'} - - # Step 2: Find all tags which reference images - print("Finding tags in the slide XML...") - blip_found = False - embed_ids = [] # List to store all found embed IDs - for blip in root.findall(".//a:blip", ns): - blip_found = True - embed = blip.attrib.get(f"{{{ns['r']}}}embed") # Get r:embed value (e.g., rId3) - if embed: - embed = embed.strip() # Strip any whitespace, just in case - embed_ids.append(embed) - print(f"Found with embed ID: '{embed}'") - - if not blip_found: - print("No tags found in the slide XML.") + + embed_ids = [blip.attrib.get(f"{{{ns['r']}}}embed") for blip in root.findall(".//a:blip", ns) if blip.attrib.get(f"{{{ns['r']}}}embed")] + if not embed_ids: return - # Step 3: Parse the relationship file to find the image reference try: - print(f"Parsing relationships XML: {rels_xml_path}") rels_tree = ET.parse(rels_xml_path) rels_root = rels_tree.getroot() except ET.ParseError: print(f"Error parsing {rels_xml_path}") return - # Step 4: Handle namespaces in relationships XML - # Extract the namespace if it exists - rels_ns = "" - if '}' in rels_root.tag: - rels_ns = rels_root.tag.split('}')[0] + '}' + rels_ns = rels_root.tag.split('}')[0] + '}' if '}' in rels_root.tag else '' + relationships = {rel.attrib.get('Id', '').strip(): rel.attrib.get('Target', '').strip() for rel in rels_root.findall(f".//{rels_ns}Relationship")} - # Step 5: Print all relationships for debugging - print("Listing all relationships from the relationships XML:") - relationships = {} - for rel in rels_root.findall(f".//{rels_ns}Relationship"): - rel_id = rel.attrib.get('Id', '').strip() - target = rel.attrib.get('Target', '').strip() - relationships[rel_id] = target - print(f"Relationship ID: '{rel_id}', Target: '{target}'") - - # Step 6: Cross-check embed IDs against relationships and extract images for embed_id in embed_ids: if embed_id in relationships: target = relationships[embed_id] - print(f"Found relationship for embed ID '{embed_id}': Target -> {target}") - - # The target should point to an image in the media folder image_path = os.path.join(media_folder, os.path.basename(target)) - print(f"Resolved image path: {image_path}") - - # Step 7: Copy the image to the output folder if os.path.exists(image_path): - image_output_path = os.path.join(output_folder, os.path.basename(target)) + slide_name = os.path.splitext(os.path.basename(slide_xml_path))[0] + slide_output_folder = os.path.join(output_folder, slide_name) + if not os.path.exists(slide_output_folder): + os.makedirs(slide_output_folder) + image_output_path = os.path.join(slide_output_folder, os.path.basename(target)) with open(image_path, 'rb') as img_in, open(image_output_path, 'wb') as img_out: img_out.write(img_in.read()) print(f"Extracted: {image_output_path}") - else: - print(f"Image not found: {image_path}") - else: - print(f"No relationship found for embed ID: '{embed_id}'") -if __name__ == "__main__": - # Create argument parser - parser = argparse.ArgumentParser(description="Extract images from a PowerPoint slide XML.") - parser.add_argument("--slide_xml_path", required=True, help="Path to the slide XML file.") - parser.add_argument("--rels_xml_path", required=True, help="Path to the relationships XML file.") - parser.add_argument("--media_folder", required=True, help="Path to the media folder containing images.") - parser.add_argument("--output_folder", required=True, help="Path to the output folder for extracted images.") - - # Parse arguments - args = parser.parse_args() - - # Ensure output folder exists - if not os.path.exists(args.output_folder): - os.makedirs(args.output_folder) +def process_pptx_folders(slides_folder, rels_folder, media_folder, output_folder): + if not os.path.exists(output_folder): + os.makedirs(output_folder) + + for slide_file in sorted(os.listdir(slides_folder)): + if slide_file.startswith("slide") and slide_file.endswith(".xml"): + slide_path = os.path.join(slides_folder, slide_file) + rels_path = os.path.join(rels_folder, slide_file + ".rels") + if os.path.exists(rels_path): + extract_images_from_slide_xml(slide_path, rels_path, media_folder, output_folder) - # Run the extraction - extract_images_from_slide_xml(args.slide_xml_path, args.rels_xml_path, args.media_folder, args.output_folder) +if __name__ == "__main__": + slides_folder = "/Users/burhankhan/Desktop/ppt/slides" + rels_folder = "/Users/burhankhan/Desktop/ppt/slides/_rels" + media_folder = "/Users/burhankhan/Desktop/ppt/media" + output_folder = "/Users/burhankhan/Desktop/AutomatedScript" + process_pptx_folders(slides_folder, rels_folder, media_folder, output_folder) From c98b7e2932a1eb5898a1409b77e9dc7daba58c84 Mon Sep 17 00:00:00 2001 From: JoshBudzynski <157167337+JoshBudzynski@users.noreply.github.com> Date: Sun, 9 Feb 2025 15:22:16 -0700 Subject: [PATCH 055/143] updated --- data_extraction/extract_ppt_annotations.py | 24 ++++++++++++++-------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/data_extraction/extract_ppt_annotations.py b/data_extraction/extract_ppt_annotations.py index ee1a1bbe..72f7cbf0 100644 --- a/data_extraction/extract_ppt_annotations.py +++ b/data_extraction/extract_ppt_annotations.py @@ -1,18 +1,18 @@ import xml.etree.ElementTree as ET import json -# this is the code for one slide at a time. +import os def parse_slide_xml(xml_file, output_json_path): # Load the XML file tree = ET.parse(xml_file) root = tree.getroot() - # Define namespaces used in the XML Not sure if this is correct + # Define namespaces used in the XML ns = { 'p': 'http://schemas.openxmlformats.org/presentationml/2006/main', 'a': 'http://schemas.openxmlformats.org/drawingml/2006/main' - } + annotations = [] middle_x_min, middle_x_max = 2000000, 6000000 # Define X range for middle middle_y_min, middle_y_max = 1000000, 5000000 # Define Y range for middle @@ -25,8 +25,8 @@ def parse_slide_xml(xml_file, output_json_path): xfrm = sp.find(".//a:xfrm", ns) if xfrm is not None: - pos = xfrm.find(".//a:off", ns) - size = xfrm.find(".//a:ext", ns) + pos = xfrm.find("./a:off", ns) + size = xfrm.find("./a:ext", ns) if pos is not None and size is not None: x, y = int(pos.attrib.get("x", 0)), int(pos.attrib.get("y", 0)) width, height = int(size.attrib.get("cx", 0)), int(size.attrib.get("cy", 0)) @@ -35,12 +35,18 @@ def parse_slide_xml(xml_file, output_json_path): annotation["text"] = text annotation["position"] = {"x": x, "y": y, "width": width, "height": height} annotations.append(annotation) - + + # Ensure output directory exists + os.makedirs(os.path.dirname(output_json_path), exist_ok=True) + with open(output_json_path, 'w') as f: json.dump(annotations, f, indent=4) print(f"Annotations saved to {output_json_path}") -xml_file = "/Users/joshbudzynski/Downloads/example_folder/ppt/slides/slide3.xml" -output_json = "slide3_annotations.json" -parse_slide_xml(xml_file, output_json) \ No newline at end of file +# File paths +xml_file = "data_extraction/slide2UpperLimb.xml" +output_json = "data_extraction/slide3_annotations.json" + +# Run function with dynamic input +parse_slide_xml(xml_file, output_json) From 3ba6b6b557694761509a3cf5d886ccc48c5f167b Mon Sep 17 00:00:00 2001 From: Burhan Khan Date: Mon, 10 Feb 2025 00:58:14 -0600 Subject: [PATCH 056/143] Addresses Issue 74. Deleted the extra script file that wasn't being used anymore. Renamed the main extraction file to avoid further confusion. Added comments to depict what is going on. Added further error handling in case some images or slides are not being extracted properly. --- data_extraction/AutomatedExtractionScript.py | 104 + data_extraction/Hardcoded.py | 59 - data_extraction/script.py | 83 - data_extraction/slide2Pelvis.xml | 11974 ++++++++++++++++- 4 files changed, 12077 insertions(+), 143 deletions(-) create mode 100644 data_extraction/AutomatedExtractionScript.py delete mode 100644 data_extraction/Hardcoded.py delete mode 100644 data_extraction/script.py diff --git a/data_extraction/AutomatedExtractionScript.py b/data_extraction/AutomatedExtractionScript.py new file mode 100644 index 00000000..e42f28c1 --- /dev/null +++ b/data_extraction/AutomatedExtractionScript.py @@ -0,0 +1,104 @@ +import os +import xml.etree.ElementTree as ET + +def extract_images_from_slide_xml(slide_xml_path, rels_xml_path, media_folder, output_folder): + + # Step 1: Try parsing the slide XML file + try: + tree = ET.parse(slide_xml_path) + root = tree.getroot() + except ET.ParseError as e: + print(f"[ERROR] Failed to parse {slide_xml_path}: {e}") + return + except FileNotFoundError: + print(f"[ERROR] Slide file not found: {slide_xml_path}") + return + + # Define XML namespaces (needed to properly find elements in the XML) + ns = {'a': 'http://schemas.openxmlformats.org/drawingml/2006/main', + 'r': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'} + + # Step 2: Find all tags, which reference images via the r:embed attribute + embed_ids = [blip.attrib.get(f"{{{ns['r']}}}embed") for blip in root.findall(".//a:blip", ns) if blip.attrib.get(f"{{{ns['r']}}}embed")] + + if not embed_ids: + print(f"[INFO] No images found in {slide_xml_path}. Skipping...") + return # If no images are found, return early + + # Step 3: Try parsing the relationships XML file + try: + rels_tree = ET.parse(rels_xml_path) + rels_root = rels_tree.getroot() + except ET.ParseError as e: + print(f"[ERROR] Failed to parse {rels_xml_path}: {e}") + return + except FileNotFoundError: + print(f"[ERROR] Relationship file not found: {rels_xml_path}") + return + + # Extract XML namespace for relationships if one exists + rels_ns = rels_root.tag.split('}')[0] + '}' if '}' in rels_root.tag else '' + + # Step 4: Create a dictionary mapping rId (e.g., "rId8") to the actual image file path + relationships = {rel.attrib.get('Id', '').strip(): rel.attrib.get('Target', '').strip() + for rel in rels_root.findall(f".//{rels_ns}Relationship")} + + # Step 5: Process each image reference found in the slide XML + for embed_id in embed_ids: + if embed_id in relationships: + target = relationships[embed_id] # The actual image filename + image_path = os.path.join(media_folder, os.path.basename(target)) # Full path to the image + + if os.path.exists(image_path): + # Extract slide name (e.g., "slide1" from "slide1.xml") + slide_name = os.path.splitext(os.path.basename(slide_xml_path))[0] + + # Create a dedicated folder for each slide in the output directory + slide_output_folder = os.path.join(output_folder, slide_name) + if not os.path.exists(slide_output_folder): + os.makedirs(slide_output_folder) + + # Define output path for the extracted image + image_output_path = os.path.join(slide_output_folder, os.path.basename(target)) + + # Copy the image from ppt/media/ to the output folder + try: + with open(image_path, 'rb') as img_in, open(image_output_path, 'wb') as img_out: + img_out.write(img_in.read()) + print(f"[SUCCESS] Extracted: {image_output_path}") + except Exception as e: + print(f"[ERROR] Failed to copy image {image_path}: {e}") + else: + print(f"[WARNING] Image file not found: {image_path} (Referenced in {rels_xml_path})") + else: + print(f"[WARNING] No relationship found for embed ID: {embed_id} (Referenced in {slide_xml_path})") + +#Processes all slides in the PowerPoint file and extracts images from each slide. +def process_pptx_folders(slides_folder, rels_folder, media_folder, output_folder): + + # Ensure the output folder exists + if not os.path.exists(output_folder): + os.makedirs(output_folder) + + # Iterate through all slides in the slides folder + for slide_file in sorted(os.listdir(slides_folder)): # Sorting ensures correct slide order + if slide_file.startswith("slide") and slide_file.endswith(".xml"): # Check if it's a slide XML file + slide_path = os.path.join(slides_folder, slide_file) + rels_path = os.path.join(rels_folder, slide_file + ".rels") # Find the corresponding .rels file + + if os.path.exists(rels_path): + extract_images_from_slide_xml(slide_path, rels_path, media_folder, output_folder) + else: + print(f"[WARNING] Missing relationship file: {rels_path}. Skipping slide {slide_file}.") + +# Main Execution Block +if __name__ == "__main__": + + # Paths to relevant folders (these should be updated dynamically or via CLI arguments) + slides_folder = "/Users/burhankhan/Desktop/ppt/slides" # Folder containing slide XML files + rels_folder = "/Users/burhankhan/Desktop/ppt/slides/_rels" # Folder containing relationships XML files + media_folder = "/Users/burhankhan/Desktop/ppt/media" # Folder containing media files + output_folder = "/Users/burhankhan/Desktop/AutomatedScript" # Folder where images will be extracted + + # Process all slides and extract images + process_pptx_folders(slides_folder, rels_folder, media_folder, output_folder) diff --git a/data_extraction/Hardcoded.py b/data_extraction/Hardcoded.py deleted file mode 100644 index a76da560..00000000 --- a/data_extraction/Hardcoded.py +++ /dev/null @@ -1,59 +0,0 @@ -import os -import xml.etree.ElementTree as ET - -def extract_images_from_slide_xml(slide_xml_path, rels_xml_path, media_folder, output_folder): - try: - tree = ET.parse(slide_xml_path) - root = tree.getroot() - except ET.ParseError: - print(f"Error parsing {slide_xml_path}") - return - - ns = {'a': 'http://schemas.openxmlformats.org/drawingml/2006/main', - 'r': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'} - - embed_ids = [blip.attrib.get(f"{{{ns['r']}}}embed") for blip in root.findall(".//a:blip", ns) if blip.attrib.get(f"{{{ns['r']}}}embed")] - if not embed_ids: - return - - try: - rels_tree = ET.parse(rels_xml_path) - rels_root = rels_tree.getroot() - except ET.ParseError: - print(f"Error parsing {rels_xml_path}") - return - - rels_ns = rels_root.tag.split('}')[0] + '}' if '}' in rels_root.tag else '' - relationships = {rel.attrib.get('Id', '').strip(): rel.attrib.get('Target', '').strip() for rel in rels_root.findall(f".//{rels_ns}Relationship")} - - for embed_id in embed_ids: - if embed_id in relationships: - target = relationships[embed_id] - image_path = os.path.join(media_folder, os.path.basename(target)) - if os.path.exists(image_path): - slide_name = os.path.splitext(os.path.basename(slide_xml_path))[0] - slide_output_folder = os.path.join(output_folder, slide_name) - if not os.path.exists(slide_output_folder): - os.makedirs(slide_output_folder) - image_output_path = os.path.join(slide_output_folder, os.path.basename(target)) - with open(image_path, 'rb') as img_in, open(image_output_path, 'wb') as img_out: - img_out.write(img_in.read()) - print(f"Extracted: {image_output_path}") - -def process_pptx_folders(slides_folder, rels_folder, media_folder, output_folder): - if not os.path.exists(output_folder): - os.makedirs(output_folder) - - for slide_file in sorted(os.listdir(slides_folder)): - if slide_file.startswith("slide") and slide_file.endswith(".xml"): - slide_path = os.path.join(slides_folder, slide_file) - rels_path = os.path.join(rels_folder, slide_file + ".rels") - if os.path.exists(rels_path): - extract_images_from_slide_xml(slide_path, rels_path, media_folder, output_folder) - -if __name__ == "__main__": - slides_folder = "/Users/burhankhan/Desktop/ppt/slides" - rels_folder = "/Users/burhankhan/Desktop/ppt/slides/_rels" - media_folder = "/Users/burhankhan/Desktop/ppt/media" - output_folder = "/Users/burhankhan/Desktop/AutomatedScript" - process_pptx_folders(slides_folder, rels_folder, media_folder, output_folder) diff --git a/data_extraction/script.py b/data_extraction/script.py deleted file mode 100644 index eaa02835..00000000 --- a/data_extraction/script.py +++ /dev/null @@ -1,83 +0,0 @@ -import xml.etree.ElementTree as ET -import os -import shutil -import argparse - -def extract_images_from_slide(slide_xml_path, rels_file_path, media_folder, output_dir): - print(f"Starting extraction for slide: {slide_xml_path}") - print(f"Using relationships file: {rels_file_path}") - print(f"Media folder: {media_folder}") - print(f"Output directory: {output_dir}") - - # Parse the slide XML file - try: - slide_tree = ET.parse(slide_xml_path) - slide_root = slide_tree.getroot() - print("Slide XML parsed successfully.") - except Exception as e: - print(f"Error parsing slide XML: {e}") - return - - # Parse the .rels file - rels_ns = {'rel': 'http://schemas.openxmlformats.org/package/2006/relationships'} - try: - rels_tree = ET.parse(rels_file_path) - rels_root = rels_tree.getroot() - - # Extract relationships - rels_map = { - rel.attrib["Id"]: rel.attrib["Target"] - for rel in rels_root.findall("rel:Relationship", rels_ns) - } - print(f"Relationships mapped: {rels_map}") - except Exception as e: - print(f"Error parsing .rels file: {e}") - return - - # Ensure the output directory exists - os.makedirs(output_dir, exist_ok=True) - print(f"Output directory checked/created: {output_dir}") - - # Debugging the structure of elements - pic_namespace = {'p': 'http://schemas.openxmlformats.org/presentationml/2006/main', - 'a': 'http://schemas.openxmlformats.org/drawingml/2006/main', - 'r': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'} - - # Find all picture elements - image_found = False - for pic in slide_root.findall(".//p:pic", pic_namespace): - try: - print(f"Processing : {ET.tostring(pic, encoding='unicode')}") - blip = pic.find(".//a:blip", pic_namespace) - if blip is not None and "embed" in blip.attrib: - r_id = blip.attrib["{http://schemas.openxmlformats.org/officeDocument/2006/relationships}embed"] - print(f"Found r:embed ID: {r_id}") - - if r_id in rels_map: - # Locate the image file - image_path = os.path.join(media_folder, os.path.basename(rels_map[r_id])) - if os.path.exists(image_path): - # Save the image to the output directory - output_path = os.path.join(output_dir, os.path.basename(image_path)) - shutil.copy(image_path, output_path) - print(f"Image saved: {output_path}") - image_found = True - else: - print(f"Image file not found in media folder: {image_path}") - else: - print(f"r:embed ID {r_id} not found in .rels map.") - else: - print(f"No or missing 'embed' attribute in : {ET.tostring(pic, encoding='unicode')}") - except Exception as e: - print(f"Error processing element: {e}") - - if not image_found: - print("No images were extracted. Check your slide XML, .rels file, and media folder paths.") - -# Example usage -slide_xml_path = '/Users/burhankhan/Desktop/CSCI-4961-01/ppt/slides/slide2.xml' -rels_file_path = "/Users/burhankhan/Desktop/CSCI-4961-01/ppt/slides/_rels/slide2.xml.rels" -media_folder = '/Users/burhankhan/Desktop/CSCI-4961-01/ppt/media' -output_dir = '/Users/burhankhan/Desktop/images' - -extract_images_from_slide(slide_xml_path, rels_file_path, media_folder, output_dir) diff --git a/data_extraction/slide2Pelvis.xml b/data_extraction/slide2Pelvis.xml index c8757aae..fa13d244 100644 --- a/data_extraction/slide2Pelvis.xml +++ b/data_extraction/slide2Pelvis.xml @@ -1,2 +1,11974 @@ -Bony PelvisHomeRight Pelvis(medial aspect)Right Pelvis (lateral aspect)Bony Pelvis Ilium Ischium Pubis IliumPubisIschiumAcetabulumObturator foramenThe bony pelvis is made up of tow hip bones, sacrum, and coccyx.Hip bones - 3 fused bones:IliumIschiumPubisThe acetabulum is a cup shaped depression that articulates with the head of the femur to from the hip joint, a ball-and-socket type synovial joint.The obturator foramen is an opening created by the inferior ramus and body of the ischium, and superior and inferior pubic rami. This foramen is partially covered by the obturator membrane. The inner and outer surfaces of the obturator membrane provide the origin for the obturator internus and externus muscles, respectively. \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Bony Pelvis + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Home + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Right Pelvis + + + + (medial aspect) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Right Pelvis + + + + (lateral aspect) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Bony Pelvis + + + + + + + + + + + + + + + + + + + + + + + + Ilium + + + + + + + + + + + + + + + + + + + + + + + + Ischium + + + + + + + + + + + + + + + + + + + + + + + + Pubis + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Ilium + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Pubis + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Ischium + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Acetabulum + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Obturator foramen + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The bony pelvis is made up of tow hip bones, sacrum, and coccyx. + + + + + + + + + + + + + + + + + + + Hip bones - 3 fused bones: + + + + + + + + + + + + + Ilium + + + + + + + + + + + + + + + + + Ischium + + + + + + + + + + + + + + + + + Pubis + + + + + + + + + + + + + + + + + + + The + + + + acetabulum + + + + is a cup shaped depression that articulates with the head of the + femur to from the hip joint, a ball-and-socket type synovial joint. + + + + + + + + + + + + + + + + + + + The + + + + obturator foramen + + + + is an opening created by the inferior ramus and body of the + ischium, and superior and inferior pubic rami. This foramen is + partially covered by the obturator membrane. The inner and outer + surfaces of the obturator membrane provide the origin for the + obturator internus and externus muscles, respectively. + + + + + + + + + + + + + + + \ No newline at end of file From c8d321d4b2a576fa25e733a2c3a771f986756bf1 Mon Sep 17 00:00:00 2001 From: Santosh Iragavarapu <157086110+jacksayshi@users.noreply.github.com> Date: Mon, 10 Feb 2025 14:36:13 -0600 Subject: [PATCH 057/143] Update .gitignore to ignore vs code configuration files --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index c2658d7d..20de9305 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ node_modules/ +.vscode/ From 649b65aafe7e5802380669490112473425e4246b Mon Sep 17 00:00:00 2001 From: tluke900 Date: Mon, 10 Feb 2025 15:05:33 -0600 Subject: [PATCH 058/143] Added a new xml for slide 4. Updated reader. --- data_extraction/BoneyPelvisSlide4.xml | 808 ++++++++++++++++++++++++++ data_extraction/XML boneset Reader.py | 6 +- 2 files changed, 812 insertions(+), 2 deletions(-) create mode 100644 data_extraction/BoneyPelvisSlide4.xml diff --git a/data_extraction/BoneyPelvisSlide4.xml b/data_extraction/BoneyPelvisSlide4.xml new file mode 100644 index 00000000..c89cb1ab --- /dev/null +++ b/data_extraction/BoneyPelvisSlide4.xml @@ -0,0 +1,808 @@ + + +Bony PelvisHomeRight Pelvis(medial aspect)Right Pelvis (lateral aspect)Iliac CrestAnterior Iliac spinesPosterior Iliac spinesAuricular surfaceBony Pelvis Ilium Ischium Pubis LabelsClick to edit Master title styleClick to edit Master text stylesSecond levelThird levelFourth levelFifth level2/10/2025‹#›Click to edit Master title styleClick to edit Master text styles2/10/2025‹#›Click to edit Master title styleClick to edit Master subtitle style2/10/2025‹#›Click to edit Master title styleClick to edit Master text stylesSecond levelThird levelFourth levelFifth level2/10/2025‹#›Click to edit Master title styleClick to edit Master text stylesSecond levelThird levelFourth levelFifth levelClick to edit Master text stylesSecond levelThird levelFourth levelFifth level2/10/2025‹#›Click to edit Master title styleClick to edit Master text stylesClick to edit Master text stylesSecond levelThird levelFourth levelFifth levelClick to edit Master text stylesClick to edit Master text stylesSecond levelThird levelFourth levelFifth level2/10/2025‹#›Click to edit Master title style2/10/2025‹#›2/10/2025‹#›Click to edit Master title styleClick to edit Master text stylesSecond levelThird levelFourth levelFifth levelClick to edit Master text styles2/10/2025‹#›Click to edit Master title styleClick to edit Master text stylesSecond levelThird levelFourth levelFifth level2/10/2025‹#›Click to edit Master title styleClick to edit Master text stylesSecond levelThird levelFourth levelFifth level2/10/2025‹#›1Click to edit Master title styleClick to edit Master text styles2/10/2025‹#›2/10/2025Click to edit Master text stylesSecond levelThird levelFourth levelFifth level‹#›/9j/4AAQSkZJRgABAQAAYABgAAD/4QB0RXhpZgAATU0AKgAAAAgABAEaAAUAAAABAAAAPgEbAAUA +AAABAAAARgEoAAMAAAABAAIAAIdpAAQAAAABAAAATgAAAAAAAABgAAAAAQAAAGAAAAABAAKgAgAE +AAAAAQAAAOugAwAEAAAAAQAAAXMAAAAA/+0AOFBob3Rvc2hvcCAzLjAAOEJJTQQEAAAAAAAAOEJJ +TQQlAAAAAAAQ1B2M2Y8AsgTpgAmY7PhCfv/iB+hJQ0NfUFJPRklMRQABAQAAB9hhcHBsAiAAAG1u +dHJSR0IgWFlaIAfZAAIAGQALABoAC2Fjc3BBUFBMAAAAAGFwcGwAAAAAAAAAAAAAAAAAAAAAAAD2 +1gABAAAAANMtYXBwbAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAC2Rlc2MAAAEIAAAAb2RzY20AAAF4AAAFnGNwcnQAAAcUAAAAOHd0cHQAAAdMAAAAFHJYWVoA +AAdgAAAAFGdYWVoAAAd0AAAAFGJYWVoAAAeIAAAAFHJUUkMAAAecAAAADmNoYWQAAAesAAAALGJU +UkMAAAecAAAADmdUUkMAAAecAAAADmRlc2MAAAAAAAAAFEdlbmVyaWMgUkdCIFByb2ZpbGUAAAAA +AAAAAAAAABRHZW5lcmljIFJHQiBQcm9maWxlAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAABtbHVjAAAAAAAAAB8AAAAMc2tTSwAAACgAAAGEZGFESwAAAC4A +AAGsY2FFUwAAACQAAAHadmlWTgAAACQAAAH+cHRCUgAAACYAAAIidWtVQQAAACoAAAJIZnJGVQAA +ACgAAAJyaHVIVQAAACgAAAKaemhUVwAAABYAAALCbmJOTwAAACYAAALYY3NDWgAAACIAAAL+aGVJ +TAAAAB4AAAMgaXRJVAAAACgAAAM+cm9STwAAACQAAANmZGVERQAAACwAAAOKa29LUgAAABYAAAO2 +c3ZTRQAAACYAAALYemhDTgAAABYAAAPMamFKUAAAABoAAAPiZWxHUgAAACIAAAP8cHRQTwAAACYA +AAQebmxOTAAAACgAAAREZXNFUwAAACYAAAQedGhUSAAAACQAAARsdHJUUgAAACIAAASQZmlGSQAA +ACgAAASyaHJIUgAAACgAAATacGxQTAAAACwAAAUCcnVSVQAAACIAAAUuYXJFRwAAACYAAAVQZW5V +UwAAACYAAAV2AFYBYQBlAG8AYgBlAGMAbgD9ACAAUgBHAEIAIABwAHIAbwBmAGkAbABHAGUAbgBl +AHIAZQBsACAAUgBHAEIALQBiAGUAcwBrAHIAaQB2AGUAbABzAGUAUABlAHIAZgBpAGwAIABSAEcA +QgAgAGcAZQBuAOgAcgBpAGMAQx6lAHUAIABoAOwAbgBoACAAUgBHAEIAIABDAGgAdQBuAGcAUABl +AHIAZgBpAGwAIABSAEcAQgAgAEcAZQBuAOkAcgBpAGMAbwQXBDAEMwQwBDsETAQ9BDgEOQAgBD8E +QAQ+BEQEMAQ5BDsAIABSAEcAQgBQAHIAbwBmAGkAbAAgAGcA6QBuAOkAcgBpAHEAdQBlACAAUgBW +AEIAwQBsAHQAYQBsAOEAbgBvAHMAIABSAEcAQgAgAHAAcgBvAGYAaQBskBp1KAAgAFIARwBCACCC +cl9pY8+P8ABHAGUAbgBlAHIAaQBzAGsAIABSAEcAQgAtAHAAcgBvAGYAaQBsAE8AYgBlAGMAbgD9 +ACAAUgBHAEIAIABwAHIAbwBmAGkAbAXkBegF1QXkBdkF3AAgAFIARwBCACAF2wXcBdwF2QBQAHIA +bwBmAGkAbABvACAAUgBHAEIAIABnAGUAbgBlAHIAaQBjAG8AUAByAG8AZgBpAGwAIABSAEcAQgAg +AGcAZQBuAGUAcgBpAGMAQQBsAGwAZwBlAG0AZQBpAG4AZQBzACAAUgBHAEIALQBQAHIAbwBmAGkA +bMd8vBgAIABSAEcAQgAg1QS4XNMMx3xmbpAaACAAUgBHAEIAIGPPj/Blh072TgCCLAAgAFIARwBC +ACAw1zDtMNUwoTCkMOsDkwO1A70DuQO6A8wAIAPAA8EDvwPGA68DuwAgAFIARwBCAFAAZQByAGYA +aQBsACAAUgBHAEIAIABnAGUAbgDpAHIAaQBjAG8AQQBsAGcAZQBtAGUAZQBuACAAUgBHAEIALQBw +AHIAbwBmAGkAZQBsDkIOGw4jDkQOHw4lDkwAIABSAEcAQgAgDhcOMQ5IDicORA4bAEcAZQBuAGUA +bAAgAFIARwBCACAAUAByAG8AZgBpAGwAaQBZAGwAZQBpAG4AZQBuACAAUgBHAEIALQBwAHIAbwBm +AGkAaQBsAGkARwBlAG4AZQByAGkBDQBrAGkAIABSAEcAQgAgAHAAcgBvAGYAaQBsAFUAbgBpAHcA +ZQByAHMAYQBsAG4AeQAgAHAAcgBvAGYAaQBsACAAUgBHAEIEHgQxBEkEOAQ5ACAEPwRABD4ERAQ4 +BDsETAAgAFIARwBCBkUGRAZBACAGKgY5BjEGSgZBACAAUgBHAEIAIAYnBkQGOQYnBkUARwBlAG4A +ZQByAGkAYwAgAFIARwBCACAAUAByAG8AZgBpAGwAZXRleHQAAAAAQ29weXJpZ2h0IDIwMDcgQXBw +bGUgSW5jLiwgYWxsIHJpZ2h0cyByZXNlcnZlZC4AWFlaIAAAAAAAAPNSAAEAAAABFs9YWVogAAAA +AAAAdE0AAD3uAAAD0FhZWiAAAAAAAABadQAArHMAABc0WFlaIAAAAAAAACgaAAAVnwAAuDZjdXJ2 +AAAAAAAAAAEBzQAAc2YzMgAAAAAAAQxCAAAF3v//8yYAAAeSAAD9kf//+6L///2jAAAD3AAAwGz/ +wAARCAFzAOsDASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAA +AgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcY +GRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKT +lJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP0 +9fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQA +AQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2 +Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOk +paanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9sAQwAC +AgICAgIDAgIDBQMDAwUGBQUFBQYIBgYGBgYICggICAgICAoKCgoKCgoKDAwMDAwMDg4ODg4PDw8P +Dw8PDw8P/9sAQwECAgIEBAQHBAQHEAsJCxAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQ +EBAQEBAQEBAQEBAQEBAQEBAQ/90ABAAP/9oADAMBAAIRAxEAPwD+f+iiigAooooAKKKKACiiigAo +oooAKKKKACiivTPhf8IfH3xh10aB4E0x72RMGaZvkt7dT/FLIeFHoOSewNKUkldvQDzOiv138Bf8 +E3NCtLdrv4jeIJ9RmIXbBp4EEStjnMjh2cDtgL/SvobQP2Pfg74ciWSy8L2kxX+O6Bumb1/124D8 +BXl1M5oR2d/RGkaTZ+GHh7wB448Wgt4Y0C+1RV6tb28kij6soIrdvvg18WNNj8698IapEnqbSUjj +6Ka/f648GxafBHa6fH9nSMbVjTiNAPRRgAAdK47V9EmhZWgmdG25OecZ9PwrzHxVQU+Vo7I5bUau +j+fa/wBK1TS5PK1OzmtH/uzRtGfyYCqFfvdqXhm31ayjtNaWO8WZT+7nhWVGT3DZ/lXzd46/ZP8A +hv4ihkm0u1bw/etlvNtcmLcf70TErj2XbXqYTOKFbRPU5q+GnTeqPyior2X4m/Azxx8MZmn1G3+3 +aST8l9bqWix28wdYz7Nx6E141XqHOmFFFFAwooooAKKKKACiiigD/9D+f+iiigAooooAKKKKACii +igAooooAKKK+sP2b/wBlnxP8btSh1fUlk0vwlFIBLdlcPcbT80duDwT2Z+i+54qKlSMI80noBlfs +2/s2+Ifjz4hYyebp/hmxybu+CcMwx+5iJ4Mhzk9Qo5PYH95/ht8OvCnwx8NQeFPBunR2Njb4LbB8 +0jkYLu3V2Pck1seBvBXh/wAFeH7Hwv4ZsE0/TbCIRxRIMADPUnqzMeSTyScnmu7a1G5Nn3Qckfy/ +CvmMXinWf93sPmtoVXCqiqu0KQeo5qusmyMxjBdRz7VbmjLygbMxk5x2GP8A69UpZI4pMkAZODnr +k9KhRVjK5kyeVOdgUlWHJx6g1zmreHft1ufIXMsJG/3BGAa7tocFpF+8oP0quI0ZxJExRscEd68b +McBGTvLqeng8Y47HgGoaZNBqJSZMRQjbxxwo/wAa5+6hluD9kjwE3EknpnGT/wDWr6A1fT4dSkeI +L5dyejDkfl6eteJaxp13YrJHIhSZXyeuSPUV4kqEqUro9inXjUWpyuoaLaXFtJFJGJIpVKOrKChD +cEMp4INfnf8AHb9lGawhu/Gfw2iMkIJkn0xVyyD+JoOckDqUxkfw8cV+j8ssjgl8ksAMfStCIwyK +gEeCB17mvqstzmcLRk9Dx8ThVduJ/O4QVJVhgjgg0lfox+1h+zvCIbv4p+CbQQvH8+qWcYwCO9wi +9j/z0A/3vWvznr7SnUjOPNE89MKKKKsYUUUUAFFFFAH/0f5/6KKKACiiigAooooAKKKKACiivsX9 +kT9nG5+M/i1PEXiC3DeDtEmH2vcxU3M23ckC45IzgyHjC8ZyRUykoq7E3Y0f2S/2W7v4y6t/wlvi +yGSDwjpsgBGCrX0o58pDx+7H8bD/AHRySR+7Xh/w3p+j6fb6dp9sltaW0axRRRqEjSNRgKqjAAFG +h6Naabp8Wn6XAlra24CxxRKERFXgKqgAAD0A4rtLVflUAYx+VfP4qt7V+RLbIYrXbGUxirTxDy/k +OMYyfpUh2j2A7UMpdQ5OBXPGCRlKRlyKd4wAd3YHGBWPclRKHlHIO7FdAYl8zzR94dCaxJoPtFyW +YbW2dunWnU0TsODuQxTkuwx8pbGfp1qKBkkmlCZUoQo+tSm0QPBEDy8pJ+nU1rrYKuW6ls5x1zXD +K9SVn0OpWirrqYk+nyzMXibZIwxnpya4zU7HUpbtVuAskboI8EZYNnknPavXfsReMMfvHgEdsjNY +2q6efKM8ib2VmG7+Ljp+FEsJbWI4V3szwTxVpul6bcJ5Tbty5IUenf8AKufkt1DLLpk37p+Ccc8D +kV3eu2MDt5bjMj4PPfOeK5++t103TobaHklyxA689fw4rhnRTcm3Y6o1XZI5GaFZPNiuP3oYY2kZ +VgeDuB6g+lfkh+0v8AJvhnqz+KvDMTP4X1CXAHU2cz8+Ux/uH+Bv+AnkAn9q4dIibQS0gD3G7PuF +NcN4n8GaX4s8P3/hvxBB9psryIxSRkDBRu4PZlOCD2IBr0cqx0qMlCezM60VL3o9D+d6ivTfi98N +NT+FHjq/8JX+6SGNvMtJyMCe2cnY47Z7NjowIrzKvs001dHKFFFFMAooooA//9L+f+iiigAooooA +KKKKAClVWdgiAlmOABySTXWeCfAvir4i+IIPDHg7T5NR1CfJ2JwqIPvO7HAVR3JOK/Y39nf9iPw3 +8N3tPGPjORNb8RQ4kjBX/RraTGf3aH7zDs7fUKDzXPiMTGmrvcEfFfwQ/Yl8c+Ob/T9Y8fxtoOgS +bZmiP/H5NGeQoT/lluHdvmA/hr9sfAvgDw54C8OWXhrwvYRadp1mNscMYOBk5JJJJZieSSSSeprW +t7cwHaigOw79h6nufpXU2cLFlI+YR8YPc+prwp4ydTRiqJbluO1RE2rkZ6D1rTij2oA3Xviq8Tl5 +zjk9PYf/AF6uFH2kj+LtWfOuhm13KRTc+Sfzp00gCsCcIKSQqsZ3L7Vg3E7zy+WudgHJrF1rC5Lm +jvVojjBGM1Wg/eCSU9FGBUSxSGIp/HJ2HatNo0jgVB6gD61y1Kt3c1hFbFJbUb1nPWPJ/pW8Iuc9 +sfqaqLDmTJ6H+lbtsvzBm+YZpYaL5maTd0LDbE2h2dd6/nXEa8ZlSW2ibIjZd568kH+QxXolxLHb +2qRZ5Zst/T9a4PVWWVpQOdx5/Su6c0tDPldjy57D94sn8CDaB9O5NUvsKXkxgA3MvzEnHA9a39Sm +3RtHGPb0/H8aq2aBFZlGDMCpbHIx2rljFWsU2zHWOSANbRchjt3eozS6jZLbaMHdd4AJbb1967G3 +0xfLVFAVx0J/rS69ZCO0mcKSoHJA4weD+NctajyRbN6c7ux+bf7W3wlHxE+H8vivSIPM1bw8rTQh +PvPbjBnjOeT8o3r7ggda/Hiv6QLq0WC7l06XBhlA255Bz7e4r8T/ANp74Q/8Kp+Is40yHZoOslrm +xI6Jz+8h/wC2bHgf3Ste9w/mLq0/Zz3QsRS5ZabM+b6KKK+iMAooooA//9P+f+iiigAooooAK9X+ +EXwb8afGbxJHoPhW1Ywoy/a7x1P2e1jP8UjepwdqjljwK3PgV8BPF3x28RPpmhYtNMsSjX99ICY4 +Ec8AAffkIB2oOuOSBzX7j/D/AOHXhf4V+H7PwT4LtPs9paDdIzYMtxKcBpZW/iduvoBgAAACvMzL +Mo0I95dEXTpuTsih8Dvgj4O+Dmhf2L4ZtRLKwBu72RR9ouXHd27AHO1Bwo98k/SkcJWHcgPTuP5V +h6fbqmzGMEDPpXQrOzrhRtAJIPt0r52NWUm3N3bCouxAiM0mRhB6nrx6V0ViC+VT5VUdfesgRM8h +YHlh27VqQP5W2CMZAqoe69SHqakC+WAFHC/r71pgr37CsmJzuPOT0FTecQrbjyK1jPoZNXM6+nCk +xk8sahgsz94Dr0P1piWpuZzO/rgZ7CteNZPtG1R+7VcfjXI9XqjZaKxDFFtlYnntT5VDFeOAf1q4 +qjJJFN2Zb8c0ppNWCKa1HRqoOW9DWrZKuP3rDauD+Gc1mnnp0Aqz+98r5BncBn6CoVTlbN4wvYXU +3RbdySCqYP4V53e3Crk5B3etdFqc7gzFs/MeR247157rdyscohBBJSsamJ6l+y6FC8mDsi5wr4PH +oKgt3FtcJM37zcrBU98jB9qyrW4a6lWSTgAYX6A4zW9FbpMq3YBO4hR7Y9q3o1eeNzCpDlZ1li0s +q4c9SCfStu4gW4tmjmUPGRj2+hrLsysSMzf3CD65P8q3bdFW1WWU7QF5Gc/jj1r0FBSVmYXa1PDv +GOjpaSR3WMochcjkbfavi/8Aay8ESeOvhJfXFra+bdaURfQED51aIfvR9Gi3ZHcha/R7xPpv9o6f +JBt3so3L26f/AFq8bhjgeGTTbpFkBypUjIYdCD7dq83Cz+r4pdmds5c9K/VH80NFd98VPCh8DfEj +xL4R2lE0u/uIYwevlByYz+KEGuBr75M5EwooooGf/9T+f+iiigAruvh18OPF3xU8UW3hHwZZNeX0 +/wAzH7scUY+9JI54VFzyfwGSQK7P4I/Ajxl8cfEY0nQIja6dAc3eoSIxggX0yPvSH+FAcnqcDJH7 +dfBn4M+CvgloY0DwlAZLiUB7y9mwbi6kA/jOMBR/Cg4HuSSfPx2YRoq28i4QuW/gt8GNK+Bvw/sf +DmjlLiVsTXtzghri6YDe+D/CMYQdlA75J9SsYvtc7XqDDMS4/r+lXtbuXi0Ayq3L42j3PQ1T0C1e +MRXRP8JDcfKCw5GPevhK9aVSv72p3wglTujqLMMAgIw3Iwev1rVZGwAPmIyeO4qjaqUYvnLPkVoh +l8vZnAH6Z7V6FLY4am5YtXdPmLZVlA24B5rQXfEm/g7u3f2rOhcRFEcjc2frVq4ZViEW7czHHHvW +rl7voZpamjZRiZPMzxmtWGzEsu1h8gBJ/pVTS4ZD83Reg+lb0XyKQOWPX3reEVyrQiW5liDZKSFy +MhQKtNBsmEUYyDnJq2g2yZbseBTWY7yQOuefpRbQChPtjOR0/wDrVXBO/J65pbs7tzvyFBx71HA3 +mEFuPSuKrUXNY3hBj3DyyiCP7uMt67uwrdeMqgwBhQBisbT2ZpmkI3EnAFX7u7MbfJyucEH2qaUo +2c31N5xaaijivENysRwOO5HtXjWrSSXV9vGQSMZ9RmvRvEpea5dVOFJJH864B0BkyeGU4ryMTeU7 +dDqpNRjcprIWYhPlH+Fd1pDo8O7HyquAT3PVj+dclbWY80FuEBP45rqoHYKqRLwMAenv+Vd2HclK +72OWqk1Y6ODkOzceg7mujsZGk+TywSw79gRyaw4I0+WTrgfN7noK27HaCzL27j0r2qcnocLWpKIw +iMpAHUDJ7D3rxjxJpsNnrKzwAKkgz6ZJOTXul2rhY3UBmJAOfQnmuK8WWivppuoY8tEQR3Iz1rkx +9P3dOmp0UJa27n4I/t6+Ff7B+OR1pFxH4h0+2uuBj95EDbuPc/uwfxr4or9SP+Cj3h+5kh8DeLI4 +yYFS6spX/uuSkiA+5AfH0r8t6+vwdTnpRkuxhFW0Ciiiuko//9X+f+vY/gR8JL/40fEWw8HW0htr +TBuL24Az5NrGRvYf7RyFX/aI7Zrxyv3H/wCCdHwXk0T4Z3fjjW7ERXniyYPC7ffOnQYEf0DyF2Hq +ApPauXGV/Z03Jb9PUa8z6I8JeDfD3w48PWXhbwpYrp2nwEoiJ1PA+dz1Zm6sx5P5V2Wn27tKXblW +75xg85rotYsYZdYkgCErCoVT3Ydz+dZunW1w+qKrMWjBI2Dup4BI9j1r88qYhuq+Z9bHpQh7mhR1 +69F41tpcRJABckjooyK2rCBrazhAO5FX88GtSbQPJkadVHmSAEk849h7ZpY43t0EZHyqOO9awou7 +lPdmUqityouQksiSJwSeV9OKsKpI3khnBwcVXgmjCiQEgN1B4IqzbqftDBeVYcV2RmrpI45J2Y87 +UcSbgc8DPc1JJMpk89yP3YAwOmTUc8cySYcem339qfYxiZ2MvIODt9/pTlPXlKhHS7On0yR3w0nA +I4Ht610sJURHHXPWuWsoXmlGCRtP4GukVljUg8LyT7mu2hK6uzCasx8YQyncOO1MmKbgJDtUck9g +BVgbABIOpAHSuZ8STtHplzHED5joVUDrk1FeqoQbKhBuVi5qaxtIEh+4wGCPSqkS7ASgyFGBWRp0 +1yunW/28/vY4wp9j6fWtVZM/uwcknHFeZKopy5kdcYNKxcsHEVu8ifeYkA47VBqUrRWiOOvr6+9X +ljjSNFQ5GP51ia24YfIeFG2tORqJTkmzgdSkOHz97oDXGSyBdxXseveuo1OcHIXo3H5VxlzvMhRf +uOccV5lSetzeC0LNtOC+WBb0ArqLM7I1aZsAc5/kBWDZ23lZCDgAAepA9a3rSIsFjYEliDx2reE5 +WMpRVzdjkJkNtGe2T6ewrY01nQgbjgZBA7ms2CLyyqgYI7e1XLQtA4iGGIyf/rV6mHbTOSpY6e33 +XEYkH3SOh4wR1rJ1SAS6fOvOMHIHuK1ICBEGUHGe3061GwWTcyNkN68g8f8A1q66lNSjbuZRdnc+ +Kf2g/hfH8WPhNq/hKHabsp9osT/du4Pmj57b+UPsxr+eieCa1nktrhDHLCxR0YYKspwQR6g1/TwL +m2juZrViAVkZT7HNfhX+2R8P/wDhA/jjq0ltH5dj4gVNTgwMLmbIlA+kqt+Yr0MgrqVLk7FVLqbT +PlaiiivdA//W/n/r+l39jvRv+Eb/AGX/AARbfaZr2XULA3atK2Si3MjSeUnoibsAV/NGAScDkmv6 +tPgp4RHhH4a+FPC6uXGi6RaWqlzk5Eal/wDx415+ZTfs+WO7Fa7VzrptIjuiVEe2RMyKR1yfl/UV +Sj0MadukRQsrn8hn/Oa7c2arN5v8Sg7fTBxkfpVHUoHJWZsArkHPoe1fKzwMYvma1On6y2rHOXDH +yUafmQHBA6HqRWNMVdGGMFCAcc9a3JrWWXzkX92GHysOf51Sit1TOzDkn5s9c0VIszja5jahGVSO +WNSB0IB7n1qfzBbzROGGH4PbFaHkLIvltllf5hu7HNZl/ZuvyxHaCcj0yPSuCqnH34nTGzXKzsPs +6XiDcD2zis2/sVtgbq0kKsp5Ht/SrGjXH7gRFssB+NaFxArRktnDcEdq669PnjzR3MacrOzJbS5V +o0cHO0YOKsPNuPDdDisK5jisYNyggH0PUnpUto+GCPk7vm+lVDFte5Lcl0eqOlWQAJk+nP8A9eud +1dhc3QjzlRj61fEshOw/dAwB6ms+YpDvlb73Yn8v8a58RU5tOhtShbUjWFZX2fwQkEjP5Vp2UPlq +Hb5nYn9arRA+SDJhR95sVs2q/IrsOME4q8NRV+bqKpUexBcTfZvmHzEDj0z14FctqTExbCcZ5zWn +Nc+fPIQ3AB//AF1zl8SSsRPK9TUzqczfY0cLJdznLyIOSyclRxnoK582zpIEUZHXn3rcuZiH8tR0 +PPpVa5Kxy7wCABkCsKkYtXW4RckyNRvfan3SMcd8da6S3tQHh5AAwWU87gBwB6VkaRavJi8l5OOB +059hXTxLtLs64Kn88/0rWnFWuzKbLY8uRipwC+AP/wBdURAsjOZZDtGc7SRnHbNS3EsdvCHbrnP0 +qC0xImTwTzn0rolK75TKKsrnRQvviUI5TP659az9Su20+1kIGG25A96liJFqQn8PDZ5PWuE8ZamU +tCd2yNBnJ6cetTjK7hC63NKFNSduh4/qOrCG8nZ22SuxkPrX58/8FCLSG90jwN4jIxMXvLcHHJQi +Nxz7HJ/GvrbUb5tT1wIkhCMwBz3PT8q+L/2/9fikHgvwulwryW63Vy8QPzKjCKONsdgdj49cGuvh +lyU2mXmEFzRsfm/RRRX2hyH/1/wO0qEXGp2dux2iWaNSfTcwGa/r60cIlssX/PLgHuR0zX8fMUjR +SJKvVCCPqK/rO+Fni+y8ZeA/Dvi20YGPWtOtbvgg7WliVmU/7pJB+lcuKWzOeuz1NgQC2eTnP41W +kjjuN8ZxuUce1NeViCFIyBUlv87yKvyyHGPcGvLrWeiFTRz91CYGUg4IyD71gwWTwyyz55kOT6Yx +iu0vdKxw53A+v6c1gXMM0Lsrjcp6AdK8uunHVo7IWehgyja+xTgDP4E1l6kZLdT1IYcg9K6gWsW4 +uFzu4INZOrWziDeQCCMN7V4+LjJxdjsoySepzmnardC6XA3ZO329q9FRwyrk5z2NcLo1qHw0w+ce +vXr1rqryTyrcHbkHAPt71WCjKFLmnK5GIcXO0UMvJFmZIyA6jjHuOTT3lRGTauCBx6Yphi3PvU8E +ce5xg0+VAjYwxwcDPQCtJX3JiuhaWeNVLjJJ4xVRrMzOJ7klkByqDu2e9SW8IeUTISQuQB2/WplM +hDOTgA4AHtUtcy1LvYsQguwVunFSzTlFeQEgcrxWP9s+yOSx5HPuSafHci7EawkhRyB3P1/nTjXu +uVblxpa8z2IEVFVnmGNwyD0yBXM31wQolHLOflH16GtzUrwSw5xsVOCPQDrXHSXxkeTChcZRfY// +AFqynOK0uVyyepL5It7Pz5upJY9+/wDWs/Di3M8oIlf+HrgAnH6Vr3mz7Koz8i43E/n/ADrKaWPd +IznATGM9qitJc1jOKe5qaRFNwz9ASVX0ro3VAAGB55PPJIrmbPUI4oGCtguwXP61eN62w3Cnhdxy +3SumlFcqVzKb1uZOqamsMpjcgkHBHTiqreK9Js+DMGc9EHNeC+P/ABQ0N9JbQylWXgsO59K+ZfiJ +8ZdN+GmlfadfuSLm4BMMCEG4lB6EL2Ud2bA/HiscNKtOo4U1dnRKlDkUpM/RmbxPbR2b3hfywuPo +2Op/wr5j+JPxMt7m3+xQzFy5JbGRkZ49xjpX5S67+198V7+aaPRriPTbJ8hYyvnOB6szcE49FA9q +8L1/4j+OPE8ry6zrNxNv6qHKJ/3ymB+lfQUeH5yfNWl9xzKul8KP0l8W/Gvwv4C02bUry586+RT9 +ntEcGWST+H/dX+8x/U8V+Z3jXxjrnj7xNe+K/EU5nvb59x67UUcKig9FUcAVy7MzsWclmPUnk02v +ocLg4UY8sEYSk5PmYUUUV0iP/9D+f+v0j/YZ/ao1/wAJeLtF+DfjC4a78M6tKtnp7bcyWVzM/wC7 +UEDJid22kHO3IIwAQfzcr7c/YL+Dc/xP+N1h4iunEWkeCpIdSuGyMvOr5towM5wXUsxxwFI6kUpW +tqZ1UnHU/orWbKAdd3FaenSOJWZ8Dnj6d8VgJMY7lkGDFkdCDke1S+eyTAEFN2BuPr9K+dqS5ZXJ +p6qx3cgjnTPAzmubntwCVzkjvUY1SRZ/IzhFHBz17VOLuBclsbmA+XqcU5JTV0Ne7ozDntDEGmUk +HIPsayLgvf2piZQCCMEVvapcxSRhITn2Hb61n2irt5HzV5tXDa8q2N41erMe1spIsk4/d8U2Y3Et +tKACcjr3GPUV0F8iwww4UkSHGfcVXhgxI8X8Ljg1w1KdnyHTB6cxlWk8hiWKYbZYvvjtg9x7VqAL +IpZzlCe1WhbRl/mA44/Gkk8uJBg7QOPrUxpNL3mVKSb0IJWSMFRkL7VReby0zgZHOPQUXMjzKT91 +E6H1PrVRXMsWCNzE/TmplLWyBIogPqVyJTlQuRn1q1a5tEEznBGRj3rQtTFDuZlyxGfYev8AjXMX +Oqxyzuyg4XkZ6En2qKNO1pN6nTOV9Fsg1CRRsLqH3HkZxng/1Nc19lWPYbo/PknjoCac146q93cu +AqgnnqST29h+tc1qeozxSC4nBGceWDxkHoaznTjvJEOo9kbeo3U0untbImA8gOScYQd/zrP1loor +bzlJZXVRnrliMceuK5a2XxDrfiO0ijlSDSIyTdFuZG9FX69zXY+JZLSH9xHjzAvygnJHp+NbSwql +FtnL7Zpo4yy1eWaaPT1GJDx+vWuo8Qa9b6NoysW3zuCgXOcLjnI964S0S1028nvAS8yoQpUkKN3B +9q4nXLi61CZlkzsB/DNVRwziveYVKik9Ecjqe6/u7ie4ZTAu6RnY/cA5JP0FfjL8WfHEnxD8d6j4 +jwVtmYQ2yH+G3i+VPxI+Y+5Nfsd8QLe60v4a+LNRtcPJDo98yN3GIWwfqM1+FVfVZNh4JOcV5GEq +km7MKKKK9skKKKKACiiigD//0f5/6+hf2fP2ivFP7Pesavf6DaQ6hba3bfZ7iCbKncmTFIjjlShY +8dCCR1wR89UUpRTVmJq5+1v/AATx+Kvj74it8Ql8WahcamLe4tLyJpWLJCbjzVeOMHhVIRSFHAx0 +61+ltxqiC3LXkhjwcBs4OenfvX8z3wL/AGiviL8AdYku/B12p02+lhe/sZUV4rlYicDJG5G2swDK +QRnuOK/TLXP+ChPwd1i5QxQ6nCigAMbdTjPUsA/X6V5mNwkn/DRlZqVz9BYtTjmu3mW7aVYc9cKq +gjvx1pk2vM0hc8BB1Jxmvz1n/bY+DC26S2WpzuzclJYJgwPuAu3P0NeZeI/25/CMTMdMt7i+c/3V +Kgf997a5aGW1uSyVvmhTqXlezP1Si8VxNu8xwXAwD6D2ro9N8RwllR2Bz3Jr8LdQ/bq16V8WGhlI +1JI33HJ+oCf1pbP9vjxtZsHXRomKngGY4/H5a0/smo1rJX+f+Qc7W0T+g03dhdQR7JAV4GPep0g+ +RimCe3p9K/HL4af8FIvDtxdRWHxE0OfSUYhftVq32iNfd0wrgf7ob6V+ovw5+JXhrx74eh1rwrqU +OqafP80c0DblPHIPcMD1BAIPUVw4rCSg+aa+Z0U6jtY7K78yKQ7Dw2Rj39qoXTHYIly2ep+nWpZp +Hluckn5eSfeswvslYyHPpn+VeFUnd2R2bbjLm4Oxbcj527VSkmW024J3nj2+lXos7pJZDnd3HpXM +aldobjeB8qj5D3z3NYTi7czHTlrZHQz3UjwnBCoE2gDuT6+wrz7UrgWrYU5baST9a0rjWolZLeFx +tiU5HXJ9TXnur65BauzM3nlycbc8UsTjIRWr1Lo0pvRG+RDcQJb3Y3RSjDjPUdxn0xxWF4lnt53X +a25YMAY6DA4AHoOlYKazNfWCzxgkBtg7ZPXj2HemRxvuzu8xxzyeM/SiNaM1ZK6InTcW7snOr3Fj +D5kLBSwHJ9/SuS1K68Q38DSaVH9puHdV+dsDB6ntWlcwSXc7BpMAOvOMrtB5GOOo4rr7C1htlkn2 +ZOPlB969SnaUeZuxxyfK9rmdqGjTJaWdqJf9IlQGRV5VGPXmsLUdIEK7WJbavLdvavQrR/KbzjGB +OQy5+vYVzniY7LLyefNfBbOOD6fhWOIq+7eJVNa2Z5j4hsy/grxPp94pMUul3ik+xhbdj8K/n+r9 +1vjT4nPhL4L+MdSmlxI2myWsRzg+ZdfuFx/33n8K/Cmvo8hi1SbfcifxOwUUUV7ggooooAKKKKAP +/9L+f+iiigAooooAKKKKACiiigAr9LP+CZfiTxDb/FbX/C1vKzaPdaVJdzRkkok0MsaRuB0BIcqf +UfQV+adfsf8A8E2fh7Lovg/xH8UL1NsmuzLp9nnqYLY7pWHs0hC/VDXDmVVQoSbHFXZ+qT3aJGS5 +w0vT6VgfbFMsjy8rjauOvFJNbPKizSnDenTGPSqM0Qh+ZyPkGfx/xr4Dmknc6mk9C7PqCeQIfuZ5 +b+g/DvXDa5qBihKR53E4yPSnXN0yAkv1J59T+Ncre6gtvNmY+dI/3R14/pXn18U5aHVTo2KLXItd +0hQlpCDuzkgAYx/jXO6pJFcyyPHlU4wPUgV0hiikizypcbuf6CudcRwSLEW3O3yjI5Gelc8cM5Gv +t+UqGdhZpBCmxSd3I6tUcd/cWhWFmMcsmQAASccE1vz2jQWpkJAwRg9jWaJlfAK/M3f3/pXWqHI7 +tnO63Next2lqjQiWZunJBPX8K3LSRXGzH7pV4PQDHQAVxNzNISm7hM9D7VM+vpFAzE/dBAH+feuu +FVuVkYzp6XOxnvbW2xLKx3Kp4J43H2/lXCatdC/ujJuDgctzjA9Oa5PVPFiIxLqGJ6ZPU+9YSeKI +I7O81W62C2tIJZpTnDIkalmP5CuvD4Wc372xlOaitD4O/bO+J0t9f2Xw106TbbW+28u1DZ/eEEQo +fopLH/eHpXwbXS+MvE174y8U6p4p1D/XalcPMRnIUMflUeyrgD2Fc1X31CkoQUUc6QUUUVsMKKKK +ACiiigD/0/5/6KKKACiiigAooooAKKKKAL+l6Zfa1qVpo+mQtcXl9KkEMajLPJIwVVHuScV/Tf8A +CHwLZ/DnwJoHgyxRFj0SzigZkGFecDM0n/A5CzH3Nfjb+wL8LJvGfxbPje8t9+l+EI/PDMMq17KC +kCj1K/NJ7bRX7pQxtDH5LDAK/j/9avluIcTqqS6am1KO7JS0007YbgfrWDqki+a8LKQAOG4xk9RW +jLLMspKZRVXA9PrXnXjPxbbeGbU3l4wlnPRT0RfU4r5GUubTzO2ENirqksFqC0zmTHRc4GfU1xF5 +rOlwYurmcSyZGQOueuMe3pXjfin4nx31tKys2CxJ425Xsv0rwPVvFt/OW8pgFySPXmuzC5NOetrI +dTEqOnU+wdW+I2iaed80iiM92P5jrXmNz8e9Nhnk+zWpfk4YDGfz5r5VvLyaWQPOzPn1JNZ0tztw +FAP86+lw2U0qfmzzZzlLc+kz8c76aVmmtFZG6DcTiuo0r4xadeSJHfwtAG43qc4/CvkdLpCRkY+l +a9tcDI2ng1rWy+nII6H26vibT77CWk4mibkHvz+tVryea3YRmNmVl3AgZyD/AFr5+8J6ytrIIpiP +Kk4J/unsfwr2nRfEc8oSOd1cxvt9iM148sv5HzJGkqrtY5HVZra1t7u9v5hFaRI0jyP0RVGST7Yr +8zviF8ffFfiWTVtD0G6ax8PXv7ry1UCWWIH+N/vDf3UHGODnnP3P+2feXVh8Jd2nW4EWo30EE8qj +BWPDSAHH95kAr8ma+lyyglDnaObSQUUUV6hYUUUUAFFFFABRRRQB/9T+f+iiigAooooAKKKKACii +ve/2Zvhb/wALg+NHh7whcxGXTRL9r1DHQWdt88gJ7b8BB7sKUmkrsGftd+xh8L/+FcfArQrOePbq +WuKdVu8jBD3SqY1P+7EEBHY5r6pkh8yZlUjIwPxqfSIorQKqAICAoUcBRjgAegqw0MUTSSLnBbPr +9K+KxcfaN1Hu2awlbQ4rxJqUWh6Vdanc8+WDt9z0A5r4K8Z+Lp9RnlkmkbEh3Fc559K94+LvjJNR +h+xQZSKBmCr/AHiCRk49etfHupt5twSx71z4SjFtS3OiUmlY5jVL57gsSML6VyFxJKCVH510l1GX +8wk7VB/T1rn2jDqWTlR39/avcpyb2MLWMl5y5wwrOl3OTsOMVv8A2cbCJADn0rOntbZOSxGfbOK6 +U5MaaILdPmzmti2wG29M1nogjG5GDKBzWhCrSHg8VMptLUvkR01jcGBwQeRz/wDrr1HQNW3uqjDM +eQP73qPrXkNrG4O9TkH1rrtOYoySISCD27H1rkniOXUJULo779oU2/iH9nvxPaopd7aOG5UEZwYZ +UZv++VB/A1+M9ft28KeIvBuu6HJymp6ddxNjGQ7wsuRnpzg1+Ile3gZqVO6OCnHlbiFFFFdZqFFF +FABRRRQAUUUUAf/V/n/ooooAKKKKACiiigAr9if+Canw2Sz8L+JPijeQ/wCkarMum2jEciC3xJMR +7PIUH/ADX5OeDfCOvePfFGm+D/DNs13qeqzLBDGvqx5Zj2VRlmPQAEmv6cfhF8N9L+F3gHQPAOlj +dBpNqsTPjHmyk75pD/vuWb8a4Mxq8tPl7kyZ6YsXlvhhnOP61R8R3P8AZvh26uomIkgHH1bpW1Ku ++UMPlC4AP1rx34t+IWsdE/s1G+a4547YP+Ar5vFWjC5dLWVj4y8calJNdTSSONxJ4HQZryi4iPls +7n/J5rsPEbSTSZPH1461zaR+Ztt3+bHX/CuTCz2iehVgkrnGy2M827ng/wAqzJLcRKIolG0DtXbX +oMRZI14J/Osj7K+5mfAz04r242Whx3b1ONZg+5VAO3rWdLEQhZB710r2W0kSoASTnbxmqc0IVMIK +qOJ0NlTVzDgUudhG0ehFWxG0UmEXAPTmtGGzxhyMEnt3q8LaM5RQN2cndxjNZzq32NbWK8BOdp5O +Pyrp9LL5AHQ1m28CHMbjDgZ47/jXQ6bEpww4xXlYiXQ2ij1jwZKBeRxSEbZCUIPQhuCPxr8gPiZ4 +Rl8CePdc8KS8rYXLrGf70TfNG34oRX6t6ddyWE9vdJjCODj2r5A/ba8PQ2njXQvFVqB5es2JR2He +S2fGf++HX8q9jhyreE4N7Hn41WqJ9GfFNFFFfRGIUUUUAFFFFABRRRQB/9b+f+iiigAooooAK6/w +R4E8V/EbxBb+GPB+nyahf3BHyoPlRe7yN0RB3Y8VyFfuf+wV4H0jTvgXp/iG2gT7br1xcy3MuPmI +ilaFAT1woTgdOScc1hia/s481ribNb9jf9kRfgzLP418Xyw3/im6QwxGLLQ2kLfeCFgCzvxubAwP +lHUk/oXJAyBSnUcL+PWqGli3gjCDjGB6da6tkQrkfwjivIqTdXVnPJ21ZjyMwjDE7cLx/vHgV8pf +FP7Ve61N5iny9uFHoAAM/pX1XfK8S5jXeFwPwNfOPxIt3OqF2xkAjjjgjI/Ovn82baiuzO/Buzuf +I+t2zfaOf4D36HFc2gWOTeeFPSvR/E9uy3BkUYHQ1wOoWkjIUt32FwVD4ztY8Ake1c+EnaSOytrE +x3XexZSG65+mcVQMYKkcbue2cfjXTTQiGBQXUunBP045rN2KVYxDfn8h+Ir6CR58WcldQqBtXlgM +tWZ5LTfMmFx3rq7m0IDM+CzDqOgqlaW2AzP/AAdh0rkqLW1zspS0uZMcOFJkHHrip0tSdxYmQdfc +DHTirSukkvlbcKOcj1rUSIbiB6ckcVEatuprJXMaC0ETrFESQO7HJOe1b1hCvmbAcnOCKbGoilWT +aCN3GelSQwsbw+V95jyOw9687E1ru5vTibtnDI56EqCMe9eEftw24fw54OuI0O22knjJ7DzURgD9 +dh/KvpuK2VTGittRMZPqRXjf7U2lXGt/B28vnX97ptzb3BAHIUHyjn8HzXvcO0Wpyb7HkZhXvKPq +flhRRRX05AUUUUAFFFFABRRRQB//1/5/6KKKACiiigAr9O/+CdPxxu9I8Xf8KW1uVTpeq+ddWJbq +l0oDPGP9l0UsB/eH+1X5iV9gfsMeCr7xf+0ToNzag+ToCTajKw7CNCiAn3d1/DNZ1mlFuWxM43Vj +9/dcvUBkl+dAcAeXgMcf561taJ4ohvpDDcnbI4yOCQR0/rXNXNuHtZJJXIdDwOoHua4iW+vtMv0Y +LtwR04H1xX5/DNVGrdLRnU8K3GzPoi8RUsfMIxjn8K8A8f2jXMUF2vL/AHW5Ge+M/hXdt4ojktUN +24LBSSB0BxXl+q6gmpTXtjFES5QyNJu4Tb0/PtXXj1zw90xoe69TwnxLp52scfvIzj6r1BrzmSFk +VuuDXslysM8X2eU75IshjjGV6/mK4XUrI2kmY/miP54P9K8aDs7no3TVjlzCtwgjmiQ4Gc9SR9Kx +XsG3EAYjU9VHrXSQrHHLux8pzn6e1aohtDGQqln4yD6V9Bh6vtY8t9Tz6q9m7nntzpjSLt+6Oo9T +WJcWv2dPIjDOrg8Ac59zXqElmAS8rEL6celc1cCJWcYBzxVVaPXYdOt0ONtdKYDMj7R2C9f/ANdb +MFrFMG88AbT93rj0zTZgwfI+Ue3amRTxIME7stz7/WsfZxSOj2je5DqCRzIUhGQw4b0+lXNKwskM +d4wVSQucc49/xqyJIRb+ecEf3eMD6VkS3ljMyxTnaCc5xz+FEMG5asmeJS0R30NxZRyMrSF5CcJt +OMN1Bq5Npdl4n0LUPDerRGS3v4ZYJvXDjH59x71xv2WBLqCDTJDJCwydxyQe59vpXZ+Hry4tp5rO +7iAZDncnKtnuDX0eGgqVrHiVZc92fi/4z8Kan4I8Uaj4W1dClzp8rRkkYDr1Rx7MuCPY1zFfrJ+0 +18Bv+Fl6GPGHhiL/AIqPSoiDGv8Ay9QLlvL/AN9edh78r6Y/J10eN2jkUqykggjBBHUEV6d09Udd +CrzR13G0UUUGwUUUUAFFFFAH/9D+f+iiigAooooAK/Yv/gnZ4Lbwv8Ptf+J17EEn1ufyLZ24b7Pa +ghiP9lpWI9ylflN8PvAuu/ErxlpXgnw3CZr7VZliX+6i9Xkc9lRcsx9BX9B2iaJpPgDw3ongbQyV +07RrVLdSRgv5QwXPu7ZY+5r5/iHGqnR9mnrI6cLS5pa7I9Oi1KaW1BmdXNwQxxnt2FVdbY3KJHGB +52C2fQAc1g2EUt8B5cm3yxlT2UdyfX2qrean9gje5cmTpHk+p9PpXw1PRWZ2VFd3RzF5qtxDuBJK +r1XPcVw2qateyQu0Uux5D8x9h61dutRS4uJVjB2vnax9a5zUEUqFX+LJznpXt5c+dXW5x4j3HqMb +xFGiCGSb9+qjMmMZPrirNleQ3tusTyq0hBz614l4gnngmbyeozgf1rldN1/XbadfLJkAORnqPoa9 +meRSl78PuOT65HY+kJbOBgQ64brx049K55yYGMhBwPwrMsvFes/ZgZ7IStjIIYA5968v+K3xo0jw +Zp6trak3MozDaRkedIfUnoqDuT+GTXPh8mqqouxNTGJxstz0fWPENhZwNJNMI40XLMxAAAHWvmvX +/j/8PrJ8W+otdSIxB8pHYfnjBr5S+I3xp17x6n2KOIadYH70aNuZ/ZmwOPYD614xX0zwdLqrmVGn +NK8nY+1r/wDad0WGXbZWVxdpjqQsY/DJJ/SsiD9pjTpZWW70qeFG43I6vj324X+dfIFFSsJSX2To ++Z9hv+0ppdsxFvZ3N2hHCtsjAP1yxr1X4f8AjF/iboV3rcUf2H7JObcRBg7ZKB0YnA68/lX5z190 +/so6U7+EvEGoZys17BHjsBFGxJ9Od4FRiYQhTcrEOB77oOtXOjPPFqAFwsmCrH5XUjptPT867nRv +F2j6ncM+Ps16vVSeCPUY457153rKK21kGML/AFrhbpJklVoyV/2gcEGsKUeeN0Yygrn3NoesW77E +JHbI7V+bv7X3wPuPD2tzfE7w1bg6RqLA3qRj/UTscCTA/hk7ns/XqK938L+NbnTysN05mCkYLHJF +e92WvaJ4q0ybQtYRJrS8QxvE43K6sMFSD6g1005cr9450nCV4n4UUV9N/Hn9nfWfhjqM2s6DG9/4 +amYski5d7fP8EmOw7N0Pfnr8yV1HoQmpK6CiiigoKKKKAP/R/n/ooooAKKKKAP2g/YG+Dlh4Q+GU +/wAZ9Xgzq/iYyW9mXAzFZxMVJT0MjjJPoq475+itenmuJIpuUJYrgHkDt+NaXgqNtA+EPw+8KWsY +Elpolj5i9CJGiVmHHvnNURE630FtNlpFRi57BiecfTOK/NM9r+0xElfbQ9nBUrU+Y7bwlcW50uS3 +XKyY2uW4PJz1+lYOtkXMixwf6hOufXp0p/nCEvbxNtRcBj2x3/GufnvSJJWU7z8wUeh/zmvPqSvF +J9CYxs2zA1AWslysIfZt+7gcDH0rG1+3jtUAUgFlDfifSrpB84koS2ev4Vm+ISJFVBy5VcmvoOH6 +a5mebm03y6HkGoWjXEjEc57/AFrQg0e1to1bYPNUZZj2rqorBOOOFIrxf41fFPTfhrom84n1K7DL +bQA4JOMb2/2FPX16V94m3oj5uKb0RwXxn+OWn+AbZtC8Ostzrsq9uUtwejP7+i/ieOv54axrOq6/ +qM2q6zdPeXc5y8khyT7ewHYDgVVvLu6v7qW+vZWmnnYu7ucszMckkmq1Ny6HsUMOoLzCiiipNwoo +ooAK/Qv9lyyuh8KtSn80WyTai+CwwXVY0BKn0zwfevhnwf4V1Pxt4jsvDOjhftN6+0M5wqKBlmbH +OFUEnAJ9K/Ve+8NaZ4R8NaR4e09z5NnapCpKhBIUAUvgZwWbLHPc15maV1GHs+rKjG7OP1KVzN5S +SBlbp7AVh3GAPKnUlT0NankFbjD4O0ZA9M1l3bozscZA4II/lWVB2hYzkryMuWONjujYLgfTBq7p +/iKa0lC7zlTwQf61QCXeTLFFvKnhgeo96W8jmkhDxxiMHtx/nFddBObtImpaJ9CeG/G0Gs2h0nUo +1uIJFKMG5OCMHjvXzJ8Q/wBl3QNc1C71XwXcHTdp3ywhQ8IDd1XIZMntkj0wK6DTbbxGsDGEBTjc +uPlLKPen6X4lv9PkuJXMsZn+WTdk4A+taYahVpVHF6xMaji1zQep8DeNPBOt+BNYbRtbjAcjfHIu +SkiHupOPxHY1yNfX/wC0tqFvqXh7w1cI3mSedcZbHIUqmBXyBXU+jta5tTldahRRRSND/9L+f+ii +igArc8Mm2XxJpLXgDQC7gMgYZBTzF3AjuMVh1NbTtbXEVynLROrj6qc00DP6j4dONzD/AGmyqFhG +AQPl29gK821NQl5LK55xkt2UVj/D/wCJun674V0/Wo5SLbWLK3nX5wwXzVDYwD1U8H3FW7mb7XII +UjkEbAOSQAWX1PoK/O8bSipOK3/U9GhJ21McagQlzIVAWMYGO2O5/GuftLw7mmXqDuLH254/OrWq +X9jYLNBG28Sk4Oe+Ow9K8+l1UGD7HG215H6g4x9a8iNN9TplrsdFd38kl0+1NnmH+9/nms+cu98b +ccgfKPbaOalN1ZQRyXUriV15BIIyTwOD6VTsp1mR7k9eefqK+vyOi1HmPAzOor8o/KxWQSIZkJIJ +I75zn8K/Iv8AaB8Uv4p+KOrOH322mv8AY4R2Cw8N+b7jX633Eg4UcBRgV+J/jjTbvSPGWt6bfrtn +gvJ1Yf8AAyc/iOa+noy0aOPCxvPmOWooorQ9EKKKKACitfQdC1XxNrFpoOiW7XV9eyCOKNepY+vo +B1JPAHJr7t+HH7J1hoWonUfiROmpGEqYbe3LLCWU5zIzAFx7AAepNZ1KsYK8mS5WNT9lj4LS+FbZ +Pi54tQLd3MDLpdow5RJhgzyf7yEhF9G3HtXr3ie/uNQ1QeaNy4+UDCgL7Cuv1fxBehRBAFjjTgEc +cDgYA4AHpXmt5M1w5zIfN6EketeDVvObqS+XoOM9LGbvjSbY38Z6+/pVSZjGSH5B70k8RgJIwzjn +AOCfeqclv9ow0Lt0JZTz+VXCT2W5Tihha2RN8crI4zkDgHNSWl0Ip2LRCWFeRgHnI6k+3pTbO0WR +C1xId6nCrtPT3roLM6bZWjPqUpkwCAmdrAnpXoYVs5cS0kyO7uGe2JRcNt4UH19eOBXzX4x+Jth4 +eupdNkiE9wOSkZ+77Meg+ldj8QfippvhW1mitGEt9MD5MKnO3jAdz6Dr718O3NzPeXEt3dOZZpmL +ux6szHJJr2edRjZrX8jnw9Jt36HWeLvG2p+LjbRXSrDbWe7yol5wXPzMT3JwK4yiiuaUm9z0IxS0 +QUUUUhn/0/5/6KKKACiiigD9m/2EvGOmeKvhY3hq9I+3eHZWt23EH91IWlhbB6Dll/4DX0V4v8Q2 +lk8tv5DN/eC9G29ASO341+N/7LXxRf4cfEiG0upAmmeIdlncFjhY3LZhkz/stwf9ljX6vX2t2V/P +NFExQD5HYgkbj9a+TzvCNVFOC0f5nThp2vc5K91ozj7W0QLMu0AdFz6CuTknW1YwE7JJmPI+8Bj1 +rcurIC6Kbh5eCF5wM+vHes2WxtrVozIPMuHYZLdFHtXiJR2tqd/S5d89pIPMkk3DIwDySR61r2lx +5GnOg++wB57d6xrUNMGGMKrHke1blza7YQwPDfpivs8vgo0bo+WxbcqlmSQqssgV25fGa+Gf2lfg +V4p1bxi3i7wXp51KG8jRbpIcb1mQbd+0kZDADJHcHPWvte582ODzYD8w5q/o91dyQu/QrktkdhWk +asYz8xw5kuaJ+Stt+z38Z7sqIPCl2dxxk7AB9SW4rrLf9lP4wTPsnsre2YY3CSdSVz67A3Sv1g8z +ADLOIA3p1qlcS4je0iQyGQZ8zPB59aHjX0idSm31PzSj/Y5+IxCNNf2MYbBOGkYgHuBs5rvdN/Yy +tVx/aviCaYlekMCxAHvy5bIH0FfdCMwwnA4HU4B9azrqVrU73kAVicZHWlLFz/r/AIIlJvqeY/C/ +4J+AvhtL/aGlW5n1JVIN1O/mSBT1C4AVQenA5713mr3IMsiK/Bzkk8CpZr8MMJkA8g4rmZ7uxt0e +e7kEcKgl2b5a5pN1JLqJ+7dszJOclW3H9K5+9QvNiUE7hhdp/KvOfGP7Q3w/0Nn0/Tbhr6VMqfs6 +bhk/7Zwv5GvG7r9ovSLtwzWdwgTocjcfTkGuyWCqWsRSnd81j6Uns7m4bZEoXcOWPcDr1/pUESW9 +jtm+0bZAc4I4HqK+Ub39oe42eVp1i5X/AKauB/LNcBqvxk8U6jE0UYjtw2eQCzc/U4/SilgmneTN +pczVkj7K1nxbo3hzN7fX0EcbdRnGPw6n8K+bPGnx1+2yTW3hu3wpyBPL/NV/ln8q+eLu/vb+UzXs +7zue7sTVSu+mlD4SFQT+Is3l5dahcveXsrTTSHLMxyTVaiik2dCQUUUUAFFFFAH/1P5/6KKKACii +igBQSDkcEV+r37MvxVm8d+Bn0vUY/tWqaMI4LlnOWkTB8mXJ6kgFW9xnvX5QV6b8KPifrPwo8VR+ +ItLT7TA6mK6tWYqk8R/hJGcEHlTjg+2RXNi8P7SDj1C5+u2qXEtmGZIEJbJBbnv3x7VztzfxuySh +AVY4B75HX6Vg6B8TvDPjPRbbxLpjs9jOCpjODJDN3SQDoR69CORxTpLr7Wf9HXCknHGK+cqZZV6x +N6WKgt2eg+G4RdKY1YNknn364rotShjtgLY/NsGfXGecV5doerHSrs7G3h+uDgiu9fUkvmaRjhjj +pzjivWwclZRtqkeVjYWba6mNfMwG9TnHYVVgl1A5CHy1fg+/tWy8KsOm4N0/CovLkSQEYDd89K2q +0XzXMaVXSxVeB5ZN9w+WYcAHjj1FTx3TRqIJvmRTwAORUmxgRkEZ9Rn3xUFxbSgCWJ/3Z4OSRn6C +uSULfCdcZfzEd1Iz7hCpIxkf4GmpdRvamKQB0dsBSD+NWwscKeY53DoQRz/9euduNSKSNFbgnJPT +rV+z5fekxKonoifVXh0+KZ7iQLH5bNnPtxX5bfFf4pav4516dLW4lg0i2Yx28KsQGA4LsB1Le/Qc +V9i/Gz4iab4T8N3emM4bUb6Nlij3ZcFhtyQOgXrnua/NqvQw0LLmtZl0lfVhRRRXQbhRRRQAUUUU +AFFFFABRRRQAUUUUAf/V/n/ooooAKKKKACiiigDuvAfxA1vwDqn23Tds9rKV+0WsuTFOqnOCB0I7 +MOR+Yr9HPAnxF8LeP7Nr7w+Et5bhUL2ZcG4hkXhsqeq8jD4wc84NflTWjpWr6poV/Fqmj3UlndwH +KSxMVZT9RTeujMKtG+q3P1S8W2eoWEtvqx3W8coAKtx8w64x2rT0zxbEPLVyCpwTjqCP518MWP7Q ++u6lHFa+M0+1CJAi3EKgSk56upO0/hircPxm0K2n8yNbnjowUfyJrKnQau27kyjzRSaP0o07WbC6 +t8+cpI65PSrHm2Lby91GiDsQD+f1r84pf2i4oLdfs1pLcS88OQgH1POa8y1z43+OdYnd4bhbGFv+ +WcS5/MnJrSa5tGjmjhHe6Z+skfiLSY1aFpVcr1YMGAA9+leSeNP2hfh94YR4ZtRiup1O0xW5818r +xzt4GPc1+Weo+J/EGrYGoahNMo5ClyFH4DisIkk5NZQpW6nR9VTd2fdev/tc2PkTLoemyXE5BCGc +BIwexIBJI9q+ZtY+NPxO1pj9o16eBCc7LbFuo/79hT+Zry2iqUEuhtGlFdCWeee6lae5kaWRySzO +SzEnuSeaiooqzQKKKKACiiigAooooAKKKKACiiigAooooA//1v5/6KKKACiiigAooooAKKKKACii +igAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA/9k=/9j/4AAQSkZJRgABAQAAYABgAAD/4QB0RXhpZgAATU0AKgAAAAgABAEaAAUAAAABAAAAPgEbAAUA +AAABAAAARgEoAAMAAAABAAIAAIdpAAQAAAABAAAATgAAAAAAAABgAAAAAQAAAGAAAAABAAKgAgAE +AAAAAQAAAQygAwAEAAAAAQAAAWgAAAAA/+0AOFBob3Rvc2hvcCAzLjAAOEJJTQQEAAAAAAAAOEJJ +TQQlAAAAAAAQ1B2M2Y8AsgTpgAmY7PhCfv/iB+hJQ0NfUFJPRklMRQABAQAAB9hhcHBsAiAAAG1u +dHJSR0IgWFlaIAfZAAIAGQALABoAC2Fjc3BBUFBMAAAAAGFwcGwAAAAAAAAAAAAAAAAAAAAAAAD2 +1gABAAAAANMtYXBwbAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAC2Rlc2MAAAEIAAAAb2RzY20AAAF4AAAFnGNwcnQAAAcUAAAAOHd0cHQAAAdMAAAAFHJYWVoA +AAdgAAAAFGdYWVoAAAd0AAAAFGJYWVoAAAeIAAAAFHJUUkMAAAecAAAADmNoYWQAAAesAAAALGJU +UkMAAAecAAAADmdUUkMAAAecAAAADmRlc2MAAAAAAAAAFEdlbmVyaWMgUkdCIFByb2ZpbGUAAAAA +AAAAAAAAABRHZW5lcmljIFJHQiBQcm9maWxlAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAABtbHVjAAAAAAAAAB8AAAAMc2tTSwAAACgAAAGEZGFESwAAAC4A +AAGsY2FFUwAAACQAAAHadmlWTgAAACQAAAH+cHRCUgAAACYAAAIidWtVQQAAACoAAAJIZnJGVQAA +ACgAAAJyaHVIVQAAACgAAAKaemhUVwAAABYAAALCbmJOTwAAACYAAALYY3NDWgAAACIAAAL+aGVJ +TAAAAB4AAAMgaXRJVAAAACgAAAM+cm9STwAAACQAAANmZGVERQAAACwAAAOKa29LUgAAABYAAAO2 +c3ZTRQAAACYAAALYemhDTgAAABYAAAPMamFKUAAAABoAAAPiZWxHUgAAACIAAAP8cHRQTwAAACYA +AAQebmxOTAAAACgAAAREZXNFUwAAACYAAAQedGhUSAAAACQAAARsdHJUUgAAACIAAASQZmlGSQAA +ACgAAASyaHJIUgAAACgAAATacGxQTAAAACwAAAUCcnVSVQAAACIAAAUuYXJFRwAAACYAAAVQZW5V +UwAAACYAAAV2AFYBYQBlAG8AYgBlAGMAbgD9ACAAUgBHAEIAIABwAHIAbwBmAGkAbABHAGUAbgBl +AHIAZQBsACAAUgBHAEIALQBiAGUAcwBrAHIAaQB2AGUAbABzAGUAUABlAHIAZgBpAGwAIABSAEcA +QgAgAGcAZQBuAOgAcgBpAGMAQx6lAHUAIABoAOwAbgBoACAAUgBHAEIAIABDAGgAdQBuAGcAUABl +AHIAZgBpAGwAIABSAEcAQgAgAEcAZQBuAOkAcgBpAGMAbwQXBDAEMwQwBDsETAQ9BDgEOQAgBD8E +QAQ+BEQEMAQ5BDsAIABSAEcAQgBQAHIAbwBmAGkAbAAgAGcA6QBuAOkAcgBpAHEAdQBlACAAUgBW +AEIAwQBsAHQAYQBsAOEAbgBvAHMAIABSAEcAQgAgAHAAcgBvAGYAaQBskBp1KAAgAFIARwBCACCC +cl9pY8+P8ABHAGUAbgBlAHIAaQBzAGsAIABSAEcAQgAtAHAAcgBvAGYAaQBsAE8AYgBlAGMAbgD9 +ACAAUgBHAEIAIABwAHIAbwBmAGkAbAXkBegF1QXkBdkF3AAgAFIARwBCACAF2wXcBdwF2QBQAHIA +bwBmAGkAbABvACAAUgBHAEIAIABnAGUAbgBlAHIAaQBjAG8AUAByAG8AZgBpAGwAIABSAEcAQgAg +AGcAZQBuAGUAcgBpAGMAQQBsAGwAZwBlAG0AZQBpAG4AZQBzACAAUgBHAEIALQBQAHIAbwBmAGkA +bMd8vBgAIABSAEcAQgAg1QS4XNMMx3xmbpAaACAAUgBHAEIAIGPPj/Blh072TgCCLAAgAFIARwBC +ACAw1zDtMNUwoTCkMOsDkwO1A70DuQO6A8wAIAPAA8EDvwPGA68DuwAgAFIARwBCAFAAZQByAGYA +aQBsACAAUgBHAEIAIABnAGUAbgDpAHIAaQBjAG8AQQBsAGcAZQBtAGUAZQBuACAAUgBHAEIALQBw +AHIAbwBmAGkAZQBsDkIOGw4jDkQOHw4lDkwAIABSAEcAQgAgDhcOMQ5IDicORA4bAEcAZQBuAGUA +bAAgAFIARwBCACAAUAByAG8AZgBpAGwAaQBZAGwAZQBpAG4AZQBuACAAUgBHAEIALQBwAHIAbwBm +AGkAaQBsAGkARwBlAG4AZQByAGkBDQBrAGkAIABSAEcAQgAgAHAAcgBvAGYAaQBsAFUAbgBpAHcA +ZQByAHMAYQBsAG4AeQAgAHAAcgBvAGYAaQBsACAAUgBHAEIEHgQxBEkEOAQ5ACAEPwRABD4ERAQ4 +BDsETAAgAFIARwBCBkUGRAZBACAGKgY5BjEGSgZBACAAUgBHAEIAIAYnBkQGOQYnBkUARwBlAG4A +ZQByAGkAYwAgAFIARwBCACAAUAByAG8AZgBpAGwAZXRleHQAAAAAQ29weXJpZ2h0IDIwMDcgQXBw +bGUgSW5jLiwgYWxsIHJpZ2h0cyByZXNlcnZlZC4AWFlaIAAAAAAAAPNSAAEAAAABFs9YWVogAAAA +AAAAdE0AAD3uAAAD0FhZWiAAAAAAAABadQAArHMAABc0WFlaIAAAAAAAACgaAAAVnwAAuDZjdXJ2 +AAAAAAAAAAEBzQAAc2YzMgAAAAAAAQxCAAAF3v//8yYAAAeSAAD9kf//+6L///2jAAAD3AAAwGz/ +wAARCAFoAQwDASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAA +AgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcY +GRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKT +lJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP0 +9fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQA +AQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2 +Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOk +paanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9sAQwAC +AgICAgIDAgIDBQMDAwUGBQUFBQYIBgYGBgYICggICAgICAoKCgoKCgoKDAwMDAwMDg4ODg4PDw8P +Dw8PDw8P/9sAQwECAgIEBAQHBAQHEAsJCxAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQ +EBAQEBAQEBAQEBAQEBAQEBAQ/90ABAAR/9oADAMBAAIRAxEAPwD+f+iiigAooooAKKKKACiiigAo +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 +Ef8ATQmsCiq5n3Fyo6iXxr4tnj8qTVrgp6byP5Vz011c3LF7mV5WPJLsWP61BRSuwSCiiikMKKKK +ACiiigAooooAKKKKACiiigAooooAKKKKAP/Z/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMDAsK +CwsNDhIQDQ4RDgsLEBYQERMUFRUVDA8XGBYUGBIUFRT/2wBDAQMEBAUEBQkFBQkUDQsNFBQUFBQU +FBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBT/wAARCACQAQADASIA +AhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQA +AAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3 +ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWm +p6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEA +AwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSEx +BhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElK +U1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3 +uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD9U6KK +KACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAoooo +AKKKKACiiigDwP8AaO/trUPiD8NdH0oX91DeLqj3Gn2fie70FZ/LhiKF57Y7jtJJCkEcmvPvCP7R +HjDT/hfcXVvfWGot4K8LxanrDa1A893rU63l9aTW9vNHMqjDae6JORL5rTRNtGfm+nvFngDwx4+h +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 +6VYooqiwooooA//ZLuke TomlinsonLuke Tomlinson12025-02-10T20:00:11Z2025-02-10T20:03:00Z036Microsoft Office PowerPointWidescreen171100falseFonts Used3Theme1Slide Titles1AptosAptos DisplayArialOffice ThemeBony Pelvisfalsefalsefalse16.0000 \ No newline at end of file diff --git a/data_extraction/XML boneset Reader.py b/data_extraction/XML boneset Reader.py index 6affbccd..d0cb5efa 100644 --- a/data_extraction/XML boneset Reader.py +++ b/data_extraction/XML boneset Reader.py @@ -24,7 +24,7 @@ def extract_bones_from_xml(xml_path): bonesets = {} # Dictionary to store bonesets - INVALID_BONESETS = {"Home", "The", "Title", "Slide", "Overview"} + INVALID_BONESETS = {"Home", "The", "Title", "Slide", "Overview", "Labels"} # Extract bonesets dynamically @@ -40,6 +40,8 @@ def extract_bones_from_xml(xml_path): continue if not boneset_name or boneset_name in INVALID_BONESETS: continue + if not all(word.isalpha() for word in boneset_name.split()): + continue if boneset_name and boneset_name not in bonesets: bonesets[boneset_name] = set() @@ -108,7 +110,7 @@ def is_valid_bone_name(name, boneset_name): current_dir = os.path.dirname(os.path.abspath(__file__)) # Define the XML and JSON file paths relative to the script's directory - xml_file_path = os.path.join(current_dir, "slide2Pelvis.xml") + xml_file_path = os.path.join(current_dir, "BoneyPelvisSlide4.xml") json_file_path = os.path.join(current_dir, "output.json") # Extract bonesets and their bones From 58ca55fd39a8de60f0d92e21cec866e971aee822 Mon Sep 17 00:00:00 2001 From: tluke900 Date: Mon, 10 Feb 2025 15:49:45 -0600 Subject: [PATCH 059/143] Nearly works --- data_extraction/XML boneset Reader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data_extraction/XML boneset Reader.py b/data_extraction/XML boneset Reader.py index d0cb5efa..0bfa6510 100644 --- a/data_extraction/XML boneset Reader.py +++ b/data_extraction/XML boneset Reader.py @@ -43,7 +43,7 @@ def extract_bones_from_xml(xml_path): if not all(word.isalpha() for word in boneset_name.split()): continue - if boneset_name and boneset_name not in bonesets: + if boneset_name not in bonesets: bonesets[boneset_name] = set() # Extract bones within this boneset From ed5b4f4f911446b8f15ee97d3657bb734380a14b Mon Sep 17 00:00:00 2001 From: JoshBudzynski <157167337+JoshBudzynski@users.noreply.github.com> Date: Mon, 10 Feb 2025 16:07:30 -0600 Subject: [PATCH 060/143] final_Issue79 --- data_extraction/extract_ppt_annotations.py | 11 +- .../slide2_pelvis_annotations.json | 74 ++++++ data_extraction/slide3_annotations.json | 238 ++---------------- 3 files changed, 96 insertions(+), 227 deletions(-) create mode 100644 data_extraction/slide2_pelvis_annotations.json diff --git a/data_extraction/extract_ppt_annotations.py b/data_extraction/extract_ppt_annotations.py index 72f7cbf0..5cbed578 100644 --- a/data_extraction/extract_ppt_annotations.py +++ b/data_extraction/extract_ppt_annotations.py @@ -15,7 +15,7 @@ def parse_slide_xml(xml_file, output_json_path): annotations = [] middle_x_min, middle_x_max = 2000000, 6000000 # Define X range for middle - middle_y_min, middle_y_max = 1000000, 5000000 # Define Y range for middle + middle_y_min, middle_y_max = 1000000, 7000000 # Define Y range for middle for sp in root.findall(".//p:sp", ns): annotation = {} @@ -37,7 +37,10 @@ def parse_slide_xml(xml_file, output_json_path): annotations.append(annotation) # Ensure output directory exists - os.makedirs(os.path.dirname(output_json_path), exist_ok=True) + output_dir = os.path.dirname(output_json_path) + if output_dir: # Only create directory if it's not empty + os.makedirs(output_dir, exist_ok=True) + with open(output_json_path, 'w') as f: json.dump(annotations, f, indent=4) @@ -45,8 +48,8 @@ def parse_slide_xml(xml_file, output_json_path): print(f"Annotations saved to {output_json_path}") # File paths -xml_file = "data_extraction/slide2UpperLimb.xml" -output_json = "data_extraction/slide3_annotations.json" +xml_file = "slide2Pelvis.xml" +output_json = "slide2_pelvis_annotations.json" # Run function with dynamic input parse_slide_xml(xml_file, output_json) diff --git a/data_extraction/slide2_pelvis_annotations.json b/data_extraction/slide2_pelvis_annotations.json new file mode 100644 index 00000000..455e7b4d --- /dev/null +++ b/data_extraction/slide2_pelvis_annotations.json @@ -0,0 +1,74 @@ +[ + { + "text": "", + "position": { + "x": 2743200, + "y": 1299410, + "width": 5943600, + "height": 4952999 + } + }, + { + "text": "Right Pelvis (lateral aspect)", + "position": { + "x": 3579863, + "y": 6251729, + "width": 1323299, + "height": 215444 + } + }, + { + "text": "Ilium", + "position": { + "x": 5586937, + "y": 2083816, + "width": 393457, + "height": 215444 + } + }, + { + "text": "", + "position": { + "x": 3873661, + "y": 4149666, + "width": 1134543, + "height": 1310358 + } + }, + { + "text": "", + "position": { + "x": 4562474, + "y": 3962400, + "width": 1014265, + "height": 1060450 + } + }, + { + "text": "Pubis", + "position": { + "x": 5682077, + "y": 4259252, + "width": 419318, + "height": 215444 + } + }, + { + "text": "Ischium", + "position": { + "x": 5446767, + "y": 5263629, + "width": 507320, + "height": 215444 + } + }, + { + "text": "Acetabulum", + "position": { + "x": 5450756, + "y": 3572102, + "width": 692939, + "height": 215444 + } + } +] \ No newline at end of file diff --git a/data_extraction/slide3_annotations.json b/data_extraction/slide3_annotations.json index 5d58b1d1..d87767e0 100644 --- a/data_extraction/slide3_annotations.json +++ b/data_extraction/slide3_annotations.json @@ -1,237 +1,29 @@ [ { + "text": "", "position": { - "x": "2743200", - "y": "1299410", - "width": "5943600", - "height": "4952999" + "x": 2743200, + "y": 1299410, + "width": 5943600, + "height": 4952999 } }, { - "text": "Bony Pelvis" - }, - { - "text": "Home", - "position": { - "x": "1752600", - "y": "6400801", - "width": "723900", - "height": "307777" - } - }, - { - "position": { - "x": "1524000", - "y": "6248400", - "width": "9144000", - "height": "0" - } - }, - { - "position": { - "x": "8686800", - "y": "1311440", - "width": "1981200", - "height": "4936961" - } - }, - { - "position": { - "x": "1524000", - "y": "1311439", - "width": "1219200", - "height": "4936961" - } - }, - { - "text": "Right Pelvis(medial aspect)", - "position": { - "x": "6477427", - "y": "6248399", - "width": "1316687", - "height": "215444" - } - }, - { - "text": "Right Pelvis (lateral aspect)", - "position": { - "x": "3579863", - "y": "6251729", - "width": "1323299", - "height": "215444" - } - }, - { - "text": "Iliac CrestAnterior Iliac spinesPosterior Iliac spinesAuricular surface", - "position": { - "x": "8823068", - "y": "1322155", - "width": "1148008", - "height": "805605" - } - }, - { - "text": "IliumThe Ilium forms the superior part of the bony pelvis.It articulates with the sacrum to form the sacroiliac joint.", - "position": { - "x": "8757818", - "y": "3505200", - "width": "1828800", - "height": "1231106" - } - }, - { - "text": "Bony Pelvis Ilium Ischium Pubis ", - "color": "000000", - "position": { - "x": "1529348", - "y": "1330154", - "width": "1213853", - "height": "1015663" - } - }, - { - "text": "Iliac Crest", - "position": { - "x": "5431116", - "y": "1912315", - "width": "595035", - "height": "215444" - } - }, - { - "text": "Anterior Superior Iliac Spine", - "position": { - "x": "5407752", - "y": "2590801", - "width": "766897", - "height": "461665" - } - }, - { - "text": "Anterior Inferior Iliac Spine", - "position": { - "x": "5271485", - "y": "3229154", - "width": "766897", - "height": "461665" - } - }, - { - "text": "Posterior Inferior Iliac Spine", - "position": { - "x": "7887969", - "y": "3816698", - "width": "766897", - "height": "461665" - } - }, - { - "text": "Posterior Superior Iliac Spine", - "position": { - "x": "7881241", - "y": "1445569", - "width": "766897", - "height": "461665" - } - }, - { - "text": "Auricular surface", - "position": { - "x": "7169365", - "y": "1417638", - "width": "624749", - "height": "338554" - } - }, - { - "text": "No labels", - "position": { - "x": "9189990", - "y": "6400801", - "width": "974820", - "height": "307777" - } - }, - { - "shape": "line", - "color": "000000", - "position": { - "x": "1524000", - "y": "1295401", - "width": "9144000", - "height": "4009" - } - }, - { - "shape": "line", - "position": { - "x": "8686800", - "y": "3459986", - "width": "1965420", - "height": "0" - } - }, - { - "shape": "line", - "position": { - "x": "4212167", - "y": "2020037", - "width": "1218948", - "height": "0" - } - }, - { - "shape": "line", - "position": { - "x": "6026150", - "y": "2020037", - "width": "1212850", - "height": "0" - } - }, - { - "shape": "line", - "position": { - "x": "5008205", - "y": "2821633", - "width": "399547", - "height": "0" - } - }, - { - "shape": "line", - "position": { - "x": "4915392", - "y": "3459986", - "width": "356092", - "height": "0" - } - }, - { - "shape": "line", - "position": { - "x": "8153401", - "y": "3505201", - "width": "118017", - "height": "311497" - } - }, - { - "shape": "line", + "text": "Humerus", "position": { - "x": "8264690", - "y": "1907233", - "width": "117311", - "height": "1145232" + "x": 5428944, + "y": 2254478, + "width": 590857, + "height": 215444 } }, { - "shape": "line", + "text": "Radius", "position": { - "x": "7481740", - "y": "1756192", - "width": "290661", - "height": "1368008" + "x": 3124200, + "y": 4189504, + "width": 483352, + "height": 215444 } } ] \ No newline at end of file From 167e7f10b3e82c3c18651de41ca6a9fdbc3d0d5b Mon Sep 17 00:00:00 2001 From: tluke900 Date: Mon, 10 Feb 2025 16:40:52 -0600 Subject: [PATCH 061/143] Updating Output and XML Files. --- data_extraction/BoneyPelvisSlide4.xml | 7459 ++++++++++++++++++++++--- data_extraction/XML boneset Reader.py | 2 +- data_extraction/output.json | 28 +- 3 files changed, 6660 insertions(+), 829 deletions(-) diff --git a/data_extraction/BoneyPelvisSlide4.xml b/data_extraction/BoneyPelvisSlide4.xml index c89cb1ab..99592512 100644 --- a/data_extraction/BoneyPelvisSlide4.xml +++ b/data_extraction/BoneyPelvisSlide4.xml @@ -1,808 +1,6655 @@ -Bony PelvisHomeRight Pelvis(medial aspect)Right Pelvis (lateral aspect)Iliac CrestAnterior Iliac spinesPosterior Iliac spinesAuricular surfaceBony Pelvis Ilium Ischium Pubis LabelsClick to edit Master title styleClick to edit Master text stylesSecond levelThird levelFourth levelFifth level2/10/2025‹#›Click to edit Master title styleClick to edit Master text styles2/10/2025‹#›Click to edit Master title styleClick to edit Master subtitle style2/10/2025‹#›Click to edit Master title styleClick to edit Master text stylesSecond levelThird levelFourth levelFifth level2/10/2025‹#›Click to edit Master title styleClick to edit Master text stylesSecond levelThird levelFourth levelFifth levelClick to edit Master text stylesSecond levelThird levelFourth levelFifth level2/10/2025‹#›Click to edit Master title styleClick to edit Master text stylesClick to edit Master text stylesSecond levelThird levelFourth levelFifth levelClick to edit Master text stylesClick to edit Master text stylesSecond levelThird levelFourth levelFifth level2/10/2025‹#›Click to edit Master title style2/10/2025‹#›2/10/2025‹#›Click to edit Master title styleClick to edit Master text stylesSecond levelThird levelFourth levelFifth levelClick to edit Master text styles2/10/2025‹#›Click to edit Master title styleClick to edit Master text stylesSecond levelThird levelFourth levelFifth level2/10/2025‹#›Click to edit Master title styleClick to edit Master text stylesSecond levelThird levelFourth levelFifth level2/10/2025‹#›1Click to edit Master title styleClick to edit Master text styles2/10/2025‹#›2/10/2025Click to edit Master text stylesSecond levelThird levelFourth levelFifth level‹#›/9j/4AAQSkZJRgABAQAAYABgAAD/4QB0RXhpZgAATU0AKgAAAAgABAEaAAUAAAABAAAAPgEbAAUA -AAABAAAARgEoAAMAAAABAAIAAIdpAAQAAAABAAAATgAAAAAAAABgAAAAAQAAAGAAAAABAAKgAgAE -AAAAAQAAAOugAwAEAAAAAQAAAXMAAAAA/+0AOFBob3Rvc2hvcCAzLjAAOEJJTQQEAAAAAAAAOEJJ -TQQlAAAAAAAQ1B2M2Y8AsgTpgAmY7PhCfv/iB+hJQ0NfUFJPRklMRQABAQAAB9hhcHBsAiAAAG1u -dHJSR0IgWFlaIAfZAAIAGQALABoAC2Fjc3BBUFBMAAAAAGFwcGwAAAAAAAAAAAAAAAAAAAAAAAD2 -1gABAAAAANMtYXBwbAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAC2Rlc2MAAAEIAAAAb2RzY20AAAF4AAAFnGNwcnQAAAcUAAAAOHd0cHQAAAdMAAAAFHJYWVoA -AAdgAAAAFGdYWVoAAAd0AAAAFGJYWVoAAAeIAAAAFHJUUkMAAAecAAAADmNoYWQAAAesAAAALGJU -UkMAAAecAAAADmdUUkMAAAecAAAADmRlc2MAAAAAAAAAFEdlbmVyaWMgUkdCIFByb2ZpbGUAAAAA -AAAAAAAAABRHZW5lcmljIFJHQiBQcm9maWxlAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAABtbHVjAAAAAAAAAB8AAAAMc2tTSwAAACgAAAGEZGFESwAAAC4A -AAGsY2FFUwAAACQAAAHadmlWTgAAACQAAAH+cHRCUgAAACYAAAIidWtVQQAAACoAAAJIZnJGVQAA -ACgAAAJyaHVIVQAAACgAAAKaemhUVwAAABYAAALCbmJOTwAAACYAAALYY3NDWgAAACIAAAL+aGVJ -TAAAAB4AAAMgaXRJVAAAACgAAAM+cm9STwAAACQAAANmZGVERQAAACwAAAOKa29LUgAAABYAAAO2 -c3ZTRQAAACYAAALYemhDTgAAABYAAAPMamFKUAAAABoAAAPiZWxHUgAAACIAAAP8cHRQTwAAACYA -AAQebmxOTAAAACgAAAREZXNFUwAAACYAAAQedGhUSAAAACQAAARsdHJUUgAAACIAAASQZmlGSQAA -ACgAAASyaHJIUgAAACgAAATacGxQTAAAACwAAAUCcnVSVQAAACIAAAUuYXJFRwAAACYAAAVQZW5V -UwAAACYAAAV2AFYBYQBlAG8AYgBlAGMAbgD9ACAAUgBHAEIAIABwAHIAbwBmAGkAbABHAGUAbgBl -AHIAZQBsACAAUgBHAEIALQBiAGUAcwBrAHIAaQB2AGUAbABzAGUAUABlAHIAZgBpAGwAIABSAEcA -QgAgAGcAZQBuAOgAcgBpAGMAQx6lAHUAIABoAOwAbgBoACAAUgBHAEIAIABDAGgAdQBuAGcAUABl -AHIAZgBpAGwAIABSAEcAQgAgAEcAZQBuAOkAcgBpAGMAbwQXBDAEMwQwBDsETAQ9BDgEOQAgBD8E -QAQ+BEQEMAQ5BDsAIABSAEcAQgBQAHIAbwBmAGkAbAAgAGcA6QBuAOkAcgBpAHEAdQBlACAAUgBW -AEIAwQBsAHQAYQBsAOEAbgBvAHMAIABSAEcAQgAgAHAAcgBvAGYAaQBskBp1KAAgAFIARwBCACCC -cl9pY8+P8ABHAGUAbgBlAHIAaQBzAGsAIABSAEcAQgAtAHAAcgBvAGYAaQBsAE8AYgBlAGMAbgD9 -ACAAUgBHAEIAIABwAHIAbwBmAGkAbAXkBegF1QXkBdkF3AAgAFIARwBCACAF2wXcBdwF2QBQAHIA -bwBmAGkAbABvACAAUgBHAEIAIABnAGUAbgBlAHIAaQBjAG8AUAByAG8AZgBpAGwAIABSAEcAQgAg -AGcAZQBuAGUAcgBpAGMAQQBsAGwAZwBlAG0AZQBpAG4AZQBzACAAUgBHAEIALQBQAHIAbwBmAGkA -bMd8vBgAIABSAEcAQgAg1QS4XNMMx3xmbpAaACAAUgBHAEIAIGPPj/Blh072TgCCLAAgAFIARwBC -ACAw1zDtMNUwoTCkMOsDkwO1A70DuQO6A8wAIAPAA8EDvwPGA68DuwAgAFIARwBCAFAAZQByAGYA -aQBsACAAUgBHAEIAIABnAGUAbgDpAHIAaQBjAG8AQQBsAGcAZQBtAGUAZQBuACAAUgBHAEIALQBw -AHIAbwBmAGkAZQBsDkIOGw4jDkQOHw4lDkwAIABSAEcAQgAgDhcOMQ5IDicORA4bAEcAZQBuAGUA -bAAgAFIARwBCACAAUAByAG8AZgBpAGwAaQBZAGwAZQBpAG4AZQBuACAAUgBHAEIALQBwAHIAbwBm -AGkAaQBsAGkARwBlAG4AZQByAGkBDQBrAGkAIABSAEcAQgAgAHAAcgBvAGYAaQBsAFUAbgBpAHcA -ZQByAHMAYQBsAG4AeQAgAHAAcgBvAGYAaQBsACAAUgBHAEIEHgQxBEkEOAQ5ACAEPwRABD4ERAQ4 -BDsETAAgAFIARwBCBkUGRAZBACAGKgY5BjEGSgZBACAAUgBHAEIAIAYnBkQGOQYnBkUARwBlAG4A -ZQByAGkAYwAgAFIARwBCACAAUAByAG8AZgBpAGwAZXRleHQAAAAAQ29weXJpZ2h0IDIwMDcgQXBw -bGUgSW5jLiwgYWxsIHJpZ2h0cyByZXNlcnZlZC4AWFlaIAAAAAAAAPNSAAEAAAABFs9YWVogAAAA -AAAAdE0AAD3uAAAD0FhZWiAAAAAAAABadQAArHMAABc0WFlaIAAAAAAAACgaAAAVnwAAuDZjdXJ2 -AAAAAAAAAAEBzQAAc2YzMgAAAAAAAQxCAAAF3v//8yYAAAeSAAD9kf//+6L///2jAAAD3AAAwGz/ -wAARCAFzAOsDASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAA -AgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcY -GRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKT -lJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP0 -9fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQA -AQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2 -Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOk -paanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9sAQwAC -AgICAgIDAgIDBQMDAwUGBQUFBQYIBgYGBgYICggICAgICAoKCgoKCgoKDAwMDAwMDg4ODg4PDw8P -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 -AHIAZQBsACAAUgBHAEIALQBiAGUAcwBrAHIAaQB2AGUAbABzAGUAUABlAHIAZgBpAGwAIABSAEcA -QgAgAGcAZQBuAOgAcgBpAGMAQx6lAHUAIABoAOwAbgBoACAAUgBHAEIAIABDAGgAdQBuAGcAUABl -AHIAZgBpAGwAIABSAEcAQgAgAEcAZQBuAOkAcgBpAGMAbwQXBDAEMwQwBDsETAQ9BDgEOQAgBD8E -QAQ+BEQEMAQ5BDsAIABSAEcAQgBQAHIAbwBmAGkAbAAgAGcA6QBuAOkAcgBpAHEAdQBlACAAUgBW -AEIAwQBsAHQAYQBsAOEAbgBvAHMAIABSAEcAQgAgAHAAcgBvAGYAaQBskBp1KAAgAFIARwBCACCC -cl9pY8+P8ABHAGUAbgBlAHIAaQBzAGsAIABSAEcAQgAtAHAAcgBvAGYAaQBsAE8AYgBlAGMAbgD9 -ACAAUgBHAEIAIABwAHIAbwBmAGkAbAXkBegF1QXkBdkF3AAgAFIARwBCACAF2wXcBdwF2QBQAHIA -bwBmAGkAbABvACAAUgBHAEIAIABnAGUAbgBlAHIAaQBjAG8AUAByAG8AZgBpAGwAIABSAEcAQgAg -AGcAZQBuAGUAcgBpAGMAQQBsAGwAZwBlAG0AZQBpAG4AZQBzACAAUgBHAEIALQBQAHIAbwBmAGkA -bMd8vBgAIABSAEcAQgAg1QS4XNMMx3xmbpAaACAAUgBHAEIAIGPPj/Blh072TgCCLAAgAFIARwBC -ACAw1zDtMNUwoTCkMOsDkwO1A70DuQO6A8wAIAPAA8EDvwPGA68DuwAgAFIARwBCAFAAZQByAGYA -aQBsACAAUgBHAEIAIABnAGUAbgDpAHIAaQBjAG8AQQBsAGcAZQBtAGUAZQBuACAAUgBHAEIALQBw -AHIAbwBmAGkAZQBsDkIOGw4jDkQOHw4lDkwAIABSAEcAQgAgDhcOMQ5IDicORA4bAEcAZQBuAGUA -bAAgAFIARwBCACAAUAByAG8AZgBpAGwAaQBZAGwAZQBpAG4AZQBuACAAUgBHAEIALQBwAHIAbwBm -AGkAaQBsAGkARwBlAG4AZQByAGkBDQBrAGkAIABSAEcAQgAgAHAAcgBvAGYAaQBsAFUAbgBpAHcA -ZQByAHMAYQBsAG4AeQAgAHAAcgBvAGYAaQBsACAAUgBHAEIEHgQxBEkEOAQ5ACAEPwRABD4ERAQ4 -BDsETAAgAFIARwBCBkUGRAZBACAGKgY5BjEGSgZBACAAUgBHAEIAIAYnBkQGOQYnBkUARwBlAG4A -ZQByAGkAYwAgAFIARwBCACAAUAByAG8AZgBpAGwAZXRleHQAAAAAQ29weXJpZ2h0IDIwMDcgQXBw -bGUgSW5jLiwgYWxsIHJpZ2h0cyByZXNlcnZlZC4AWFlaIAAAAAAAAPNSAAEAAAABFs9YWVogAAAA -AAAAdE0AAD3uAAAD0FhZWiAAAAAAAABadQAArHMAABc0WFlaIAAAAAAAACgaAAAVnwAAuDZjdXJ2 -AAAAAAAAAAEBzQAAc2YzMgAAAAAAAQxCAAAF3v//8yYAAAeSAAD9kf//+6L///2jAAAD3AAAwGz/ -wAARCAFoAQwDASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAA -AgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcY -GRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKT -lJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP0 -9fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQA -AQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2 -Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOk -paanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9sAQwAC -AgICAgIDAgIDBQMDAwUGBQUFBQYIBgYGBgYICggICAgICAoKCgoKCgoKDAwMDAwMDg4ODg4PDw8P -Dw8PDw8P/9sAQwECAgIEBAQHBAQHEAsJCxAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQ -EBAQEBAQEBAQEBAQEBAQEBAQ/90ABAAR/9oADAMBAAIRAxEAPwD+f+iiigAooooAKKKKACiiigAo -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 -Ef8ATQmsCiq5n3Fyo6iXxr4tnj8qTVrgp6byP5Vz011c3LF7mV5WPJLsWP61BRSuwSCiiikMKKKK -ACiiigAooooAKKKKACiiigAooooAKKKKAP/Z/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMDAsK -CwsNDhIQDQ4RDgsLEBYQERMUFRUVDA8XGBYUGBIUFRT/2wBDAQMEBAUEBQkFBQkUDQsNFBQUFBQU -FBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBT/wAARCACQAQADASIA -AhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQA -AAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3 -ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWm -p6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEA -AwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSEx -BhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElK -U1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3 -uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD9U6KK -KACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAoooo -AKKKKACiiigDwP8AaO/trUPiD8NdH0oX91DeLqj3Gn2fie70FZ/LhiKF57Y7jtJJCkEcmvPvCP7R -HjDT/hfcXVvfWGot4K8LxanrDa1A893rU63l9aTW9vNHMqjDae6JORL5rTRNtGfm+nvFngDwx4+h -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 -6VYooqiwooooA//ZLuke TomlinsonLuke Tomlinson12025-02-10T20:00:11Z2025-02-10T20:03:00Z036Microsoft Office PowerPointWidescreen171100falseFonts Used3Theme1Slide Titles1AptosAptos DisplayArialOffice ThemeBony Pelvisfalsefalsefalse16.0000 \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Bony Pelvis + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Home + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Right Pelvis + + + + (medial aspect) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Right Pelvis + + + + (lateral aspect) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Iliac Crest + + + + + + + + + + + + + Anterior Iliac spines + + + + + + + + + + + + + + Posterior Iliac spines + + + + + + + + + + + + + + Auricular surface + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Bony Pelvis + + + + + + + + + + + + + + + + + + + + + + + + Ilium + + + + + + + + + + + + + + + + + + + + + + + + Ischium + + + + + + + + + + + + + + + + + + + + + + + + Pubis + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Labels + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master title style + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master text styles + + + + + + + Second level + + + + + + + Third level + + + + + + + Fourth level + + + + + + + Fifth level + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2/10/2025 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ‹#› + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master title style + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master text styles + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2/10/2025 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ‹#› + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master title style + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master subtitle style + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2/10/2025 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ‹#› + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master title style + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master text styles + + + + + + + Second level + + + + + + + Third level + + + + + + + Fourth level + + + + + + + Fifth level + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2/10/2025 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ‹#› + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master title style + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master text styles + + + + + + + Second level + + + + + + + Third level + + + + + + + Fourth level + + + + + + + Fifth level + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master text styles + + + + + + + Second level + + + + + + + Third level + + + + + + + Fourth level + + + + + + + Fifth level + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2/10/2025 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ‹#› + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master title style + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master text styles + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master text styles + + + + + + + Second level + + + + + + + Third level + + + + + + + Fourth level + + + + + + + Fifth level + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master text styles + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master text styles + + + + + + + Second level + + + + + + + Third level + + + + + + + Fourth level + + + + + + + Fifth level + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2/10/2025 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ‹#› + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master title style + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2/10/2025 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ‹#› + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2/10/2025 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ‹#› + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master title style + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master text styles + + + + + + + Second level + + + + + + + Third level + + + + + + + Fourth level + + + + + + + Fifth level + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master text styles + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2/10/2025 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ‹#› + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master title style + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master text styles + + + + + + + Second level + + + + + + + Third level + + + + + + + Fourth level + + + + + + + Fifth level + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2/10/2025 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ‹#› + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master title style + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master text styles + + + + + + + Second level + + + + + + + Third level + + + + + + + Fourth level + + + + + + + Fifth level + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2/10/2025 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ‹#› + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master title style + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master text styles + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2/10/2025 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ‹#› + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2/10/2025 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master text styles + + + + + + + Second level + + + + + + + Third level + + + + + + + Fourth level + + + + + + + Fifth level + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ‹#› + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + /9j/4AAQSkZJRgABAQAAYABgAAD/4QB0RXhpZgAATU0AKgAAAAgABAEaAAUAAAABAAAAPgEbAAUA + AAABAAAARgEoAAMAAAABAAIAAIdpAAQAAAABAAAATgAAAAAAAABgAAAAAQAAAGAAAAABAAKgAgAE + AAAAAQAAAOugAwAEAAAAAQAAAXMAAAAA/+0AOFBob3Rvc2hvcCAzLjAAOEJJTQQEAAAAAAAAOEJJ + TQQlAAAAAAAQ1B2M2Y8AsgTpgAmY7PhCfv/iB+hJQ0NfUFJPRklMRQABAQAAB9hhcHBsAiAAAG1u + dHJSR0IgWFlaIAfZAAIAGQALABoAC2Fjc3BBUFBMAAAAAGFwcGwAAAAAAAAAAAAAAAAAAAAAAAD2 + 1gABAAAAANMtYXBwbAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAC2Rlc2MAAAEIAAAAb2RzY20AAAF4AAAFnGNwcnQAAAcUAAAAOHd0cHQAAAdMAAAAFHJYWVoA + AAdgAAAAFGdYWVoAAAd0AAAAFGJYWVoAAAeIAAAAFHJUUkMAAAecAAAADmNoYWQAAAesAAAALGJU + UkMAAAecAAAADmdUUkMAAAecAAAADmRlc2MAAAAAAAAAFEdlbmVyaWMgUkdCIFByb2ZpbGUAAAAA + AAAAAAAAABRHZW5lcmljIFJHQiBQcm9maWxlAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAABtbHVjAAAAAAAAAB8AAAAMc2tTSwAAACgAAAGEZGFESwAAAC4A + AAGsY2FFUwAAACQAAAHadmlWTgAAACQAAAH+cHRCUgAAACYAAAIidWtVQQAAACoAAAJIZnJGVQAA + ACgAAAJyaHVIVQAAACgAAAKaemhUVwAAABYAAALCbmJOTwAAACYAAALYY3NDWgAAACIAAAL+aGVJ + TAAAAB4AAAMgaXRJVAAAACgAAAM+cm9STwAAACQAAANmZGVERQAAACwAAAOKa29LUgAAABYAAAO2 + c3ZTRQAAACYAAALYemhDTgAAABYAAAPMamFKUAAAABoAAAPiZWxHUgAAACIAAAP8cHRQTwAAACYA + AAQebmxOTAAAACgAAAREZXNFUwAAACYAAAQedGhUSAAAACQAAARsdHJUUgAAACIAAASQZmlGSQAA + ACgAAASyaHJIUgAAACgAAATacGxQTAAAACwAAAUCcnVSVQAAACIAAAUuYXJFRwAAACYAAAVQZW5V + UwAAACYAAAV2AFYBYQBlAG8AYgBlAGMAbgD9ACAAUgBHAEIAIABwAHIAbwBmAGkAbABHAGUAbgBl + AHIAZQBsACAAUgBHAEIALQBiAGUAcwBrAHIAaQB2AGUAbABzAGUAUABlAHIAZgBpAGwAIABSAEcA + QgAgAGcAZQBuAOgAcgBpAGMAQx6lAHUAIABoAOwAbgBoACAAUgBHAEIAIABDAGgAdQBuAGcAUABl + AHIAZgBpAGwAIABSAEcAQgAgAEcAZQBuAOkAcgBpAGMAbwQXBDAEMwQwBDsETAQ9BDgEOQAgBD8E + QAQ+BEQEMAQ5BDsAIABSAEcAQgBQAHIAbwBmAGkAbAAgAGcA6QBuAOkAcgBpAHEAdQBlACAAUgBW + AEIAwQBsAHQAYQBsAOEAbgBvAHMAIABSAEcAQgAgAHAAcgBvAGYAaQBskBp1KAAgAFIARwBCACCC + cl9pY8+P8ABHAGUAbgBlAHIAaQBzAGsAIABSAEcAQgAtAHAAcgBvAGYAaQBsAE8AYgBlAGMAbgD9 + ACAAUgBHAEIAIABwAHIAbwBmAGkAbAXkBegF1QXkBdkF3AAgAFIARwBCACAF2wXcBdwF2QBQAHIA + bwBmAGkAbABvACAAUgBHAEIAIABnAGUAbgBlAHIAaQBjAG8AUAByAG8AZgBpAGwAIABSAEcAQgAg + AGcAZQBuAGUAcgBpAGMAQQBsAGwAZwBlAG0AZQBpAG4AZQBzACAAUgBHAEIALQBQAHIAbwBmAGkA + bMd8vBgAIABSAEcAQgAg1QS4XNMMx3xmbpAaACAAUgBHAEIAIGPPj/Blh072TgCCLAAgAFIARwBC + ACAw1zDtMNUwoTCkMOsDkwO1A70DuQO6A8wAIAPAA8EDvwPGA68DuwAgAFIARwBCAFAAZQByAGYA + aQBsACAAUgBHAEIAIABnAGUAbgDpAHIAaQBjAG8AQQBsAGcAZQBtAGUAZQBuACAAUgBHAEIALQBw + AHIAbwBmAGkAZQBsDkIOGw4jDkQOHw4lDkwAIABSAEcAQgAgDhcOMQ5IDicORA4bAEcAZQBuAGUA + bAAgAFIARwBCACAAUAByAG8AZgBpAGwAaQBZAGwAZQBpAG4AZQBuACAAUgBHAEIALQBwAHIAbwBm + AGkAaQBsAGkARwBlAG4AZQByAGkBDQBrAGkAIABSAEcAQgAgAHAAcgBvAGYAaQBsAFUAbgBpAHcA + ZQByAHMAYQBsAG4AeQAgAHAAcgBvAGYAaQBsACAAUgBHAEIEHgQxBEkEOAQ5ACAEPwRABD4ERAQ4 + BDsETAAgAFIARwBCBkUGRAZBACAGKgY5BjEGSgZBACAAUgBHAEIAIAYnBkQGOQYnBkUARwBlAG4A + ZQByAGkAYwAgAFIARwBCACAAUAByAG8AZgBpAGwAZXRleHQAAAAAQ29weXJpZ2h0IDIwMDcgQXBw + bGUgSW5jLiwgYWxsIHJpZ2h0cyByZXNlcnZlZC4AWFlaIAAAAAAAAPNSAAEAAAABFs9YWVogAAAA + AAAAdE0AAD3uAAAD0FhZWiAAAAAAAABadQAArHMAABc0WFlaIAAAAAAAACgaAAAVnwAAuDZjdXJ2 + AAAAAAAAAAEBzQAAc2YzMgAAAAAAAQxCAAAF3v//8yYAAAeSAAD9kf//+6L///2jAAAD3AAAwGz/ + wAARCAFzAOsDASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAA + AgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcY + GRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKT + lJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP0 + 9fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQA + AQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2 + Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOk + paanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9sAQwAC + AgICAgIDAgIDBQMDAwUGBQUFBQYIBgYGBgYICggICAgICAoKCgoKCgoKDAwMDAwMDg4ODg4PDw8P + Dw8PDw8P/9sAQwECAgIEBAQHBAQHEAsJCxAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQ + EBAQEBAQEBAQEBAQEBAQEBAQ/90ABAAP/9oADAMBAAIRAxEAPwD+f+iiigAooooAKKKKACiiigAo + oooAKKKKACiivTPhf8IfH3xh10aB4E0x72RMGaZvkt7dT/FLIeFHoOSewNKUkldvQDzOiv138Bf8 + E3NCtLdrv4jeIJ9RmIXbBp4EEStjnMjh2cDtgL/SvobQP2Pfg74ciWSy8L2kxX+O6Bumb1/124D8 + BXl1M5oR2d/RGkaTZ+GHh7wB448Wgt4Y0C+1RV6tb28kij6soIrdvvg18WNNj8698IapEnqbSUjj + 6Ka/f648GxafBHa6fH9nSMbVjTiNAPRRgAAdK47V9EmhZWgmdG25OecZ9PwrzHxVQU+Vo7I5bUau + j+fa/wBK1TS5PK1OzmtH/uzRtGfyYCqFfvdqXhm31ayjtNaWO8WZT+7nhWVGT3DZ/lXzd46/ZP8A + hv4ihkm0u1bw/etlvNtcmLcf70TErj2XbXqYTOKFbRPU5q+GnTeqPyior2X4m/Azxx8MZmn1G3+3 + aST8l9bqWix28wdYz7Nx6E141XqHOmFFFFAwooooAKKKKACiiigD/9D+f+iiigAooooAKKKKACii + igAooooAKKK+sP2b/wBlnxP8btSh1fUlk0vwlFIBLdlcPcbT80duDwT2Z+i+54qKlSMI80noBlfs + 2/s2+Ifjz4hYyebp/hmxybu+CcMwx+5iJ4Mhzk9Qo5PYH95/ht8OvCnwx8NQeFPBunR2Njb4LbB8 + 0jkYLu3V2Pck1seBvBXh/wAFeH7Hwv4ZsE0/TbCIRxRIMADPUnqzMeSTyScnmu7a1G5Nn3Qckfy/ + CvmMXinWf93sPmtoVXCqiqu0KQeo5qusmyMxjBdRz7VbmjLygbMxk5x2GP8A69UpZI4pMkAZODnr + k9KhRVjK5kyeVOdgUlWHJx6g1zmreHft1ufIXMsJG/3BGAa7tocFpF+8oP0quI0ZxJExRscEd68b + McBGTvLqeng8Y47HgGoaZNBqJSZMRQjbxxwo/wAa5+6hluD9kjwE3EknpnGT/wDWr6A1fT4dSkeI + L5dyejDkfl6eteJaxp13YrJHIhSZXyeuSPUV4kqEqUro9inXjUWpyuoaLaXFtJFJGJIpVKOrKChD + cEMp4INfnf8AHb9lGawhu/Gfw2iMkIJkn0xVyyD+JoOckDqUxkfw8cV+j8ssjgl8ksAMfStCIwyK + gEeCB17mvqstzmcLRk9Dx8ThVduJ/O4QVJVhgjgg0lfox+1h+zvCIbv4p+CbQQvH8+qWcYwCO9wi + 9j/z0A/3vWvznr7SnUjOPNE89MKKKKsYUUUUAFFFFAH/0f5/6KKKACiiigAooooAKKKKACiivsX9 + kT9nG5+M/i1PEXiC3DeDtEmH2vcxU3M23ckC45IzgyHjC8ZyRUykoq7E3Y0f2S/2W7v4y6t/wlvi + yGSDwjpsgBGCrX0o58pDx+7H8bD/AHRySR+7Xh/w3p+j6fb6dp9sltaW0axRRRqEjSNRgKqjAAFG + h6Naabp8Wn6XAlra24CxxRKERFXgKqgAAD0A4rtLVflUAYx+VfP4qt7V+RLbIYrXbGUxirTxDy/k + OMYyfpUh2j2A7UMpdQ5OBXPGCRlKRlyKd4wAd3YHGBWPclRKHlHIO7FdAYl8zzR94dCaxJoPtFyW + YbW2dunWnU0TsODuQxTkuwx8pbGfp1qKBkkmlCZUoQo+tSm0QPBEDy8pJ+nU1rrYKuW6ls5x1zXD + K9SVn0OpWirrqYk+nyzMXibZIwxnpya4zU7HUpbtVuAskboI8EZYNnknPavXfsReMMfvHgEdsjNY + 2q6efKM8ib2VmG7+Ljp+FEsJbWI4V3szwTxVpul6bcJ5Tbty5IUenf8AKufkt1DLLpk37p+Ccc8D + kV3eu2MDt5bjMj4PPfOeK5++t103TobaHklyxA689fw4rhnRTcm3Y6o1XZI5GaFZPNiuP3oYY2kZ + VgeDuB6g+lfkh+0v8AJvhnqz+KvDMTP4X1CXAHU2cz8+Ux/uH+Bv+AnkAn9q4dIibQS0gD3G7PuF + NcN4n8GaX4s8P3/hvxBB9psryIxSRkDBRu4PZlOCD2IBr0cqx0qMlCezM60VL3o9D+d6ivTfi98N + NT+FHjq/8JX+6SGNvMtJyMCe2cnY47Z7NjowIrzKvs001dHKFFFFMAooooA//9L+f+iiigAooooA + KKKKAClVWdgiAlmOABySTXWeCfAvir4i+IIPDHg7T5NR1CfJ2JwqIPvO7HAVR3JOK/Y39nf9iPw3 + 8N3tPGPjORNb8RQ4kjBX/RraTGf3aH7zDs7fUKDzXPiMTGmrvcEfFfwQ/Yl8c+Ob/T9Y8fxtoOgS + bZmiP/H5NGeQoT/lluHdvmA/hr9sfAvgDw54C8OWXhrwvYRadp1mNscMYOBk5JJJJZieSSSSeprW + t7cwHaigOw79h6nufpXU2cLFlI+YR8YPc+prwp4ydTRiqJbluO1RE2rkZ6D1rTij2oA3Xviq8Tl5 + zjk9PYf/AF6uFH2kj+LtWfOuhm13KRTc+Sfzp00gCsCcIKSQqsZ3L7Vg3E7zy+WudgHJrF1rC5Lm + jvVojjBGM1Wg/eCSU9FGBUSxSGIp/HJ2HatNo0jgVB6gD61y1Kt3c1hFbFJbUb1nPWPJ/pW8Iuc9 + sfqaqLDmTJ6H+lbtsvzBm+YZpYaL5maTd0LDbE2h2dd6/nXEa8ZlSW2ibIjZd568kH+QxXolxLHb + 2qRZ5Zst/T9a4PVWWVpQOdx5/Su6c0tDPldjy57D94sn8CDaB9O5NUvsKXkxgA3MvzEnHA9a39Sm + 3RtHGPb0/H8aq2aBFZlGDMCpbHIx2rljFWsU2zHWOSANbRchjt3eozS6jZLbaMHdd4AJbb1967G3 + 0xfLVFAVx0J/rS69ZCO0mcKSoHJA4weD+NctajyRbN6c7ux+bf7W3wlHxE+H8vivSIPM1bw8rTQh + PvPbjBnjOeT8o3r7ggda/Hiv6QLq0WC7l06XBhlA255Bz7e4r8T/ANp74Q/8Kp+Is40yHZoOslrm + xI6Jz+8h/wC2bHgf3Ste9w/mLq0/Zz3QsRS5ZabM+b6KKK+iMAooooA//9P+f+iiigAooooAK9X+ + EXwb8afGbxJHoPhW1Ywoy/a7x1P2e1jP8UjepwdqjljwK3PgV8BPF3x28RPpmhYtNMsSjX99ICY4 + Ec8AAffkIB2oOuOSBzX7j/D/AOHXhf4V+H7PwT4LtPs9paDdIzYMtxKcBpZW/iduvoBgAAACvMzL + Mo0I95dEXTpuTsih8Dvgj4O+Dmhf2L4ZtRLKwBu72RR9ouXHd27AHO1Bwo98k/SkcJWHcgPTuP5V + h6fbqmzGMEDPpXQrOzrhRtAJIPt0r52NWUm3N3bCouxAiM0mRhB6nrx6V0ViC+VT5VUdfesgRM8h + YHlh27VqQP5W2CMZAqoe69SHqakC+WAFHC/r71pgr37CsmJzuPOT0FTecQrbjyK1jPoZNXM6+nCk + xk8sahgsz94Dr0P1piWpuZzO/rgZ7CteNZPtG1R+7VcfjXI9XqjZaKxDFFtlYnntT5VDFeOAf1q4 + qjJJFN2Zb8c0ppNWCKa1HRqoOW9DWrZKuP3rDauD+Gc1mnnp0Aqz+98r5BncBn6CoVTlbN4wvYXU + 3RbdySCqYP4V53e3Crk5B3etdFqc7gzFs/MeR247157rdyscohBBJSsamJ6l+y6FC8mDsi5wr4PH + oKgt3FtcJM37zcrBU98jB9qyrW4a6lWSTgAYX6A4zW9FbpMq3YBO4hR7Y9q3o1eeNzCpDlZ1li0s + q4c9SCfStu4gW4tmjmUPGRj2+hrLsysSMzf3CD65P8q3bdFW1WWU7QF5Gc/jj1r0FBSVmYXa1PDv + GOjpaSR3WMochcjkbfavi/8Aay8ESeOvhJfXFra+bdaURfQED51aIfvR9Gi3ZHcha/R7xPpv9o6f + JBt3so3L26f/AFq8bhjgeGTTbpFkBypUjIYdCD7dq83Cz+r4pdmds5c9K/VH80NFd98VPCh8DfEj + xL4R2lE0u/uIYwevlByYz+KEGuBr75M5EwooooGf/9T+f+iiigAruvh18OPF3xU8UW3hHwZZNeX0 + /wAzH7scUY+9JI54VFzyfwGSQK7P4I/Ajxl8cfEY0nQIja6dAc3eoSIxggX0yPvSH+FAcnqcDJH7 + dfBn4M+CvgloY0DwlAZLiUB7y9mwbi6kA/jOMBR/Cg4HuSSfPx2YRoq28i4QuW/gt8GNK+Bvw/sf + DmjlLiVsTXtzghri6YDe+D/CMYQdlA75J9SsYvtc7XqDDMS4/r+lXtbuXi0Ayq3L42j3PQ1T0C1e + MRXRP8JDcfKCw5GPevhK9aVSv72p3wglTujqLMMAgIw3Iwev1rVZGwAPmIyeO4qjaqUYvnLPkVoh + l8vZnAH6Z7V6FLY4am5YtXdPmLZVlA24B5rQXfEm/g7u3f2rOhcRFEcjc2frVq4ZViEW7czHHHvW + rl7voZpamjZRiZPMzxmtWGzEsu1h8gBJ/pVTS4ZD83Reg+lb0XyKQOWPX3reEVyrQiW5liDZKSFy + MhQKtNBsmEUYyDnJq2g2yZbseBTWY7yQOuefpRbQChPtjOR0/wDrVXBO/J65pbs7tzvyFBx71HA3 + mEFuPSuKrUXNY3hBj3DyyiCP7uMt67uwrdeMqgwBhQBisbT2ZpmkI3EnAFX7u7MbfJyucEH2qaUo + 2c31N5xaaijivENysRwOO5HtXjWrSSXV9vGQSMZ9RmvRvEpea5dVOFJJH864B0BkyeGU4ryMTeU7 + dDqpNRjcprIWYhPlH+Fd1pDo8O7HyquAT3PVj+dclbWY80FuEBP45rqoHYKqRLwMAenv+Vd2HclK + 72OWqk1Y6ODkOzceg7mujsZGk+TywSw79gRyaw4I0+WTrgfN7noK27HaCzL27j0r2qcnocLWpKIw + iMpAHUDJ7D3rxjxJpsNnrKzwAKkgz6ZJOTXul2rhY3UBmJAOfQnmuK8WWivppuoY8tEQR3Iz1rkx + 9P3dOmp0UJa27n4I/t6+Ff7B+OR1pFxH4h0+2uuBj95EDbuPc/uwfxr4or9SP+Cj3h+5kh8DeLI4 + yYFS6spX/uuSkiA+5AfH0r8t6+vwdTnpRkuxhFW0Ciiiuko//9X+f+vY/gR8JL/40fEWw8HW0htr + TBuL24Az5NrGRvYf7RyFX/aI7Zrxyv3H/wCCdHwXk0T4Z3fjjW7ERXniyYPC7ffOnQYEf0DyF2Hq + ApPauXGV/Z03Jb9PUa8z6I8JeDfD3w48PWXhbwpYrp2nwEoiJ1PA+dz1Zm6sx5P5V2Wn27tKXblW + 75xg85rotYsYZdYkgCErCoVT3Ydz+dZunW1w+qKrMWjBI2Dup4BI9j1r88qYhuq+Z9bHpQh7mhR1 + 69F41tpcRJABckjooyK2rCBrazhAO5FX88GtSbQPJkadVHmSAEk849h7ZpY43t0EZHyqOO9awou7 + lPdmUqityouQksiSJwSeV9OKsKpI3khnBwcVXgmjCiQEgN1B4IqzbqftDBeVYcV2RmrpI45J2Y87 + UcSbgc8DPc1JJMpk89yP3YAwOmTUc8cySYcem339qfYxiZ2MvIODt9/pTlPXlKhHS7On0yR3w0nA + I4Ht610sJURHHXPWuWsoXmlGCRtP4GukVljUg8LyT7mu2hK6uzCasx8YQyncOO1MmKbgJDtUck9g + BVgbABIOpAHSuZ8STtHplzHED5joVUDrk1FeqoQbKhBuVi5qaxtIEh+4wGCPSqkS7ASgyFGBWRp0 + 1yunW/28/vY4wp9j6fWtVZM/uwcknHFeZKopy5kdcYNKxcsHEVu8ifeYkA47VBqUrRWiOOvr6+9X + ljjSNFQ5GP51ia24YfIeFG2tORqJTkmzgdSkOHz97oDXGSyBdxXseveuo1OcHIXo3H5VxlzvMhRf + uOccV5lSetzeC0LNtOC+WBb0ArqLM7I1aZsAc5/kBWDZ23lZCDgAAepA9a3rSIsFjYEliDx2reE5 + WMpRVzdjkJkNtGe2T6ewrY01nQgbjgZBA7ms2CLyyqgYI7e1XLQtA4iGGIyf/rV6mHbTOSpY6e33 + XEYkH3SOh4wR1rJ1SAS6fOvOMHIHuK1ICBEGUHGe3061GwWTcyNkN68g8f8A1q66lNSjbuZRdnc+ + Kf2g/hfH8WPhNq/hKHabsp9osT/du4Pmj57b+UPsxr+eieCa1nktrhDHLCxR0YYKspwQR6g1/TwL + m2juZrViAVkZT7HNfhX+2R8P/wDhA/jjq0ltH5dj4gVNTgwMLmbIlA+kqt+Yr0MgrqVLk7FVLqbT + PlaiiivdA//W/n/r+l39jvRv+Eb/AGX/AARbfaZr2XULA3atK2Si3MjSeUnoibsAV/NGAScDkmv6 + tPgp4RHhH4a+FPC6uXGi6RaWqlzk5Eal/wDx415+ZTfs+WO7Fa7VzrptIjuiVEe2RMyKR1yfl/UV + Sj0MadukRQsrn8hn/Oa7c2arN5v8Sg7fTBxkfpVHUoHJWZsArkHPoe1fKzwMYvma1On6y2rHOXDH + yUafmQHBA6HqRWNMVdGGMFCAcc9a3JrWWXzkX92GHysOf51Sit1TOzDkn5s9c0VIszja5jahGVSO + WNSB0IB7n1qfzBbzROGGH4PbFaHkLIvltllf5hu7HNZl/ZuvyxHaCcj0yPSuCqnH34nTGzXKzsPs + 6XiDcD2zis2/sVtgbq0kKsp5Ht/SrGjXH7gRFssB+NaFxArRktnDcEdq669PnjzR3MacrOzJbS5V + o0cHO0YOKsPNuPDdDisK5jisYNyggH0PUnpUto+GCPk7vm+lVDFte5Lcl0eqOlWQAJk+nP8A9eud + 1dhc3QjzlRj61fEshOw/dAwB6ms+YpDvlb73Yn8v8a58RU5tOhtShbUjWFZX2fwQkEjP5Vp2UPlq + Hb5nYn9arRA+SDJhR95sVs2q/IrsOME4q8NRV+bqKpUexBcTfZvmHzEDj0z14FctqTExbCcZ5zWn + Nc+fPIQ3AB//AF1zl8SSsRPK9TUzqczfY0cLJdznLyIOSyclRxnoK582zpIEUZHXn3rcuZiH8tR0 + PPpVa5Kxy7wCABkCsKkYtXW4RckyNRvfan3SMcd8da6S3tQHh5AAwWU87gBwB6VkaRavJi8l5OOB + 059hXTxLtLs64Kn88/0rWnFWuzKbLY8uRipwC+AP/wBdURAsjOZZDtGc7SRnHbNS3EsdvCHbrnP0 + qC0xImTwTzn0rolK75TKKsrnRQvviUI5TP659az9Su20+1kIGG25A96liJFqQn8PDZ5PWuE8ZamU + tCd2yNBnJ6cetTjK7hC63NKFNSduh4/qOrCG8nZ22SuxkPrX58/8FCLSG90jwN4jIxMXvLcHHJQi + Nxz7HJ/GvrbUb5tT1wIkhCMwBz3PT8q+L/2/9fikHgvwulwryW63Vy8QPzKjCKONsdgdj49cGuvh + lyU2mXmEFzRsfm/RRRX2hyH/1/wO0qEXGp2dux2iWaNSfTcwGa/r60cIlssX/PLgHuR0zX8fMUjR + SJKvVCCPqK/rO+Fni+y8ZeA/Dvi20YGPWtOtbvgg7WliVmU/7pJB+lcuKWzOeuz1NgQC2eTnP41W + kjjuN8ZxuUce1NeViCFIyBUlv87yKvyyHGPcGvLrWeiFTRz91CYGUg4IyD71gwWTwyyz55kOT6Yx + iu0vdKxw53A+v6c1gXMM0Lsrjcp6AdK8uunHVo7IWehgyja+xTgDP4E1l6kZLdT1IYcg9K6gWsW4 + uFzu4INZOrWziDeQCCMN7V4+LjJxdjsoySepzmnardC6XA3ZO329q9FRwyrk5z2NcLo1qHw0w+ce + vXr1rqryTyrcHbkHAPt71WCjKFLmnK5GIcXO0UMvJFmZIyA6jjHuOTT3lRGTauCBx6Yphi3PvU8E + ce5xg0+VAjYwxwcDPQCtJX3JiuhaWeNVLjJJ4xVRrMzOJ7klkByqDu2e9SW8IeUTISQuQB2/WplM + hDOTgA4AHtUtcy1LvYsQguwVunFSzTlFeQEgcrxWP9s+yOSx5HPuSafHci7EawkhRyB3P1/nTjXu + uVblxpa8z2IEVFVnmGNwyD0yBXM31wQolHLOflH16GtzUrwSw5xsVOCPQDrXHSXxkeTChcZRfY// + AFqynOK0uVyyepL5It7Pz5upJY9+/wDWs/Di3M8oIlf+HrgAnH6Vr3mz7Koz8i43E/n/ADrKaWPd + IznATGM9qitJc1jOKe5qaRFNwz9ASVX0ro3VAAGB55PPJIrmbPUI4oGCtguwXP61eN62w3Cnhdxy + 3SumlFcqVzKb1uZOqamsMpjcgkHBHTiqreK9Js+DMGc9EHNeC+P/ABQ0N9JbQylWXgsO59K+ZfiJ + 8ZdN+GmlfadfuSLm4BMMCEG4lB6EL2Ud2bA/HiscNKtOo4U1dnRKlDkUpM/RmbxPbR2b3hfywuPo + 2Op/wr5j+JPxMt7m3+xQzFy5JbGRkZ49xjpX5S67+198V7+aaPRriPTbJ8hYyvnOB6szcE49FA9q + 8L1/4j+OPE8ry6zrNxNv6qHKJ/3ymB+lfQUeH5yfNWl9xzKul8KP0l8W/Gvwv4C02bUry586+RT9 + ntEcGWST+H/dX+8x/U8V+Z3jXxjrnj7xNe+K/EU5nvb59x67UUcKig9FUcAVy7MzsWclmPUnk02v + ocLg4UY8sEYSk5PmYUUUV0iP/9D+f+v0j/YZ/ao1/wAJeLtF+DfjC4a78M6tKtnp7bcyWVzM/wC7 + UEDJid22kHO3IIwAQfzcr7c/YL+Dc/xP+N1h4iunEWkeCpIdSuGyMvOr5towM5wXUsxxwFI6kUpW + tqZ1UnHU/orWbKAdd3FaenSOJWZ8Dnj6d8VgJMY7lkGDFkdCDke1S+eyTAEFN2BuPr9K+dqS5ZXJ + p6qx3cgjnTPAzmubntwCVzkjvUY1SRZ/IzhFHBz17VOLuBclsbmA+XqcU5JTV0Ne7ozDntDEGmUk + HIPsayLgvf2piZQCCMEVvapcxSRhITn2Hb61n2irt5HzV5tXDa8q2N41erMe1spIsk4/d8U2Y3Et + tKACcjr3GPUV0F8iwww4UkSHGfcVXhgxI8X8Ljg1w1KdnyHTB6cxlWk8hiWKYbZYvvjtg9x7VqAL + IpZzlCe1WhbRl/mA44/Gkk8uJBg7QOPrUxpNL3mVKSb0IJWSMFRkL7VReby0zgZHOPQUXMjzKT91 + E6H1PrVRXMsWCNzE/TmplLWyBIogPqVyJTlQuRn1q1a5tEEznBGRj3rQtTFDuZlyxGfYev8AjXMX + Oqxyzuyg4XkZ6En2qKNO1pN6nTOV9Fsg1CRRsLqH3HkZxng/1Nc19lWPYbo/PknjoCac146q93cu + AqgnnqST29h+tc1qeozxSC4nBGceWDxkHoaznTjvJEOo9kbeo3U0untbImA8gOScYQd/zrP1loor + bzlJZXVRnrliMceuK5a2XxDrfiO0ijlSDSIyTdFuZG9FX69zXY+JZLSH9xHjzAvygnJHp+NbSwql + FtnL7Zpo4yy1eWaaPT1GJDx+vWuo8Qa9b6NoysW3zuCgXOcLjnI964S0S1028nvAS8yoQpUkKN3B + 9q4nXLi61CZlkzsB/DNVRwziveYVKik9Ecjqe6/u7ie4ZTAu6RnY/cA5JP0FfjL8WfHEnxD8d6j4 + jwVtmYQ2yH+G3i+VPxI+Y+5Nfsd8QLe60v4a+LNRtcPJDo98yN3GIWwfqM1+FVfVZNh4JOcV5GEq + km7MKKKK9skKKKKACiiigD//0f5/6+hf2fP2ivFP7Pesavf6DaQ6hba3bfZ7iCbKncmTFIjjlShY + 8dCCR1wR89UUpRTVmJq5+1v/AATx+Kvj74it8Ql8WahcamLe4tLyJpWLJCbjzVeOMHhVIRSFHAx0 + 61+ltxqiC3LXkhjwcBs4OenfvX8z3wL/AGiviL8AdYku/B12p02+lhe/sZUV4rlYicDJG5G2swDK + QRnuOK/TLXP+ChPwd1i5QxQ6nCigAMbdTjPUsA/X6V5mNwkn/DRlZqVz9BYtTjmu3mW7aVYc9cKq + gjvx1pk2vM0hc8BB1Jxmvz1n/bY+DC26S2WpzuzclJYJgwPuAu3P0NeZeI/25/CMTMdMt7i+c/3V + Kgf997a5aGW1uSyVvmhTqXlezP1Si8VxNu8xwXAwD6D2ro9N8RwllR2Bz3Jr8LdQ/bq16V8WGhlI + 1JI33HJ+oCf1pbP9vjxtZsHXRomKngGY4/H5a0/smo1rJX+f+Qc7W0T+g03dhdQR7JAV4GPep0g+ + RimCe3p9K/HL4af8FIvDtxdRWHxE0OfSUYhftVq32iNfd0wrgf7ob6V+ovw5+JXhrx74eh1rwrqU + OqafP80c0DblPHIPcMD1BAIPUVw4rCSg+aa+Z0U6jtY7K78yKQ7Dw2Rj39qoXTHYIly2ep+nWpZp + Hluckn5eSfeswvslYyHPpn+VeFUnd2R2bbjLm4Oxbcj527VSkmW024J3nj2+lXos7pJZDnd3HpXM + aldobjeB8qj5D3z3NYTi7czHTlrZHQz3UjwnBCoE2gDuT6+wrz7UrgWrYU5baST9a0rjWolZLeFx + tiU5HXJ9TXnur65BauzM3nlycbc8UsTjIRWr1Lo0pvRG+RDcQJb3Y3RSjDjPUdxn0xxWF4lnt53X + a25YMAY6DA4AHoOlYKazNfWCzxgkBtg7ZPXj2HemRxvuzu8xxzyeM/SiNaM1ZK6InTcW7snOr3Fj + D5kLBSwHJ9/SuS1K68Q38DSaVH9puHdV+dsDB6ntWlcwSXc7BpMAOvOMrtB5GOOo4rr7C1htlkn2 + ZOPlB969SnaUeZuxxyfK9rmdqGjTJaWdqJf9IlQGRV5VGPXmsLUdIEK7WJbavLdvavQrR/KbzjGB + OQy5+vYVzniY7LLyefNfBbOOD6fhWOIq+7eJVNa2Z5j4hsy/grxPp94pMUul3ik+xhbdj8K/n+r9 + 1vjT4nPhL4L+MdSmlxI2myWsRzg+ZdfuFx/33n8K/Cmvo8hi1SbfcifxOwUUUV7ggooooAKKKKAP + /9L+f+iiigAooooAKKKKACiiigAr9LP+CZfiTxDb/FbX/C1vKzaPdaVJdzRkkok0MsaRuB0BIcqf + UfQV+adfsf8A8E2fh7Lovg/xH8UL1NsmuzLp9nnqYLY7pWHs0hC/VDXDmVVQoSbHFXZ+qT3aJGS5 + w0vT6VgfbFMsjy8rjauOvFJNbPKizSnDenTGPSqM0Qh+ZyPkGfx/xr4Dmknc6mk9C7PqCeQIfuZ5 + b+g/DvXDa5qBihKR53E4yPSnXN0yAkv1J59T+Ncre6gtvNmY+dI/3R14/pXn18U5aHVTo2KLXItd + 0hQlpCDuzkgAYx/jXO6pJFcyyPHlU4wPUgV0hiikizypcbuf6CudcRwSLEW3O3yjI5Gelc8cM5Gv + t+UqGdhZpBCmxSd3I6tUcd/cWhWFmMcsmQAASccE1vz2jQWpkJAwRg9jWaJlfAK/M3f3/pXWqHI7 + tnO63Next2lqjQiWZunJBPX8K3LSRXGzH7pV4PQDHQAVxNzNISm7hM9D7VM+vpFAzE/dBAH+feuu + FVuVkYzp6XOxnvbW2xLKx3Kp4J43H2/lXCatdC/ujJuDgctzjA9Oa5PVPFiIxLqGJ6ZPU+9YSeKI + I7O81W62C2tIJZpTnDIkalmP5CuvD4Wc372xlOaitD4O/bO+J0t9f2Xw106TbbW+28u1DZ/eEEQo + fopLH/eHpXwbXS+MvE174y8U6p4p1D/XalcPMRnIUMflUeyrgD2Fc1X31CkoQUUc6QUUUVsMKKKK + ACiiigD/0/5/6KKKACiiigAooooAKKKKAL+l6Zfa1qVpo+mQtcXl9KkEMajLPJIwVVHuScV/Tf8A + CHwLZ/DnwJoHgyxRFj0SzigZkGFecDM0n/A5CzH3Nfjb+wL8LJvGfxbPje8t9+l+EI/PDMMq17KC + kCj1K/NJ7bRX7pQxtDH5LDAK/j/9avluIcTqqS6am1KO7JS0007YbgfrWDqki+a8LKQAOG4xk9RW + jLLMspKZRVXA9PrXnXjPxbbeGbU3l4wlnPRT0RfU4r5GUubTzO2ENirqksFqC0zmTHRc4GfU1xF5 + rOlwYurmcSyZGQOueuMe3pXjfin4nx31tKys2CxJ425Xsv0rwPVvFt/OW8pgFySPXmuzC5NOetrI + dTEqOnU+wdW+I2iaed80iiM92P5jrXmNz8e9Nhnk+zWpfk4YDGfz5r5VvLyaWQPOzPn1JNZ0tztw + FAP86+lw2U0qfmzzZzlLc+kz8c76aVmmtFZG6DcTiuo0r4xadeSJHfwtAG43qc4/CvkdLpCRkY+l + a9tcDI2ng1rWy+nII6H26vibT77CWk4mibkHvz+tVryea3YRmNmVl3AgZyD/AFr5+8J6ytrIIpiP + Kk4J/unsfwr2nRfEc8oSOd1cxvt9iM148sv5HzJGkqrtY5HVZra1t7u9v5hFaRI0jyP0RVGST7Yr + 8zviF8ffFfiWTVtD0G6ax8PXv7ry1UCWWIH+N/vDf3UHGODnnP3P+2feXVh8Jd2nW4EWo30EE8qj + BWPDSAHH95kAr8ma+lyyglDnaObSQUUUV6hYUUUUAFFFFABRRRQB/9T+f+iiigAooooAKKKKACii + ve/2Zvhb/wALg+NHh7whcxGXTRL9r1DHQWdt88gJ7b8BB7sKUmkrsGftd+xh8L/+FcfArQrOePbq + WuKdVu8jBD3SqY1P+7EEBHY5r6pkh8yZlUjIwPxqfSIorQKqAICAoUcBRjgAegqw0MUTSSLnBbPr + 9K+KxcfaN1Hu2awlbQ4rxJqUWh6Vdanc8+WDt9z0A5r4K8Z+Lp9RnlkmkbEh3Fc559K94+LvjJNR + h+xQZSKBmCr/AHiCRk49etfHupt5twSx71z4SjFtS3OiUmlY5jVL57gsSML6VyFxJKCVH510l1GX + 8wk7VB/T1rn2jDqWTlR39/avcpyb2MLWMl5y5wwrOl3OTsOMVv8A2cbCJADn0rOntbZOSxGfbOK6 + U5MaaILdPmzmti2wG29M1nogjG5GDKBzWhCrSHg8VMptLUvkR01jcGBwQeRz/wDrr1HQNW3uqjDM + eQP73qPrXkNrG4O9TkH1rrtOYoySISCD27H1rkniOXUJULo779oU2/iH9nvxPaopd7aOG5UEZwYZ + UZv++VB/A1+M9ft28KeIvBuu6HJymp6ddxNjGQ7wsuRnpzg1+Ile3gZqVO6OCnHlbiFFFFdZqFFF + FABRRRQAUUUUAf/V/n/ooooAKKKKACiiigAr9if+Canw2Sz8L+JPijeQ/wCkarMum2jEciC3xJMR + 7PIUH/ADX5OeDfCOvePfFGm+D/DNs13qeqzLBDGvqx5Zj2VRlmPQAEmv6cfhF8N9L+F3gHQPAOlj + dBpNqsTPjHmyk75pD/vuWb8a4Mxq8tPl7kyZ6YsXlvhhnOP61R8R3P8AZvh26uomIkgHH1bpW1Ku + +UMPlC4AP1rx34t+IWsdE/s1G+a4547YP+Ar5vFWjC5dLWVj4y8calJNdTSSONxJ4HQZryi4iPls + 7n/J5rsPEbSTSZPH1461zaR+Ztt3+bHX/CuTCz2iehVgkrnGy2M827ng/wAqzJLcRKIolG0DtXbX + oMRZI14J/Osj7K+5mfAz04r242Whx3b1ONZg+5VAO3rWdLEQhZB710r2W0kSoASTnbxmqc0IVMIK + qOJ0NlTVzDgUudhG0ehFWxG0UmEXAPTmtGGzxhyMEnt3q8LaM5RQN2cndxjNZzq32NbWK8BOdp5O + Pyrp9LL5AHQ1m28CHMbjDgZ47/jXQ6bEpww4xXlYiXQ2ij1jwZKBeRxSEbZCUIPQhuCPxr8gPiZ4 + Rl8CePdc8KS8rYXLrGf70TfNG34oRX6t6ddyWE9vdJjCODj2r5A/ba8PQ2njXQvFVqB5es2JR2He + S2fGf++HX8q9jhyreE4N7Hn41WqJ9GfFNFFFfRGIUUUUAFFFFABRRRQB/9b+f+iiigAooooAK6/w + R4E8V/EbxBb+GPB+nyahf3BHyoPlRe7yN0RB3Y8VyFfuf+wV4H0jTvgXp/iG2gT7br1xcy3MuPmI + ilaFAT1woTgdOScc1hia/s481ribNb9jf9kRfgzLP418Xyw3/im6QwxGLLQ2kLfeCFgCzvxubAwP + lHUk/oXJAyBSnUcL+PWqGli3gjCDjGB6da6tkQrkfwjivIqTdXVnPJ21ZjyMwjDE7cLx/vHgV8pf + FP7Ve61N5iny9uFHoAAM/pX1XfK8S5jXeFwPwNfOPxIt3OqF2xkAjjjgjI/Ovn82baiuzO/Buzuf + I+t2zfaOf4D36HFc2gWOTeeFPSvR/E9uy3BkUYHQ1wOoWkjIUt32FwVD4ztY8Ake1c+EnaSOytrE + x3XexZSG65+mcVQMYKkcbue2cfjXTTQiGBQXUunBP045rN2KVYxDfn8h+Ir6CR58WcldQqBtXlgM + tWZ5LTfMmFx3rq7m0IDM+CzDqOgqlaW2AzP/AAdh0rkqLW1zspS0uZMcOFJkHHrip0tSdxYmQdfc + DHTirSukkvlbcKOcj1rUSIbiB6ckcVEatuprJXMaC0ETrFESQO7HJOe1b1hCvmbAcnOCKbGoilWT + aCN3GelSQwsbw+V95jyOw9687E1ru5vTibtnDI56EqCMe9eEftw24fw54OuI0O22knjJ7DzURgD9 + dh/KvpuK2VTGittRMZPqRXjf7U2lXGt/B28vnX97ptzb3BAHIUHyjn8HzXvcO0Wpyb7HkZhXvKPq + flhRRRX05AUUUUAFFFFABRRRQB//1/5/6KKKACiiigAr9O/+CdPxxu9I8Xf8KW1uVTpeq+ddWJbq + l0oDPGP9l0UsB/eH+1X5iV9gfsMeCr7xf+0ToNzag+ToCTajKw7CNCiAn3d1/DNZ1mlFuWxM43Vj + 9/dcvUBkl+dAcAeXgMcf561taJ4ohvpDDcnbI4yOCQR0/rXNXNuHtZJJXIdDwOoHua4iW+vtMv0Y + LtwR04H1xX5/DNVGrdLRnU8K3GzPoi8RUsfMIxjn8K8A8f2jXMUF2vL/AHW5Ge+M/hXdt4ojktUN + 24LBSSB0BxXl+q6gmpTXtjFES5QyNJu4Tb0/PtXXj1zw90xoe69TwnxLp52scfvIzj6r1BrzmSFk + VuuDXslysM8X2eU75IshjjGV6/mK4XUrI2kmY/miP54P9K8aDs7no3TVjlzCtwgjmiQ4Gc9SR9Kx + XsG3EAYjU9VHrXSQrHHLux8pzn6e1aohtDGQqln4yD6V9Bh6vtY8t9Tz6q9m7nntzpjSLt+6Oo9T + WJcWv2dPIjDOrg8Ac59zXqElmAS8rEL6celc1cCJWcYBzxVVaPXYdOt0ONtdKYDMj7R2C9f/ANdb + MFrFMG88AbT93rj0zTZgwfI+Ue3amRTxIME7stz7/WsfZxSOj2je5DqCRzIUhGQw4b0+lXNKwskM + d4wVSQucc49/xqyJIRb+ecEf3eMD6VkS3ljMyxTnaCc5xz+FEMG5asmeJS0R30NxZRyMrSF5CcJt + OMN1Bq5Npdl4n0LUPDerRGS3v4ZYJvXDjH59x71xv2WBLqCDTJDJCwydxyQe59vpXZ+Hry4tp5rO + 7iAZDncnKtnuDX0eGgqVrHiVZc92fi/4z8Kan4I8Uaj4W1dClzp8rRkkYDr1Rx7MuCPY1zFfrJ+0 + 18Bv+Fl6GPGHhiL/AIqPSoiDGv8Ay9QLlvL/AN9edh78r6Y/J10eN2jkUqykggjBBHUEV6d09Udd + CrzR13G0UUUGwUUUUAFFFFAH/9D+f+iiigAooooAK/Yv/gnZ4Lbwv8Ptf+J17EEn1ufyLZ24b7Pa + ghiP9lpWI9ylflN8PvAuu/ErxlpXgnw3CZr7VZliX+6i9Xkc9lRcsx9BX9B2iaJpPgDw3ongbQyV + 07RrVLdSRgv5QwXPu7ZY+5r5/iHGqnR9mnrI6cLS5pa7I9Oi1KaW1BmdXNwQxxnt2FVdbY3KJHGB + 52C2fQAc1g2EUt8B5cm3yxlT2UdyfX2qrean9gje5cmTpHk+p9PpXw1PRWZ2VFd3RzF5qtxDuBJK + r1XPcVw2qateyQu0Uux5D8x9h61dutRS4uJVjB2vnax9a5zUEUqFX+LJznpXt5c+dXW5x4j3HqMb + xFGiCGSb9+qjMmMZPrirNleQ3tusTyq0hBz614l4gnngmbyeozgf1rldN1/XbadfLJkAORnqPoa9 + meRSl78PuOT65HY+kJbOBgQ64brx049K55yYGMhBwPwrMsvFes/ZgZ7IStjIIYA5968v+K3xo0jw + Zp6trak3MozDaRkedIfUnoqDuT+GTXPh8mqqouxNTGJxstz0fWPENhZwNJNMI40XLMxAAAHWvmvX + /j/8PrJ8W+otdSIxB8pHYfnjBr5S+I3xp17x6n2KOIadYH70aNuZ/ZmwOPYD614xX0zwdLqrmVGn + NK8nY+1r/wDad0WGXbZWVxdpjqQsY/DJJ/SsiD9pjTpZWW70qeFG43I6vj324X+dfIFFSsJSX2To + +Z9hv+0ppdsxFvZ3N2hHCtsjAP1yxr1X4f8AjF/iboV3rcUf2H7JObcRBg7ZKB0YnA68/lX5z190 + /so6U7+EvEGoZys17BHjsBFGxJ9Od4FRiYQhTcrEOB77oOtXOjPPFqAFwsmCrH5XUjptPT867nRv + F2j6ncM+Ps16vVSeCPUY457153rKK21kGML/AFrhbpJklVoyV/2gcEGsKUeeN0Yygrn3NoesW77E + JHbI7V+bv7X3wPuPD2tzfE7w1bg6RqLA3qRj/UTscCTA/hk7ns/XqK938L+NbnTysN05mCkYLHJF + e92WvaJ4q0ybQtYRJrS8QxvE43K6sMFSD6g1005cr9450nCV4n4UUV9N/Hn9nfWfhjqM2s6DG9/4 + amYski5d7fP8EmOw7N0Pfnr8yV1HoQmpK6CiiigoKKKKAP/R/n/ooooAKKKKAP2g/YG+Dlh4Q+GU + /wAZ9Xgzq/iYyW9mXAzFZxMVJT0MjjJPoq475+itenmuJIpuUJYrgHkDt+NaXgqNtA+EPw+8KWsY + Elpolj5i9CJGiVmHHvnNURE630FtNlpFRi57BiecfTOK/NM9r+0xElfbQ9nBUrU+Y7bwlcW50uS3 + XKyY2uW4PJz1+lYOtkXMixwf6hOufXp0p/nCEvbxNtRcBj2x3/GufnvSJJWU7z8wUeh/zmvPqSvF + J9CYxs2zA1AWslysIfZt+7gcDH0rG1+3jtUAUgFlDfifSrpB84koS2ev4Vm+ISJFVBy5VcmvoOH6 + a5mebm03y6HkGoWjXEjEc57/AFrQg0e1to1bYPNUZZj2rqorBOOOFIrxf41fFPTfhrom84n1K7DL + bQA4JOMb2/2FPX16V94m3oj5uKb0RwXxn+OWn+AbZtC8Ostzrsq9uUtwejP7+i/ieOv54axrOq6/ + qM2q6zdPeXc5y8khyT7ewHYDgVVvLu6v7qW+vZWmnnYu7ucszMckkmq1Ny6HsUMOoLzCiiipNwoo + ooAK/Qv9lyyuh8KtSn80WyTai+CwwXVY0BKn0zwfevhnwf4V1Pxt4jsvDOjhftN6+0M5wqKBlmbH + OFUEnAJ9K/Ve+8NaZ4R8NaR4e09z5NnapCpKhBIUAUvgZwWbLHPc15maV1GHs+rKjG7OP1KVzN5S + SBlbp7AVh3GAPKnUlT0NankFbjD4O0ZA9M1l3bozscZA4II/lWVB2hYzkryMuWONjujYLgfTBq7p + /iKa0lC7zlTwQf61QCXeTLFFvKnhgeo96W8jmkhDxxiMHtx/nFddBObtImpaJ9CeG/G0Gs2h0nUo + 1uIJFKMG5OCMHjvXzJ8Q/wBl3QNc1C71XwXcHTdp3ywhQ8IDd1XIZMntkj0wK6DTbbxGsDGEBTjc + uPlLKPen6X4lv9PkuJXMsZn+WTdk4A+taYahVpVHF6xMaji1zQep8DeNPBOt+BNYbRtbjAcjfHIu + SkiHupOPxHY1yNfX/wC0tqFvqXh7w1cI3mSedcZbHIUqmBXyBXU+jta5tTldahRRRSND/9L+f+ii + igArc8Mm2XxJpLXgDQC7gMgYZBTzF3AjuMVh1NbTtbXEVynLROrj6qc00DP6j4dONzD/AGmyqFhG + AQPl29gK821NQl5LK55xkt2UVj/D/wCJun674V0/Wo5SLbWLK3nX5wwXzVDYwD1U8H3FW7mb7XII + UjkEbAOSQAWX1PoK/O8bSipOK3/U9GhJ21McagQlzIVAWMYGO2O5/GuftLw7mmXqDuLH254/OrWq + X9jYLNBG28Sk4Oe+Ow9K8+l1UGD7HG215H6g4x9a8iNN9TplrsdFd38kl0+1NnmH+9/nms+cu98b + ccgfKPbaOalN1ZQRyXUriV15BIIyTwOD6VTsp1mR7k9eefqK+vyOi1HmPAzOor8o/KxWQSIZkJIJ + I75zn8K/Iv8AaB8Uv4p+KOrOH322mv8AY4R2Cw8N+b7jX633Eg4UcBRgV+J/jjTbvSPGWt6bfrtn + gvJ1Yf8AAyc/iOa+noy0aOPCxvPmOWooorQ9EKKKKACitfQdC1XxNrFpoOiW7XV9eyCOKNepY+vo + B1JPAHJr7t+HH7J1hoWonUfiROmpGEqYbe3LLCWU5zIzAFx7AAepNZ1KsYK8mS5WNT9lj4LS+FbZ + Pi54tQLd3MDLpdow5RJhgzyf7yEhF9G3HtXr3ie/uNQ1QeaNy4+UDCgL7Cuv1fxBehRBAFjjTgEc + cDgYA4AHpXmt5M1w5zIfN6EketeDVvObqS+XoOM9LGbvjSbY38Z6+/pVSZjGSH5B70k8RgJIwzjn + AOCfeqclv9ow0Lt0JZTz+VXCT2W5Tihha2RN8crI4zkDgHNSWl0Ip2LRCWFeRgHnI6k+3pTbO0WR + C1xId6nCrtPT3roLM6bZWjPqUpkwCAmdrAnpXoYVs5cS0kyO7uGe2JRcNt4UH19eOBXzX4x+Jth4 + eupdNkiE9wOSkZ+77Meg+ldj8QfippvhW1mitGEt9MD5MKnO3jAdz6Dr718O3NzPeXEt3dOZZpmL + ux6szHJJr2edRjZrX8jnw9Jt36HWeLvG2p+LjbRXSrDbWe7yol5wXPzMT3JwK4yiiuaUm9z0IxS0 + QUUUUhn/0/5/6KKKACiiigD9m/2EvGOmeKvhY3hq9I+3eHZWt23EH91IWlhbB6Dll/4DX0V4v8Q2 + lk8tv5DN/eC9G29ASO341+N/7LXxRf4cfEiG0upAmmeIdlncFjhY3LZhkz/stwf9ljX6vX2t2V/P + NFExQD5HYgkbj9a+TzvCNVFOC0f5nThp2vc5K91ozj7W0QLMu0AdFz6CuTknW1YwE7JJmPI+8Bj1 + rcurIC6Kbh5eCF5wM+vHes2WxtrVozIPMuHYZLdFHtXiJR2tqd/S5d89pIPMkk3DIwDySR61r2lx + 5GnOg++wB57d6xrUNMGGMKrHke1blza7YQwPDfpivs8vgo0bo+WxbcqlmSQqssgV25fGa+Gf2lfg + V4p1bxi3i7wXp51KG8jRbpIcb1mQbd+0kZDADJHcHPWvte582ODzYD8w5q/o91dyQu/QrktkdhWk + asYz8xw5kuaJ+Stt+z38Z7sqIPCl2dxxk7AB9SW4rrLf9lP4wTPsnsre2YY3CSdSVz67A3Sv1g8z + ADLOIA3p1qlcS4je0iQyGQZ8zPB59aHjX0idSm31PzSj/Y5+IxCNNf2MYbBOGkYgHuBs5rvdN/Yy + tVx/aviCaYlekMCxAHvy5bIH0FfdCMwwnA4HU4B9azrqVrU73kAVicZHWlLFz/r/AIIlJvqeY/C/ + 4J+AvhtL/aGlW5n1JVIN1O/mSBT1C4AVQenA5713mr3IMsiK/Bzkk8CpZr8MMJkA8g4rmZ7uxt0e + e7kEcKgl2b5a5pN1JLqJ+7dszJOclW3H9K5+9QvNiUE7hhdp/KvOfGP7Q3w/0Nn0/Tbhr6VMqfs6 + bhk/7Zwv5GvG7r9ovSLtwzWdwgTocjcfTkGuyWCqWsRSnd81j6Uns7m4bZEoXcOWPcDr1/pUESW9 + jtm+0bZAc4I4HqK+Ub39oe42eVp1i5X/AKauB/LNcBqvxk8U6jE0UYjtw2eQCzc/U4/SilgmneTN + pczVkj7K1nxbo3hzN7fX0EcbdRnGPw6n8K+bPGnx1+2yTW3hu3wpyBPL/NV/ln8q+eLu/vb+UzXs + 7zue7sTVSu+mlD4SFQT+Is3l5dahcveXsrTTSHLMxyTVaiik2dCQUUUUAFFFFAH/1P5/6KKKACii + igBQSDkcEV+r37MvxVm8d+Bn0vUY/tWqaMI4LlnOWkTB8mXJ6kgFW9xnvX5QV6b8KPifrPwo8VR+ + ItLT7TA6mK6tWYqk8R/hJGcEHlTjg+2RXNi8P7SDj1C5+u2qXEtmGZIEJbJBbnv3x7VztzfxuySh + AVY4B75HX6Vg6B8TvDPjPRbbxLpjs9jOCpjODJDN3SQDoR69CORxTpLr7Wf9HXCknHGK+cqZZV6x + N6WKgt2eg+G4RdKY1YNknn364rotShjtgLY/NsGfXGecV5doerHSrs7G3h+uDgiu9fUkvmaRjhjj + pzjivWwclZRtqkeVjYWba6mNfMwG9TnHYVVgl1A5CHy1fg+/tWy8KsOm4N0/CovLkSQEYDd89K2q + 0XzXMaVXSxVeB5ZN9w+WYcAHjj1FTx3TRqIJvmRTwAORUmxgRkEZ9Rn3xUFxbSgCWJ/3Z4OSRn6C + uSULfCdcZfzEd1Iz7hCpIxkf4GmpdRvamKQB0dsBSD+NWwscKeY53DoQRz/9euduNSKSNFbgnJPT + rV+z5fekxKonoifVXh0+KZ7iQLH5bNnPtxX5bfFf4pav4516dLW4lg0i2Yx28KsQGA4LsB1Le/Qc + V9i/Gz4iab4T8N3emM4bUb6Nlij3ZcFhtyQOgXrnua/NqvQw0LLmtZl0lfVhRRRXQbhRRRQAUUUU + AFFFFABRRRQAUUUUAf/V/n/ooooAKKKKACiiigDuvAfxA1vwDqn23Tds9rKV+0WsuTFOqnOCB0I7 + MOR+Yr9HPAnxF8LeP7Nr7w+Et5bhUL2ZcG4hkXhsqeq8jD4wc84NflTWjpWr6poV/Fqmj3UlndwH + KSxMVZT9RTeujMKtG+q3P1S8W2eoWEtvqx3W8coAKtx8w64x2rT0zxbEPLVyCpwTjqCP518MWP7Q + +u6lHFa+M0+1CJAi3EKgSk56upO0/hircPxm0K2n8yNbnjowUfyJrKnQau27kyjzRSaP0o07WbC6 + t8+cpI65PSrHm2Lby91GiDsQD+f1r84pf2i4oLdfs1pLcS88OQgH1POa8y1z43+OdYnd4bhbGFv+ + WcS5/MnJrSa5tGjmjhHe6Z+skfiLSY1aFpVcr1YMGAA9+leSeNP2hfh94YR4ZtRiup1O0xW5818r + xzt4GPc1+Weo+J/EGrYGoahNMo5ClyFH4DisIkk5NZQpW6nR9VTd2fdev/tc2PkTLoemyXE5BCGc + BIwexIBJI9q+ZtY+NPxO1pj9o16eBCc7LbFuo/79hT+Zry2iqUEuhtGlFdCWeee6lae5kaWRySzO + SzEnuSeaiooqzQKKKKACiiigAooooAKKKKACiiigAooooA//1v5/6KKKACiiigAooooAKKKKACii + igAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA/9k= + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + /9j/4AAQSkZJRgABAQAAYABgAAD/4QB0RXhpZgAATU0AKgAAAAgABAEaAAUAAAABAAAAPgEbAAUA + AAABAAAARgEoAAMAAAABAAIAAIdpAAQAAAABAAAATgAAAAAAAABgAAAAAQAAAGAAAAABAAKgAgAE + AAAAAQAAAQygAwAEAAAAAQAAAWgAAAAA/+0AOFBob3Rvc2hvcCAzLjAAOEJJTQQEAAAAAAAAOEJJ + TQQlAAAAAAAQ1B2M2Y8AsgTpgAmY7PhCfv/iB+hJQ0NfUFJPRklMRQABAQAAB9hhcHBsAiAAAG1u + dHJSR0IgWFlaIAfZAAIAGQALABoAC2Fjc3BBUFBMAAAAAGFwcGwAAAAAAAAAAAAAAAAAAAAAAAD2 + 1gABAAAAANMtYXBwbAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAC2Rlc2MAAAEIAAAAb2RzY20AAAF4AAAFnGNwcnQAAAcUAAAAOHd0cHQAAAdMAAAAFHJYWVoA + AAdgAAAAFGdYWVoAAAd0AAAAFGJYWVoAAAeIAAAAFHJUUkMAAAecAAAADmNoYWQAAAesAAAALGJU + UkMAAAecAAAADmdUUkMAAAecAAAADmRlc2MAAAAAAAAAFEdlbmVyaWMgUkdCIFByb2ZpbGUAAAAA + AAAAAAAAABRHZW5lcmljIFJHQiBQcm9maWxlAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAABtbHVjAAAAAAAAAB8AAAAMc2tTSwAAACgAAAGEZGFESwAAAC4A + AAGsY2FFUwAAACQAAAHadmlWTgAAACQAAAH+cHRCUgAAACYAAAIidWtVQQAAACoAAAJIZnJGVQAA + ACgAAAJyaHVIVQAAACgAAAKaemhUVwAAABYAAALCbmJOTwAAACYAAALYY3NDWgAAACIAAAL+aGVJ + TAAAAB4AAAMgaXRJVAAAACgAAAM+cm9STwAAACQAAANmZGVERQAAACwAAAOKa29LUgAAABYAAAO2 + c3ZTRQAAACYAAALYemhDTgAAABYAAAPMamFKUAAAABoAAAPiZWxHUgAAACIAAAP8cHRQTwAAACYA + AAQebmxOTAAAACgAAAREZXNFUwAAACYAAAQedGhUSAAAACQAAARsdHJUUgAAACIAAASQZmlGSQAA + ACgAAASyaHJIUgAAACgAAATacGxQTAAAACwAAAUCcnVSVQAAACIAAAUuYXJFRwAAACYAAAVQZW5V + UwAAACYAAAV2AFYBYQBlAG8AYgBlAGMAbgD9ACAAUgBHAEIAIABwAHIAbwBmAGkAbABHAGUAbgBl + AHIAZQBsACAAUgBHAEIALQBiAGUAcwBrAHIAaQB2AGUAbABzAGUAUABlAHIAZgBpAGwAIABSAEcA + QgAgAGcAZQBuAOgAcgBpAGMAQx6lAHUAIABoAOwAbgBoACAAUgBHAEIAIABDAGgAdQBuAGcAUABl + AHIAZgBpAGwAIABSAEcAQgAgAEcAZQBuAOkAcgBpAGMAbwQXBDAEMwQwBDsETAQ9BDgEOQAgBD8E + QAQ+BEQEMAQ5BDsAIABSAEcAQgBQAHIAbwBmAGkAbAAgAGcA6QBuAOkAcgBpAHEAdQBlACAAUgBW + AEIAwQBsAHQAYQBsAOEAbgBvAHMAIABSAEcAQgAgAHAAcgBvAGYAaQBskBp1KAAgAFIARwBCACCC + cl9pY8+P8ABHAGUAbgBlAHIAaQBzAGsAIABSAEcAQgAtAHAAcgBvAGYAaQBsAE8AYgBlAGMAbgD9 + ACAAUgBHAEIAIABwAHIAbwBmAGkAbAXkBegF1QXkBdkF3AAgAFIARwBCACAF2wXcBdwF2QBQAHIA + bwBmAGkAbABvACAAUgBHAEIAIABnAGUAbgBlAHIAaQBjAG8AUAByAG8AZgBpAGwAIABSAEcAQgAg + AGcAZQBuAGUAcgBpAGMAQQBsAGwAZwBlAG0AZQBpAG4AZQBzACAAUgBHAEIALQBQAHIAbwBmAGkA + bMd8vBgAIABSAEcAQgAg1QS4XNMMx3xmbpAaACAAUgBHAEIAIGPPj/Blh072TgCCLAAgAFIARwBC + ACAw1zDtMNUwoTCkMOsDkwO1A70DuQO6A8wAIAPAA8EDvwPGA68DuwAgAFIARwBCAFAAZQByAGYA + aQBsACAAUgBHAEIAIABnAGUAbgDpAHIAaQBjAG8AQQBsAGcAZQBtAGUAZQBuACAAUgBHAEIALQBw + AHIAbwBmAGkAZQBsDkIOGw4jDkQOHw4lDkwAIABSAEcAQgAgDhcOMQ5IDicORA4bAEcAZQBuAGUA + bAAgAFIARwBCACAAUAByAG8AZgBpAGwAaQBZAGwAZQBpAG4AZQBuACAAUgBHAEIALQBwAHIAbwBm + AGkAaQBsAGkARwBlAG4AZQByAGkBDQBrAGkAIABSAEcAQgAgAHAAcgBvAGYAaQBsAFUAbgBpAHcA + ZQByAHMAYQBsAG4AeQAgAHAAcgBvAGYAaQBsACAAUgBHAEIEHgQxBEkEOAQ5ACAEPwRABD4ERAQ4 + BDsETAAgAFIARwBCBkUGRAZBACAGKgY5BjEGSgZBACAAUgBHAEIAIAYnBkQGOQYnBkUARwBlAG4A + ZQByAGkAYwAgAFIARwBCACAAUAByAG8AZgBpAGwAZXRleHQAAAAAQ29weXJpZ2h0IDIwMDcgQXBw + bGUgSW5jLiwgYWxsIHJpZ2h0cyByZXNlcnZlZC4AWFlaIAAAAAAAAPNSAAEAAAABFs9YWVogAAAA + AAAAdE0AAD3uAAAD0FhZWiAAAAAAAABadQAArHMAABc0WFlaIAAAAAAAACgaAAAVnwAAuDZjdXJ2 + AAAAAAAAAAEBzQAAc2YzMgAAAAAAAQxCAAAF3v//8yYAAAeSAAD9kf//+6L///2jAAAD3AAAwGz/ + wAARCAFoAQwDASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAA + AgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcY + GRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKT + lJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP0 + 9fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQA + AQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2 + Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOk + paanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9sAQwAC + AgICAgIDAgIDBQMDAwUGBQUFBQYIBgYGBgYICggICAgICAoKCgoKCgoKDAwMDAwMDg4ODg4PDw8P + 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 + ACiiigAooooAKKKKACiiigAooooAKKKKAP/Z + + + + + + + + + + /9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMDAsK + CwsNDhIQDQ4RDgsLEBYQERMUFRUVDA8XGBYUGBIUFRT/2wBDAQMEBAUEBQkFBQkUDQsNFBQUFBQU + FBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBT/wAARCACQAQADASIA + AhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQA + AAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3 + ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWm + p6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEA + AwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSEx + BhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElK + U1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3 + uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD9U6KK + KACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAoooo + AKKKKACiiigDwP8AaO/trUPiD8NdH0oX91DeLqj3Gn2fie70FZ/LhiKF57Y7jtJJCkEcmvPvCP7R + HjDT/hfcXVvfWGot4K8LxanrDa1A893rU63l9aTW9vNHMqjDae6JORL5rTRNtGfm+nvFngDwx4+h + t4fE/hvSPEcNuxeGPVrGK6WJiMEqJFOCR3FVNe8D+FhY2d9P4X0a6m0CFpNLM1hExstgDKISV/d8 + ov3cdB6UAeIal8dPiZB517p9p4e1SK51XxJpenaRHYXC3Ltpk1z5e6X7RtZpY7V0wEUB5FbOAUNn + 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 + 6VYooqiwooooA//Z + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Luke Tomlinson + Luke Tomlinson + 1 + 2025-02-10T20:00:11Z + 2025-02-10T20:03:00Z + + + + + + + 0 + 36 + Microsoft Office PowerPoint + Widescreen + 17 + 1 + 1 + 0 + 0 + false + + + + Fonts Used + + + 3 + + + Theme + + + 1 + + + Slide Titles + + + 1 + + + + + + Aptos + Aptos Display + Arial + Office Theme + Bony Pelvis + + + + false + false + false + 16.0000 + + + + \ No newline at end of file diff --git a/data_extraction/XML boneset Reader.py b/data_extraction/XML boneset Reader.py index 0bfa6510..dda6d9ba 100644 --- a/data_extraction/XML boneset Reader.py +++ b/data_extraction/XML boneset Reader.py @@ -24,7 +24,7 @@ def extract_bones_from_xml(xml_path): bonesets = {} # Dictionary to store bonesets - INVALID_BONESETS = {"Home", "The", "Title", "Slide", "Overview", "Labels"} + INVALID_BONESETS = {"Home", "The", "Title", "Slide", "Overview", "Labels", "Right Pelvis" } # Extract bonesets dynamically diff --git a/data_extraction/output.json b/data_extraction/output.json index c91cf78f..890ee3bf 100644 --- a/data_extraction/output.json +++ b/data_extraction/output.json @@ -8,27 +8,11 @@ ] }, { - "boneset": "Right Pelvis", - "bones": [] - }, - { - "boneset": "Ilium", - "bones": [] - }, - { - "boneset": "Pubis", - "bones": [] - }, - { - "boneset": "Ischium", - "bones": [] - }, - { - "boneset": "Acetabulum", - "bones": [] - }, - { - "boneset": "Obturator foramen", - "bones": [] + "boneset": "Iliac Crest", + "bones": [ + "Anterior iliac spines", + "Auricular surface", + "Posterior iliac spines" + ] } ] \ No newline at end of file From 842916061e8306f5167ecc04d0d4d352362896d7 Mon Sep 17 00:00:00 2001 From: JoshBudzynski <157167337+JoshBudzynski@users.noreply.github.com> Date: Mon, 10 Feb 2025 16:55:03 -0600 Subject: [PATCH 062/143] Refined_Final_Issue79) --- data_extraction/extract_ppt_annotations.py | 42 +++++++++-- .../slide2_pelvis_annotations.json | 70 ++++++++++++++++++- 2 files changed, 104 insertions(+), 8 deletions(-) diff --git a/data_extraction/extract_ppt_annotations.py b/data_extraction/extract_ppt_annotations.py index 5cbed578..eb580951 100644 --- a/data_extraction/extract_ppt_annotations.py +++ b/data_extraction/extract_ppt_annotations.py @@ -20,13 +20,15 @@ def parse_slide_xml(xml_file, output_json_path): for sp in root.findall(".//p:sp", ns): annotation = {} + # Extract text, if present text_elements = sp.findall(".//a:t", ns) text = ''.join([t.text for t in text_elements if t.text]) + # Extract position and size xfrm = sp.find(".//a:xfrm", ns) if xfrm is not None: - pos = xfrm.find("./a:off", ns) - size = xfrm.find("./a:ext", ns) + pos = xfrm.find("a:off", ns) + size = xfrm.find("a:ext", ns) if pos is not None and size is not None: x, y = int(pos.attrib.get("x", 0)), int(pos.attrib.get("y", 0)) width, height = int(size.attrib.get("cx", 0)), int(size.attrib.get("cy", 0)) @@ -34,14 +36,42 @@ def parse_slide_xml(xml_file, output_json_path): if middle_x_min <= x <= middle_x_max and middle_y_min <= y <= middle_y_max: annotation["text"] = text annotation["position"] = {"x": x, "y": y, "width": width, "height": height} + + # Extract fill color, if present + fill_color = sp.find(".//a:solidFill/a:srgbClr", ns) + if fill_color is not None: + annotation["color"] = fill_color.attrib.get("val") + annotations.append(annotation) - + + # Collect lines + for ln in root.findall(".//p:cxnSp", ns): + annotation = {"shape": "line"} + + # Extract line color + line_color = ln.find(".//a:ln/a:solidFill/a:srgbClr", ns) + if line_color is not None: + annotation["color"] = line_color.attrib.get("val") + annotations.append(annotation["color"]) + + # Extract position and size + xfrm = ln.find(".//a:xfrm", ns) + if xfrm is not None: + pos = xfrm.find("a:off", ns) + size = xfrm.find("a:ext", ns) + if pos is not None and size is not None: + x, y = int(pos.attrib.get("x", 0)), int(pos.attrib.get("y", 0)) + width, height = int(size.attrib.get("cx", 0)), int(size.attrib.get("cy", 0)) + + if middle_x_min <= x <= middle_x_max and middle_y_min <= y <= middle_y_max: + annotation["position"] = {"x": x, "y": y, "width": width, "height": height} + annotations.append(annotation) + # Ensure output directory exists output_dir = os.path.dirname(output_json_path) - if output_dir: # Only create directory if it's not empty + if output_dir: os.makedirs(output_dir, exist_ok=True) - - + with open(output_json_path, 'w') as f: json.dump(annotations, f, indent=4) diff --git a/data_extraction/slide2_pelvis_annotations.json b/data_extraction/slide2_pelvis_annotations.json index 455e7b4d..d7a9c797 100644 --- a/data_extraction/slide2_pelvis_annotations.json +++ b/data_extraction/slide2_pelvis_annotations.json @@ -33,7 +33,8 @@ "y": 4149666, "width": 1134543, "height": 1310358 - } + }, + "color": "C133AD" }, { "text": "", @@ -42,7 +43,8 @@ "y": 3962400, "width": 1014265, "height": 1060450 - } + }, + "color": "2F8E29" }, { "text": "Pubis", @@ -70,5 +72,69 @@ "width": 692939, "height": 215444 } + }, + "000000", + { + "shape": "line", + "position": { + "x": 4267200, + "y": 2191538, + "width": 1319736, + "height": 0 + } + }, + { + "shape": "line", + "position": { + "x": 5980394, + "y": 2191538, + "width": 1411007, + "height": 0 + } + }, + { + "shape": "line", + "position": { + "x": 5954088, + "y": 5371351, + "width": 961063, + "height": 0 + } + }, + { + "shape": "line", + "position": { + "x": 4495801, + "y": 5371351, + "width": 950967, + "height": 0 + } + }, + { + "shape": "line", + "position": { + "x": 5105401, + "y": 4366974, + "width": 576677, + "height": 0 + } + }, + { + "shape": "line", + "position": { + "x": 5171505, + "y": 3679824, + "width": 279250, + "height": 0 + } + }, + { + "shape": "line", + "position": { + "x": 4562474, + "y": 3679825, + "width": 609031, + "height": 495300 + } } ] \ No newline at end of file From 3b864ac41b37447d9cb04c05747a3795aa798cd1 Mon Sep 17 00:00:00 2001 From: Santosh Iragavarapu <157086110+jacksayshi@users.noreply.github.com> Date: Tue, 11 Feb 2025 01:57:58 -0600 Subject: [PATCH 063/143] Delete .vscode directory Removing vs code configurations --- .vscode/launch.json | 15 --------------- .vscode/settings.json | 3 --- 2 files changed, 18 deletions(-) delete mode 100644 .vscode/launch.json delete mode 100644 .vscode/settings.json diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index 2ba986f6..00000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "type": "chrome", - "request": "launch", - "name": "Launch Chrome against localhost", - "url": "http://localhost:8080", - "webRoot": "${workspaceFolder}" - } - ] -} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 6f3a2913..00000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "liveServer.settings.port": 5501 -} \ No newline at end of file From b315a3f492f393c7c106fba01df3c59077b12129 Mon Sep 17 00:00:00 2001 From: tluke900 Date: Sun, 16 Feb 2025 13:16:38 -0600 Subject: [PATCH 064/143] Updated to correctly grab only Hyperlinks, every bonese that should exists does. --- data_extraction/XML boneset Reader.py | 95 ++++++++++----------------- data_extraction/output.json | 16 +++-- 2 files changed, 45 insertions(+), 66 deletions(-) diff --git a/data_extraction/XML boneset Reader.py b/data_extraction/XML boneset Reader.py index dda6d9ba..42928afb 100644 --- a/data_extraction/XML boneset Reader.py +++ b/data_extraction/XML boneset Reader.py @@ -5,7 +5,8 @@ def extract_bones_from_xml(xml_path): """ Parses the XML file and extracts bonesets and their associated bones. - Returns a dictionary with boneset names as keys and lists of bones as values. + Bonesets are determined by hyperlink text with size 1200. + Bones with size 900 are assigned to the most recent bolded boneset. """ try: print(f"Parsing XML: {xml_path}") @@ -23,34 +24,39 @@ def extract_bones_from_xml(xml_path): } bonesets = {} # Dictionary to store bonesets - - INVALID_BONESETS = {"Home", "The", "Title", "Slide", "Overview", "Labels", "Right Pelvis" } - - - # Extract bonesets dynamically - for boneset_element in root.findall(".//p:sp", ns): - boneset_name_element = boneset_element.find(".//p:txBody//a:t", ns) - - if boneset_name_element is not None: - - boneset_name = boneset_name_element.text.strip() - - boneset_name_words = boneset_name.split() - if not len(boneset_name_words) < 4: - continue - if not boneset_name or boneset_name in INVALID_BONESETS: - continue - if not all(word.isalpha() for word in boneset_name.split()): - continue - - if boneset_name not in bonesets: - bonesets[boneset_name] = set() - - # Extract bones within this boneset - for bone_element in boneset_element.findall(".//p:txBody//a:r/a:t", ns): - bone_name = bone_element.text.strip() if bone_element.text else None - if bone_name and is_valid_bone_name(bone_name, boneset_name): - bonesets[boneset_name].add(bone_name.capitalize()) + current_boneset = None + total_boneset = None + bolded_set = None + + # Extract bonesets based on hyperlinks and size attributes + for sp_element in root.findall(".//p:sp", ns): + for r_element in sp_element.findall(".//p:txBody//a:r", ns): + rPr_element = r_element.find("a:rPr", ns) + text_element = r_element.find("a:t", ns) + + if rPr_element is not None and text_element is not None: + text = text_element.text.strip() + size = rPr_element.get("sz") + is_bold = rPr_element.get("b") == "1" + has_hyperlink = rPr_element.find("a:hlinkClick", ns) is not None + + if has_hyperlink: + if size == "1200": + if total_boneset is None: + total_boneset = text + bonesets[total_boneset] = set() + continue + # These are their own bonesets + current_boneset = text + bonesets[current_boneset] = set() + bonesets[total_boneset].add(text.capitalize()) + if is_bold and size == "1200": + # If bold text appears, set it as the new active boneset for size 900 bones + bolded_set = text + elif size == "900" and bolded_set: + # These are bones assigned to the most recent bolded boneset + bonesets[bolded_set].add(text.capitalize()) + return bonesets @@ -74,37 +80,6 @@ def generate_json_output(bonesets, output_json_path): except IOError as e: print(f"Error writing to {output_json_path}: {e}") -def is_valid_bone_name(name, boneset_name): - """ - Determines if a given name is a valid bone name, excluding those that contain the boneset name. - """ - if not name: # Ignore empty strings - return False - - if len(name) > 50: # Ignore overly long strings - return False - - if "(" in name or ")" in name: # Ignore text with parentheses - return False - - if any(char.isdigit() for char in name): # Ignore names with digits - return False - - if name.islower(): # Ignore entirely lowercase words - return False - - if len(name.split()) > 3: - return False - - # Exclude names that contain the boneset name (case-insensitive) - if boneset_name and boneset_name.lower() in name.lower(): - return False - - if "Home" in name or "The" in name: - return False - - return True - if __name__ == "__main__": # Get the directory of the current script current_dir = os.path.dirname(os.path.abspath(__file__)) diff --git a/data_extraction/output.json b/data_extraction/output.json index 890ee3bf..c7306e11 100644 --- a/data_extraction/output.json +++ b/data_extraction/output.json @@ -8,11 +8,15 @@ ] }, { - "boneset": "Iliac Crest", - "bones": [ - "Anterior iliac spines", - "Auricular surface", - "Posterior iliac spines" - ] + "boneset": "Ilium", + "bones": [] + }, + { + "boneset": "Ischium", + "bones": [] + }, + { + "boneset": "Pubis", + "bones": [] } ] \ No newline at end of file From 8b5fa5ff4dc68774f4be8b50ca9c7405348c5f75 Mon Sep 17 00:00:00 2001 From: tluke900 Date: Sun, 16 Feb 2025 13:44:12 -0600 Subject: [PATCH 065/143] Working on Slide 4, commiting for posterity --- data_extraction/XML boneset Reader.py | 30 +++++++++++++++------------ data_extraction/output.json | 7 ++++++- 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/data_extraction/XML boneset Reader.py b/data_extraction/XML boneset Reader.py index 42928afb..5be8f6c3 100644 --- a/data_extraction/XML boneset Reader.py +++ b/data_extraction/XML boneset Reader.py @@ -27,6 +27,7 @@ def extract_bones_from_xml(xml_path): current_boneset = None total_boneset = None bolded_set = None + boldedList= [] # Extract bonesets based on hyperlinks and size attributes for sp_element in root.findall(".//p:sp", ns): @@ -41,21 +42,24 @@ def extract_bones_from_xml(xml_path): has_hyperlink = rPr_element.find("a:hlinkClick", ns) is not None if has_hyperlink: - if size == "1200": - if total_boneset is None: - total_boneset = text - bonesets[total_boneset] = set() - continue - # These are their own bonesets - current_boneset = text - bonesets[current_boneset] = set() - bonesets[total_boneset].add(text.capitalize()) - if is_bold and size == "1200": - # If bold text appears, set it as the new active boneset for size 900 bones + if size == "1200": + if is_bold: bolded_set = text - elif size == "900" and bolded_set: - # These are bones assigned to the most recent bolded boneset + if total_boneset is None: + total_boneset = text + bonesets[total_boneset] = set() + continue + # These are their own bonesets + current_boneset = text + bonesets[current_boneset] = set() + bonesets[total_boneset].add(text.capitalize()) + elif size == "900": + if not bolded_set: + boldedList.append(text.capitalize()) + else: bonesets[bolded_set].add(text.capitalize()) + for i in range(len(boldedList)): + bonesets[bolded_set].add(boldedList[i]) return bonesets diff --git a/data_extraction/output.json b/data_extraction/output.json index c7306e11..18d9c086 100644 --- a/data_extraction/output.json +++ b/data_extraction/output.json @@ -9,7 +9,12 @@ }, { "boneset": "Ilium", - "bones": [] + "bones": [ + "Anterior iliac spines", + "Auricular surface", + "Iliac crest", + "Posterior iliac spines" + ] }, { "boneset": "Ischium", From 0b6eb028d9bfbcd7a034eee7708f377d708da95b Mon Sep 17 00:00:00 2001 From: tluke900 Date: Sun, 16 Feb 2025 13:55:38 -0600 Subject: [PATCH 066/143] Added additional slide to test other bonesets, formatted hand slide, and included updated output --- data_extraction/XML boneset Reader.py | 2 +- data_extraction/output.json | 14 +- data_extraction/slide2UpperLimb.xml | 1237 ++++- data_extraction/slide9Pelvis.xml | 7341 +++++++++++++++++++++++++ 4 files changed, 8585 insertions(+), 9 deletions(-) create mode 100644 data_extraction/slide9Pelvis.xml diff --git a/data_extraction/XML boneset Reader.py b/data_extraction/XML boneset Reader.py index 5be8f6c3..47782aa1 100644 --- a/data_extraction/XML boneset Reader.py +++ b/data_extraction/XML boneset Reader.py @@ -89,7 +89,7 @@ def generate_json_output(bonesets, output_json_path): current_dir = os.path.dirname(os.path.abspath(__file__)) # Define the XML and JSON file paths relative to the script's directory - xml_file_path = os.path.join(current_dir, "BoneyPelvisSlide4.xml") + xml_file_path = os.path.join(current_dir, "slide9Pelvis.xml") json_file_path = os.path.join(current_dir, "output.json") # Extract bonesets and their bones diff --git a/data_extraction/output.json b/data_extraction/output.json index 18d9c086..32f5eb7f 100644 --- a/data_extraction/output.json +++ b/data_extraction/output.json @@ -9,16 +9,16 @@ }, { "boneset": "Ilium", - "bones": [ - "Anterior iliac spines", - "Auricular surface", - "Iliac crest", - "Posterior iliac spines" - ] + "bones": [] }, { "boneset": "Ischium", - "bones": [] + "bones": [ + "Ischial spine", + "Ischial tuberosity", + "Ramus", + "Sciatic notches" + ] }, { "boneset": "Pubis", diff --git a/data_extraction/slide2UpperLimb.xml b/data_extraction/slide2UpperLimb.xml index 547015cf..95989940 100644 --- a/data_extraction/slide2UpperLimb.xml +++ b/data_extraction/slide2UpperLimb.xml @@ -1,2 +1,1237 @@ -Upper LimbHomeUpper limb Humerus Ulna Radius Hand Right upper limb (posterior aspect)Right upper limb (anterior aspect)HumerusUlnaRadiusRadiusHand \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Upper Limb + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Home + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Upper limb + + + + + + + + + + + + + Humerus + + + + + + + + + + + + + Ulna + + + + + + + + + + + + + Radius + + + + + + + + + + + + + Hand + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Right upper limb + + + + (posterior aspect) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Right upper limb + + + + (anterior aspect) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Humerus + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Ulna + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Radius + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Radius + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Hand + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data_extraction/slide9Pelvis.xml b/data_extraction/slide9Pelvis.xml new file mode 100644 index 00000000..0cde45f4 --- /dev/null +++ b/data_extraction/slide9Pelvis.xml @@ -0,0 +1,7341 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Bony Pelvis + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Home + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Right Pelvis + + + + (medial aspect) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Right Pelvis + + + + (lateral aspect) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Bony Pelvis + + + + + + + + + + + + + + + + + + + + + + + + Ilium + + + + + + + + + + + + + + + + + + + + + + + + Ischium + + + + + + + + + + + + + + + + + + + + + + + + Pubis + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Ramus + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Ischial tuberosity + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Ramus + + + + + + + + + + + + + + Ischial tuberosity + + + + + + + + + + + + + + Ischial spine + + + + + + + + + + + + + + Sciatic notches + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Ischium + + + + + + + + + + + + + + + + + Forms the inferoposterior part of the pelvic bone. + + + + + + + + + + Situated posterior to the pubis and inferior to the + ilium. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Ischial spine + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Greater sciatic notch + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Lesser sciatic notch + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + No labels + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Body + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Body + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master title style + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master text styles + + + + + + + Second level + + + + + + + Third level + + + + + + + Fourth level + + + + + + + Fifth level + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2/16/2025 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ‹#› + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master title style + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master text styles + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2/16/2025 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ‹#› + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master title style + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master subtitle style + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2/16/2025 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ‹#› + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master title style + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master text styles + + + + + + + Second level + + + + + + + Third level + + + + + + + Fourth level + + + + + + + Fifth level + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2/16/2025 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ‹#› + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master title style + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master text styles + + + + + + + Second level + + + + + + + Third level + + + + + + + Fourth level + + + + + + + Fifth level + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master text styles + + + + + + + Second level + + + + + + + Third level + + + + + + + Fourth level + + + + + + + Fifth level + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2/16/2025 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ‹#› + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master title style + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master text styles + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master text styles + + + + + + + Second level + + + + + + + Third level + + + + + + + Fourth level + + + + + + + Fifth level + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master text styles + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master text styles + + + + + + + Second level + + + + + + + Third level + + + + + + + Fourth level + + + + + + + Fifth level + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2/16/2025 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ‹#› + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master title style + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2/16/2025 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ‹#› + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2/16/2025 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ‹#› + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master title style + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master text styles + + + + + + + Second level + + + + + + + Third level + + + + + + + Fourth level + + + + + + + Fifth level + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master text styles + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2/16/2025 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ‹#› + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master title style + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master text styles + + + + + + + Second level + + + + + + + Third level + + + + + + + Fourth level + + + + + + + Fifth level + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2/16/2025 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ‹#› + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master title style + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master text styles + + + + + + + Second level + + + + + + + Third level + + + + + + + Fourth level + + + + + + + Fifth level + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2/16/2025 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ‹#› + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master title style + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master text styles + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2/16/2025 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ‹#› + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2/16/2025 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master text styles + + + + + + + Second level + + + + + + + Third level + + + + + + + Fourth level + + + + + + + Fifth level + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ‹#› + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + /9j/4AAQSkZJRgABAQAAYABgAAD/4QB0RXhpZgAATU0AKgAAAAgABAEaAAUAAAABAAAAPgEbAAUA + AAABAAAARgEoAAMAAAABAAIAAIdpAAQAAAABAAAATgAAAAAAAABgAAAAAQAAAGAAAAABAAKgAgAE + AAAAAQAAAOugAwAEAAAAAQAAAXMAAAAA/+0AOFBob3Rvc2hvcCAzLjAAOEJJTQQEAAAAAAAAOEJJ + TQQlAAAAAAAQ1B2M2Y8AsgTpgAmY7PhCfv/iB+hJQ0NfUFJPRklMRQABAQAAB9hhcHBsAiAAAG1u + dHJSR0IgWFlaIAfZAAIAGQALABoAC2Fjc3BBUFBMAAAAAGFwcGwAAAAAAAAAAAAAAAAAAAAAAAD2 + 1gABAAAAANMtYXBwbAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAC2Rlc2MAAAEIAAAAb2RzY20AAAF4AAAFnGNwcnQAAAcUAAAAOHd0cHQAAAdMAAAAFHJYWVoA + AAdgAAAAFGdYWVoAAAd0AAAAFGJYWVoAAAeIAAAAFHJUUkMAAAecAAAADmNoYWQAAAesAAAALGJU + UkMAAAecAAAADmdUUkMAAAecAAAADmRlc2MAAAAAAAAAFEdlbmVyaWMgUkdCIFByb2ZpbGUAAAAA + AAAAAAAAABRHZW5lcmljIFJHQiBQcm9maWxlAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAABtbHVjAAAAAAAAAB8AAAAMc2tTSwAAACgAAAGEZGFESwAAAC4A + AAGsY2FFUwAAACQAAAHadmlWTgAAACQAAAH+cHRCUgAAACYAAAIidWtVQQAAACoAAAJIZnJGVQAA + ACgAAAJyaHVIVQAAACgAAAKaemhUVwAAABYAAALCbmJOTwAAACYAAALYY3NDWgAAACIAAAL+aGVJ + TAAAAB4AAAMgaXRJVAAAACgAAAM+cm9STwAAACQAAANmZGVERQAAACwAAAOKa29LUgAAABYAAAO2 + c3ZTRQAAACYAAALYemhDTgAAABYAAAPMamFKUAAAABoAAAPiZWxHUgAAACIAAAP8cHRQTwAAACYA + AAQebmxOTAAAACgAAAREZXNFUwAAACYAAAQedGhUSAAAACQAAARsdHJUUgAAACIAAASQZmlGSQAA + ACgAAASyaHJIUgAAACgAAATacGxQTAAAACwAAAUCcnVSVQAAACIAAAUuYXJFRwAAACYAAAVQZW5V + UwAAACYAAAV2AFYBYQBlAG8AYgBlAGMAbgD9ACAAUgBHAEIAIABwAHIAbwBmAGkAbABHAGUAbgBl + AHIAZQBsACAAUgBHAEIALQBiAGUAcwBrAHIAaQB2AGUAbABzAGUAUABlAHIAZgBpAGwAIABSAEcA + QgAgAGcAZQBuAOgAcgBpAGMAQx6lAHUAIABoAOwAbgBoACAAUgBHAEIAIABDAGgAdQBuAGcAUABl + AHIAZgBpAGwAIABSAEcAQgAgAEcAZQBuAOkAcgBpAGMAbwQXBDAEMwQwBDsETAQ9BDgEOQAgBD8E + QAQ+BEQEMAQ5BDsAIABSAEcAQgBQAHIAbwBmAGkAbAAgAGcA6QBuAOkAcgBpAHEAdQBlACAAUgBW + AEIAwQBsAHQAYQBsAOEAbgBvAHMAIABSAEcAQgAgAHAAcgBvAGYAaQBskBp1KAAgAFIARwBCACCC + cl9pY8+P8ABHAGUAbgBlAHIAaQBzAGsAIABSAEcAQgAtAHAAcgBvAGYAaQBsAE8AYgBlAGMAbgD9 + ACAAUgBHAEIAIABwAHIAbwBmAGkAbAXkBegF1QXkBdkF3AAgAFIARwBCACAF2wXcBdwF2QBQAHIA + bwBmAGkAbABvACAAUgBHAEIAIABnAGUAbgBlAHIAaQBjAG8AUAByAG8AZgBpAGwAIABSAEcAQgAg + AGcAZQBuAGUAcgBpAGMAQQBsAGwAZwBlAG0AZQBpAG4AZQBzACAAUgBHAEIALQBQAHIAbwBmAGkA + bMd8vBgAIABSAEcAQgAg1QS4XNMMx3xmbpAaACAAUgBHAEIAIGPPj/Blh072TgCCLAAgAFIARwBC + ACAw1zDtMNUwoTCkMOsDkwO1A70DuQO6A8wAIAPAA8EDvwPGA68DuwAgAFIARwBCAFAAZQByAGYA + aQBsACAAUgBHAEIAIABnAGUAbgDpAHIAaQBjAG8AQQBsAGcAZQBtAGUAZQBuACAAUgBHAEIALQBw + AHIAbwBmAGkAZQBsDkIOGw4jDkQOHw4lDkwAIABSAEcAQgAgDhcOMQ5IDicORA4bAEcAZQBuAGUA + bAAgAFIARwBCACAAUAByAG8AZgBpAGwAaQBZAGwAZQBpAG4AZQBuACAAUgBHAEIALQBwAHIAbwBm + AGkAaQBsAGkARwBlAG4AZQByAGkBDQBrAGkAIABSAEcAQgAgAHAAcgBvAGYAaQBsAFUAbgBpAHcA + ZQByAHMAYQBsAG4AeQAgAHAAcgBvAGYAaQBsACAAUgBHAEIEHgQxBEkEOAQ5ACAEPwRABD4ERAQ4 + BDsETAAgAFIARwBCBkUGRAZBACAGKgY5BjEGSgZBACAAUgBHAEIAIAYnBkQGOQYnBkUARwBlAG4A + ZQByAGkAYwAgAFIARwBCACAAUAByAG8AZgBpAGwAZXRleHQAAAAAQ29weXJpZ2h0IDIwMDcgQXBw + bGUgSW5jLiwgYWxsIHJpZ2h0cyByZXNlcnZlZC4AWFlaIAAAAAAAAPNSAAEAAAABFs9YWVogAAAA + AAAAdE0AAD3uAAAD0FhZWiAAAAAAAABadQAArHMAABc0WFlaIAAAAAAAACgaAAAVnwAAuDZjdXJ2 + AAAAAAAAAAEBzQAAc2YzMgAAAAAAAQxCAAAF3v//8yYAAAeSAAD9kf//+6L///2jAAAD3AAAwGz/ + wAARCAFzAOsDASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAA + AgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcY + GRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKT + lJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP0 + 9fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQA + AQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2 + Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOk + paanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9sAQwAC + AgICAgIDAgIDBQMDAwUGBQUFBQYIBgYGBgYICggICAgICAoKCgoKCgoKDAwMDAwMDg4ODg4PDw8P + Dw8PDw8P/9sAQwECAgIEBAQHBAQHEAsJCxAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQ + EBAQEBAQEBAQEBAQEBAQEBAQ/90ABAAP/9oADAMBAAIRAxEAPwD+f+iiigAooooAKKKKACiiigAo + oooAKKKKACiivTPhf8IfH3xh10aB4E0x72RMGaZvkt7dT/FLIeFHoOSewNKUkldvQDzOiv138Bf8 + E3NCtLdrv4jeIJ9RmIXbBp4EEStjnMjh2cDtgL/SvobQP2Pfg74ciWSy8L2kxX+O6Bumb1/124D8 + BXl1M5oR2d/RGkaTZ+GHh7wB448Wgt4Y0C+1RV6tb28kij6soIrdvvg18WNNj8698IapEnqbSUjj + 6Ka/f648GxafBHa6fH9nSMbVjTiNAPRRgAAdK47V9EmhZWgmdG25OecZ9PwrzHxVQU+Vo7I5bUau + j+fa/wBK1TS5PK1OzmtH/uzRtGfyYCqFfvdqXhm31ayjtNaWO8WZT+7nhWVGT3DZ/lXzd46/ZP8A + hv4ihkm0u1bw/etlvNtcmLcf70TErj2XbXqYTOKFbRPU5q+GnTeqPyior2X4m/Azxx8MZmn1G3+3 + aST8l9bqWix28wdYz7Nx6E141XqHOmFFFFAwooooAKKKKACiiigD/9D+f+iiigAooooAKKKKACii + igAooooAKKK+sP2b/wBlnxP8btSh1fUlk0vwlFIBLdlcPcbT80duDwT2Z+i+54qKlSMI80noBlfs + 2/s2+Ifjz4hYyebp/hmxybu+CcMwx+5iJ4Mhzk9Qo5PYH95/ht8OvCnwx8NQeFPBunR2Njb4LbB8 + 0jkYLu3V2Pck1seBvBXh/wAFeH7Hwv4ZsE0/TbCIRxRIMADPUnqzMeSTyScnmu7a1G5Nn3Qckfy/ + CvmMXinWf93sPmtoVXCqiqu0KQeo5qusmyMxjBdRz7VbmjLygbMxk5x2GP8A69UpZI4pMkAZODnr + k9KhRVjK5kyeVOdgUlWHJx6g1zmreHft1ufIXMsJG/3BGAa7tocFpF+8oP0quI0ZxJExRscEd68b + McBGTvLqeng8Y47HgGoaZNBqJSZMRQjbxxwo/wAa5+6hluD9kjwE3EknpnGT/wDWr6A1fT4dSkeI + L5dyejDkfl6eteJaxp13YrJHIhSZXyeuSPUV4kqEqUro9inXjUWpyuoaLaXFtJFJGJIpVKOrKChD + cEMp4INfnf8AHb9lGawhu/Gfw2iMkIJkn0xVyyD+JoOckDqUxkfw8cV+j8ssjgl8ksAMfStCIwyK + gEeCB17mvqstzmcLRk9Dx8ThVduJ/O4QVJVhgjgg0lfox+1h+zvCIbv4p+CbQQvH8+qWcYwCO9wi + 9j/z0A/3vWvznr7SnUjOPNE89MKKKKsYUUUUAFFFFAH/0f5/6KKKACiiigAooooAKKKKACiivsX9 + kT9nG5+M/i1PEXiC3DeDtEmH2vcxU3M23ckC45IzgyHjC8ZyRUykoq7E3Y0f2S/2W7v4y6t/wlvi + yGSDwjpsgBGCrX0o58pDx+7H8bD/AHRySR+7Xh/w3p+j6fb6dp9sltaW0axRRRqEjSNRgKqjAAFG + h6Naabp8Wn6XAlra24CxxRKERFXgKqgAAD0A4rtLVflUAYx+VfP4qt7V+RLbIYrXbGUxirTxDy/k + OMYyfpUh2j2A7UMpdQ5OBXPGCRlKRlyKd4wAd3YHGBWPclRKHlHIO7FdAYl8zzR94dCaxJoPtFyW + YbW2dunWnU0TsODuQxTkuwx8pbGfp1qKBkkmlCZUoQo+tSm0QPBEDy8pJ+nU1rrYKuW6ls5x1zXD + K9SVn0OpWirrqYk+nyzMXibZIwxnpya4zU7HUpbtVuAskboI8EZYNnknPavXfsReMMfvHgEdsjNY + 2q6efKM8ib2VmG7+Ljp+FEsJbWI4V3szwTxVpul6bcJ5Tbty5IUenf8AKufkt1DLLpk37p+Ccc8D + kV3eu2MDt5bjMj4PPfOeK5++t103TobaHklyxA689fw4rhnRTcm3Y6o1XZI5GaFZPNiuP3oYY2kZ + VgeDuB6g+lfkh+0v8AJvhnqz+KvDMTP4X1CXAHU2cz8+Ux/uH+Bv+AnkAn9q4dIibQS0gD3G7PuF + NcN4n8GaX4s8P3/hvxBB9psryIxSRkDBRu4PZlOCD2IBr0cqx0qMlCezM60VL3o9D+d6ivTfi98N + NT+FHjq/8JX+6SGNvMtJyMCe2cnY47Z7NjowIrzKvs001dHKFFFFMAooooA//9L+f+iiigAooooA + KKKKAClVWdgiAlmOABySTXWeCfAvir4i+IIPDHg7T5NR1CfJ2JwqIPvO7HAVR3JOK/Y39nf9iPw3 + 8N3tPGPjORNb8RQ4kjBX/RraTGf3aH7zDs7fUKDzXPiMTGmrvcEfFfwQ/Yl8c+Ob/T9Y8fxtoOgS + bZmiP/H5NGeQoT/lluHdvmA/hr9sfAvgDw54C8OWXhrwvYRadp1mNscMYOBk5JJJJZieSSSSeprW + t7cwHaigOw79h6nufpXU2cLFlI+YR8YPc+prwp4ydTRiqJbluO1RE2rkZ6D1rTij2oA3Xviq8Tl5 + zjk9PYf/AF6uFH2kj+LtWfOuhm13KRTc+Sfzp00gCsCcIKSQqsZ3L7Vg3E7zy+WudgHJrF1rC5Lm + jvVojjBGM1Wg/eCSU9FGBUSxSGIp/HJ2HatNo0jgVB6gD61y1Kt3c1hFbFJbUb1nPWPJ/pW8Iuc9 + sfqaqLDmTJ6H+lbtsvzBm+YZpYaL5maTd0LDbE2h2dd6/nXEa8ZlSW2ibIjZd568kH+QxXolxLHb + 2qRZ5Zst/T9a4PVWWVpQOdx5/Su6c0tDPldjy57D94sn8CDaB9O5NUvsKXkxgA3MvzEnHA9a39Sm + 3RtHGPb0/H8aq2aBFZlGDMCpbHIx2rljFWsU2zHWOSANbRchjt3eozS6jZLbaMHdd4AJbb1967G3 + 0xfLVFAVx0J/rS69ZCO0mcKSoHJA4weD+NctajyRbN6c7ux+bf7W3wlHxE+H8vivSIPM1bw8rTQh + PvPbjBnjOeT8o3r7ggda/Hiv6QLq0WC7l06XBhlA255Bz7e4r8T/ANp74Q/8Kp+Is40yHZoOslrm + xI6Jz+8h/wC2bHgf3Ste9w/mLq0/Zz3QsRS5ZabM+b6KKK+iMAooooA//9P+f+iiigAooooAK9X+ + EXwb8afGbxJHoPhW1Ywoy/a7x1P2e1jP8UjepwdqjljwK3PgV8BPF3x28RPpmhYtNMsSjX99ICY4 + Ec8AAffkIB2oOuOSBzX7j/D/AOHXhf4V+H7PwT4LtPs9paDdIzYMtxKcBpZW/iduvoBgAAACvMzL + Mo0I95dEXTpuTsih8Dvgj4O+Dmhf2L4ZtRLKwBu72RR9ouXHd27AHO1Bwo98k/SkcJWHcgPTuP5V + h6fbqmzGMEDPpXQrOzrhRtAJIPt0r52NWUm3N3bCouxAiM0mRhB6nrx6V0ViC+VT5VUdfesgRM8h + YHlh27VqQP5W2CMZAqoe69SHqakC+WAFHC/r71pgr37CsmJzuPOT0FTecQrbjyK1jPoZNXM6+nCk + xk8sahgsz94Dr0P1piWpuZzO/rgZ7CteNZPtG1R+7VcfjXI9XqjZaKxDFFtlYnntT5VDFeOAf1q4 + qjJJFN2Zb8c0ppNWCKa1HRqoOW9DWrZKuP3rDauD+Gc1mnnp0Aqz+98r5BncBn6CoVTlbN4wvYXU + 3RbdySCqYP4V53e3Crk5B3etdFqc7gzFs/MeR247157rdyscohBBJSsamJ6l+y6FC8mDsi5wr4PH + oKgt3FtcJM37zcrBU98jB9qyrW4a6lWSTgAYX6A4zW9FbpMq3YBO4hR7Y9q3o1eeNzCpDlZ1li0s + q4c9SCfStu4gW4tmjmUPGRj2+hrLsysSMzf3CD65P8q3bdFW1WWU7QF5Gc/jj1r0FBSVmYXa1PDv + GOjpaSR3WMochcjkbfavi/8Aay8ESeOvhJfXFra+bdaURfQED51aIfvR9Gi3ZHcha/R7xPpv9o6f + JBt3so3L26f/AFq8bhjgeGTTbpFkBypUjIYdCD7dq83Cz+r4pdmds5c9K/VH80NFd98VPCh8DfEj + xL4R2lE0u/uIYwevlByYz+KEGuBr75M5EwooooGf/9T+f+iiigAruvh18OPF3xU8UW3hHwZZNeX0 + /wAzH7scUY+9JI54VFzyfwGSQK7P4I/Ajxl8cfEY0nQIja6dAc3eoSIxggX0yPvSH+FAcnqcDJH7 + dfBn4M+CvgloY0DwlAZLiUB7y9mwbi6kA/jOMBR/Cg4HuSSfPx2YRoq28i4QuW/gt8GNK+Bvw/sf + DmjlLiVsTXtzghri6YDe+D/CMYQdlA75J9SsYvtc7XqDDMS4/r+lXtbuXi0Ayq3L42j3PQ1T0C1e + MRXRP8JDcfKCw5GPevhK9aVSv72p3wglTujqLMMAgIw3Iwev1rVZGwAPmIyeO4qjaqUYvnLPkVoh + l8vZnAH6Z7V6FLY4am5YtXdPmLZVlA24B5rQXfEm/g7u3f2rOhcRFEcjc2frVq4ZViEW7czHHHvW + rl7voZpamjZRiZPMzxmtWGzEsu1h8gBJ/pVTS4ZD83Reg+lb0XyKQOWPX3reEVyrQiW5liDZKSFy + MhQKtNBsmEUYyDnJq2g2yZbseBTWY7yQOuefpRbQChPtjOR0/wDrVXBO/J65pbs7tzvyFBx71HA3 + mEFuPSuKrUXNY3hBj3DyyiCP7uMt67uwrdeMqgwBhQBisbT2ZpmkI3EnAFX7u7MbfJyucEH2qaUo + 2c31N5xaaijivENysRwOO5HtXjWrSSXV9vGQSMZ9RmvRvEpea5dVOFJJH864B0BkyeGU4ryMTeU7 + dDqpNRjcprIWYhPlH+Fd1pDo8O7HyquAT3PVj+dclbWY80FuEBP45rqoHYKqRLwMAenv+Vd2HclK + 72OWqk1Y6ODkOzceg7mujsZGk+TywSw79gRyaw4I0+WTrgfN7noK27HaCzL27j0r2qcnocLWpKIw + iMpAHUDJ7D3rxjxJpsNnrKzwAKkgz6ZJOTXul2rhY3UBmJAOfQnmuK8WWivppuoY8tEQR3Iz1rkx + 9P3dOmp0UJa27n4I/t6+Ff7B+OR1pFxH4h0+2uuBj95EDbuPc/uwfxr4or9SP+Cj3h+5kh8DeLI4 + yYFS6spX/uuSkiA+5AfH0r8t6+vwdTnpRkuxhFW0Ciiiuko//9X+f+vY/gR8JL/40fEWw8HW0htr + TBuL24Az5NrGRvYf7RyFX/aI7Zrxyv3H/wCCdHwXk0T4Z3fjjW7ERXniyYPC7ffOnQYEf0DyF2Hq + ApPauXGV/Z03Jb9PUa8z6I8JeDfD3w48PWXhbwpYrp2nwEoiJ1PA+dz1Zm6sx5P5V2Wn27tKXblW + 75xg85rotYsYZdYkgCErCoVT3Ydz+dZunW1w+qKrMWjBI2Dup4BI9j1r88qYhuq+Z9bHpQh7mhR1 + 69F41tpcRJABckjooyK2rCBrazhAO5FX88GtSbQPJkadVHmSAEk849h7ZpY43t0EZHyqOO9awou7 + lPdmUqityouQksiSJwSeV9OKsKpI3khnBwcVXgmjCiQEgN1B4IqzbqftDBeVYcV2RmrpI45J2Y87 + UcSbgc8DPc1JJMpk89yP3YAwOmTUc8cySYcem339qfYxiZ2MvIODt9/pTlPXlKhHS7On0yR3w0nA + I4Ht610sJURHHXPWuWsoXmlGCRtP4GukVljUg8LyT7mu2hK6uzCasx8YQyncOO1MmKbgJDtUck9g + BVgbABIOpAHSuZ8STtHplzHED5joVUDrk1FeqoQbKhBuVi5qaxtIEh+4wGCPSqkS7ASgyFGBWRp0 + 1yunW/28/vY4wp9j6fWtVZM/uwcknHFeZKopy5kdcYNKxcsHEVu8ifeYkA47VBqUrRWiOOvr6+9X + ljjSNFQ5GP51ia24YfIeFG2tORqJTkmzgdSkOHz97oDXGSyBdxXseveuo1OcHIXo3H5VxlzvMhRf + uOccV5lSetzeC0LNtOC+WBb0ArqLM7I1aZsAc5/kBWDZ23lZCDgAAepA9a3rSIsFjYEliDx2reE5 + WMpRVzdjkJkNtGe2T6ewrY01nQgbjgZBA7ms2CLyyqgYI7e1XLQtA4iGGIyf/rV6mHbTOSpY6e33 + XEYkH3SOh4wR1rJ1SAS6fOvOMHIHuK1ICBEGUHGe3061GwWTcyNkN68g8f8A1q66lNSjbuZRdnc+ + Kf2g/hfH8WPhNq/hKHabsp9osT/du4Pmj57b+UPsxr+eieCa1nktrhDHLCxR0YYKspwQR6g1/TwL + m2juZrViAVkZT7HNfhX+2R8P/wDhA/jjq0ltH5dj4gVNTgwMLmbIlA+kqt+Yr0MgrqVLk7FVLqbT + PlaiiivdA//W/n/r+l39jvRv+Eb/AGX/AARbfaZr2XULA3atK2Si3MjSeUnoibsAV/NGAScDkmv6 + tPgp4RHhH4a+FPC6uXGi6RaWqlzk5Eal/wDx415+ZTfs+WO7Fa7VzrptIjuiVEe2RMyKR1yfl/UV + Sj0MadukRQsrn8hn/Oa7c2arN5v8Sg7fTBxkfpVHUoHJWZsArkHPoe1fKzwMYvma1On6y2rHOXDH + yUafmQHBA6HqRWNMVdGGMFCAcc9a3JrWWXzkX92GHysOf51Sit1TOzDkn5s9c0VIszja5jahGVSO + WNSB0IB7n1qfzBbzROGGH4PbFaHkLIvltllf5hu7HNZl/ZuvyxHaCcj0yPSuCqnH34nTGzXKzsPs + 6XiDcD2zis2/sVtgbq0kKsp5Ht/SrGjXH7gRFssB+NaFxArRktnDcEdq669PnjzR3MacrOzJbS5V + o0cHO0YOKsPNuPDdDisK5jisYNyggH0PUnpUto+GCPk7vm+lVDFte5Lcl0eqOlWQAJk+nP8A9eud + 1dhc3QjzlRj61fEshOw/dAwB6ms+YpDvlb73Yn8v8a58RU5tOhtShbUjWFZX2fwQkEjP5Vp2UPlq + Hb5nYn9arRA+SDJhR95sVs2q/IrsOME4q8NRV+bqKpUexBcTfZvmHzEDj0z14FctqTExbCcZ5zWn + Nc+fPIQ3AB//AF1zl8SSsRPK9TUzqczfY0cLJdznLyIOSyclRxnoK582zpIEUZHXn3rcuZiH8tR0 + PPpVa5Kxy7wCABkCsKkYtXW4RckyNRvfan3SMcd8da6S3tQHh5AAwWU87gBwB6VkaRavJi8l5OOB + 059hXTxLtLs64Kn88/0rWnFWuzKbLY8uRipwC+AP/wBdURAsjOZZDtGc7SRnHbNS3EsdvCHbrnP0 + qC0xImTwTzn0rolK75TKKsrnRQvviUI5TP659az9Su20+1kIGG25A96liJFqQn8PDZ5PWuE8ZamU + tCd2yNBnJ6cetTjK7hC63NKFNSduh4/qOrCG8nZ22SuxkPrX58/8FCLSG90jwN4jIxMXvLcHHJQi + Nxz7HJ/GvrbUb5tT1wIkhCMwBz3PT8q+L/2/9fikHgvwulwryW63Vy8QPzKjCKONsdgdj49cGuvh + lyU2mXmEFzRsfm/RRRX2hyH/1/wO0qEXGp2dux2iWaNSfTcwGa/r60cIlssX/PLgHuR0zX8fMUjR + SJKvVCCPqK/rO+Fni+y8ZeA/Dvi20YGPWtOtbvgg7WliVmU/7pJB+lcuKWzOeuz1NgQC2eTnP41W + kjjuN8ZxuUce1NeViCFIyBUlv87yKvyyHGPcGvLrWeiFTRz91CYGUg4IyD71gwWTwyyz55kOT6Yx + iu0vdKxw53A+v6c1gXMM0Lsrjcp6AdK8uunHVo7IWehgyja+xTgDP4E1l6kZLdT1IYcg9K6gWsW4 + uFzu4INZOrWziDeQCCMN7V4+LjJxdjsoySepzmnardC6XA3ZO329q9FRwyrk5z2NcLo1qHw0w+ce + vXr1rqryTyrcHbkHAPt71WCjKFLmnK5GIcXO0UMvJFmZIyA6jjHuOTT3lRGTauCBx6Yphi3PvU8E + ce5xg0+VAjYwxwcDPQCtJX3JiuhaWeNVLjJJ4xVRrMzOJ7klkByqDu2e9SW8IeUTISQuQB2/WplM + hDOTgA4AHtUtcy1LvYsQguwVunFSzTlFeQEgcrxWP9s+yOSx5HPuSafHci7EawkhRyB3P1/nTjXu + uVblxpa8z2IEVFVnmGNwyD0yBXM31wQolHLOflH16GtzUrwSw5xsVOCPQDrXHSXxkeTChcZRfY// + AFqynOK0uVyyepL5It7Pz5upJY9+/wDWs/Di3M8oIlf+HrgAnH6Vr3mz7Koz8i43E/n/ADrKaWPd + IznATGM9qitJc1jOKe5qaRFNwz9ASVX0ro3VAAGB55PPJIrmbPUI4oGCtguwXP61eN62w3Cnhdxy + 3SumlFcqVzKb1uZOqamsMpjcgkHBHTiqreK9Js+DMGc9EHNeC+P/ABQ0N9JbQylWXgsO59K+ZfiJ + 8ZdN+GmlfadfuSLm4BMMCEG4lB6EL2Ud2bA/HiscNKtOo4U1dnRKlDkUpM/RmbxPbR2b3hfywuPo + 2Op/wr5j+JPxMt7m3+xQzFy5JbGRkZ49xjpX5S67+198V7+aaPRriPTbJ8hYyvnOB6szcE49FA9q + 8L1/4j+OPE8ry6zrNxNv6qHKJ/3ymB+lfQUeH5yfNWl9xzKul8KP0l8W/Gvwv4C02bUry586+RT9 + ntEcGWST+H/dX+8x/U8V+Z3jXxjrnj7xNe+K/EU5nvb59x67UUcKig9FUcAVy7MzsWclmPUnk02v + ocLg4UY8sEYSk5PmYUUUV0iP/9D+f+v0j/YZ/ao1/wAJeLtF+DfjC4a78M6tKtnp7bcyWVzM/wC7 + UEDJid22kHO3IIwAQfzcr7c/YL+Dc/xP+N1h4iunEWkeCpIdSuGyMvOr5towM5wXUsxxwFI6kUpW + tqZ1UnHU/orWbKAdd3FaenSOJWZ8Dnj6d8VgJMY7lkGDFkdCDke1S+eyTAEFN2BuPr9K+dqS5ZXJ + p6qx3cgjnTPAzmubntwCVzkjvUY1SRZ/IzhFHBz17VOLuBclsbmA+XqcU5JTV0Ne7ozDntDEGmUk + HIPsayLgvf2piZQCCMEVvapcxSRhITn2Hb61n2irt5HzV5tXDa8q2N41erMe1spIsk4/d8U2Y3Et + tKACcjr3GPUV0F8iwww4UkSHGfcVXhgxI8X8Ljg1w1KdnyHTB6cxlWk8hiWKYbZYvvjtg9x7VqAL + IpZzlCe1WhbRl/mA44/Gkk8uJBg7QOPrUxpNL3mVKSb0IJWSMFRkL7VReby0zgZHOPQUXMjzKT91 + E6H1PrVRXMsWCNzE/TmplLWyBIogPqVyJTlQuRn1q1a5tEEznBGRj3rQtTFDuZlyxGfYev8AjXMX + Oqxyzuyg4XkZ6En2qKNO1pN6nTOV9Fsg1CRRsLqH3HkZxng/1Nc19lWPYbo/PknjoCac146q93cu + AqgnnqST29h+tc1qeozxSC4nBGceWDxkHoaznTjvJEOo9kbeo3U0untbImA8gOScYQd/zrP1loor + bzlJZXVRnrliMceuK5a2XxDrfiO0ijlSDSIyTdFuZG9FX69zXY+JZLSH9xHjzAvygnJHp+NbSwql + FtnL7Zpo4yy1eWaaPT1GJDx+vWuo8Qa9b6NoysW3zuCgXOcLjnI964S0S1028nvAS8yoQpUkKN3B + 9q4nXLi61CZlkzsB/DNVRwziveYVKik9Ecjqe6/u7ie4ZTAu6RnY/cA5JP0FfjL8WfHEnxD8d6j4 + jwVtmYQ2yH+G3i+VPxI+Y+5Nfsd8QLe60v4a+LNRtcPJDo98yN3GIWwfqM1+FVfVZNh4JOcV5GEq + km7MKKKK9skKKKKACiiigD//0f5/6+hf2fP2ivFP7Pesavf6DaQ6hba3bfZ7iCbKncmTFIjjlShY + 8dCCR1wR89UUpRTVmJq5+1v/AATx+Kvj74it8Ql8WahcamLe4tLyJpWLJCbjzVeOMHhVIRSFHAx0 + 61+ltxqiC3LXkhjwcBs4OenfvX8z3wL/AGiviL8AdYku/B12p02+lhe/sZUV4rlYicDJG5G2swDK + QRnuOK/TLXP+ChPwd1i5QxQ6nCigAMbdTjPUsA/X6V5mNwkn/DRlZqVz9BYtTjmu3mW7aVYc9cKq + gjvx1pk2vM0hc8BB1Jxmvz1n/bY+DC26S2WpzuzclJYJgwPuAu3P0NeZeI/25/CMTMdMt7i+c/3V + Kgf997a5aGW1uSyVvmhTqXlezP1Si8VxNu8xwXAwD6D2ro9N8RwllR2Bz3Jr8LdQ/bq16V8WGhlI + 1JI33HJ+oCf1pbP9vjxtZsHXRomKngGY4/H5a0/smo1rJX+f+Qc7W0T+g03dhdQR7JAV4GPep0g+ + RimCe3p9K/HL4af8FIvDtxdRWHxE0OfSUYhftVq32iNfd0wrgf7ob6V+ovw5+JXhrx74eh1rwrqU + OqafP80c0DblPHIPcMD1BAIPUVw4rCSg+aa+Z0U6jtY7K78yKQ7Dw2Rj39qoXTHYIly2ep+nWpZp + Hluckn5eSfeswvslYyHPpn+VeFUnd2R2bbjLm4Oxbcj527VSkmW024J3nj2+lXos7pJZDnd3HpXM + aldobjeB8qj5D3z3NYTi7czHTlrZHQz3UjwnBCoE2gDuT6+wrz7UrgWrYU5baST9a0rjWolZLeFx + tiU5HXJ9TXnur65BauzM3nlycbc8UsTjIRWr1Lo0pvRG+RDcQJb3Y3RSjDjPUdxn0xxWF4lnt53X + a25YMAY6DA4AHoOlYKazNfWCzxgkBtg7ZPXj2HemRxvuzu8xxzyeM/SiNaM1ZK6InTcW7snOr3Fj + D5kLBSwHJ9/SuS1K68Q38DSaVH9puHdV+dsDB6ntWlcwSXc7BpMAOvOMrtB5GOOo4rr7C1htlkn2 + ZOPlB969SnaUeZuxxyfK9rmdqGjTJaWdqJf9IlQGRV5VGPXmsLUdIEK7WJbavLdvavQrR/KbzjGB + OQy5+vYVzniY7LLyefNfBbOOD6fhWOIq+7eJVNa2Z5j4hsy/grxPp94pMUul3ik+xhbdj8K/n+r9 + 1vjT4nPhL4L+MdSmlxI2myWsRzg+ZdfuFx/33n8K/Cmvo8hi1SbfcifxOwUUUV7ggooooAKKKKAP + /9L+f+iiigAooooAKKKKACiiigAr9LP+CZfiTxDb/FbX/C1vKzaPdaVJdzRkkok0MsaRuB0BIcqf + UfQV+adfsf8A8E2fh7Lovg/xH8UL1NsmuzLp9nnqYLY7pWHs0hC/VDXDmVVQoSbHFXZ+qT3aJGS5 + w0vT6VgfbFMsjy8rjauOvFJNbPKizSnDenTGPSqM0Qh+ZyPkGfx/xr4Dmknc6mk9C7PqCeQIfuZ5 + b+g/DvXDa5qBihKR53E4yPSnXN0yAkv1J59T+Ncre6gtvNmY+dI/3R14/pXn18U5aHVTo2KLXItd + 0hQlpCDuzkgAYx/jXO6pJFcyyPHlU4wPUgV0hiikizypcbuf6CudcRwSLEW3O3yjI5Gelc8cM5Gv + t+UqGdhZpBCmxSd3I6tUcd/cWhWFmMcsmQAASccE1vz2jQWpkJAwRg9jWaJlfAK/M3f3/pXWqHI7 + tnO63Next2lqjQiWZunJBPX8K3LSRXGzH7pV4PQDHQAVxNzNISm7hM9D7VM+vpFAzE/dBAH+feuu + FVuVkYzp6XOxnvbW2xLKx3Kp4J43H2/lXCatdC/ujJuDgctzjA9Oa5PVPFiIxLqGJ6ZPU+9YSeKI + I7O81W62C2tIJZpTnDIkalmP5CuvD4Wc372xlOaitD4O/bO+J0t9f2Xw106TbbW+28u1DZ/eEEQo + fopLH/eHpXwbXS+MvE174y8U6p4p1D/XalcPMRnIUMflUeyrgD2Fc1X31CkoQUUc6QUUUVsMKKKK + ACiiigD/0/5/6KKKACiiigAooooAKKKKAL+l6Zfa1qVpo+mQtcXl9KkEMajLPJIwVVHuScV/Tf8A + CHwLZ/DnwJoHgyxRFj0SzigZkGFecDM0n/A5CzH3Nfjb+wL8LJvGfxbPje8t9+l+EI/PDMMq17KC + kCj1K/NJ7bRX7pQxtDH5LDAK/j/9avluIcTqqS6am1KO7JS0007YbgfrWDqki+a8LKQAOG4xk9RW + jLLMspKZRVXA9PrXnXjPxbbeGbU3l4wlnPRT0RfU4r5GUubTzO2ENirqksFqC0zmTHRc4GfU1xF5 + rOlwYurmcSyZGQOueuMe3pXjfin4nx31tKys2CxJ425Xsv0rwPVvFt/OW8pgFySPXmuzC5NOetrI + dTEqOnU+wdW+I2iaed80iiM92P5jrXmNz8e9Nhnk+zWpfk4YDGfz5r5VvLyaWQPOzPn1JNZ0tztw + FAP86+lw2U0qfmzzZzlLc+kz8c76aVmmtFZG6DcTiuo0r4xadeSJHfwtAG43qc4/CvkdLpCRkY+l + a9tcDI2ng1rWy+nII6H26vibT77CWk4mibkHvz+tVryea3YRmNmVl3AgZyD/AFr5+8J6ytrIIpiP + Kk4J/unsfwr2nRfEc8oSOd1cxvt9iM148sv5HzJGkqrtY5HVZra1t7u9v5hFaRI0jyP0RVGST7Yr + 8zviF8ffFfiWTVtD0G6ax8PXv7ry1UCWWIH+N/vDf3UHGODnnP3P+2feXVh8Jd2nW4EWo30EE8qj + BWPDSAHH95kAr8ma+lyyglDnaObSQUUUV6hYUUUUAFFFFABRRRQB/9T+f+iiigAooooAKKKKACii + ve/2Zvhb/wALg+NHh7whcxGXTRL9r1DHQWdt88gJ7b8BB7sKUmkrsGftd+xh8L/+FcfArQrOePbq + WuKdVu8jBD3SqY1P+7EEBHY5r6pkh8yZlUjIwPxqfSIorQKqAICAoUcBRjgAegqw0MUTSSLnBbPr + 9K+KxcfaN1Hu2awlbQ4rxJqUWh6Vdanc8+WDt9z0A5r4K8Z+Lp9RnlkmkbEh3Fc559K94+LvjJNR + h+xQZSKBmCr/AHiCRk49etfHupt5twSx71z4SjFtS3OiUmlY5jVL57gsSML6VyFxJKCVH510l1GX + 8wk7VB/T1rn2jDqWTlR39/avcpyb2MLWMl5y5wwrOl3OTsOMVv8A2cbCJADn0rOntbZOSxGfbOK6 + U5MaaILdPmzmti2wG29M1nogjG5GDKBzWhCrSHg8VMptLUvkR01jcGBwQeRz/wDrr1HQNW3uqjDM + eQP73qPrXkNrG4O9TkH1rrtOYoySISCD27H1rkniOXUJULo779oU2/iH9nvxPaopd7aOG5UEZwYZ + UZv++VB/A1+M9ft28KeIvBuu6HJymp6ddxNjGQ7wsuRnpzg1+Ile3gZqVO6OCnHlbiFFFFdZqFFF + FABRRRQAUUUUAf/V/n/ooooAKKKKACiiigAr9if+Canw2Sz8L+JPijeQ/wCkarMum2jEciC3xJMR + 7PIUH/ADX5OeDfCOvePfFGm+D/DNs13qeqzLBDGvqx5Zj2VRlmPQAEmv6cfhF8N9L+F3gHQPAOlj + dBpNqsTPjHmyk75pD/vuWb8a4Mxq8tPl7kyZ6YsXlvhhnOP61R8R3P8AZvh26uomIkgHH1bpW1Ku + +UMPlC4AP1rx34t+IWsdE/s1G+a4547YP+Ar5vFWjC5dLWVj4y8calJNdTSSONxJ4HQZryi4iPls + 7n/J5rsPEbSTSZPH1461zaR+Ztt3+bHX/CuTCz2iehVgkrnGy2M827ng/wAqzJLcRKIolG0DtXbX + oMRZI14J/Osj7K+5mfAz04r242Whx3b1ONZg+5VAO3rWdLEQhZB710r2W0kSoASTnbxmqc0IVMIK + qOJ0NlTVzDgUudhG0ehFWxG0UmEXAPTmtGGzxhyMEnt3q8LaM5RQN2cndxjNZzq32NbWK8BOdp5O + Pyrp9LL5AHQ1m28CHMbjDgZ47/jXQ6bEpww4xXlYiXQ2ij1jwZKBeRxSEbZCUIPQhuCPxr8gPiZ4 + Rl8CePdc8KS8rYXLrGf70TfNG34oRX6t6ddyWE9vdJjCODj2r5A/ba8PQ2njXQvFVqB5es2JR2He + S2fGf++HX8q9jhyreE4N7Hn41WqJ9GfFNFFFfRGIUUUUAFFFFABRRRQB/9b+f+iiigAooooAK6/w + R4E8V/EbxBb+GPB+nyahf3BHyoPlRe7yN0RB3Y8VyFfuf+wV4H0jTvgXp/iG2gT7br1xcy3MuPmI + ilaFAT1woTgdOScc1hia/s481ribNb9jf9kRfgzLP418Xyw3/im6QwxGLLQ2kLfeCFgCzvxubAwP + lHUk/oXJAyBSnUcL+PWqGli3gjCDjGB6da6tkQrkfwjivIqTdXVnPJ21ZjyMwjDE7cLx/vHgV8pf + FP7Ve61N5iny9uFHoAAM/pX1XfK8S5jXeFwPwNfOPxIt3OqF2xkAjjjgjI/Ovn82baiuzO/Buzuf + I+t2zfaOf4D36HFc2gWOTeeFPSvR/E9uy3BkUYHQ1wOoWkjIUt32FwVD4ztY8Ake1c+EnaSOytrE + x3XexZSG65+mcVQMYKkcbue2cfjXTTQiGBQXUunBP045rN2KVYxDfn8h+Ir6CR58WcldQqBtXlgM + tWZ5LTfMmFx3rq7m0IDM+CzDqOgqlaW2AzP/AAdh0rkqLW1zspS0uZMcOFJkHHrip0tSdxYmQdfc + DHTirSukkvlbcKOcj1rUSIbiB6ckcVEatuprJXMaC0ETrFESQO7HJOe1b1hCvmbAcnOCKbGoilWT + aCN3GelSQwsbw+V95jyOw9687E1ru5vTibtnDI56EqCMe9eEftw24fw54OuI0O22knjJ7DzURgD9 + dh/KvpuK2VTGittRMZPqRXjf7U2lXGt/B28vnX97ptzb3BAHIUHyjn8HzXvcO0Wpyb7HkZhXvKPq + flhRRRX05AUUUUAFFFFABRRRQB//1/5/6KKKACiiigAr9O/+CdPxxu9I8Xf8KW1uVTpeq+ddWJbq + l0oDPGP9l0UsB/eH+1X5iV9gfsMeCr7xf+0ToNzag+ToCTajKw7CNCiAn3d1/DNZ1mlFuWxM43Vj + 9/dcvUBkl+dAcAeXgMcf561taJ4ohvpDDcnbI4yOCQR0/rXNXNuHtZJJXIdDwOoHua4iW+vtMv0Y + LtwR04H1xX5/DNVGrdLRnU8K3GzPoi8RUsfMIxjn8K8A8f2jXMUF2vL/AHW5Ge+M/hXdt4ojktUN + 24LBSSB0BxXl+q6gmpTXtjFES5QyNJu4Tb0/PtXXj1zw90xoe69TwnxLp52scfvIzj6r1BrzmSFk + VuuDXslysM8X2eU75IshjjGV6/mK4XUrI2kmY/miP54P9K8aDs7no3TVjlzCtwgjmiQ4Gc9SR9Kx + XsG3EAYjU9VHrXSQrHHLux8pzn6e1aohtDGQqln4yD6V9Bh6vtY8t9Tz6q9m7nntzpjSLt+6Oo9T + WJcWv2dPIjDOrg8Ac59zXqElmAS8rEL6celc1cCJWcYBzxVVaPXYdOt0ONtdKYDMj7R2C9f/ANdb + MFrFMG88AbT93rj0zTZgwfI+Ue3amRTxIME7stz7/WsfZxSOj2je5DqCRzIUhGQw4b0+lXNKwskM + d4wVSQucc49/xqyJIRb+ecEf3eMD6VkS3ljMyxTnaCc5xz+FEMG5asmeJS0R30NxZRyMrSF5CcJt + OMN1Bq5Npdl4n0LUPDerRGS3v4ZYJvXDjH59x71xv2WBLqCDTJDJCwydxyQe59vpXZ+Hry4tp5rO + 7iAZDncnKtnuDX0eGgqVrHiVZc92fi/4z8Kan4I8Uaj4W1dClzp8rRkkYDr1Rx7MuCPY1zFfrJ+0 + 18Bv+Fl6GPGHhiL/AIqPSoiDGv8Ay9QLlvL/AN9edh78r6Y/J10eN2jkUqykggjBBHUEV6d09Udd + CrzR13G0UUUGwUUUUAFFFFAH/9D+f+iiigAooooAK/Yv/gnZ4Lbwv8Ptf+J17EEn1ufyLZ24b7Pa + ghiP9lpWI9ylflN8PvAuu/ErxlpXgnw3CZr7VZliX+6i9Xkc9lRcsx9BX9B2iaJpPgDw3ongbQyV + 07RrVLdSRgv5QwXPu7ZY+5r5/iHGqnR9mnrI6cLS5pa7I9Oi1KaW1BmdXNwQxxnt2FVdbY3KJHGB + 52C2fQAc1g2EUt8B5cm3yxlT2UdyfX2qrean9gje5cmTpHk+p9PpXw1PRWZ2VFd3RzF5qtxDuBJK + r1XPcVw2qateyQu0Uux5D8x9h61dutRS4uJVjB2vnax9a5zUEUqFX+LJznpXt5c+dXW5x4j3HqMb + xFGiCGSb9+qjMmMZPrirNleQ3tusTyq0hBz614l4gnngmbyeozgf1rldN1/XbadfLJkAORnqPoa9 + meRSl78PuOT65HY+kJbOBgQ64brx049K55yYGMhBwPwrMsvFes/ZgZ7IStjIIYA5968v+K3xo0jw + Zp6trak3MozDaRkedIfUnoqDuT+GTXPh8mqqouxNTGJxstz0fWPENhZwNJNMI40XLMxAAAHWvmvX + /j/8PrJ8W+otdSIxB8pHYfnjBr5S+I3xp17x6n2KOIadYH70aNuZ/ZmwOPYD614xX0zwdLqrmVGn + NK8nY+1r/wDad0WGXbZWVxdpjqQsY/DJJ/SsiD9pjTpZWW70qeFG43I6vj324X+dfIFFSsJSX2To + +Z9hv+0ppdsxFvZ3N2hHCtsjAP1yxr1X4f8AjF/iboV3rcUf2H7JObcRBg7ZKB0YnA68/lX5z190 + /so6U7+EvEGoZys17BHjsBFGxJ9Od4FRiYQhTcrEOB77oOtXOjPPFqAFwsmCrH5XUjptPT867nRv + F2j6ncM+Ps16vVSeCPUY457153rKK21kGML/AFrhbpJklVoyV/2gcEGsKUeeN0Yygrn3NoesW77E + JHbI7V+bv7X3wPuPD2tzfE7w1bg6RqLA3qRj/UTscCTA/hk7ns/XqK938L+NbnTysN05mCkYLHJF + e92WvaJ4q0ybQtYRJrS8QxvE43K6sMFSD6g1005cr9450nCV4n4UUV9N/Hn9nfWfhjqM2s6DG9/4 + amYski5d7fP8EmOw7N0Pfnr8yV1HoQmpK6CiiigoKKKKAP/R/n/ooooAKKKKAP2g/YG+Dlh4Q+GU + /wAZ9Xgzq/iYyW9mXAzFZxMVJT0MjjJPoq475+itenmuJIpuUJYrgHkDt+NaXgqNtA+EPw+8KWsY + Elpolj5i9CJGiVmHHvnNURE630FtNlpFRi57BiecfTOK/NM9r+0xElfbQ9nBUrU+Y7bwlcW50uS3 + XKyY2uW4PJz1+lYOtkXMixwf6hOufXp0p/nCEvbxNtRcBj2x3/GufnvSJJWU7z8wUeh/zmvPqSvF + J9CYxs2zA1AWslysIfZt+7gcDH0rG1+3jtUAUgFlDfifSrpB84koS2ev4Vm+ISJFVBy5VcmvoOH6 + a5mebm03y6HkGoWjXEjEc57/AFrQg0e1to1bYPNUZZj2rqorBOOOFIrxf41fFPTfhrom84n1K7DL + bQA4JOMb2/2FPX16V94m3oj5uKb0RwXxn+OWn+AbZtC8Ostzrsq9uUtwejP7+i/ieOv54axrOq6/ + qM2q6zdPeXc5y8khyT7ewHYDgVVvLu6v7qW+vZWmnnYu7ucszMckkmq1Ny6HsUMOoLzCiiipNwoo + ooAK/Qv9lyyuh8KtSn80WyTai+CwwXVY0BKn0zwfevhnwf4V1Pxt4jsvDOjhftN6+0M5wqKBlmbH + OFUEnAJ9K/Ve+8NaZ4R8NaR4e09z5NnapCpKhBIUAUvgZwWbLHPc15maV1GHs+rKjG7OP1KVzN5S + SBlbp7AVh3GAPKnUlT0NankFbjD4O0ZA9M1l3bozscZA4II/lWVB2hYzkryMuWONjujYLgfTBq7p + /iKa0lC7zlTwQf61QCXeTLFFvKnhgeo96W8jmkhDxxiMHtx/nFddBObtImpaJ9CeG/G0Gs2h0nUo + 1uIJFKMG5OCMHjvXzJ8Q/wBl3QNc1C71XwXcHTdp3ywhQ8IDd1XIZMntkj0wK6DTbbxGsDGEBTjc + uPlLKPen6X4lv9PkuJXMsZn+WTdk4A+taYahVpVHF6xMaji1zQep8DeNPBOt+BNYbRtbjAcjfHIu + SkiHupOPxHY1yNfX/wC0tqFvqXh7w1cI3mSedcZbHIUqmBXyBXU+jta5tTldahRRRSND/9L+f+ii + igArc8Mm2XxJpLXgDQC7gMgYZBTzF3AjuMVh1NbTtbXEVynLROrj6qc00DP6j4dONzD/AGmyqFhG + AQPl29gK821NQl5LK55xkt2UVj/D/wCJun674V0/Wo5SLbWLK3nX5wwXzVDYwD1U8H3FW7mb7XII + UjkEbAOSQAWX1PoK/O8bSipOK3/U9GhJ21McagQlzIVAWMYGO2O5/GuftLw7mmXqDuLH254/OrWq + X9jYLNBG28Sk4Oe+Ow9K8+l1UGD7HG215H6g4x9a8iNN9TplrsdFd38kl0+1NnmH+9/nms+cu98b + ccgfKPbaOalN1ZQRyXUriV15BIIyTwOD6VTsp1mR7k9eefqK+vyOi1HmPAzOor8o/KxWQSIZkJIJ + I75zn8K/Iv8AaB8Uv4p+KOrOH322mv8AY4R2Cw8N+b7jX633Eg4UcBRgV+J/jjTbvSPGWt6bfrtn + gvJ1Yf8AAyc/iOa+noy0aOPCxvPmOWooorQ9EKKKKACitfQdC1XxNrFpoOiW7XV9eyCOKNepY+vo + B1JPAHJr7t+HH7J1hoWonUfiROmpGEqYbe3LLCWU5zIzAFx7AAepNZ1KsYK8mS5WNT9lj4LS+FbZ + Pi54tQLd3MDLpdow5RJhgzyf7yEhF9G3HtXr3ie/uNQ1QeaNy4+UDCgL7Cuv1fxBehRBAFjjTgEc + cDgYA4AHpXmt5M1w5zIfN6EketeDVvObqS+XoOM9LGbvjSbY38Z6+/pVSZjGSH5B70k8RgJIwzjn + AOCfeqclv9ow0Lt0JZTz+VXCT2W5Tihha2RN8crI4zkDgHNSWl0Ip2LRCWFeRgHnI6k+3pTbO0WR + C1xId6nCrtPT3roLM6bZWjPqUpkwCAmdrAnpXoYVs5cS0kyO7uGe2JRcNt4UH19eOBXzX4x+Jth4 + eupdNkiE9wOSkZ+77Meg+ldj8QfippvhW1mitGEt9MD5MKnO3jAdz6Dr718O3NzPeXEt3dOZZpmL + ux6szHJJr2edRjZrX8jnw9Jt36HWeLvG2p+LjbRXSrDbWe7yol5wXPzMT3JwK4yiiuaUm9z0IxS0 + QUUUUhn/0/5/6KKKACiiigD9m/2EvGOmeKvhY3hq9I+3eHZWt23EH91IWlhbB6Dll/4DX0V4v8Q2 + lk8tv5DN/eC9G29ASO341+N/7LXxRf4cfEiG0upAmmeIdlncFjhY3LZhkz/stwf9ljX6vX2t2V/P + NFExQD5HYgkbj9a+TzvCNVFOC0f5nThp2vc5K91ozj7W0QLMu0AdFz6CuTknW1YwE7JJmPI+8Bj1 + rcurIC6Kbh5eCF5wM+vHes2WxtrVozIPMuHYZLdFHtXiJR2tqd/S5d89pIPMkk3DIwDySR61r2lx + 5GnOg++wB57d6xrUNMGGMKrHke1blza7YQwPDfpivs8vgo0bo+WxbcqlmSQqssgV25fGa+Gf2lfg + V4p1bxi3i7wXp51KG8jRbpIcb1mQbd+0kZDADJHcHPWvte582ODzYD8w5q/o91dyQu/QrktkdhWk + asYz8xw5kuaJ+Stt+z38Z7sqIPCl2dxxk7AB9SW4rrLf9lP4wTPsnsre2YY3CSdSVz67A3Sv1g8z + ADLOIA3p1qlcS4je0iQyGQZ8zPB59aHjX0idSm31PzSj/Y5+IxCNNf2MYbBOGkYgHuBs5rvdN/Yy + tVx/aviCaYlekMCxAHvy5bIH0FfdCMwwnA4HU4B9azrqVrU73kAVicZHWlLFz/r/AIIlJvqeY/C/ + 4J+AvhtL/aGlW5n1JVIN1O/mSBT1C4AVQenA5713mr3IMsiK/Bzkk8CpZr8MMJkA8g4rmZ7uxt0e + e7kEcKgl2b5a5pN1JLqJ+7dszJOclW3H9K5+9QvNiUE7hhdp/KvOfGP7Q3w/0Nn0/Tbhr6VMqfs6 + bhk/7Zwv5GvG7r9ovSLtwzWdwgTocjcfTkGuyWCqWsRSnd81j6Uns7m4bZEoXcOWPcDr1/pUESW9 + jtm+0bZAc4I4HqK+Ub39oe42eVp1i5X/AKauB/LNcBqvxk8U6jE0UYjtw2eQCzc/U4/SilgmneTN + pczVkj7K1nxbo3hzN7fX0EcbdRnGPw6n8K+bPGnx1+2yTW3hu3wpyBPL/NV/ln8q+eLu/vb+UzXs + 7zue7sTVSu+mlD4SFQT+Is3l5dahcveXsrTTSHLMxyTVaiik2dCQUUUUAFFFFAH/1P5/6KKKACii + igBQSDkcEV+r37MvxVm8d+Bn0vUY/tWqaMI4LlnOWkTB8mXJ6kgFW9xnvX5QV6b8KPifrPwo8VR+ + ItLT7TA6mK6tWYqk8R/hJGcEHlTjg+2RXNi8P7SDj1C5+u2qXEtmGZIEJbJBbnv3x7VztzfxuySh + AVY4B75HX6Vg6B8TvDPjPRbbxLpjs9jOCpjODJDN3SQDoR69CORxTpLr7Wf9HXCknHGK+cqZZV6x + N6WKgt2eg+G4RdKY1YNknn364rotShjtgLY/NsGfXGecV5doerHSrs7G3h+uDgiu9fUkvmaRjhjj + pzjivWwclZRtqkeVjYWba6mNfMwG9TnHYVVgl1A5CHy1fg+/tWy8KsOm4N0/CovLkSQEYDd89K2q + 0XzXMaVXSxVeB5ZN9w+WYcAHjj1FTx3TRqIJvmRTwAORUmxgRkEZ9Rn3xUFxbSgCWJ/3Z4OSRn6C + uSULfCdcZfzEd1Iz7hCpIxkf4GmpdRvamKQB0dsBSD+NWwscKeY53DoQRz/9euduNSKSNFbgnJPT + rV+z5fekxKonoifVXh0+KZ7iQLH5bNnPtxX5bfFf4pav4516dLW4lg0i2Yx28KsQGA4LsB1Le/Qc + V9i/Gz4iab4T8N3emM4bUb6Nlij3ZcFhtyQOgXrnua/NqvQw0LLmtZl0lfVhRRRXQbhRRRQAUUUU + AFFFFABRRRQAUUUUAf/V/n/ooooAKKKKACiiigDuvAfxA1vwDqn23Tds9rKV+0WsuTFOqnOCB0I7 + MOR+Yr9HPAnxF8LeP7Nr7w+Et5bhUL2ZcG4hkXhsqeq8jD4wc84NflTWjpWr6poV/Fqmj3UlndwH + KSxMVZT9RTeujMKtG+q3P1S8W2eoWEtvqx3W8coAKtx8w64x2rT0zxbEPLVyCpwTjqCP518MWP7Q + +u6lHFa+M0+1CJAi3EKgSk56upO0/hircPxm0K2n8yNbnjowUfyJrKnQau27kyjzRSaP0o07WbC6 + t8+cpI65PSrHm2Lby91GiDsQD+f1r84pf2i4oLdfs1pLcS88OQgH1POa8y1z43+OdYnd4bhbGFv+ + WcS5/MnJrSa5tGjmjhHe6Z+skfiLSY1aFpVcr1YMGAA9+leSeNP2hfh94YR4ZtRiup1O0xW5818r + xzt4GPc1+Weo+J/EGrYGoahNMo5ClyFH4DisIkk5NZQpW6nR9VTd2fdev/tc2PkTLoemyXE5BCGc + BIwexIBJI9q+ZtY+NPxO1pj9o16eBCc7LbFuo/79hT+Zry2iqUEuhtGlFdCWeee6lae5kaWRySzO + SzEnuSeaiooqzQKKKKACiiigAooooAKKKKACiiigAooooA//1v5/6KKKACiiigAooooAKKKKACii + igAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA/9k= + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + /9j/4AAQSkZJRgABAQAAYABgAAD/4QB0RXhpZgAATU0AKgAAAAgABAEaAAUAAAABAAAAPgEbAAUA + AAABAAAARgEoAAMAAAABAAIAAIdpAAQAAAABAAAATgAAAAAAAABgAAAAAQAAAGAAAAABAAKgAgAE + AAAAAQAAAQygAwAEAAAAAQAAAWgAAAAA/+0AOFBob3Rvc2hvcCAzLjAAOEJJTQQEAAAAAAAAOEJJ + TQQlAAAAAAAQ1B2M2Y8AsgTpgAmY7PhCfv/iB+hJQ0NfUFJPRklMRQABAQAAB9hhcHBsAiAAAG1u + dHJSR0IgWFlaIAfZAAIAGQALABoAC2Fjc3BBUFBMAAAAAGFwcGwAAAAAAAAAAAAAAAAAAAAAAAD2 + 1gABAAAAANMtYXBwbAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAC2Rlc2MAAAEIAAAAb2RzY20AAAF4AAAFnGNwcnQAAAcUAAAAOHd0cHQAAAdMAAAAFHJYWVoA + AAdgAAAAFGdYWVoAAAd0AAAAFGJYWVoAAAeIAAAAFHJUUkMAAAecAAAADmNoYWQAAAesAAAALGJU + UkMAAAecAAAADmdUUkMAAAecAAAADmRlc2MAAAAAAAAAFEdlbmVyaWMgUkdCIFByb2ZpbGUAAAAA + AAAAAAAAABRHZW5lcmljIFJHQiBQcm9maWxlAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAABtbHVjAAAAAAAAAB8AAAAMc2tTSwAAACgAAAGEZGFESwAAAC4A + AAGsY2FFUwAAACQAAAHadmlWTgAAACQAAAH+cHRCUgAAACYAAAIidWtVQQAAACoAAAJIZnJGVQAA + ACgAAAJyaHVIVQAAACgAAAKaemhUVwAAABYAAALCbmJOTwAAACYAAALYY3NDWgAAACIAAAL+aGVJ + TAAAAB4AAAMgaXRJVAAAACgAAAM+cm9STwAAACQAAANmZGVERQAAACwAAAOKa29LUgAAABYAAAO2 + c3ZTRQAAACYAAALYemhDTgAAABYAAAPMamFKUAAAABoAAAPiZWxHUgAAACIAAAP8cHRQTwAAACYA + AAQebmxOTAAAACgAAAREZXNFUwAAACYAAAQedGhUSAAAACQAAARsdHJUUgAAACIAAASQZmlGSQAA + ACgAAASyaHJIUgAAACgAAATacGxQTAAAACwAAAUCcnVSVQAAACIAAAUuYXJFRwAAACYAAAVQZW5V + UwAAACYAAAV2AFYBYQBlAG8AYgBlAGMAbgD9ACAAUgBHAEIAIABwAHIAbwBmAGkAbABHAGUAbgBl + AHIAZQBsACAAUgBHAEIALQBiAGUAcwBrAHIAaQB2AGUAbABzAGUAUABlAHIAZgBpAGwAIABSAEcA + QgAgAGcAZQBuAOgAcgBpAGMAQx6lAHUAIABoAOwAbgBoACAAUgBHAEIAIABDAGgAdQBuAGcAUABl + AHIAZgBpAGwAIABSAEcAQgAgAEcAZQBuAOkAcgBpAGMAbwQXBDAEMwQwBDsETAQ9BDgEOQAgBD8E + QAQ+BEQEMAQ5BDsAIABSAEcAQgBQAHIAbwBmAGkAbAAgAGcA6QBuAOkAcgBpAHEAdQBlACAAUgBW + AEIAwQBsAHQAYQBsAOEAbgBvAHMAIABSAEcAQgAgAHAAcgBvAGYAaQBskBp1KAAgAFIARwBCACCC + cl9pY8+P8ABHAGUAbgBlAHIAaQBzAGsAIABSAEcAQgAtAHAAcgBvAGYAaQBsAE8AYgBlAGMAbgD9 + ACAAUgBHAEIAIABwAHIAbwBmAGkAbAXkBegF1QXkBdkF3AAgAFIARwBCACAF2wXcBdwF2QBQAHIA + bwBmAGkAbABvACAAUgBHAEIAIABnAGUAbgBlAHIAaQBjAG8AUAByAG8AZgBpAGwAIABSAEcAQgAg + AGcAZQBuAGUAcgBpAGMAQQBsAGwAZwBlAG0AZQBpAG4AZQBzACAAUgBHAEIALQBQAHIAbwBmAGkA + bMd8vBgAIABSAEcAQgAg1QS4XNMMx3xmbpAaACAAUgBHAEIAIGPPj/Blh072TgCCLAAgAFIARwBC + ACAw1zDtMNUwoTCkMOsDkwO1A70DuQO6A8wAIAPAA8EDvwPGA68DuwAgAFIARwBCAFAAZQByAGYA + aQBsACAAUgBHAEIAIABnAGUAbgDpAHIAaQBjAG8AQQBsAGcAZQBtAGUAZQBuACAAUgBHAEIALQBw + AHIAbwBmAGkAZQBsDkIOGw4jDkQOHw4lDkwAIABSAEcAQgAgDhcOMQ5IDicORA4bAEcAZQBuAGUA + bAAgAFIARwBCACAAUAByAG8AZgBpAGwAaQBZAGwAZQBpAG4AZQBuACAAUgBHAEIALQBwAHIAbwBm + AGkAaQBsAGkARwBlAG4AZQByAGkBDQBrAGkAIABSAEcAQgAgAHAAcgBvAGYAaQBsAFUAbgBpAHcA + ZQByAHMAYQBsAG4AeQAgAHAAcgBvAGYAaQBsACAAUgBHAEIEHgQxBEkEOAQ5ACAEPwRABD4ERAQ4 + BDsETAAgAFIARwBCBkUGRAZBACAGKgY5BjEGSgZBACAAUgBHAEIAIAYnBkQGOQYnBkUARwBlAG4A + ZQByAGkAYwAgAFIARwBCACAAUAByAG8AZgBpAGwAZXRleHQAAAAAQ29weXJpZ2h0IDIwMDcgQXBw + bGUgSW5jLiwgYWxsIHJpZ2h0cyByZXNlcnZlZC4AWFlaIAAAAAAAAPNSAAEAAAABFs9YWVogAAAA + AAAAdE0AAD3uAAAD0FhZWiAAAAAAAABadQAArHMAABc0WFlaIAAAAAAAACgaAAAVnwAAuDZjdXJ2 + AAAAAAAAAAEBzQAAc2YzMgAAAAAAAQxCAAAF3v//8yYAAAeSAAD9kf//+6L///2jAAAD3AAAwGz/ + wAARCAFoAQwDASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAA + AgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcY + GRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKT + lJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP0 + 9fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQA + AQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2 + Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOk + paanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9sAQwAC + AgICAgIDAgIDBQMDAwUGBQUFBQYIBgYGBgYICggICAgICAoKCgoKCgoKDAwMDAwMDg4ODg4PDw8P + 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 + ACiiigAooooAKKKKACiiigAooooAKKKKAP/Z + + + + + + + + + + /9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMDAsK + CwsNDhIQDQ4RDgsLEBYQERMUFRUVDA8XGBYUGBIUFRT/2wBDAQMEBAUEBQkFBQkUDQsNFBQUFBQU + FBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBT/wAARCACQAQADASIA + AhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQA + AAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3 + ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWm + p6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEA + AwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSEx + BhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElK + U1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3 + uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD9U6KK + KACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAoooo + AKKKKACiiigDwP8AaO/trUPiD8NdH0oX91DeLqj3Gn2fie70FZ/LhiKF57Y7jtJJCkEcmvPvCP7R + HjDT/hfcXVvfWGot4K8LxanrDa1A893rU63l9aTW9vNHMqjDae6JORL5rTRNtGfm+nvFngDwx4+h + t4fE/hvSPEcNuxeGPVrGK6WJiMEqJFOCR3FVNe8D+FhY2d9P4X0a6m0CFpNLM1hExstgDKISV/d8 + ov3cdB6UAeIal8dPiZB517p9p4e1SK51XxJpenaRHYXC3Ltpk1z5e6X7RtZpY7V0wEUB5FbOAUNn + 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 + 2gDP6VYooqzUKKKKAP/Z + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Luke Tomlinson + Luke Tomlinson + 1 + 2025-02-16T19:50:39Z + 2025-02-16T19:51:45Z + + + + + + + 1 + 68 + Microsoft Office PowerPoint + Widescreen + 27 + 1 + 1 + 0 + 0 + false + + + + Fonts Used + + + 3 + + + Theme + + + 1 + + + Slide Titles + + + 1 + + + + + + Aptos + Aptos Display + Arial + Office Theme + Bony Pelvis + + + + false + false + false + 16.0000 + + + + \ No newline at end of file From 29426b24d387dddac420e896bbbc64492e7d429a Mon Sep 17 00:00:00 2001 From: JoshBudzynski <157167337+JoshBudzynski@users.noreply.github.com> Date: Mon, 17 Feb 2025 11:29:33 -0600 Subject: [PATCH 067/143] First_Issue_85 --- data_extraction/Extract_Bone_Descriptions.py | 79 ++++++++++++++++++++ data_extraction/slide3_annotations.json | 1 - 2 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 data_extraction/Extract_Bone_Descriptions.py diff --git a/data_extraction/Extract_Bone_Descriptions.py b/data_extraction/Extract_Bone_Descriptions.py new file mode 100644 index 00000000..6124171a --- /dev/null +++ b/data_extraction/Extract_Bone_Descriptions.py @@ -0,0 +1,79 @@ +import xml.etree.ElementTree as ET +import json +# this is the code for one slide at a time. + +def parse_slide_xml(xml_file, output_json_path): + # Load the XML file + tree = ET.parse(xml_file) + root = tree.getroot() + + # Define namespaces used in the XML Not sure if this is correct + ns = { + 'p': 'http://schemas.openxmlformats.org/presentationml/2006/main', + 'a': 'http://schemas.openxmlformats.org/drawingml/2006/main' + + } + + annotations = [] + + # Parse shapes (`p:sp` elements for text and shapes) + for sp in root.findall(".//p:sp", ns): + annotation = {} + + # Extract text, if present + text_elements = sp.findall(".//a:t", ns) + text = ''.join([t.text for t in text_elements if t.text]) + if text: + annotation["text"] = text + + + # Extract position and size + xfrm = sp.find(".//a:xfrm", ns) + if xfrm is not None: + pos = xfrm.find(".//a:off", ns) + size = xfrm.find(".//a:ext", ns) + if pos is not None and size is not None: + annotation["position"] = { + "x": pos.attrib.get("x"), + "y": pos.attrib.get("y"), + "width": size.attrib.get("cx"), + "height": size.attrib.get("cy") + } + + if annotation: + annotations.append(annotation) + + # Parse lines (`p:cxnSp` elements for line annotations) + for ln in root.findall(".//p:cxnSp", ns): + annotation = {"shape": "line"} + + # Extract line color + line_color = ln.find(".//a:ln/a:solidFill/a:srgbClr", ns) + if line_color is not None: + annotation["color"] = line_color.attrib.get("val") + + # Extract position and size + xfrm = ln.find(".//a:xfrm", ns) + if xfrm is not None: + pos = xfrm.find(".//a:off", ns) + size = xfrm.find(".//a:ext", ns) + if pos is not None and size is not None: + annotation["position"] = { + "x": pos.attrib.get("x"), + "y": pos.attrib.get("y"), + "width": size.attrib.get("cx"), + "height": size.attrib.get("cy") + } + + annotations.append(annotation) + + # Save annotations to a JSON file + with open(output_json_path, 'w') as f: + json.dump(annotations, f, indent=4) + + print(f"Annotations saved to {output_json_path}") + +# Example usage +xml_file = "/Users/joshbudzynski/Downloads/example_folder/ppt/slides/slide3.xml" # path to XML file +output_json = "slide3_annotations.json" # Output JSON file +parse_slide_xml(xml_file, output_json) diff --git a/data_extraction/slide3_annotations.json b/data_extraction/slide3_annotations.json index 5d58b1d1..6c44fb51 100644 --- a/data_extraction/slide3_annotations.json +++ b/data_extraction/slide3_annotations.json @@ -81,7 +81,6 @@ }, { "text": "Bony Pelvis Ilium Ischium Pubis ", - "color": "000000", "position": { "x": "1529348", "y": "1330154", From 3498d2363ed96fb1d6b73507c949ef5887734381 Mon Sep 17 00:00:00 2001 From: Burhan Khan Date: Mon, 17 Feb 2025 14:10:38 -0600 Subject: [PATCH 068/143] Addresses Issue 86. Changed names of images being extracted to allow for clear titles. Next step is to automate a JSON file. --- data_extraction/AutomatedExtractionScript.py | 88 ++++++++++++-------- 1 file changed, 54 insertions(+), 34 deletions(-) diff --git a/data_extraction/AutomatedExtractionScript.py b/data_extraction/AutomatedExtractionScript.py index e42f28c1..35a5b8ad 100644 --- a/data_extraction/AutomatedExtractionScript.py +++ b/data_extraction/AutomatedExtractionScript.py @@ -2,8 +2,16 @@ import xml.etree.ElementTree as ET def extract_images_from_slide_xml(slide_xml_path, rels_xml_path, media_folder, output_folder): + """ + Extracts images from a single slide XML file and renames them based on rId. + + Args: + slide_xml_path (str): Path to the slide XML file. + rels_xml_path (str): Path to the relationships XML file for the slide. + media_folder (str): Path to the media folder containing images. + output_folder (str): Path to store extracted images. + """ - # Step 1: Try parsing the slide XML file try: tree = ET.parse(slide_xml_path) root = tree.getroot() @@ -14,18 +22,17 @@ def extract_images_from_slide_xml(slide_xml_path, rels_xml_path, media_folder, o print(f"[ERROR] Slide file not found: {slide_xml_path}") return - # Define XML namespaces (needed to properly find elements in the XML) + # Define XML namespaces ns = {'a': 'http://schemas.openxmlformats.org/drawingml/2006/main', 'r': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'} - # Step 2: Find all tags, which reference images via the r:embed attribute + # Extract r:embed IDs that reference images embed_ids = [blip.attrib.get(f"{{{ns['r']}}}embed") for blip in root.findall(".//a:blip", ns) if blip.attrib.get(f"{{{ns['r']}}}embed")] if not embed_ids: print(f"[INFO] No images found in {slide_xml_path}. Skipping...") - return # If no images are found, return early + return - # Step 3: Try parsing the relationships XML file try: rels_tree = ET.parse(rels_xml_path) rels_root = rels_tree.getroot() @@ -36,32 +43,37 @@ def extract_images_from_slide_xml(slide_xml_path, rels_xml_path, media_folder, o print(f"[ERROR] Relationship file not found: {rels_xml_path}") return - # Extract XML namespace for relationships if one exists + # Extract XML namespace for relationships rels_ns = rels_root.tag.split('}')[0] + '}' if '}' in rels_root.tag else '' - # Step 4: Create a dictionary mapping rId (e.g., "rId8") to the actual image file path + # Map rId to the actual image filename relationships = {rel.attrib.get('Id', '').strip(): rel.attrib.get('Target', '').strip() for rel in rels_root.findall(f".//{rels_ns}Relationship")} - # Step 5: Process each image reference found in the slide XML + # Extract slide number (e.g., "slide1" → 1) + slide_name = os.path.splitext(os.path.basename(slide_xml_path))[0] # "slide1" + slide_number = slide_name.replace("slide", "") # Extracts "1" + + # Create output folder for slide images + slide_output_folder = os.path.join(output_folder, slide_name) + if not os.path.exists(slide_output_folder): + os.makedirs(slide_output_folder) + + # Process each embedded image for embed_id in embed_ids: if embed_id in relationships: - target = relationships[embed_id] # The actual image filename - image_path = os.path.join(media_folder, os.path.basename(target)) # Full path to the image + target = relationships[embed_id] + image_path = os.path.join(media_folder, os.path.basename(target)) if os.path.exists(image_path): - # Extract slide name (e.g., "slide1" from "slide1.xml") - slide_name = os.path.splitext(os.path.basename(slide_xml_path))[0] + # Extract image extension (e.g., ".png", ".jpg") + image_extension = os.path.splitext(target)[-1] - # Create a dedicated folder for each slide in the output directory - slide_output_folder = os.path.join(output_folder, slide_name) - if not os.path.exists(slide_output_folder): - os.makedirs(slide_output_folder) + # New image name: "slide1_rId8.png" + new_image_name = f"slide{slide_number}_{embed_id}{image_extension}" + image_output_path = os.path.join(slide_output_folder, new_image_name) - # Define output path for the extracted image - image_output_path = os.path.join(slide_output_folder, os.path.basename(target)) - - # Copy the image from ppt/media/ to the output folder + # Copy and rename the image try: with open(image_path, 'rb') as img_in, open(image_output_path, 'wb') as img_out: img_out.write(img_in.read()) @@ -73,32 +85,40 @@ def extract_images_from_slide_xml(slide_xml_path, rels_xml_path, media_folder, o else: print(f"[WARNING] No relationship found for embed ID: {embed_id} (Referenced in {slide_xml_path})") -#Processes all slides in the PowerPoint file and extracts images from each slide. def process_pptx_folders(slides_folder, rels_folder, media_folder, output_folder): - - # Ensure the output folder exists + """ + Processes all slides in the PowerPoint file and extracts images from each slide. + + Args: + slides_folder (str): Path to the folder containing slide XML files. + rels_folder (str): Path to the folder containing relationships XML files. + media_folder (str): Path to the media folder containing images. + output_folder (str): Path to store extracted images. + """ + if not os.path.exists(output_folder): os.makedirs(output_folder) - # Iterate through all slides in the slides folder - for slide_file in sorted(os.listdir(slides_folder)): # Sorting ensures correct slide order - if slide_file.startswith("slide") and slide_file.endswith(".xml"): # Check if it's a slide XML file + for slide_file in sorted(os.listdir(slides_folder)): + if slide_file.startswith("slide") and slide_file.endswith(".xml"): slide_path = os.path.join(slides_folder, slide_file) - rels_path = os.path.join(rels_folder, slide_file + ".rels") # Find the corresponding .rels file + rels_path = os.path.join(rels_folder, slide_file + ".rels") if os.path.exists(rels_path): extract_images_from_slide_xml(slide_path, rels_path, media_folder, output_folder) else: print(f"[WARNING] Missing relationship file: {rels_path}. Skipping slide {slide_file}.") -# Main Execution Block if __name__ == "__main__": + """ + Main execution block: + - Defines necessary folder paths. + - Calls process_pptx_folders() to extract images from all slides. + """ - # Paths to relevant folders (these should be updated dynamically or via CLI arguments) - slides_folder = "/Users/burhankhan/Desktop/ppt/slides" # Folder containing slide XML files - rels_folder = "/Users/burhankhan/Desktop/ppt/slides/_rels" # Folder containing relationships XML files - media_folder = "/Users/burhankhan/Desktop/ppt/media" # Folder containing media files - output_folder = "/Users/burhankhan/Desktop/AutomatedScript" # Folder where images will be extracted + slides_folder = "/Users/burhankhan/Desktop/ppt/slides" + rels_folder = "/Users/burhankhan/Desktop/ppt/slides/_rels" + media_folder = "/Users/burhankhan/Desktop/ppt/media" + output_folder = "/Users/burhankhan/Desktop/AutomatedScript" - # Process all slides and extract images process_pptx_folders(slides_folder, rels_folder, media_folder, output_folder) From 96ca291f5d7a5df2df05b2198a898e1ec95af807 Mon Sep 17 00:00:00 2001 From: JoshBudzynski <157167337+JoshBudzynski@users.noreply.github.com> Date: Sat, 22 Feb 2025 21:19:43 -0600 Subject: [PATCH 069/143] pushing_for_check --- data_extraction/Extract_Bone_Descriptions.py | 78 ++++++-------------- data_extraction/slide3_Descriptions.json | 47 ++++++++++++ data_extraction/slide3_annotations.json | 1 + 3 files changed, 72 insertions(+), 54 deletions(-) create mode 100644 data_extraction/slide3_Descriptions.json diff --git a/data_extraction/Extract_Bone_Descriptions.py b/data_extraction/Extract_Bone_Descriptions.py index 6124171a..878c729d 100644 --- a/data_extraction/Extract_Bone_Descriptions.py +++ b/data_extraction/Extract_Bone_Descriptions.py @@ -1,79 +1,49 @@ import xml.etree.ElementTree as ET import json -# this is the code for one slide at a time. def parse_slide_xml(xml_file, output_json_path): - # Load the XML file tree = ET.parse(xml_file) root = tree.getroot() - # Define namespaces used in the XML Not sure if this is correct ns = { 'p': 'http://schemas.openxmlformats.org/presentationml/2006/main', 'a': 'http://schemas.openxmlformats.org/drawingml/2006/main' - } annotations = [] - - # Parse shapes (`p:sp` elements for text and shapes) + for sp in root.findall(".//p:sp", ns): - annotation = {} - - # Extract text, if present - text_elements = sp.findall(".//a:t", ns) - text = ''.join([t.text for t in text_elements if t.text]) - if text: - annotation["text"] = text - - - # Extract position and size xfrm = sp.find(".//a:xfrm", ns) if xfrm is not None: - pos = xfrm.find(".//a:off", ns) - size = xfrm.find(".//a:ext", ns) - if pos is not None and size is not None: - annotation["position"] = { - "x": pos.attrib.get("x"), - "y": pos.attrib.get("y"), - "width": size.attrib.get("cx"), - "height": size.attrib.get("cy") - } - - if annotation: - annotations.append(annotation) - - # Parse lines (`p:cxnSp` elements for line annotations) - for ln in root.findall(".//p:cxnSp", ns): - annotation = {"shape": "line"} - - # Extract line color - line_color = ln.find(".//a:ln/a:solidFill/a:srgbClr", ns) - if line_color is not None: - annotation["color"] = line_color.attrib.get("val") - - # Extract position and size - xfrm = ln.find(".//a:xfrm", ns) - if xfrm is not None: - pos = xfrm.find(".//a:off", ns) - size = xfrm.find(".//a:ext", ns) + pos = xfrm.find("a:off", ns) + size = xfrm.find("a:ext", ns) + if pos is not None and size is not None: - annotation["position"] = { - "x": pos.attrib.get("x"), - "y": pos.attrib.get("y"), - "width": size.attrib.get("cx"), - "height": size.attrib.get("cy") - } - - annotations.append(annotation) + x, y = int(pos.attrib.get("x", 0)), int(pos.attrib.get("y", 0)) + width, height = int(size.attrib.get("cx", 0)), int(size.attrib.get("cy", 0)) + + # Define approximate position range for the bottom-right box + if x > 7000000 and y > 3000000: # Adjust based on actual XML values + text_elements = sp.findall(".//a:t", ns) + bullet_points = [t.text for t in text_elements if t.text] + + for text in bullet_points: + annotations.append({ + "text": text, + "position": { + "x": str(x), + "y": str(y), + "width": str(width), + "height": str(height) + } + }) - # Save annotations to a JSON file with open(output_json_path, 'w') as f: json.dump(annotations, f, indent=4) print(f"Annotations saved to {output_json_path}") # Example usage -xml_file = "/Users/joshbudzynski/Downloads/example_folder/ppt/slides/slide3.xml" # path to XML file -output_json = "slide3_annotations.json" # Output JSON file +xml_file = "/Users/joshbudzynski/Downloads/example_folder/ppt/slides/slide3.xml" +output_json = "slide3_Descriptions.json" parse_slide_xml(xml_file, output_json) diff --git a/data_extraction/slide3_Descriptions.json b/data_extraction/slide3_Descriptions.json new file mode 100644 index 00000000..e8329fd1 --- /dev/null +++ b/data_extraction/slide3_Descriptions.json @@ -0,0 +1,47 @@ +[ + { + "text": "Ilium", + "position": { + "x": "8757818", + "y": "3505200", + "width": "1828800", + "height": "1231106" + } + }, + { + "text": "The Ilium forms the superior part of the bony pelvis.", + "position": { + "x": "8757818", + "y": "3505200", + "width": "1828800", + "height": "1231106" + } + }, + { + "text": "It articulates with the sacrum to form the sacroiliac joint.", + "position": { + "x": "8757818", + "y": "3505200", + "width": "1828800", + "height": "1231106" + } + }, + { + "text": "Posterior Inferior Iliac Spine", + "position": { + "x": "7887969", + "y": "3816698", + "width": "766897", + "height": "461665" + } + }, + { + "text": "No labels", + "position": { + "x": "9189990", + "y": "6400801", + "width": "974820", + "height": "307777" + } + } +] \ No newline at end of file diff --git a/data_extraction/slide3_annotations.json b/data_extraction/slide3_annotations.json index 6c44fb51..5d58b1d1 100644 --- a/data_extraction/slide3_annotations.json +++ b/data_extraction/slide3_annotations.json @@ -81,6 +81,7 @@ }, { "text": "Bony Pelvis Ilium Ischium Pubis ", + "color": "000000", "position": { "x": "1529348", "y": "1330154", From 418d9653c0cc0610be620a58075526c14ab87b99 Mon Sep 17 00:00:00 2001 From: tluke900 Date: Sun, 23 Feb 2025 13:38:17 -0600 Subject: [PATCH 070/143] Empty sets no longer created --- data_extraction/XML boneset Reader.py | 7 ++++--- data_extraction/output.json | 8 -------- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/data_extraction/XML boneset Reader.py b/data_extraction/XML boneset Reader.py index 47782aa1..10e8ef55 100644 --- a/data_extraction/XML boneset Reader.py +++ b/data_extraction/XML boneset Reader.py @@ -45,13 +45,14 @@ def extract_bones_from_xml(xml_path): if size == "1200": if is_bold: bolded_set = text + bonesets[bolded_set] = set() + bonesets[total_boneset].add(text.capitalize()) + if total_boneset is None: total_boneset = text bonesets[total_boneset] = set() continue - # These are their own bonesets - current_boneset = text - bonesets[current_boneset] = set() + # These are their own bonesets bonesets[total_boneset].add(text.capitalize()) elif size == "900": if not bolded_set: diff --git a/data_extraction/output.json b/data_extraction/output.json index 32f5eb7f..de52149a 100644 --- a/data_extraction/output.json +++ b/data_extraction/output.json @@ -7,10 +7,6 @@ "Pubis" ] }, - { - "boneset": "Ilium", - "bones": [] - }, { "boneset": "Ischium", "bones": [ @@ -19,9 +15,5 @@ "Ramus", "Sciatic notches" ] - }, - { - "boneset": "Pubis", - "bones": [] } ] \ No newline at end of file From adf71f6a8bfac572942febc23b62c469cf17a485 Mon Sep 17 00:00:00 2001 From: tluke900 Date: Sun, 23 Feb 2025 14:28:32 -0600 Subject: [PATCH 071/143] Removed Alphabetical sorting, removed Extra XML files. --- data_extraction/BoneyPelvisSlide4.xml | 6655 ------------- data_extraction/XML boneset Reader.py | 18 +- data_extraction/output.json | 4 +- data_extraction/slide2Pelvis.xml | 11974 ------------------------ data_extraction/slide2UpperLimb.xml | 1237 --- 5 files changed, 10 insertions(+), 19878 deletions(-) delete mode 100644 data_extraction/BoneyPelvisSlide4.xml delete mode 100644 data_extraction/slide2Pelvis.xml delete mode 100644 data_extraction/slide2UpperLimb.xml diff --git a/data_extraction/BoneyPelvisSlide4.xml b/data_extraction/BoneyPelvisSlide4.xml deleted file mode 100644 index 99592512..00000000 --- a/data_extraction/BoneyPelvisSlide4.xml +++ /dev/null @@ -1,6655 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Bony Pelvis - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Home - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Right Pelvis - - - - (medial aspect) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Right Pelvis - - - - (lateral aspect) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Iliac Crest - - - - - - - - - - - - - Anterior Iliac spines - - - - - - - - - - - - - - Posterior Iliac spines - - - - - - - - - - - - - - Auricular surface - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Bony Pelvis - - - - - - - - - - - - - - - - - - - - - - - - Ilium - - - - - - - - - - - - - - - - - - - - - - - - Ischium - - - - - - - - - - - - - - - - - - - - - - - - Pubis - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Labels - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Click to edit Master title style - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Click to edit Master text styles - - - - - - - Second level - - - - - - - Third level - - - - - - - Fourth level - - - - - - - Fifth level - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 2/10/2025 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ‹#› - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Click to edit Master title style - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Click to edit Master text styles - - - - - - - - - - - - - - - - - - - - - - - - - - - - 2/10/2025 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ‹#› - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Click to edit Master title style - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Click to edit Master subtitle style - - - - - - - - - - - - - - - - - - - - - - - - - - - - 2/10/2025 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ‹#› - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Click to edit Master title style - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Click to edit Master text styles - - - - - - - Second level - - - - - - - Third level - - - - - - - Fourth level - - - - - - - Fifth level - - - - - - - - - - - - - - - - - - - - - - - - - - - - 2/10/2025 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ‹#› - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Click to edit Master title style - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Click to edit Master text styles - - - - - - - Second level - - - - - - - Third level - - - - - - - Fourth level - - - - - - - Fifth level - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Click to edit Master text styles - - - - - - - Second level - - - - - - - Third level - - - - - - - Fourth level - - - - - - - Fifth level - - - - - - - - - - - - - - - - - - - - - - - - - - - - 2/10/2025 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ‹#› - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Click to edit Master title style - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Click to edit Master text styles - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Click to edit Master text styles - - - - - - - Second level - - - - - - - Third level - - - - - - - Fourth level - - - - - - - Fifth level - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Click to edit Master text styles - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Click to edit Master text styles - - - - - - - Second level - - - - - - - Third level - - - - - - - Fourth level - - - - - - - Fifth level - - - - - - - - - - - - - - - - - - - - - - - - - - - - 2/10/2025 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ‹#› - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Click to edit Master title style - - - - - - - - - - - - - - - - - - - - - - - - - - - - 2/10/2025 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ‹#› - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 2/10/2025 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ‹#› - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Click to edit Master title style - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Click to edit Master text styles - - - - - - - Second level - - - - - - - Third level - - - - - - - Fourth level - - - - - - - Fifth level - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Click to edit Master text styles - - - - - - - - - - - - - - - - - - - - - - - - - - - - 2/10/2025 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ‹#› - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Click to edit Master title style - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Click to edit Master text styles - - - - - - - Second level - - - - - - - Third level - - - - - - - Fourth level - - - - - - - Fifth level - - - - - - - - - - - - - - - - - - - - - - - - - - - - 2/10/2025 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ‹#› - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Click to edit Master title style - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Click to edit Master text styles - - - - - - - Second level - - - - - - - Third level - - - - - - - Fourth level - - - - - - - Fifth level - - - - - - - - - - - - - - - - - - - - - - - - - - - - 2/10/2025 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ‹#› - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Click to edit Master title style - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Click to edit Master text styles - - - - - - - - - - - - - - - - - - - - - - - - - - - - 2/10/2025 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ‹#› - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 2/10/2025 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Click to edit Master text styles - - - - - - - Second level - - - - - - - Third level - - - - - - - Fourth level - - - - - - - Fifth level - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ‹#› - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - /9j/4AAQSkZJRgABAQAAYABgAAD/4QB0RXhpZgAATU0AKgAAAAgABAEaAAUAAAABAAAAPgEbAAUA - AAABAAAARgEoAAMAAAABAAIAAIdpAAQAAAABAAAATgAAAAAAAABgAAAAAQAAAGAAAAABAAKgAgAE - AAAAAQAAAOugAwAEAAAAAQAAAXMAAAAA/+0AOFBob3Rvc2hvcCAzLjAAOEJJTQQEAAAAAAAAOEJJ - TQQlAAAAAAAQ1B2M2Y8AsgTpgAmY7PhCfv/iB+hJQ0NfUFJPRklMRQABAQAAB9hhcHBsAiAAAG1u - dHJSR0IgWFlaIAfZAAIAGQALABoAC2Fjc3BBUFBMAAAAAGFwcGwAAAAAAAAAAAAAAAAAAAAAAAD2 - 1gABAAAAANMtYXBwbAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAC2Rlc2MAAAEIAAAAb2RzY20AAAF4AAAFnGNwcnQAAAcUAAAAOHd0cHQAAAdMAAAAFHJYWVoA - AAdgAAAAFGdYWVoAAAd0AAAAFGJYWVoAAAeIAAAAFHJUUkMAAAecAAAADmNoYWQAAAesAAAALGJU - UkMAAAecAAAADmdUUkMAAAecAAAADmRlc2MAAAAAAAAAFEdlbmVyaWMgUkdCIFByb2ZpbGUAAAAA - AAAAAAAAABRHZW5lcmljIFJHQiBQcm9maWxlAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAABtbHVjAAAAAAAAAB8AAAAMc2tTSwAAACgAAAGEZGFESwAAAC4A - AAGsY2FFUwAAACQAAAHadmlWTgAAACQAAAH+cHRCUgAAACYAAAIidWtVQQAAACoAAAJIZnJGVQAA - ACgAAAJyaHVIVQAAACgAAAKaemhUVwAAABYAAALCbmJOTwAAACYAAALYY3NDWgAAACIAAAL+aGVJ - TAAAAB4AAAMgaXRJVAAAACgAAAM+cm9STwAAACQAAANmZGVERQAAACwAAAOKa29LUgAAABYAAAO2 - c3ZTRQAAACYAAALYemhDTgAAABYAAAPMamFKUAAAABoAAAPiZWxHUgAAACIAAAP8cHRQTwAAACYA - AAQebmxOTAAAACgAAAREZXNFUwAAACYAAAQedGhUSAAAACQAAARsdHJUUgAAACIAAASQZmlGSQAA - ACgAAASyaHJIUgAAACgAAATacGxQTAAAACwAAAUCcnVSVQAAACIAAAUuYXJFRwAAACYAAAVQZW5V - UwAAACYAAAV2AFYBYQBlAG8AYgBlAGMAbgD9ACAAUgBHAEIAIABwAHIAbwBmAGkAbABHAGUAbgBl - AHIAZQBsACAAUgBHAEIALQBiAGUAcwBrAHIAaQB2AGUAbABzAGUAUABlAHIAZgBpAGwAIABSAEcA - QgAgAGcAZQBuAOgAcgBpAGMAQx6lAHUAIABoAOwAbgBoACAAUgBHAEIAIABDAGgAdQBuAGcAUABl - AHIAZgBpAGwAIABSAEcAQgAgAEcAZQBuAOkAcgBpAGMAbwQXBDAEMwQwBDsETAQ9BDgEOQAgBD8E - QAQ+BEQEMAQ5BDsAIABSAEcAQgBQAHIAbwBmAGkAbAAgAGcA6QBuAOkAcgBpAHEAdQBlACAAUgBW - AEIAwQBsAHQAYQBsAOEAbgBvAHMAIABSAEcAQgAgAHAAcgBvAGYAaQBskBp1KAAgAFIARwBCACCC - cl9pY8+P8ABHAGUAbgBlAHIAaQBzAGsAIABSAEcAQgAtAHAAcgBvAGYAaQBsAE8AYgBlAGMAbgD9 - ACAAUgBHAEIAIABwAHIAbwBmAGkAbAXkBegF1QXkBdkF3AAgAFIARwBCACAF2wXcBdwF2QBQAHIA - bwBmAGkAbABvACAAUgBHAEIAIABnAGUAbgBlAHIAaQBjAG8AUAByAG8AZgBpAGwAIABSAEcAQgAg - AGcAZQBuAGUAcgBpAGMAQQBsAGwAZwBlAG0AZQBpAG4AZQBzACAAUgBHAEIALQBQAHIAbwBmAGkA - bMd8vBgAIABSAEcAQgAg1QS4XNMMx3xmbpAaACAAUgBHAEIAIGPPj/Blh072TgCCLAAgAFIARwBC - ACAw1zDtMNUwoTCkMOsDkwO1A70DuQO6A8wAIAPAA8EDvwPGA68DuwAgAFIARwBCAFAAZQByAGYA - aQBsACAAUgBHAEIAIABnAGUAbgDpAHIAaQBjAG8AQQBsAGcAZQBtAGUAZQBuACAAUgBHAEIALQBw - AHIAbwBmAGkAZQBsDkIOGw4jDkQOHw4lDkwAIABSAEcAQgAgDhcOMQ5IDicORA4bAEcAZQBuAGUA - bAAgAFIARwBCACAAUAByAG8AZgBpAGwAaQBZAGwAZQBpAG4AZQBuACAAUgBHAEIALQBwAHIAbwBm - AGkAaQBsAGkARwBlAG4AZQByAGkBDQBrAGkAIABSAEcAQgAgAHAAcgBvAGYAaQBsAFUAbgBpAHcA - ZQByAHMAYQBsAG4AeQAgAHAAcgBvAGYAaQBsACAAUgBHAEIEHgQxBEkEOAQ5ACAEPwRABD4ERAQ4 - BDsETAAgAFIARwBCBkUGRAZBACAGKgY5BjEGSgZBACAAUgBHAEIAIAYnBkQGOQYnBkUARwBlAG4A - ZQByAGkAYwAgAFIARwBCACAAUAByAG8AZgBpAGwAZXRleHQAAAAAQ29weXJpZ2h0IDIwMDcgQXBw - bGUgSW5jLiwgYWxsIHJpZ2h0cyByZXNlcnZlZC4AWFlaIAAAAAAAAPNSAAEAAAABFs9YWVogAAAA - AAAAdE0AAD3uAAAD0FhZWiAAAAAAAABadQAArHMAABc0WFlaIAAAAAAAACgaAAAVnwAAuDZjdXJ2 - AAAAAAAAAAEBzQAAc2YzMgAAAAAAAQxCAAAF3v//8yYAAAeSAAD9kf//+6L///2jAAAD3AAAwGz/ - wAARCAFzAOsDASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAA - AgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcY - GRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKT - lJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP0 - 9fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQA - AQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2 - Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOk - paanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9sAQwAC - AgICAgIDAgIDBQMDAwUGBQUFBQYIBgYGBgYICggICAgICAoKCgoKCgoKDAwMDAwMDg4ODg4PDw8P - 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 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - /9j/4AAQSkZJRgABAQAAYABgAAD/4QB0RXhpZgAATU0AKgAAAAgABAEaAAUAAAABAAAAPgEbAAUA - AAABAAAARgEoAAMAAAABAAIAAIdpAAQAAAABAAAATgAAAAAAAABgAAAAAQAAAGAAAAABAAKgAgAE - AAAAAQAAAQygAwAEAAAAAQAAAWgAAAAA/+0AOFBob3Rvc2hvcCAzLjAAOEJJTQQEAAAAAAAAOEJJ - TQQlAAAAAAAQ1B2M2Y8AsgTpgAmY7PhCfv/iB+hJQ0NfUFJPRklMRQABAQAAB9hhcHBsAiAAAG1u - dHJSR0IgWFlaIAfZAAIAGQALABoAC2Fjc3BBUFBMAAAAAGFwcGwAAAAAAAAAAAAAAAAAAAAAAAD2 - 1gABAAAAANMtYXBwbAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAC2Rlc2MAAAEIAAAAb2RzY20AAAF4AAAFnGNwcnQAAAcUAAAAOHd0cHQAAAdMAAAAFHJYWVoA - AAdgAAAAFGdYWVoAAAd0AAAAFGJYWVoAAAeIAAAAFHJUUkMAAAecAAAADmNoYWQAAAesAAAALGJU - UkMAAAecAAAADmdUUkMAAAecAAAADmRlc2MAAAAAAAAAFEdlbmVyaWMgUkdCIFByb2ZpbGUAAAAA - AAAAAAAAABRHZW5lcmljIFJHQiBQcm9maWxlAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAABtbHVjAAAAAAAAAB8AAAAMc2tTSwAAACgAAAGEZGFESwAAAC4A - AAGsY2FFUwAAACQAAAHadmlWTgAAACQAAAH+cHRCUgAAACYAAAIidWtVQQAAACoAAAJIZnJGVQAA - ACgAAAJyaHVIVQAAACgAAAKaemhUVwAAABYAAALCbmJOTwAAACYAAALYY3NDWgAAACIAAAL+aGVJ - TAAAAB4AAAMgaXRJVAAAACgAAAM+cm9STwAAACQAAANmZGVERQAAACwAAAOKa29LUgAAABYAAAO2 - c3ZTRQAAACYAAALYemhDTgAAABYAAAPMamFKUAAAABoAAAPiZWxHUgAAACIAAAP8cHRQTwAAACYA - AAQebmxOTAAAACgAAAREZXNFUwAAACYAAAQedGhUSAAAACQAAARsdHJUUgAAACIAAASQZmlGSQAA - ACgAAASyaHJIUgAAACgAAATacGxQTAAAACwAAAUCcnVSVQAAACIAAAUuYXJFRwAAACYAAAVQZW5V - UwAAACYAAAV2AFYBYQBlAG8AYgBlAGMAbgD9ACAAUgBHAEIAIABwAHIAbwBmAGkAbABHAGUAbgBl - AHIAZQBsACAAUgBHAEIALQBiAGUAcwBrAHIAaQB2AGUAbABzAGUAUABlAHIAZgBpAGwAIABSAEcA - QgAgAGcAZQBuAOgAcgBpAGMAQx6lAHUAIABoAOwAbgBoACAAUgBHAEIAIABDAGgAdQBuAGcAUABl - AHIAZgBpAGwAIABSAEcAQgAgAEcAZQBuAOkAcgBpAGMAbwQXBDAEMwQwBDsETAQ9BDgEOQAgBD8E - QAQ+BEQEMAQ5BDsAIABSAEcAQgBQAHIAbwBmAGkAbAAgAGcA6QBuAOkAcgBpAHEAdQBlACAAUgBW - AEIAwQBsAHQAYQBsAOEAbgBvAHMAIABSAEcAQgAgAHAAcgBvAGYAaQBskBp1KAAgAFIARwBCACCC - cl9pY8+P8ABHAGUAbgBlAHIAaQBzAGsAIABSAEcAQgAtAHAAcgBvAGYAaQBsAE8AYgBlAGMAbgD9 - ACAAUgBHAEIAIABwAHIAbwBmAGkAbAXkBegF1QXkBdkF3AAgAFIARwBCACAF2wXcBdwF2QBQAHIA - bwBmAGkAbABvACAAUgBHAEIAIABnAGUAbgBlAHIAaQBjAG8AUAByAG8AZgBpAGwAIABSAEcAQgAg - AGcAZQBuAGUAcgBpAGMAQQBsAGwAZwBlAG0AZQBpAG4AZQBzACAAUgBHAEIALQBQAHIAbwBmAGkA - bMd8vBgAIABSAEcAQgAg1QS4XNMMx3xmbpAaACAAUgBHAEIAIGPPj/Blh072TgCCLAAgAFIARwBC - ACAw1zDtMNUwoTCkMOsDkwO1A70DuQO6A8wAIAPAA8EDvwPGA68DuwAgAFIARwBCAFAAZQByAGYA - aQBsACAAUgBHAEIAIABnAGUAbgDpAHIAaQBjAG8AQQBsAGcAZQBtAGUAZQBuACAAUgBHAEIALQBw - AHIAbwBmAGkAZQBsDkIOGw4jDkQOHw4lDkwAIABSAEcAQgAgDhcOMQ5IDicORA4bAEcAZQBuAGUA - bAAgAFIARwBCACAAUAByAG8AZgBpAGwAaQBZAGwAZQBpAG4AZQBuACAAUgBHAEIALQBwAHIAbwBm - AGkAaQBsAGkARwBlAG4AZQByAGkBDQBrAGkAIABSAEcAQgAgAHAAcgBvAGYAaQBsAFUAbgBpAHcA - ZQByAHMAYQBsAG4AeQAgAHAAcgBvAGYAaQBsACAAUgBHAEIEHgQxBEkEOAQ5ACAEPwRABD4ERAQ4 - BDsETAAgAFIARwBCBkUGRAZBACAGKgY5BjEGSgZBACAAUgBHAEIAIAYnBkQGOQYnBkUARwBlAG4A - ZQByAGkAYwAgAFIARwBCACAAUAByAG8AZgBpAGwAZXRleHQAAAAAQ29weXJpZ2h0IDIwMDcgQXBw - bGUgSW5jLiwgYWxsIHJpZ2h0cyByZXNlcnZlZC4AWFlaIAAAAAAAAPNSAAEAAAABFs9YWVogAAAA - AAAAdE0AAD3uAAAD0FhZWiAAAAAAAABadQAArHMAABc0WFlaIAAAAAAAACgaAAAVnwAAuDZjdXJ2 - AAAAAAAAAAEBzQAAc2YzMgAAAAAAAQxCAAAF3v//8yYAAAeSAAD9kf//+6L///2jAAAD3AAAwGz/ - wAARCAFoAQwDASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAA - AgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcY - GRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKT - lJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP0 - 9fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQA - AQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2 - Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOk - paanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9sAQwAC - AgICAgIDAgIDBQMDAwUGBQUFBQYIBgYGBgYICggICAgICAoKCgoKCgoKDAwMDAwMDg4ODg4PDw8P - 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 - ACiiigAooooAKKKKACiiigAooooAKKKKAP/Z - - - - - - - - - - /9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMDAsK - CwsNDhIQDQ4RDgsLEBYQERMUFRUVDA8XGBYUGBIUFRT/2wBDAQMEBAUEBQkFBQkUDQsNFBQUFBQU - FBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBT/wAARCACQAQADASIA - AhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQA - AAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3 - ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWm - p6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEA - AwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSEx - BhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElK - U1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3 - uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD9U6KK - KACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAoooo - AKKKKACiiigDwP8AaO/trUPiD8NdH0oX91DeLqj3Gn2fie70FZ/LhiKF57Y7jtJJCkEcmvPvCP7R - HjDT/hfcXVvfWGot4K8LxanrDa1A893rU63l9aTW9vNHMqjDae6JORL5rTRNtGfm+nvFngDwx4+h - t4fE/hvSPEcNuxeGPVrGK6WJiMEqJFOCR3FVNe8D+FhY2d9P4X0a6m0CFpNLM1hExstgDKISV/d8 - ov3cdB6UAeIal8dPiZB517p9p4e1SK51XxJpenaRHYXC3Ltpk1z5e6X7RtZpY7V0wEUB5FbOAUNn - 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 - 6VYooqiwooooA//Z - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Luke Tomlinson - Luke Tomlinson - 1 - 2025-02-10T20:00:11Z - 2025-02-10T20:03:00Z - - - - - - - 0 - 36 - Microsoft Office PowerPoint - Widescreen - 17 - 1 - 1 - 0 - 0 - false - - - - Fonts Used - - - 3 - - - Theme - - - 1 - - - Slide Titles - - - 1 - - - - - - Aptos - Aptos Display - Arial - Office Theme - Bony Pelvis - - - - false - false - false - 16.0000 - - - - \ No newline at end of file diff --git a/data_extraction/XML boneset Reader.py b/data_extraction/XML boneset Reader.py index 10e8ef55..29fad28e 100644 --- a/data_extraction/XML boneset Reader.py +++ b/data_extraction/XML boneset Reader.py @@ -24,10 +24,9 @@ def extract_bones_from_xml(xml_path): } bonesets = {} # Dictionary to store bonesets - current_boneset = None total_boneset = None bolded_set = None - boldedList= [] + boldedList=[] # Extract bonesets based on hyperlinks and size attributes for sp_element in root.findall(".//p:sp", ns): @@ -45,22 +44,21 @@ def extract_bones_from_xml(xml_path): if size == "1200": if is_bold: bolded_set = text - bonesets[bolded_set] = set() - bonesets[total_boneset].add(text.capitalize()) + bonesets[bolded_set] = list() if total_boneset is None: total_boneset = text - bonesets[total_boneset] = set() + bonesets[total_boneset] = list() continue # These are their own bonesets - bonesets[total_boneset].add(text.capitalize()) + bonesets[total_boneset].append(text.capitalize()) elif size == "900": if not bolded_set: boldedList.append(text.capitalize()) else: - bonesets[bolded_set].add(text.capitalize()) - for i in range(len(boldedList)): - bonesets[bolded_set].add(boldedList[i]) + bonesets[bolded_set].append(text.capitalize()) + for i in boldedList: + bonesets[bolded_set].append(i) return bonesets @@ -74,7 +72,7 @@ def generate_json_output(bonesets, output_json_path): for boneset_name, bones in bonesets.items(): structured_data.append({ "boneset": boneset_name, - "bones": sorted(bones), + "bones": bones, }) # Save to JSON file diff --git a/data_extraction/output.json b/data_extraction/output.json index de52149a..38a3bfcc 100644 --- a/data_extraction/output.json +++ b/data_extraction/output.json @@ -10,9 +10,9 @@ { "boneset": "Ischium", "bones": [ - "Ischial spine", - "Ischial tuberosity", "Ramus", + "Ischial tuberosity", + "Ischial spine", "Sciatic notches" ] } diff --git a/data_extraction/slide2Pelvis.xml b/data_extraction/slide2Pelvis.xml deleted file mode 100644 index fa13d244..00000000 --- a/data_extraction/slide2Pelvis.xml +++ /dev/null @@ -1,11974 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Bony Pelvis - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Home - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Right Pelvis - - - - (medial aspect) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Right Pelvis - - - - (lateral aspect) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Bony Pelvis - - - - - - - - - - - - - - - - - - - - - - - - Ilium - - - - - - - - - - - - - - - - - - - - - - - - Ischium - - - - - - - - - - - - - - - - - - - - - - - - Pubis - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Ilium - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Pubis - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Ischium - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Acetabulum - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Obturator foramen - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - The bony pelvis is made up of tow hip bones, sacrum, and coccyx. - - - - - - - - - - - - - - - - - - - Hip bones - 3 fused bones: - - - - - - - - - - - - - Ilium - - - - - - - - - - - - - - - - - Ischium - - - - - - - - - - - - - - - - - Pubis - - - - - - - - - - - - - - - - - - - The - - - - acetabulum - - - - is a cup shaped depression that articulates with the head of the - femur to from the hip joint, a ball-and-socket type synovial joint. - - - - - - - - - - - - - - - - - - - The - - - - obturator foramen - - - - is an opening created by the inferior ramus and body of the - ischium, and superior and inferior pubic rami. This foramen is - partially covered by the obturator membrane. The inner and outer - surfaces of the obturator membrane provide the origin for the - obturator internus and externus muscles, respectively. - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/data_extraction/slide2UpperLimb.xml b/data_extraction/slide2UpperLimb.xml deleted file mode 100644 index 95989940..00000000 --- a/data_extraction/slide2UpperLimb.xml +++ /dev/null @@ -1,1237 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Upper Limb - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Home - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Upper limb - - - - - - - - - - - - - Humerus - - - - - - - - - - - - - Ulna - - - - - - - - - - - - - Radius - - - - - - - - - - - - - Hand - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Right upper limb - - - - (posterior aspect) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Right upper limb - - - - (anterior aspect) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Humerus - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Ulna - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Radius - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Radius - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Hand - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file From d444096105e877db90cd50e047a66e836851215c Mon Sep 17 00:00:00 2001 From: JoshBudzynski <157167337+JoshBudzynski@users.noreply.github.com> Date: Mon, 24 Feb 2025 13:15:10 -0600 Subject: [PATCH 072/143] pushing 85 --- .gitignore | 1 + data_extraction/AutomatedExtractionScript.py | 104 + data_extraction/BoneyPelvisSlide4.xml | 6655 +++++++++ data_extraction/Hardcoded.py | 96 - data_extraction/XML boneset Reader.py | 99 +- data_extraction/extract_ppt_annotations.py | 80 +- data_extraction/output.json | 27 +- data_extraction/script.py | 83 - data_extraction/slide2Pelvis.xml | 11974 +++++++++++++++- .../slide2_pelvis_annotations.json | 140 + data_extraction/slide3_annotations.json | 238 +- 11 files changed, 19008 insertions(+), 489 deletions(-) create mode 100644 data_extraction/AutomatedExtractionScript.py create mode 100644 data_extraction/BoneyPelvisSlide4.xml delete mode 100644 data_extraction/Hardcoded.py delete mode 100644 data_extraction/script.py create mode 100644 data_extraction/slide2_pelvis_annotations.json diff --git a/.gitignore b/.gitignore index c2658d7d..20de9305 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ node_modules/ +.vscode/ diff --git a/data_extraction/AutomatedExtractionScript.py b/data_extraction/AutomatedExtractionScript.py new file mode 100644 index 00000000..e42f28c1 --- /dev/null +++ b/data_extraction/AutomatedExtractionScript.py @@ -0,0 +1,104 @@ +import os +import xml.etree.ElementTree as ET + +def extract_images_from_slide_xml(slide_xml_path, rels_xml_path, media_folder, output_folder): + + # Step 1: Try parsing the slide XML file + try: + tree = ET.parse(slide_xml_path) + root = tree.getroot() + except ET.ParseError as e: + print(f"[ERROR] Failed to parse {slide_xml_path}: {e}") + return + except FileNotFoundError: + print(f"[ERROR] Slide file not found: {slide_xml_path}") + return + + # Define XML namespaces (needed to properly find elements in the XML) + ns = {'a': 'http://schemas.openxmlformats.org/drawingml/2006/main', + 'r': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'} + + # Step 2: Find all tags, which reference images via the r:embed attribute + embed_ids = [blip.attrib.get(f"{{{ns['r']}}}embed") for blip in root.findall(".//a:blip", ns) if blip.attrib.get(f"{{{ns['r']}}}embed")] + + if not embed_ids: + print(f"[INFO] No images found in {slide_xml_path}. Skipping...") + return # If no images are found, return early + + # Step 3: Try parsing the relationships XML file + try: + rels_tree = ET.parse(rels_xml_path) + rels_root = rels_tree.getroot() + except ET.ParseError as e: + print(f"[ERROR] Failed to parse {rels_xml_path}: {e}") + return + except FileNotFoundError: + print(f"[ERROR] Relationship file not found: {rels_xml_path}") + return + + # Extract XML namespace for relationships if one exists + rels_ns = rels_root.tag.split('}')[0] + '}' if '}' in rels_root.tag else '' + + # Step 4: Create a dictionary mapping rId (e.g., "rId8") to the actual image file path + relationships = {rel.attrib.get('Id', '').strip(): rel.attrib.get('Target', '').strip() + for rel in rels_root.findall(f".//{rels_ns}Relationship")} + + # Step 5: Process each image reference found in the slide XML + for embed_id in embed_ids: + if embed_id in relationships: + target = relationships[embed_id] # The actual image filename + image_path = os.path.join(media_folder, os.path.basename(target)) # Full path to the image + + if os.path.exists(image_path): + # Extract slide name (e.g., "slide1" from "slide1.xml") + slide_name = os.path.splitext(os.path.basename(slide_xml_path))[0] + + # Create a dedicated folder for each slide in the output directory + slide_output_folder = os.path.join(output_folder, slide_name) + if not os.path.exists(slide_output_folder): + os.makedirs(slide_output_folder) + + # Define output path for the extracted image + image_output_path = os.path.join(slide_output_folder, os.path.basename(target)) + + # Copy the image from ppt/media/ to the output folder + try: + with open(image_path, 'rb') as img_in, open(image_output_path, 'wb') as img_out: + img_out.write(img_in.read()) + print(f"[SUCCESS] Extracted: {image_output_path}") + except Exception as e: + print(f"[ERROR] Failed to copy image {image_path}: {e}") + else: + print(f"[WARNING] Image file not found: {image_path} (Referenced in {rels_xml_path})") + else: + print(f"[WARNING] No relationship found for embed ID: {embed_id} (Referenced in {slide_xml_path})") + +#Processes all slides in the PowerPoint file and extracts images from each slide. +def process_pptx_folders(slides_folder, rels_folder, media_folder, output_folder): + + # Ensure the output folder exists + if not os.path.exists(output_folder): + os.makedirs(output_folder) + + # Iterate through all slides in the slides folder + for slide_file in sorted(os.listdir(slides_folder)): # Sorting ensures correct slide order + if slide_file.startswith("slide") and slide_file.endswith(".xml"): # Check if it's a slide XML file + slide_path = os.path.join(slides_folder, slide_file) + rels_path = os.path.join(rels_folder, slide_file + ".rels") # Find the corresponding .rels file + + if os.path.exists(rels_path): + extract_images_from_slide_xml(slide_path, rels_path, media_folder, output_folder) + else: + print(f"[WARNING] Missing relationship file: {rels_path}. Skipping slide {slide_file}.") + +# Main Execution Block +if __name__ == "__main__": + + # Paths to relevant folders (these should be updated dynamically or via CLI arguments) + slides_folder = "/Users/burhankhan/Desktop/ppt/slides" # Folder containing slide XML files + rels_folder = "/Users/burhankhan/Desktop/ppt/slides/_rels" # Folder containing relationships XML files + media_folder = "/Users/burhankhan/Desktop/ppt/media" # Folder containing media files + output_folder = "/Users/burhankhan/Desktop/AutomatedScript" # Folder where images will be extracted + + # Process all slides and extract images + process_pptx_folders(slides_folder, rels_folder, media_folder, output_folder) diff --git a/data_extraction/BoneyPelvisSlide4.xml b/data_extraction/BoneyPelvisSlide4.xml new file mode 100644 index 00000000..99592512 --- /dev/null +++ b/data_extraction/BoneyPelvisSlide4.xml @@ -0,0 +1,6655 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Bony Pelvis + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Home + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Right Pelvis + + + + (medial aspect) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Right Pelvis + + + + (lateral aspect) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Iliac Crest + + + + + + + + + + + + + Anterior Iliac spines + + + + + + + + + + + + + + Posterior Iliac spines + + + + + + + + + + + + + + Auricular surface + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Bony Pelvis + + + + + + + + + + + + + + + + + + + + + + + + Ilium + + + + + + + + + + + + + + + + + + + + + + + + Ischium + + + + + + + + + + + + + + + + + + + + + + + + Pubis + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Labels + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master title style + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master text styles + + + + + + + Second level + + + + + + + Third level + + + + + + + Fourth level + + + + + + + Fifth level + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2/10/2025 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ‹#› + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master title style + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master text styles + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2/10/2025 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ‹#› + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master title style + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master subtitle style + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2/10/2025 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ‹#› + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master title style + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master text styles + + + + + + + Second level + + + + + + + Third level + + + + + + + Fourth level + + + + + + + Fifth level + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2/10/2025 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ‹#› + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master title style + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master text styles + + + + + + + Second level + + + + + + + Third level + + + + + + + Fourth level + + + + + + + Fifth level + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master text styles + + + + + + + Second level + + + + + + + Third level + + + + + + + Fourth level + + + + + + + Fifth level + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2/10/2025 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ‹#› + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master title style + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master text styles + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master text styles + + + + + + + Second level + + + + + + + Third level + + + + + + + Fourth level + + + + + + + Fifth level + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master text styles + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master text styles + + + + + + + Second level + + + + + + + Third level + + + + + + + Fourth level + + + + + + + Fifth level + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2/10/2025 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ‹#› + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master title style + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2/10/2025 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ‹#› + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2/10/2025 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ‹#› + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master title style + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master text styles + + + + + + + Second level + + + + + + + Third level + + + + + + + Fourth level + + + + + + + Fifth level + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master text styles + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2/10/2025 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ‹#› + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master title style + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master text styles + + + + + + + Second level + + + + + + + Third level + + + + + + + Fourth level + + + + + + + Fifth level + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2/10/2025 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ‹#› + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master title style + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master text styles + + + + + + + Second level + + + + + + + Third level + + + + + + + Fourth level + + + + + + + Fifth level + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2/10/2025 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ‹#› + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master title style + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master text styles + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2/10/2025 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ‹#› + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2/10/2025 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master text styles + + + + + + + Second level + + + + + + + Third level + + + + + + + Fourth level + + + + + + + Fifth level + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ‹#› + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + /9j/4AAQSkZJRgABAQAAYABgAAD/4QB0RXhpZgAATU0AKgAAAAgABAEaAAUAAAABAAAAPgEbAAUA + AAABAAAARgEoAAMAAAABAAIAAIdpAAQAAAABAAAATgAAAAAAAABgAAAAAQAAAGAAAAABAAKgAgAE + AAAAAQAAAOugAwAEAAAAAQAAAXMAAAAA/+0AOFBob3Rvc2hvcCAzLjAAOEJJTQQEAAAAAAAAOEJJ + TQQlAAAAAAAQ1B2M2Y8AsgTpgAmY7PhCfv/iB+hJQ0NfUFJPRklMRQABAQAAB9hhcHBsAiAAAG1u + dHJSR0IgWFlaIAfZAAIAGQALABoAC2Fjc3BBUFBMAAAAAGFwcGwAAAAAAAAAAAAAAAAAAAAAAAD2 + 1gABAAAAANMtYXBwbAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAC2Rlc2MAAAEIAAAAb2RzY20AAAF4AAAFnGNwcnQAAAcUAAAAOHd0cHQAAAdMAAAAFHJYWVoA + AAdgAAAAFGdYWVoAAAd0AAAAFGJYWVoAAAeIAAAAFHJUUkMAAAecAAAADmNoYWQAAAesAAAALGJU + UkMAAAecAAAADmdUUkMAAAecAAAADmRlc2MAAAAAAAAAFEdlbmVyaWMgUkdCIFByb2ZpbGUAAAAA + AAAAAAAAABRHZW5lcmljIFJHQiBQcm9maWxlAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAABtbHVjAAAAAAAAAB8AAAAMc2tTSwAAACgAAAGEZGFESwAAAC4A + AAGsY2FFUwAAACQAAAHadmlWTgAAACQAAAH+cHRCUgAAACYAAAIidWtVQQAAACoAAAJIZnJGVQAA + ACgAAAJyaHVIVQAAACgAAAKaemhUVwAAABYAAALCbmJOTwAAACYAAALYY3NDWgAAACIAAAL+aGVJ + TAAAAB4AAAMgaXRJVAAAACgAAAM+cm9STwAAACQAAANmZGVERQAAACwAAAOKa29LUgAAABYAAAO2 + c3ZTRQAAACYAAALYemhDTgAAABYAAAPMamFKUAAAABoAAAPiZWxHUgAAACIAAAP8cHRQTwAAACYA + AAQebmxOTAAAACgAAAREZXNFUwAAACYAAAQedGhUSAAAACQAAARsdHJUUgAAACIAAASQZmlGSQAA + ACgAAASyaHJIUgAAACgAAATacGxQTAAAACwAAAUCcnVSVQAAACIAAAUuYXJFRwAAACYAAAVQZW5V + UwAAACYAAAV2AFYBYQBlAG8AYgBlAGMAbgD9ACAAUgBHAEIAIABwAHIAbwBmAGkAbABHAGUAbgBl + AHIAZQBsACAAUgBHAEIALQBiAGUAcwBrAHIAaQB2AGUAbABzAGUAUABlAHIAZgBpAGwAIABSAEcA + QgAgAGcAZQBuAOgAcgBpAGMAQx6lAHUAIABoAOwAbgBoACAAUgBHAEIAIABDAGgAdQBuAGcAUABl + AHIAZgBpAGwAIABSAEcAQgAgAEcAZQBuAOkAcgBpAGMAbwQXBDAEMwQwBDsETAQ9BDgEOQAgBD8E + QAQ+BEQEMAQ5BDsAIABSAEcAQgBQAHIAbwBmAGkAbAAgAGcA6QBuAOkAcgBpAHEAdQBlACAAUgBW + AEIAwQBsAHQAYQBsAOEAbgBvAHMAIABSAEcAQgAgAHAAcgBvAGYAaQBskBp1KAAgAFIARwBCACCC + cl9pY8+P8ABHAGUAbgBlAHIAaQBzAGsAIABSAEcAQgAtAHAAcgBvAGYAaQBsAE8AYgBlAGMAbgD9 + ACAAUgBHAEIAIABwAHIAbwBmAGkAbAXkBegF1QXkBdkF3AAgAFIARwBCACAF2wXcBdwF2QBQAHIA + bwBmAGkAbABvACAAUgBHAEIAIABnAGUAbgBlAHIAaQBjAG8AUAByAG8AZgBpAGwAIABSAEcAQgAg + AGcAZQBuAGUAcgBpAGMAQQBsAGwAZwBlAG0AZQBpAG4AZQBzACAAUgBHAEIALQBQAHIAbwBmAGkA + bMd8vBgAIABSAEcAQgAg1QS4XNMMx3xmbpAaACAAUgBHAEIAIGPPj/Blh072TgCCLAAgAFIARwBC + ACAw1zDtMNUwoTCkMOsDkwO1A70DuQO6A8wAIAPAA8EDvwPGA68DuwAgAFIARwBCAFAAZQByAGYA + aQBsACAAUgBHAEIAIABnAGUAbgDpAHIAaQBjAG8AQQBsAGcAZQBtAGUAZQBuACAAUgBHAEIALQBw + AHIAbwBmAGkAZQBsDkIOGw4jDkQOHw4lDkwAIABSAEcAQgAgDhcOMQ5IDicORA4bAEcAZQBuAGUA + bAAgAFIARwBCACAAUAByAG8AZgBpAGwAaQBZAGwAZQBpAG4AZQBuACAAUgBHAEIALQBwAHIAbwBm + AGkAaQBsAGkARwBlAG4AZQByAGkBDQBrAGkAIABSAEcAQgAgAHAAcgBvAGYAaQBsAFUAbgBpAHcA + ZQByAHMAYQBsAG4AeQAgAHAAcgBvAGYAaQBsACAAUgBHAEIEHgQxBEkEOAQ5ACAEPwRABD4ERAQ4 + BDsETAAgAFIARwBCBkUGRAZBACAGKgY5BjEGSgZBACAAUgBHAEIAIAYnBkQGOQYnBkUARwBlAG4A + ZQByAGkAYwAgAFIARwBCACAAUAByAG8AZgBpAGwAZXRleHQAAAAAQ29weXJpZ2h0IDIwMDcgQXBw + bGUgSW5jLiwgYWxsIHJpZ2h0cyByZXNlcnZlZC4AWFlaIAAAAAAAAPNSAAEAAAABFs9YWVogAAAA + AAAAdE0AAD3uAAAD0FhZWiAAAAAAAABadQAArHMAABc0WFlaIAAAAAAAACgaAAAVnwAAuDZjdXJ2 + AAAAAAAAAAEBzQAAc2YzMgAAAAAAAQxCAAAF3v//8yYAAAeSAAD9kf//+6L///2jAAAD3AAAwGz/ + wAARCAFzAOsDASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAA + AgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcY + GRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKT + lJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP0 + 9fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQA + AQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2 + Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOk + paanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9sAQwAC + AgICAgIDAgIDBQMDAwUGBQUFBQYIBgYGBgYICggICAgICAoKCgoKCgoKDAwMDAwMDg4ODg4PDw8P + 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + /9j/4AAQSkZJRgABAQAAYABgAAD/4QB0RXhpZgAATU0AKgAAAAgABAEaAAUAAAABAAAAPgEbAAUA + AAABAAAARgEoAAMAAAABAAIAAIdpAAQAAAABAAAATgAAAAAAAABgAAAAAQAAAGAAAAABAAKgAgAE + AAAAAQAAAQygAwAEAAAAAQAAAWgAAAAA/+0AOFBob3Rvc2hvcCAzLjAAOEJJTQQEAAAAAAAAOEJJ + TQQlAAAAAAAQ1B2M2Y8AsgTpgAmY7PhCfv/iB+hJQ0NfUFJPRklMRQABAQAAB9hhcHBsAiAAAG1u + dHJSR0IgWFlaIAfZAAIAGQALABoAC2Fjc3BBUFBMAAAAAGFwcGwAAAAAAAAAAAAAAAAAAAAAAAD2 + 1gABAAAAANMtYXBwbAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAC2Rlc2MAAAEIAAAAb2RzY20AAAF4AAAFnGNwcnQAAAcUAAAAOHd0cHQAAAdMAAAAFHJYWVoA + AAdgAAAAFGdYWVoAAAd0AAAAFGJYWVoAAAeIAAAAFHJUUkMAAAecAAAADmNoYWQAAAesAAAALGJU + UkMAAAecAAAADmdUUkMAAAecAAAADmRlc2MAAAAAAAAAFEdlbmVyaWMgUkdCIFByb2ZpbGUAAAAA + AAAAAAAAABRHZW5lcmljIFJHQiBQcm9maWxlAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAABtbHVjAAAAAAAAAB8AAAAMc2tTSwAAACgAAAGEZGFESwAAAC4A + AAGsY2FFUwAAACQAAAHadmlWTgAAACQAAAH+cHRCUgAAACYAAAIidWtVQQAAACoAAAJIZnJGVQAA + ACgAAAJyaHVIVQAAACgAAAKaemhUVwAAABYAAALCbmJOTwAAACYAAALYY3NDWgAAACIAAAL+aGVJ + TAAAAB4AAAMgaXRJVAAAACgAAAM+cm9STwAAACQAAANmZGVERQAAACwAAAOKa29LUgAAABYAAAO2 + c3ZTRQAAACYAAALYemhDTgAAABYAAAPMamFKUAAAABoAAAPiZWxHUgAAACIAAAP8cHRQTwAAACYA + AAQebmxOTAAAACgAAAREZXNFUwAAACYAAAQedGhUSAAAACQAAARsdHJUUgAAACIAAASQZmlGSQAA + ACgAAASyaHJIUgAAACgAAATacGxQTAAAACwAAAUCcnVSVQAAACIAAAUuYXJFRwAAACYAAAVQZW5V + UwAAACYAAAV2AFYBYQBlAG8AYgBlAGMAbgD9ACAAUgBHAEIAIABwAHIAbwBmAGkAbABHAGUAbgBl + AHIAZQBsACAAUgBHAEIALQBiAGUAcwBrAHIAaQB2AGUAbABzAGUAUABlAHIAZgBpAGwAIABSAEcA + QgAgAGcAZQBuAOgAcgBpAGMAQx6lAHUAIABoAOwAbgBoACAAUgBHAEIAIABDAGgAdQBuAGcAUABl + AHIAZgBpAGwAIABSAEcAQgAgAEcAZQBuAOkAcgBpAGMAbwQXBDAEMwQwBDsETAQ9BDgEOQAgBD8E + QAQ+BEQEMAQ5BDsAIABSAEcAQgBQAHIAbwBmAGkAbAAgAGcA6QBuAOkAcgBpAHEAdQBlACAAUgBW + AEIAwQBsAHQAYQBsAOEAbgBvAHMAIABSAEcAQgAgAHAAcgBvAGYAaQBskBp1KAAgAFIARwBCACCC + cl9pY8+P8ABHAGUAbgBlAHIAaQBzAGsAIABSAEcAQgAtAHAAcgBvAGYAaQBsAE8AYgBlAGMAbgD9 + ACAAUgBHAEIAIABwAHIAbwBmAGkAbAXkBegF1QXkBdkF3AAgAFIARwBCACAF2wXcBdwF2QBQAHIA + bwBmAGkAbABvACAAUgBHAEIAIABnAGUAbgBlAHIAaQBjAG8AUAByAG8AZgBpAGwAIABSAEcAQgAg + AGcAZQBuAGUAcgBpAGMAQQBsAGwAZwBlAG0AZQBpAG4AZQBzACAAUgBHAEIALQBQAHIAbwBmAGkA + bMd8vBgAIABSAEcAQgAg1QS4XNMMx3xmbpAaACAAUgBHAEIAIGPPj/Blh072TgCCLAAgAFIARwBC + ACAw1zDtMNUwoTCkMOsDkwO1A70DuQO6A8wAIAPAA8EDvwPGA68DuwAgAFIARwBCAFAAZQByAGYA + aQBsACAAUgBHAEIAIABnAGUAbgDpAHIAaQBjAG8AQQBsAGcAZQBtAGUAZQBuACAAUgBHAEIALQBw + AHIAbwBmAGkAZQBsDkIOGw4jDkQOHw4lDkwAIABSAEcAQgAgDhcOMQ5IDicORA4bAEcAZQBuAGUA + bAAgAFIARwBCACAAUAByAG8AZgBpAGwAaQBZAGwAZQBpAG4AZQBuACAAUgBHAEIALQBwAHIAbwBm + AGkAaQBsAGkARwBlAG4AZQByAGkBDQBrAGkAIABSAEcAQgAgAHAAcgBvAGYAaQBsAFUAbgBpAHcA + ZQByAHMAYQBsAG4AeQAgAHAAcgBvAGYAaQBsACAAUgBHAEIEHgQxBEkEOAQ5ACAEPwRABD4ERAQ4 + BDsETAAgAFIARwBCBkUGRAZBACAGKgY5BjEGSgZBACAAUgBHAEIAIAYnBkQGOQYnBkUARwBlAG4A + ZQByAGkAYwAgAFIARwBCACAAUAByAG8AZgBpAGwAZXRleHQAAAAAQ29weXJpZ2h0IDIwMDcgQXBw + bGUgSW5jLiwgYWxsIHJpZ2h0cyByZXNlcnZlZC4AWFlaIAAAAAAAAPNSAAEAAAABFs9YWVogAAAA + AAAAdE0AAD3uAAAD0FhZWiAAAAAAAABadQAArHMAABc0WFlaIAAAAAAAACgaAAAVnwAAuDZjdXJ2 + AAAAAAAAAAEBzQAAc2YzMgAAAAAAAQxCAAAF3v//8yYAAAeSAAD9kf//+6L///2jAAAD3AAAwGz/ + wAARCAFoAQwDASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAA + AgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcY + GRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKT + lJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP0 + 9fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQA + AQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2 + Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOk + paanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9sAQwAC + AgICAgIDAgIDBQMDAwUGBQUFBQYIBgYGBgYICggICAgICAoKCgoKCgoKDAwMDAwMDg4ODg4PDw8P + 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 + ACiiigAooooAKKKKACiiigAooooAKKKKAP/Z + + + + + + + + + + /9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMDAsK + CwsNDhIQDQ4RDgsLEBYQERMUFRUVDA8XGBYUGBIUFRT/2wBDAQMEBAUEBQkFBQkUDQsNFBQUFBQU + FBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBT/wAARCACQAQADASIA + AhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQA + AAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3 + ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWm + p6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEA + AwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSEx + BhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElK + U1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3 + uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD9U6KK + KACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAoooo + AKKKKACiiigDwP8AaO/trUPiD8NdH0oX91DeLqj3Gn2fie70FZ/LhiKF57Y7jtJJCkEcmvPvCP7R + HjDT/hfcXVvfWGot4K8LxanrDa1A893rU63l9aTW9vNHMqjDae6JORL5rTRNtGfm+nvFngDwx4+h + t4fE/hvSPEcNuxeGPVrGK6WJiMEqJFOCR3FVNe8D+FhY2d9P4X0a6m0CFpNLM1hExstgDKISV/d8 + ov3cdB6UAeIal8dPiZB517p9p4e1SK51XxJpenaRHYXC3Ltpk1z5e6X7RtZpY7V0wEUB5FbOAUNn + 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 + 6VYooqiwooooA//Z + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Luke Tomlinson + Luke Tomlinson + 1 + 2025-02-10T20:00:11Z + 2025-02-10T20:03:00Z + + + + + + + 0 + 36 + Microsoft Office PowerPoint + Widescreen + 17 + 1 + 1 + 0 + 0 + false + + + + Fonts Used + + + 3 + + + Theme + + + 1 + + + Slide Titles + + + 1 + + + + + + Aptos + Aptos Display + Arial + Office Theme + Bony Pelvis + + + + false + false + false + 16.0000 + + + + \ No newline at end of file diff --git a/data_extraction/Hardcoded.py b/data_extraction/Hardcoded.py deleted file mode 100644 index daa061d9..00000000 --- a/data_extraction/Hardcoded.py +++ /dev/null @@ -1,96 +0,0 @@ -import os -import argparse -import xml.etree.ElementTree as ET - -def extract_images_from_slide_xml(slide_xml_path, rels_xml_path, media_folder, output_folder): - # Step 1: Parse the slide XML to find image relationships - try: - print(f"Parsing slide XML: {slide_xml_path}") - tree = ET.parse(slide_xml_path) - root = tree.getroot() - except ET.ParseError: - print(f"Error parsing {slide_xml_path}") - return - - # Namespace handling for slide XML - ns = {'a': 'http://schemas.openxmlformats.org/drawingml/2006/main', - 'r': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'} - - # Step 2: Find all tags which reference images - print("Finding tags in the slide XML...") - blip_found = False - embed_ids = [] # List to store all found embed IDs - for blip in root.findall(".//a:blip", ns): - blip_found = True - embed = blip.attrib.get(f"{{{ns['r']}}}embed") # Get r:embed value (e.g., rId3) - if embed: - embed = embed.strip() # Strip any whitespace, just in case - embed_ids.append(embed) - print(f"Found with embed ID: '{embed}'") - - if not blip_found: - print("No tags found in the slide XML.") - return - - # Step 3: Parse the relationship file to find the image reference - try: - print(f"Parsing relationships XML: {rels_xml_path}") - rels_tree = ET.parse(rels_xml_path) - rels_root = rels_tree.getroot() - except ET.ParseError: - print(f"Error parsing {rels_xml_path}") - return - - # Step 4: Handle namespaces in relationships XML - # Extract the namespace if it exists - rels_ns = "" - if '}' in rels_root.tag: - rels_ns = rels_root.tag.split('}')[0] + '}' - - # Step 5: Print all relationships for debugging - print("Listing all relationships from the relationships XML:") - relationships = {} - for rel in rels_root.findall(f".//{rels_ns}Relationship"): - rel_id = rel.attrib.get('Id', '').strip() - target = rel.attrib.get('Target', '').strip() - relationships[rel_id] = target - print(f"Relationship ID: '{rel_id}', Target: '{target}'") - - # Step 6: Cross-check embed IDs against relationships and extract images - for embed_id in embed_ids: - if embed_id in relationships: - target = relationships[embed_id] - print(f"Found relationship for embed ID '{embed_id}': Target -> {target}") - - # The target should point to an image in the media folder - image_path = os.path.join(media_folder, os.path.basename(target)) - print(f"Resolved image path: {image_path}") - - # Step 7: Copy the image to the output folder - if os.path.exists(image_path): - image_output_path = os.path.join(output_folder, os.path.basename(target)) - with open(image_path, 'rb') as img_in, open(image_output_path, 'wb') as img_out: - img_out.write(img_in.read()) - print(f"Extracted: {image_output_path}") - else: - print(f"Image not found: {image_path}") - else: - print(f"No relationship found for embed ID: '{embed_id}'") - -if __name__ == "__main__": - # Create argument parser - parser = argparse.ArgumentParser(description="Extract images from a PowerPoint slide XML.") - parser.add_argument("--slide_xml_path", required=True, help="Path to the slide XML file.") - parser.add_argument("--rels_xml_path", required=True, help="Path to the relationships XML file.") - parser.add_argument("--media_folder", required=True, help="Path to the media folder containing images.") - parser.add_argument("--output_folder", required=True, help="Path to the output folder for extracted images.") - - # Parse arguments - args = parser.parse_args() - - # Ensure output folder exists - if not os.path.exists(args.output_folder): - os.makedirs(args.output_folder) - - # Run the extraction - extract_images_from_slide_xml(args.slide_xml_path, args.rels_xml_path, args.media_folder, args.output_folder) diff --git a/data_extraction/XML boneset Reader.py b/data_extraction/XML boneset Reader.py index 4689b069..dda6d9ba 100644 --- a/data_extraction/XML boneset Reader.py +++ b/data_extraction/XML boneset Reader.py @@ -2,15 +2,18 @@ import xml.etree.ElementTree as ET import json -def extract_bones_from_xml_to_json(xml_path, output_json_path): - # Step 1: Parse the XML file +def extract_bones_from_xml(xml_path): + """ + Parses the XML file and extracts bonesets and their associated bones. + Returns a dictionary with boneset names as keys and lists of bones as values. + """ try: print(f"Parsing XML: {xml_path}") tree = ET.parse(xml_path) root = tree.getroot() except ET.ParseError as e: print(f"Error parsing {xml_path}: {e}") - return + return {} # Namespace handling for XML ns = { @@ -19,37 +22,54 @@ def extract_bones_from_xml_to_json(xml_path, output_json_path): 'r': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships' } - # Step 2: Extract boneset name dynamically - boneset_name = None - boneset_element = root.find(".//p:sp/p:txBody//a:t", ns) - if boneset_element is not None: - boneset_name = boneset_element.text.strip() - print(f"Found boneset name: {boneset_name}") - else: - print("No boneset name found.") - boneset_name = "Unknown Boneset" - - # Step 3: Extract bones and use a set to prevent duplicates - unique_bones = set() - - for boneset in root.findall(".//p:sp", ns): # Adjust this path as necessary for your XML structure - for bone in boneset.findall(".//p:txBody//a:r/a:t", ns): - bone_name = bone.text.strip() if bone.text else None - if bone_name and is_valid_bone_name(bone_name, boneset_name): - # Normalize the bone name to lowercase to handle case insensitivity - unique_bones.add(bone_name.lower()) - - # Step 4: Create a single boneset with all unique bones - # Capitalize the first letter of each bone for consistency in JSON output - output_data = { - 'boneset': boneset_name, - 'bones': sorted(bone.capitalize() for bone in unique_bones) # Convert the set to a sorted list - } + bonesets = {} # Dictionary to store bonesets + + INVALID_BONESETS = {"Home", "The", "Title", "Slide", "Overview", "Labels", "Right Pelvis" } + + + # Extract bonesets dynamically + for boneset_element in root.findall(".//p:sp", ns): + boneset_name_element = boneset_element.find(".//p:txBody//a:t", ns) + + if boneset_name_element is not None: + + boneset_name = boneset_name_element.text.strip() + + boneset_name_words = boneset_name.split() + if not len(boneset_name_words) < 4: + continue + if not boneset_name or boneset_name in INVALID_BONESETS: + continue + if not all(word.isalpha() for word in boneset_name.split()): + continue + + if boneset_name not in bonesets: + bonesets[boneset_name] = set() + + # Extract bones within this boneset + for bone_element in boneset_element.findall(".//p:txBody//a:r/a:t", ns): + bone_name = bone_element.text.strip() if bone_element.text else None + if bone_name and is_valid_bone_name(bone_name, boneset_name): + bonesets[boneset_name].add(bone_name.capitalize()) - # Step 5: Write the JSON data to a file + return bonesets + +def generate_json_output(bonesets, output_json_path): + """ + Converts bonesets dictionary into a structured JSON format and writes it to a file. + """ + structured_data = [] + + for boneset_name, bones in bonesets.items(): + structured_data.append({ + "boneset": boneset_name, + "bones": sorted(bones), + }) + + # Save to JSON file try: with open(output_json_path, 'w') as json_file: - json.dump(output_data, json_file, indent=4) + json.dump(structured_data, json_file, indent=4) print(f"JSON file saved: {output_json_path}") except IOError as e: print(f"Error writing to {output_json_path}: {e}") @@ -73,14 +93,14 @@ def is_valid_bone_name(name, boneset_name): if name.islower(): # Ignore entirely lowercase words return False - # Exclude names that contain the boneset name (case-insensitive) - if boneset_name and boneset_name.lower() in name.lower(): + if len(name.split()) > 3: return False - if "Home" in name: + # Exclude names that contain the boneset name (case-insensitive) + if boneset_name and boneset_name.lower() in name.lower(): return False - if "The" in name: + if "Home" in name or "The" in name: return False return True @@ -90,8 +110,11 @@ def is_valid_bone_name(name, boneset_name): current_dir = os.path.dirname(os.path.abspath(__file__)) # Define the XML and JSON file paths relative to the script's directory - xml_file_path = os.path.join(current_dir, "slide2UpperLimb.xml") + xml_file_path = os.path.join(current_dir, "BoneyPelvisSlide4.xml") json_file_path = os.path.join(current_dir, "output.json") - # Run the extraction and save as JSON - extract_bones_from_xml_to_json(xml_file_path, json_file_path) \ No newline at end of file + # Extract bonesets and their bones + bonesets = extract_bones_from_xml(xml_file_path) + + # Generate and save JSON output + generate_json_output(bonesets, json_file_path) diff --git a/data_extraction/extract_ppt_annotations.py b/data_extraction/extract_ppt_annotations.py index 58373bcf..eb580951 100644 --- a/data_extraction/extract_ppt_annotations.py +++ b/data_extraction/extract_ppt_annotations.py @@ -1,53 +1,50 @@ import xml.etree.ElementTree as ET import json -# this is the code for one slide at a time. +import os def parse_slide_xml(xml_file, output_json_path): # Load the XML file tree = ET.parse(xml_file) root = tree.getroot() - # Define namespaces used in the XML Not sure if this is correct + # Define namespaces used in the XML ns = { 'p': 'http://schemas.openxmlformats.org/presentationml/2006/main', 'a': 'http://schemas.openxmlformats.org/drawingml/2006/main' - } annotations = [] + middle_x_min, middle_x_max = 2000000, 6000000 # Define X range for middle + middle_y_min, middle_y_max = 1000000, 7000000 # Define Y range for middle - # Parse shapes (`p:sp` elements for text and shapes) for sp in root.findall(".//p:sp", ns): annotation = {} - + # Extract text, if present text_elements = sp.findall(".//a:t", ns) text = ''.join([t.text for t in text_elements if t.text]) - if text: - annotation["text"] = text - - # Extract fill color, if present - fill_color = sp.find(".//a:solidFill/a:srgbClr", ns) - if fill_color is not None: - annotation["color"] = fill_color.attrib.get("val") # Extract position and size xfrm = sp.find(".//a:xfrm", ns) if xfrm is not None: - pos = xfrm.find(".//a:off", ns) - size = xfrm.find(".//a:ext", ns) + pos = xfrm.find("a:off", ns) + size = xfrm.find("a:ext", ns) if pos is not None and size is not None: - annotation["position"] = { - "x": pos.attrib.get("x"), - "y": pos.attrib.get("y"), - "width": size.attrib.get("cx"), - "height": size.attrib.get("cy") - } - - if annotation: - annotations.append(annotation) - - # Parse lines (`p:cxnSp` elements for line annotations) + x, y = int(pos.attrib.get("x", 0)), int(pos.attrib.get("y", 0)) + width, height = int(size.attrib.get("cx", 0)), int(size.attrib.get("cy", 0)) + + if middle_x_min <= x <= middle_x_max and middle_y_min <= y <= middle_y_max: + annotation["text"] = text + annotation["position"] = {"x": x, "y": y, "width": width, "height": height} + + # Extract fill color, if present + fill_color = sp.find(".//a:solidFill/a:srgbClr", ns) + if fill_color is not None: + annotation["color"] = fill_color.attrib.get("val") + + annotations.append(annotation) + + # Collect lines for ln in root.findall(".//p:cxnSp", ns): annotation = {"shape": "line"} @@ -55,29 +52,34 @@ def parse_slide_xml(xml_file, output_json_path): line_color = ln.find(".//a:ln/a:solidFill/a:srgbClr", ns) if line_color is not None: annotation["color"] = line_color.attrib.get("val") + annotations.append(annotation["color"]) # Extract position and size xfrm = ln.find(".//a:xfrm", ns) if xfrm is not None: - pos = xfrm.find(".//a:off", ns) - size = xfrm.find(".//a:ext", ns) + pos = xfrm.find("a:off", ns) + size = xfrm.find("a:ext", ns) if pos is not None and size is not None: - annotation["position"] = { - "x": pos.attrib.get("x"), - "y": pos.attrib.get("y"), - "width": size.attrib.get("cx"), - "height": size.attrib.get("cy") - } - - annotations.append(annotation) + x, y = int(pos.attrib.get("x", 0)), int(pos.attrib.get("y", 0)) + width, height = int(size.attrib.get("cx", 0)), int(size.attrib.get("cy", 0)) + + if middle_x_min <= x <= middle_x_max and middle_y_min <= y <= middle_y_max: + annotation["position"] = {"x": x, "y": y, "width": width, "height": height} + annotations.append(annotation) + + # Ensure output directory exists + output_dir = os.path.dirname(output_json_path) + if output_dir: + os.makedirs(output_dir, exist_ok=True) - # Save annotations to a JSON file with open(output_json_path, 'w') as f: json.dump(annotations, f, indent=4) print(f"Annotations saved to {output_json_path}") -# Example usage -xml_file = "/Users/joshbudzynski/Downloads/example_folder/ppt/slides/slide3.xml" # path to XML file -output_json = "slide3_annotations.json" # Output JSON file +# File paths +xml_file = "slide2Pelvis.xml" +output_json = "slide2_pelvis_annotations.json" + +# Run function with dynamic input parse_slide_xml(xml_file, output_json) diff --git a/data_extraction/output.json b/data_extraction/output.json index a8cbea24..890ee3bf 100644 --- a/data_extraction/output.json +++ b/data_extraction/output.json @@ -1,9 +1,18 @@ -{ - "boneset": "Upper Limb", - "bones": [ - "Hand", - "Humerus", - "Radius", - "Ulna" - ] -} \ No newline at end of file +[ + { + "boneset": "Bony Pelvis", + "bones": [ + "Ilium", + "Ischium", + "Pubis" + ] + }, + { + "boneset": "Iliac Crest", + "bones": [ + "Anterior iliac spines", + "Auricular surface", + "Posterior iliac spines" + ] + } +] \ No newline at end of file diff --git a/data_extraction/script.py b/data_extraction/script.py deleted file mode 100644 index eaa02835..00000000 --- a/data_extraction/script.py +++ /dev/null @@ -1,83 +0,0 @@ -import xml.etree.ElementTree as ET -import os -import shutil -import argparse - -def extract_images_from_slide(slide_xml_path, rels_file_path, media_folder, output_dir): - print(f"Starting extraction for slide: {slide_xml_path}") - print(f"Using relationships file: {rels_file_path}") - print(f"Media folder: {media_folder}") - print(f"Output directory: {output_dir}") - - # Parse the slide XML file - try: - slide_tree = ET.parse(slide_xml_path) - slide_root = slide_tree.getroot() - print("Slide XML parsed successfully.") - except Exception as e: - print(f"Error parsing slide XML: {e}") - return - - # Parse the .rels file - rels_ns = {'rel': 'http://schemas.openxmlformats.org/package/2006/relationships'} - try: - rels_tree = ET.parse(rels_file_path) - rels_root = rels_tree.getroot() - - # Extract relationships - rels_map = { - rel.attrib["Id"]: rel.attrib["Target"] - for rel in rels_root.findall("rel:Relationship", rels_ns) - } - print(f"Relationships mapped: {rels_map}") - except Exception as e: - print(f"Error parsing .rels file: {e}") - return - - # Ensure the output directory exists - os.makedirs(output_dir, exist_ok=True) - print(f"Output directory checked/created: {output_dir}") - - # Debugging the structure of elements - pic_namespace = {'p': 'http://schemas.openxmlformats.org/presentationml/2006/main', - 'a': 'http://schemas.openxmlformats.org/drawingml/2006/main', - 'r': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'} - - # Find all picture elements - image_found = False - for pic in slide_root.findall(".//p:pic", pic_namespace): - try: - print(f"Processing : {ET.tostring(pic, encoding='unicode')}") - blip = pic.find(".//a:blip", pic_namespace) - if blip is not None and "embed" in blip.attrib: - r_id = blip.attrib["{http://schemas.openxmlformats.org/officeDocument/2006/relationships}embed"] - print(f"Found r:embed ID: {r_id}") - - if r_id in rels_map: - # Locate the image file - image_path = os.path.join(media_folder, os.path.basename(rels_map[r_id])) - if os.path.exists(image_path): - # Save the image to the output directory - output_path = os.path.join(output_dir, os.path.basename(image_path)) - shutil.copy(image_path, output_path) - print(f"Image saved: {output_path}") - image_found = True - else: - print(f"Image file not found in media folder: {image_path}") - else: - print(f"r:embed ID {r_id} not found in .rels map.") - else: - print(f"No or missing 'embed' attribute in : {ET.tostring(pic, encoding='unicode')}") - except Exception as e: - print(f"Error processing element: {e}") - - if not image_found: - print("No images were extracted. Check your slide XML, .rels file, and media folder paths.") - -# Example usage -slide_xml_path = '/Users/burhankhan/Desktop/CSCI-4961-01/ppt/slides/slide2.xml' -rels_file_path = "/Users/burhankhan/Desktop/CSCI-4961-01/ppt/slides/_rels/slide2.xml.rels" -media_folder = '/Users/burhankhan/Desktop/CSCI-4961-01/ppt/media' -output_dir = '/Users/burhankhan/Desktop/images' - -extract_images_from_slide(slide_xml_path, rels_file_path, media_folder, output_dir) diff --git a/data_extraction/slide2Pelvis.xml b/data_extraction/slide2Pelvis.xml index c8757aae..fa13d244 100644 --- a/data_extraction/slide2Pelvis.xml +++ b/data_extraction/slide2Pelvis.xml @@ -1,2 +1,11974 @@ -Bony PelvisHomeRight Pelvis(medial aspect)Right Pelvis (lateral aspect)Bony Pelvis Ilium Ischium Pubis IliumPubisIschiumAcetabulumObturator foramenThe bony pelvis is made up of tow hip bones, sacrum, and coccyx.Hip bones - 3 fused bones:IliumIschiumPubisThe acetabulum is a cup shaped depression that articulates with the head of the femur to from the hip joint, a ball-and-socket type synovial joint.The obturator foramen is an opening created by the inferior ramus and body of the ischium, and superior and inferior pubic rami. This foramen is partially covered by the obturator membrane. The inner and outer surfaces of the obturator membrane provide the origin for the obturator internus and externus muscles, respectively. \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Bony Pelvis + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Home + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Right Pelvis + + + + (medial aspect) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Right Pelvis + + + + (lateral aspect) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Bony Pelvis + + + + + + + + + + + + + + + + + + + + + + + + Ilium + + + + + + + + + + + + + + + + + + + + + + + + Ischium + + + + + + + + + + + + + + + + + + + + + + + + Pubis + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Ilium + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Pubis + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Ischium + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Acetabulum + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Obturator foramen + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The bony pelvis is made up of tow hip bones, sacrum, and coccyx. + + + + + + + + + + + + + + + + + + + Hip bones - 3 fused bones: + + + + + + + + + + + + + Ilium + + + + + + + + + + + + + + + + + Ischium + + + + + + + + + + + + + + + + + Pubis + + + + + + + + + + + + + + + + + + + The + + + + acetabulum + + + + is a cup shaped depression that articulates with the head of the + femur to from the hip joint, a ball-and-socket type synovial joint. + + + + + + + + + + + + + + + + + + + The + + + + obturator foramen + + + + is an opening created by the inferior ramus and body of the + ischium, and superior and inferior pubic rami. This foramen is + partially covered by the obturator membrane. The inner and outer + surfaces of the obturator membrane provide the origin for the + obturator internus and externus muscles, respectively. + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data_extraction/slide2_pelvis_annotations.json b/data_extraction/slide2_pelvis_annotations.json new file mode 100644 index 00000000..d7a9c797 --- /dev/null +++ b/data_extraction/slide2_pelvis_annotations.json @@ -0,0 +1,140 @@ +[ + { + "text": "", + "position": { + "x": 2743200, + "y": 1299410, + "width": 5943600, + "height": 4952999 + } + }, + { + "text": "Right Pelvis (lateral aspect)", + "position": { + "x": 3579863, + "y": 6251729, + "width": 1323299, + "height": 215444 + } + }, + { + "text": "Ilium", + "position": { + "x": 5586937, + "y": 2083816, + "width": 393457, + "height": 215444 + } + }, + { + "text": "", + "position": { + "x": 3873661, + "y": 4149666, + "width": 1134543, + "height": 1310358 + }, + "color": "C133AD" + }, + { + "text": "", + "position": { + "x": 4562474, + "y": 3962400, + "width": 1014265, + "height": 1060450 + }, + "color": "2F8E29" + }, + { + "text": "Pubis", + "position": { + "x": 5682077, + "y": 4259252, + "width": 419318, + "height": 215444 + } + }, + { + "text": "Ischium", + "position": { + "x": 5446767, + "y": 5263629, + "width": 507320, + "height": 215444 + } + }, + { + "text": "Acetabulum", + "position": { + "x": 5450756, + "y": 3572102, + "width": 692939, + "height": 215444 + } + }, + "000000", + { + "shape": "line", + "position": { + "x": 4267200, + "y": 2191538, + "width": 1319736, + "height": 0 + } + }, + { + "shape": "line", + "position": { + "x": 5980394, + "y": 2191538, + "width": 1411007, + "height": 0 + } + }, + { + "shape": "line", + "position": { + "x": 5954088, + "y": 5371351, + "width": 961063, + "height": 0 + } + }, + { + "shape": "line", + "position": { + "x": 4495801, + "y": 5371351, + "width": 950967, + "height": 0 + } + }, + { + "shape": "line", + "position": { + "x": 5105401, + "y": 4366974, + "width": 576677, + "height": 0 + } + }, + { + "shape": "line", + "position": { + "x": 5171505, + "y": 3679824, + "width": 279250, + "height": 0 + } + }, + { + "shape": "line", + "position": { + "x": 4562474, + "y": 3679825, + "width": 609031, + "height": 495300 + } + } +] \ No newline at end of file diff --git a/data_extraction/slide3_annotations.json b/data_extraction/slide3_annotations.json index 5d58b1d1..d87767e0 100644 --- a/data_extraction/slide3_annotations.json +++ b/data_extraction/slide3_annotations.json @@ -1,237 +1,29 @@ [ { + "text": "", "position": { - "x": "2743200", - "y": "1299410", - "width": "5943600", - "height": "4952999" + "x": 2743200, + "y": 1299410, + "width": 5943600, + "height": 4952999 } }, { - "text": "Bony Pelvis" - }, - { - "text": "Home", - "position": { - "x": "1752600", - "y": "6400801", - "width": "723900", - "height": "307777" - } - }, - { - "position": { - "x": "1524000", - "y": "6248400", - "width": "9144000", - "height": "0" - } - }, - { - "position": { - "x": "8686800", - "y": "1311440", - "width": "1981200", - "height": "4936961" - } - }, - { - "position": { - "x": "1524000", - "y": "1311439", - "width": "1219200", - "height": "4936961" - } - }, - { - "text": "Right Pelvis(medial aspect)", - "position": { - "x": "6477427", - "y": "6248399", - "width": "1316687", - "height": "215444" - } - }, - { - "text": "Right Pelvis (lateral aspect)", - "position": { - "x": "3579863", - "y": "6251729", - "width": "1323299", - "height": "215444" - } - }, - { - "text": "Iliac CrestAnterior Iliac spinesPosterior Iliac spinesAuricular surface", - "position": { - "x": "8823068", - "y": "1322155", - "width": "1148008", - "height": "805605" - } - }, - { - "text": "IliumThe Ilium forms the superior part of the bony pelvis.It articulates with the sacrum to form the sacroiliac joint.", - "position": { - "x": "8757818", - "y": "3505200", - "width": "1828800", - "height": "1231106" - } - }, - { - "text": "Bony Pelvis Ilium Ischium Pubis ", - "color": "000000", - "position": { - "x": "1529348", - "y": "1330154", - "width": "1213853", - "height": "1015663" - } - }, - { - "text": "Iliac Crest", - "position": { - "x": "5431116", - "y": "1912315", - "width": "595035", - "height": "215444" - } - }, - { - "text": "Anterior Superior Iliac Spine", - "position": { - "x": "5407752", - "y": "2590801", - "width": "766897", - "height": "461665" - } - }, - { - "text": "Anterior Inferior Iliac Spine", - "position": { - "x": "5271485", - "y": "3229154", - "width": "766897", - "height": "461665" - } - }, - { - "text": "Posterior Inferior Iliac Spine", - "position": { - "x": "7887969", - "y": "3816698", - "width": "766897", - "height": "461665" - } - }, - { - "text": "Posterior Superior Iliac Spine", - "position": { - "x": "7881241", - "y": "1445569", - "width": "766897", - "height": "461665" - } - }, - { - "text": "Auricular surface", - "position": { - "x": "7169365", - "y": "1417638", - "width": "624749", - "height": "338554" - } - }, - { - "text": "No labels", - "position": { - "x": "9189990", - "y": "6400801", - "width": "974820", - "height": "307777" - } - }, - { - "shape": "line", - "color": "000000", - "position": { - "x": "1524000", - "y": "1295401", - "width": "9144000", - "height": "4009" - } - }, - { - "shape": "line", - "position": { - "x": "8686800", - "y": "3459986", - "width": "1965420", - "height": "0" - } - }, - { - "shape": "line", - "position": { - "x": "4212167", - "y": "2020037", - "width": "1218948", - "height": "0" - } - }, - { - "shape": "line", - "position": { - "x": "6026150", - "y": "2020037", - "width": "1212850", - "height": "0" - } - }, - { - "shape": "line", - "position": { - "x": "5008205", - "y": "2821633", - "width": "399547", - "height": "0" - } - }, - { - "shape": "line", - "position": { - "x": "4915392", - "y": "3459986", - "width": "356092", - "height": "0" - } - }, - { - "shape": "line", - "position": { - "x": "8153401", - "y": "3505201", - "width": "118017", - "height": "311497" - } - }, - { - "shape": "line", + "text": "Humerus", "position": { - "x": "8264690", - "y": "1907233", - "width": "117311", - "height": "1145232" + "x": 5428944, + "y": 2254478, + "width": 590857, + "height": 215444 } }, { - "shape": "line", + "text": "Radius", "position": { - "x": "7481740", - "y": "1756192", - "width": "290661", - "height": "1368008" + "x": 3124200, + "y": 4189504, + "width": 483352, + "height": 215444 } } ] \ No newline at end of file From 9c0e013fd014d7fda26b3cb0ef94c841985add3e Mon Sep 17 00:00:00 2001 From: Burhan Khan Date: Mon, 24 Feb 2025 13:18:27 -0600 Subject: [PATCH 073/143] Addresses Issue 86. Edited extract_ppt_annotations.py file to include the extracted name and RiD. Outputs to the JSON file that was initiallyoutputting the annotations. --- data_extraction/extract_ppt_annotations.py | 200 ++++++++++++++------- 1 file changed, 131 insertions(+), 69 deletions(-) diff --git a/data_extraction/extract_ppt_annotations.py b/data_extraction/extract_ppt_annotations.py index eb580951..487bef49 100644 --- a/data_extraction/extract_ppt_annotations.py +++ b/data_extraction/extract_ppt_annotations.py @@ -1,85 +1,147 @@ +import os import xml.etree.ElementTree as ET import json -import os -def parse_slide_xml(xml_file, output_json_path): - # Load the XML file - tree = ET.parse(xml_file) - root = tree.getroot() - - # Define namespaces used in the XML - ns = { - 'p': 'http://schemas.openxmlformats.org/presentationml/2006/main', - 'a': 'http://schemas.openxmlformats.org/drawingml/2006/main' +def extract_images_from_slide_xml(slide_xml_path, rels_xml_path, media_folder, output_folder, json_output): + """ + Extract images from a slide XML, rename them, and write image details to a JSON file. + """ + try: + tree = ET.parse(slide_xml_path) + root = tree.getroot() + except (ET.ParseError, FileNotFoundError) as e: + print(f"[ERROR] Failed to parse {slide_xml_path}: {e}") + return + + ns = {'a': 'http://schemas.openxmlformats.org/drawingml/2006/main', + 'r': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'} + + # Extract image IDs from the slide XML + embed_ids = [blip.attrib.get(f"{{{ns['r']}}}embed") for blip in root.findall(".//a:blip", ns) if blip.attrib.get(f"{{{ns['r']}}}embed")] + + if not embed_ids: + print(f"[INFO] No images found in {slide_xml_path}. Skipping...") + return + + try: + rels_tree = ET.parse(rels_xml_path) + rels_root = rels_tree.getroot() + except (ET.ParseError, FileNotFoundError) as e: + print(f"[ERROR] Failed to parse {rels_xml_path}: {e}") + return + + # Namespace for relationships + rels_ns = rels_root.tag.split('}')[0] + '}' if '}' in rels_root.tag else '' + + # Map rId to actual image path + relationships = {rel.attrib.get('Id', '').strip(): rel.attrib.get('Target', '').strip() + for rel in rels_root.findall(f".//{rels_ns}Relationship")} + + # Extract slide number and set up output folder + slide_name = os.path.splitext(os.path.basename(slide_xml_path))[0] + slide_number = slide_name.replace("slide", "") + slide_output_folder = os.path.join(output_folder, slide_name) + os.makedirs(slide_output_folder, exist_ok=True) + + # JSON structure for image and annotation details + slide_data = { + "slide": slide_name, + "images": [], + "annotations": [] } - - annotations = [] - middle_x_min, middle_x_max = 2000000, 6000000 # Define X range for middle - middle_y_min, middle_y_max = 1000000, 7000000 # Define Y range for middle - for sp in root.findall(".//p:sp", ns): + # Extract and rename images + for embed_id in embed_ids: + if embed_id in relationships: + target = relationships[embed_id] + image_path = os.path.join(media_folder, os.path.basename(target)) + + if os.path.exists(image_path): + image_extension = os.path.splitext(target)[-1] + new_image_name = f"slide{slide_number}_{embed_id}{image_extension}" + image_output_path = os.path.join(slide_output_folder, new_image_name) + + try: + with open(image_path, 'rb') as img_in, open(image_output_path, 'wb') as img_out: + img_out.write(img_in.read()) + + print(f"[SUCCESS] Extracted: {image_output_path}") + + # Add image metadata to JSON (Simplified) + slide_data["images"].append({ + "rId": embed_id, + "extracted_name": new_image_name + }) + except Exception as e: + print(f"[ERROR] Failed to copy image {image_path}: {e}") + else: + print(f"[WARNING] Image not found: {image_path}") + + # Extract annotations from slide XML + ns_p = {'p': 'http://schemas.openxmlformats.org/presentationml/2006/main', + 'a': 'http://schemas.openxmlformats.org/drawingml/2006/main'} + middle_x_min, middle_x_max = 2000000, 6000000 + middle_y_min, middle_y_max = 1000000, 7000000 + + for sp in root.findall(".//p:sp", ns_p): annotation = {} - - # Extract text, if present - text_elements = sp.findall(".//a:t", ns) + text_elements = sp.findall(".//a:t", ns_p) text = ''.join([t.text for t in text_elements if t.text]) - - # Extract position and size - xfrm = sp.find(".//a:xfrm", ns) + + xfrm = sp.find(".//a:xfrm", ns_p) if xfrm is not None: - pos = xfrm.find("a:off", ns) - size = xfrm.find("a:ext", ns) + pos = xfrm.find("a:off", ns_p) + size = xfrm.find("a:ext", ns_p) if pos is not None and size is not None: x, y = int(pos.attrib.get("x", 0)), int(pos.attrib.get("y", 0)) width, height = int(size.attrib.get("cx", 0)), int(size.attrib.get("cy", 0)) - + if middle_x_min <= x <= middle_x_max and middle_y_min <= y <= middle_y_max: annotation["text"] = text annotation["position"] = {"x": x, "y": y, "width": width, "height": height} - - # Extract fill color, if present - fill_color = sp.find(".//a:solidFill/a:srgbClr", ns) + + # Extract fill color if present + fill_color = sp.find(".//a:solidFill/a:srgbClr", ns_p) if fill_color is not None: annotation["color"] = fill_color.attrib.get("val") - - annotations.append(annotation) - - # Collect lines - for ln in root.findall(".//p:cxnSp", ns): - annotation = {"shape": "line"} - - # Extract line color - line_color = ln.find(".//a:ln/a:solidFill/a:srgbClr", ns) - if line_color is not None: - annotation["color"] = line_color.attrib.get("val") - annotations.append(annotation["color"]) - - # Extract position and size - xfrm = ln.find(".//a:xfrm", ns) - if xfrm is not None: - pos = xfrm.find("a:off", ns) - size = xfrm.find("a:ext", ns) - if pos is not None and size is not None: - x, y = int(pos.attrib.get("x", 0)), int(pos.attrib.get("y", 0)) - width, height = int(size.attrib.get("cx", 0)), int(size.attrib.get("cy", 0)) - - if middle_x_min <= x <= middle_x_max and middle_y_min <= y <= middle_y_max: - annotation["position"] = {"x": x, "y": y, "width": width, "height": height} - annotations.append(annotation) - - # Ensure output directory exists - output_dir = os.path.dirname(output_json_path) - if output_dir: - os.makedirs(output_dir, exist_ok=True) - - with open(output_json_path, 'w') as f: - json.dump(annotations, f, indent=4) - - print(f"Annotations saved to {output_json_path}") - -# File paths -xml_file = "slide2Pelvis.xml" -output_json = "slide2_pelvis_annotations.json" - -# Run function with dynamic input -parse_slide_xml(xml_file, output_json) + + slide_data["annotations"].append(annotation) + + # Write JSON file for the slide + json_output_path = os.path.join(json_output, f"{slide_name}_annotations.json") + os.makedirs(json_output, exist_ok=True) + + with open(json_output_path, 'w') as json_file: + json.dump(slide_data, json_file, indent=4) + + print(f"[SUCCESS] JSON saved: {json_output_path}") + + +def process_pptx_folders(slides_folder, rels_folder, media_folder, output_folder, json_output): + """ + Processes all slides, extracts images, annotations, and writes JSON files. + """ + os.makedirs(output_folder, exist_ok=True) + os.makedirs(json_output, exist_ok=True) + + for slide_file in sorted(os.listdir(slides_folder)): + if slide_file.startswith("slide") and slide_file.endswith(".xml"): + slide_path = os.path.join(slides_folder, slide_file) + rels_path = os.path.join(rels_folder, slide_file + ".rels") + + if os.path.exists(rels_path): + extract_images_from_slide_xml(slide_path, rels_path, media_folder, output_folder, json_output) + else: + print(f"[WARNING] Missing relationship file: {rels_path}. Skipping {slide_file}.") + + +if __name__ == "__main__": + # Folder paths (replace with your paths) + slides_folder = "/Users/burhankhan/Desktop/ppt/slides" + rels_folder = "/Users/burhankhan/Desktop/ppt/slides/_rels" + media_folder = "/Users/burhankhan/Desktop/ppt/media" + output_folder = "/Users/burhankhan/Desktop/AutomatedScript" + json_output = "/Users/burhankhan/Desktop/AutomatedScript/json_output" + + # Run the process for all slides + process_pptx_folders(slides_folder, rels_folder, media_folder, output_folder, json_output) From 0ecebb48b30c755a002ed233761c7bbd384953e5 Mon Sep 17 00:00:00 2001 From: JoshBudzynski <157167337+JoshBudzynski@users.noreply.github.com> Date: Mon, 24 Feb 2025 13:18:48 -0600 Subject: [PATCH 074/143] Final Issue_85 --- data_extraction/Extract_Bone_Descriptions.py | 2 +- data_extraction/slide3_Descriptions.json | 9 --------- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/data_extraction/Extract_Bone_Descriptions.py b/data_extraction/Extract_Bone_Descriptions.py index 878c729d..42eb9dc2 100644 --- a/data_extraction/Extract_Bone_Descriptions.py +++ b/data_extraction/Extract_Bone_Descriptions.py @@ -23,7 +23,7 @@ def parse_slide_xml(xml_file, output_json_path): width, height = int(size.attrib.get("cx", 0)), int(size.attrib.get("cy", 0)) # Define approximate position range for the bottom-right box - if x > 7000000 and y > 3000000: # Adjust based on actual XML values + if x > 8011000 and y > 3000000: # Adjust based on actual XML values text_elements = sp.findall(".//a:t", ns) bullet_points = [t.text for t in text_elements if t.text] diff --git a/data_extraction/slide3_Descriptions.json b/data_extraction/slide3_Descriptions.json index e8329fd1..653c3d5f 100644 --- a/data_extraction/slide3_Descriptions.json +++ b/data_extraction/slide3_Descriptions.json @@ -26,15 +26,6 @@ "height": "1231106" } }, - { - "text": "Posterior Inferior Iliac Spine", - "position": { - "x": "7887969", - "y": "3816698", - "width": "766897", - "height": "461665" - } - }, { "text": "No labels", "position": { From 9ff801fabbd52404b40d741d024e165c0bfedc1b Mon Sep 17 00:00:00 2001 From: JoshBudzynski <157167337+JoshBudzynski@users.noreply.github.com> Date: Mon, 24 Feb 2025 14:22:19 -0600 Subject: [PATCH 075/143] Testing Push --- data_extraction/Extract_Bone_Descriptions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data_extraction/Extract_Bone_Descriptions.py b/data_extraction/Extract_Bone_Descriptions.py index 42eb9dc2..a3f78dba 100644 --- a/data_extraction/Extract_Bone_Descriptions.py +++ b/data_extraction/Extract_Bone_Descriptions.py @@ -22,8 +22,8 @@ def parse_slide_xml(xml_file, output_json_path): x, y = int(pos.attrib.get("x", 0)), int(pos.attrib.get("y", 0)) width, height = int(size.attrib.get("cx", 0)), int(size.attrib.get("cy", 0)) - # Define approximate position range for the bottom-right box - if x > 8011000 and y > 3000000: # Adjust based on actual XML values + # Range in which the descriptions are held + if x > 8011000 and y > 3000000: text_elements = sp.findall(".//a:t", ns) bullet_points = [t.text for t in text_elements if t.text] From 94a8825eba0c3854c6c243308c86f6fc7b63fdac Mon Sep 17 00:00:00 2001 From: JoshBudzynski <157167337+JoshBudzynski@users.noreply.github.com> Date: Mon, 24 Feb 2025 14:32:58 -0600 Subject: [PATCH 076/143] testing comment --- data_extraction/Extract_Bone_Descriptions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data_extraction/Extract_Bone_Descriptions.py b/data_extraction/Extract_Bone_Descriptions.py index a3f78dba..35dfeacc 100644 --- a/data_extraction/Extract_Bone_Descriptions.py +++ b/data_extraction/Extract_Bone_Descriptions.py @@ -21,7 +21,7 @@ def parse_slide_xml(xml_file, output_json_path): if pos is not None and size is not None: x, y = int(pos.attrib.get("x", 0)), int(pos.attrib.get("y", 0)) width, height = int(size.attrib.get("cx", 0)), int(size.attrib.get("cy", 0)) - + #HElLO # Range in which the descriptions are held if x > 8011000 and y > 3000000: text_elements = sp.findall(".//a:t", ns) From cbaf6dd8b3a10c308bf7fd1788a915441c3b8ff2 Mon Sep 17 00:00:00 2001 From: JoshBudzynski <157167337+JoshBudzynski@users.noreply.github.com> Date: Mon, 24 Feb 2025 14:40:36 -0600 Subject: [PATCH 077/143] 3rdtime --- data_extraction/Extract_Bone_Descriptions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data_extraction/Extract_Bone_Descriptions.py b/data_extraction/Extract_Bone_Descriptions.py index 35dfeacc..1c990c1f 100644 --- a/data_extraction/Extract_Bone_Descriptions.py +++ b/data_extraction/Extract_Bone_Descriptions.py @@ -17,7 +17,7 @@ def parse_slide_xml(xml_file, output_json_path): if xfrm is not None: pos = xfrm.find("a:off", ns) size = xfrm.find("a:ext", ns) - + #second time if pos is not None and size is not None: x, y = int(pos.attrib.get("x", 0)), int(pos.attrib.get("y", 0)) width, height = int(size.attrib.get("cx", 0)), int(size.attrib.get("cy", 0)) From 58af31fb29a7a4b4ffdc140f39aef369fdcbe85c Mon Sep 17 00:00:00 2001 From: tluke900 Date: Sun, 2 Mar 2025 16:11:35 -0600 Subject: [PATCH 078/143] Updated to Match new JSON Specifications --- data_extraction/XML boneset Reader.py | 14 ++++++++------ data_extraction/output.json | 6 ++++-- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/data_extraction/XML boneset Reader.py b/data_extraction/XML boneset Reader.py index 29fad28e..bd9fc79f 100644 --- a/data_extraction/XML boneset Reader.py +++ b/data_extraction/XML boneset Reader.py @@ -24,6 +24,7 @@ def extract_bones_from_xml(xml_path): } bonesets = {} # Dictionary to store bonesets + bonesetContent =[] total_boneset = None bolded_set = None boldedList=[] @@ -54,14 +55,14 @@ def extract_bones_from_xml(xml_path): bonesets[total_boneset].append(text.capitalize()) elif size == "900": if not bolded_set: - boldedList.append(text.capitalize()) + bonesetContent.append(text.capitalize()) else: bonesets[bolded_set].append(text.capitalize()) for i in boldedList: bonesets[bolded_set].append(i) - return bonesets + return bonesets, bonesetContent def generate_json_output(bonesets, output_json_path): """ @@ -69,10 +70,11 @@ def generate_json_output(bonesets, output_json_path): """ structured_data = [] - for boneset_name, bones in bonesets.items(): + for boneset_name, bonesetContent in bonesets.items(): structured_data.append({ - "boneset": boneset_name, - "bones": bones, + "name": boneset_name, + "id": boneset_name.lower().replace(" ", "_"), + "bones": bonesetContent }) # Save to JSON file @@ -92,7 +94,7 @@ def generate_json_output(bonesets, output_json_path): json_file_path = os.path.join(current_dir, "output.json") # Extract bonesets and their bones - bonesets = extract_bones_from_xml(xml_file_path) + bonesets, bonesetContent = extract_bones_from_xml(xml_file_path) # Generate and save JSON output generate_json_output(bonesets, json_file_path) diff --git a/data_extraction/output.json b/data_extraction/output.json index 38a3bfcc..004bf27c 100644 --- a/data_extraction/output.json +++ b/data_extraction/output.json @@ -1,6 +1,7 @@ [ { - "boneset": "Bony Pelvis", + "name": "Bony Pelvis", + "id": "bony_pelvis", "bones": [ "Ilium", "Ischium", @@ -8,7 +9,8 @@ ] }, { - "boneset": "Ischium", + "name": "Ischium", + "id": "ischium", "bones": [ "Ramus", "Ischial tuberosity", From 11d7006af121b86bec7960204b52f60753c98cb4 Mon Sep 17 00:00:00 2001 From: JoshBudzynski <157167337+JoshBudzynski@users.noreply.github.com> Date: Mon, 3 Mar 2025 09:31:45 -0600 Subject: [PATCH 079/143] First_Issue91 --- data_extraction/Extract_Bone_Descriptions.py | 33 +++++++------- data_extraction/slide3_Descriptions.json | 47 ++++---------------- 2 files changed, 27 insertions(+), 53 deletions(-) diff --git a/data_extraction/Extract_Bone_Descriptions.py b/data_extraction/Extract_Bone_Descriptions.py index 1c990c1f..a4367b0f 100644 --- a/data_extraction/Extract_Bone_Descriptions.py +++ b/data_extraction/Extract_Bone_Descriptions.py @@ -1,5 +1,6 @@ import xml.etree.ElementTree as ET import json +import os def parse_slide_xml(xml_file, output_json_path): tree = ET.parse(xml_file) @@ -10,38 +11,40 @@ def parse_slide_xml(xml_file, output_json_path): 'a': 'http://schemas.openxmlformats.org/drawingml/2006/main' } - annotations = [] + descriptions = [] + bone_name = "Unknown" for sp in root.findall(".//p:sp", ns): xfrm = sp.find(".//a:xfrm", ns) if xfrm is not None: pos = xfrm.find("a:off", ns) size = xfrm.find("a:ext", ns) - #second time + if pos is not None and size is not None: x, y = int(pos.attrib.get("x", 0)), int(pos.attrib.get("y", 0)) width, height = int(size.attrib.get("cx", 0)), int(size.attrib.get("cy", 0)) - #HElLO + # Range in which the descriptions are held if x > 8011000 and y > 3000000: text_elements = sp.findall(".//a:t", ns) bullet_points = [t.text for t in text_elements if t.text] - for text in bullet_points: - annotations.append({ - "text": text, - "position": { - "x": str(x), - "y": str(y), - "width": str(width), - "height": str(height) - } - }) + if bullet_points: + if bone_name == "Unknown": + bone_name = bullet_points[0] # Assign first extracted text as the bone name + bullet_points = bullet_points[1:] # Remove name from descriptions + descriptions.extend(bullet_points) + + bone_data = { + "name": bone_name, + "id": bone_name.lower().replace(" ", "_"), # Generate an ID from the name + "description": descriptions + } with open(output_json_path, 'w') as f: - json.dump(annotations, f, indent=4) + json.dump(bone_data, f, indent=4) - print(f"Annotations saved to {output_json_path}") + print(f"Descriptions saved to {output_json_path}") # Example usage xml_file = "/Users/joshbudzynski/Downloads/example_folder/ppt/slides/slide3.xml" diff --git a/data_extraction/slide3_Descriptions.json b/data_extraction/slide3_Descriptions.json index 653c3d5f..69c9e2f8 100644 --- a/data_extraction/slide3_Descriptions.json +++ b/data_extraction/slide3_Descriptions.json @@ -1,38 +1,9 @@ -[ - { - "text": "Ilium", - "position": { - "x": "8757818", - "y": "3505200", - "width": "1828800", - "height": "1231106" - } - }, - { - "text": "The Ilium forms the superior part of the bony pelvis.", - "position": { - "x": "8757818", - "y": "3505200", - "width": "1828800", - "height": "1231106" - } - }, - { - "text": "It articulates with the sacrum to form the sacroiliac joint.", - "position": { - "x": "8757818", - "y": "3505200", - "width": "1828800", - "height": "1231106" - } - }, - { - "text": "No labels", - "position": { - "x": "9189990", - "y": "6400801", - "width": "974820", - "height": "307777" - } - } -] \ No newline at end of file +{ + "name": "Ilium", + "id": "ilium", + "description": [ + "The Ilium forms the superior part of the bony pelvis.", + "It articulates with the sacrum to form the sacroiliac joint.", + "No labels" + ] +} \ No newline at end of file From bfedad07b2ae72196a3a0ff61bf61f7f90635772 Mon Sep 17 00:00:00 2001 From: Burhan Khan Date: Mon, 3 Mar 2025 13:26:25 -0600 Subject: [PATCH 080/143] Addresses Issue Nintey Three. Created a file for an API to fetch JSON data from the remote data branch. Working on getting it to extract data properly. --- data_extraction/api.py | 50 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 data_extraction/api.py diff --git a/data_extraction/api.py b/data_extraction/api.py new file mode 100644 index 00000000..99b03423 --- /dev/null +++ b/data_extraction/api.py @@ -0,0 +1,50 @@ +import requests +from fastapi import FastAPI, HTTPException + +app = FastAPI() + +# GitHub raw URLs (update if necessary) +GITHUB_REPO = "https://raw.githubusercontent.com/oss-slu/DigitalBonesBox/data/databones/" +BONESETS_JSON_URL = GITHUB_REPO + "json/bonesets.json" +BONES_JSON_URL = GITHUB_REPO + "json/bones.json" + +# Helper function to fetch JSON from GitHub +def fetch_json(url): + response = requests.get(url) + if response.status_code != 200: + raise HTTPException(status_code=500, detail=f"Failed to fetch data from {url}") + return response.json() + +@app.get("/") +def home(): + return {"message": "Welcome to the Boneset API (GitHub-Integrated)"} + +@app.get("/bonesets/{boneset_id}") +def get_boneset(boneset_id: str): + """Fetch boneset details from GitHub""" + bonesets = fetch_json(BONESETS_JSON_URL) + + if boneset_id not in bonesets: + raise HTTPException(status_code=404, detail="Boneset not found") + + return { + "id": bonesets[boneset_id]["id"], + "name": bonesets[boneset_id]["name"], + "description": bonesets[boneset_id]["description"], + "bones": bonesets[boneset_id]["bones"] + } + +@app.get("/bones/{bone_id}") +def get_bone(bone_id: str): + """Fetch bone details from GitHub""" + bones = fetch_json(BONES_JSON_URL) + + if bone_id not in bones: + raise HTTPException(status_code=404, detail="Bone not found") + + return { + "id": bones[bone_id]["id"], + "name": bones[bone_id]["name"], + "description": bones[bone_id]["description"], + "sub_bones": bones[bone_id]["sub_bones"] + } From 1c0ec9437449859c9de44898675b56c5470437d8 Mon Sep 17 00:00:00 2001 From: Burhan Khan Date: Sun, 9 Mar 2025 03:56:00 -0500 Subject: [PATCH 081/143] Changed directory to DataPelvis instead. --- data_extraction/api.py | 41 +++++++++++------------------------------ 1 file changed, 11 insertions(+), 30 deletions(-) diff --git a/data_extraction/api.py b/data_extraction/api.py index 99b03423..3a4070ab 100644 --- a/data_extraction/api.py +++ b/data_extraction/api.py @@ -3,10 +3,10 @@ app = FastAPI() -# GitHub raw URLs (update if necessary) -GITHUB_REPO = "https://raw.githubusercontent.com/oss-slu/DigitalBonesBox/data/databones/" -BONESETS_JSON_URL = GITHUB_REPO + "json/bonesets.json" -BONES_JSON_URL = GITHUB_REPO + "json/bones.json" +# GitHub raw URLs for JSON files +GITHUB_REPO = "https://raw.githubusercontent.com/oss-slu/DigitalBonesBox/data/DataPelvis/" +BONESET_JSON_URL = GITHUB_REPO + "boneset/bony_pelvis.json" +BONES_DIR_URL = GITHUB_REPO + "bones/" # Directory for individual bone JSON files # Helper function to fetch JSON from GitHub def fetch_json(url): @@ -19,32 +19,13 @@ def fetch_json(url): def home(): return {"message": "Welcome to the Boneset API (GitHub-Integrated)"} -@app.get("/bonesets/{boneset_id}") -def get_boneset(boneset_id: str): - """Fetch boneset details from GitHub""" - bonesets = fetch_json(BONESETS_JSON_URL) - - if boneset_id not in bonesets: - raise HTTPException(status_code=404, detail="Boneset not found") - - return { - "id": bonesets[boneset_id]["id"], - "name": bonesets[boneset_id]["name"], - "description": bonesets[boneset_id]["description"], - "bones": bonesets[boneset_id]["bones"] - } +@app.get("/boneset") +def get_boneset(): + """Fetch bony pelvis details from GitHub""" + return fetch_json(BONESET_JSON_URL) @app.get("/bones/{bone_id}") def get_bone(bone_id: str): - """Fetch bone details from GitHub""" - bones = fetch_json(BONES_JSON_URL) - - if bone_id not in bones: - raise HTTPException(status_code=404, detail="Bone not found") - - return { - "id": bones[bone_id]["id"], - "name": bones[bone_id]["name"], - "description": bones[bone_id]["description"], - "sub_bones": bones[bone_id]["sub_bones"] - } + """Fetch a specific bone's details from GitHub""" + bone_json_url = BONES_DIR_URL + f"{bone_id}.json" + return fetch_json(bone_json_url) From b7c45aa16a8c9c1fcafecad0347511a6cfe88f31 Mon Sep 17 00:00:00 2001 From: tluke900 Date: Sun, 9 Mar 2025 12:32:26 -0500 Subject: [PATCH 082/143] Updated name of boneset reader --- data_extraction/{XML boneset Reader.py => xml_boneset_Reader.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename data_extraction/{XML boneset Reader.py => xml_boneset_Reader.py} (100%) diff --git a/data_extraction/XML boneset Reader.py b/data_extraction/xml_boneset_Reader.py similarity index 100% rename from data_extraction/XML boneset Reader.py rename to data_extraction/xml_boneset_Reader.py From 586466b3de1dfe9eb58812652be412ab00276281 Mon Sep 17 00:00:00 2001 From: tluke900 Date: Sun, 9 Mar 2025 12:37:55 -0500 Subject: [PATCH 083/143] Updated name --- data_extraction/{XML boneset Reader.py => xml_boneset_reader.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename data_extraction/{XML boneset Reader.py => xml_boneset_reader.py} (100%) diff --git a/data_extraction/XML boneset Reader.py b/data_extraction/xml_boneset_reader.py similarity index 100% rename from data_extraction/XML boneset Reader.py rename to data_extraction/xml_boneset_reader.py From 5ef5e1be34bc2ec15a4b9ab96d155c55765cbdfc Mon Sep 17 00:00:00 2001 From: Burhan Khan Date: Sun, 9 Mar 2025 13:50:40 -0500 Subject: [PATCH 084/143] Was having trouble running the API but found out that you can't run it on a virtual environment because it has trouble importing requests --- data_extraction/__pycache__/api.cpython-310.pyc | Bin 0 -> 1301 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 data_extraction/__pycache__/api.cpython-310.pyc diff --git a/data_extraction/__pycache__/api.cpython-310.pyc b/data_extraction/__pycache__/api.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..74bb369b4903378537e4357a48d7cb0c52f7d6fa GIT binary patch literal 1301 zcmaJ=O>f&q5Zzr;5+hlz;(Qh9g_oig3X2U;6i5&RiEPg zyvIM67sSXz*s1`-38xA1(X)h3na`${Z&4!HG4X9~`4v&&_ATO9!B@c7c$L>qi0=sd z>C}bh8%l>dS3#6=;a9oPaJgM69V!0Eh0?0D#zM;qWRn({(niqm zo|Vf4Z>cPuDe;EL2AqC;;_bnNlsC-fB%JNT_Ev@3*J_i0%TVXm^!V~CF)Sw#Cp_=zTV8tJH6fD@y_NT z*xP>cO>7OIEw})3^IwRTM7Tw34$*ss7lN1{m9>kf|Hu$YH{--4nxNb`J!QAZ5!E0# z(*Y}fnRgJnDicvWiX#{E{iMFtM^Q1t2Msk%C&6le+q8j Date: Mon, 10 Mar 2025 15:28:10 -0500 Subject: [PATCH 085/143] exclude 'labels' --- data_extraction/Extract_Bone_Descriptions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data_extraction/Extract_Bone_Descriptions.py b/data_extraction/Extract_Bone_Descriptions.py index a4367b0f..cc0c1eed 100644 --- a/data_extraction/Extract_Bone_Descriptions.py +++ b/data_extraction/Extract_Bone_Descriptions.py @@ -27,7 +27,7 @@ def parse_slide_xml(xml_file, output_json_path): # Range in which the descriptions are held if x > 8011000 and y > 3000000: text_elements = sp.findall(".//a:t", ns) - bullet_points = [t.text for t in text_elements if t.text] + bullet_points = [t.text for t in text_elements if t.text and t.text != 'No Labels'] if bullet_points: if bone_name == "Unknown": From 40df59f689af03c7b673b0133584774b9947b616 Mon Sep 17 00:00:00 2001 From: Santosh Iragavarapu <157086110+jacksayshi@users.noreply.github.com> Date: Sun, 23 Mar 2025 15:41:45 -0500 Subject: [PATCH 086/143] Rename xml_boneset_Reader.py to xml_boneset_Reader2.py --- data_extraction/{xml_boneset_Reader.py => xml_boneset_Reader2.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename data_extraction/{xml_boneset_Reader.py => xml_boneset_Reader2.py} (100%) diff --git a/data_extraction/xml_boneset_Reader.py b/data_extraction/xml_boneset_Reader2.py similarity index 100% rename from data_extraction/xml_boneset_Reader.py rename to data_extraction/xml_boneset_Reader2.py From d06d8a9a9b9eff847a7c548f37c19562c6d4a334 Mon Sep 17 00:00:00 2001 From: Santosh Iragavarapu <157086110+jacksayshi@users.noreply.github.com> Date: Sun, 23 Mar 2025 15:47:56 -0500 Subject: [PATCH 087/143] Delete data_extraction/xml_boneset_Reader2.py --- data_extraction/xml_boneset_Reader2.py | 98 -------------------------- 1 file changed, 98 deletions(-) delete mode 100644 data_extraction/xml_boneset_Reader2.py diff --git a/data_extraction/xml_boneset_Reader2.py b/data_extraction/xml_boneset_Reader2.py deleted file mode 100644 index 29fad28e..00000000 --- a/data_extraction/xml_boneset_Reader2.py +++ /dev/null @@ -1,98 +0,0 @@ -import os -import xml.etree.ElementTree as ET -import json - -def extract_bones_from_xml(xml_path): - """ - Parses the XML file and extracts bonesets and their associated bones. - Bonesets are determined by hyperlink text with size 1200. - Bones with size 900 are assigned to the most recent bolded boneset. - """ - try: - print(f"Parsing XML: {xml_path}") - tree = ET.parse(xml_path) - root = tree.getroot() - except ET.ParseError as e: - print(f"Error parsing {xml_path}: {e}") - return {} - - # Namespace handling for XML - ns = { - 'p': 'http://schemas.openxmlformats.org/presentationml/2006/main', - 'a': 'http://schemas.openxmlformats.org/drawingml/2006/main', - 'r': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships' - } - - bonesets = {} # Dictionary to store bonesets - total_boneset = None - bolded_set = None - boldedList=[] - - # Extract bonesets based on hyperlinks and size attributes - for sp_element in root.findall(".//p:sp", ns): - for r_element in sp_element.findall(".//p:txBody//a:r", ns): - rPr_element = r_element.find("a:rPr", ns) - text_element = r_element.find("a:t", ns) - - if rPr_element is not None and text_element is not None: - text = text_element.text.strip() - size = rPr_element.get("sz") - is_bold = rPr_element.get("b") == "1" - has_hyperlink = rPr_element.find("a:hlinkClick", ns) is not None - - if has_hyperlink: - if size == "1200": - if is_bold: - bolded_set = text - bonesets[bolded_set] = list() - - if total_boneset is None: - total_boneset = text - bonesets[total_boneset] = list() - continue - # These are their own bonesets - bonesets[total_boneset].append(text.capitalize()) - elif size == "900": - if not bolded_set: - boldedList.append(text.capitalize()) - else: - bonesets[bolded_set].append(text.capitalize()) - for i in boldedList: - bonesets[bolded_set].append(i) - - - return bonesets - -def generate_json_output(bonesets, output_json_path): - """ - Converts bonesets dictionary into a structured JSON format and writes it to a file. - """ - structured_data = [] - - for boneset_name, bones in bonesets.items(): - structured_data.append({ - "boneset": boneset_name, - "bones": bones, - }) - - # Save to JSON file - try: - with open(output_json_path, 'w') as json_file: - json.dump(structured_data, json_file, indent=4) - print(f"JSON file saved: {output_json_path}") - except IOError as e: - print(f"Error writing to {output_json_path}: {e}") - -if __name__ == "__main__": - # Get the directory of the current script - current_dir = os.path.dirname(os.path.abspath(__file__)) - - # Define the XML and JSON file paths relative to the script's directory - xml_file_path = os.path.join(current_dir, "slide9Pelvis.xml") - json_file_path = os.path.join(current_dir, "output.json") - - # Extract bonesets and their bones - bonesets = extract_bones_from_xml(xml_file_path) - - # Generate and save JSON output - generate_json_output(bonesets, json_file_path) From 6d4308c2d49dc29966468c8760edf0fd01beb284 Mon Sep 17 00:00:00 2001 From: JoshBudzynski <157167337+JoshBudzynski@users.noreply.github.com> Date: Mon, 24 Mar 2025 12:01:46 -0500 Subject: [PATCH 088/143] Edited Annotation Extract --- data_extraction/extract_ppt_annotations.py | 64 ++++++++++++++++----- data_extraction/slide3_Descriptions.json | 65 ++++++++++++++++++++-- 2 files changed, 110 insertions(+), 19 deletions(-) diff --git a/data_extraction/extract_ppt_annotations.py b/data_extraction/extract_ppt_annotations.py index 487bef49..07a8016a 100644 --- a/data_extraction/extract_ppt_annotations.py +++ b/data_extraction/extract_ppt_annotations.py @@ -2,7 +2,40 @@ import xml.etree.ElementTree as ET import json -def extract_images_from_slide_xml(slide_xml_path, rels_xml_path, media_folder, output_folder, json_output): + +def load_bone_data(json_directory): + """ + Loads known bonesets, bones, and sub-bones from JSON files into dictionaries. + """ + categories = ["bonesets", "bones", "subbones"] + bone_data = {category: set() for category in categories} # Store names for fast lookup + + for category in categories: + json_path = os.path.join(json_directory, f"{category}.json") + if os.path.exists(json_path): + with open(json_path, 'r') as file: + try: + data = json.load(file) + if isinstance(data, list): + bone_data[category].update({entry.lower().replace(" ", "_") for entry in data}) + except json.JSONDecodeError as e: + print(f"[ERROR] Could not load {category}.json: {e}") + + return bone_data + + +def generate_annotation_link(text, bone_data): + """Generate a hyperlink for an annotation based on its category.""" + text_key = text.lower().replace(" ", "_") # Normalize text + + for category, names in bone_data.items(): + if text_key in names: + return f"/data/json/{category}/{text_key}.json" + + return None # No link if not found + + +def extract_images_from_slide_xml(slide_xml_path, rels_xml_path, media_folder, output_folder, json_output, bone_data): """ Extract images from a slide XML, rename them, and write image details to a JSON file. """ @@ -100,13 +133,14 @@ def extract_images_from_slide_xml(slide_xml_path, rels_xml_path, media_folder, o annotation["text"] = text annotation["position"] = {"x": x, "y": y, "width": width, "height": height} - # Extract fill color if present - fill_color = sp.find(".//a:solidFill/a:srgbClr", ns_p) - if fill_color is not None: - annotation["color"] = fill_color.attrib.get("val") + # Add the 'link' field dynamically based on text + if text: + json_filename = text.lower().replace(" ", "_") + ".json" + annotation["link"] = f"/data/json/bonesets/{json_filename}" slide_data["annotations"].append(annotation) + # Write JSON file for the slide json_output_path = os.path.join(json_output, f"{slide_name}_annotations.json") os.makedirs(json_output, exist_ok=True) @@ -117,31 +151,35 @@ def extract_images_from_slide_xml(slide_xml_path, rels_xml_path, media_folder, o print(f"[SUCCESS] JSON saved: {json_output_path}") -def process_pptx_folders(slides_folder, rels_folder, media_folder, output_folder, json_output): +def process_pptx_folders(slides_folder, rels_folder, media_folder, output_folder, json_output, json_directory): """ Processes all slides, extracts images, annotations, and writes JSON files. """ os.makedirs(output_folder, exist_ok=True) os.makedirs(json_output, exist_ok=True) + # Load bone data dynamically + bone_data = load_bone_data(json_directory) + for slide_file in sorted(os.listdir(slides_folder)): if slide_file.startswith("slide") and slide_file.endswith(".xml"): slide_path = os.path.join(slides_folder, slide_file) rels_path = os.path.join(rels_folder, slide_file + ".rels") if os.path.exists(rels_path): - extract_images_from_slide_xml(slide_path, rels_path, media_folder, output_folder, json_output) + extract_images_from_slide_xml(slide_path, rels_path, media_folder, output_folder, json_output, bone_data) else: print(f"[WARNING] Missing relationship file: {rels_path}. Skipping {slide_file}.") if __name__ == "__main__": # Folder paths (replace with your paths) - slides_folder = "/Users/burhankhan/Desktop/ppt/slides" - rels_folder = "/Users/burhankhan/Desktop/ppt/slides/_rels" - media_folder = "/Users/burhankhan/Desktop/ppt/media" - output_folder = "/Users/burhankhan/Desktop/AutomatedScript" - json_output = "/Users/burhankhan/Desktop/AutomatedScript/json_output" + slides_folder = "/Users/joshbudzynski/Downloads/example_folder/ppt/slides" + rels_folder = "/Users/joshbudzynski/Downloads/example_folder/ppt/slides/_rels" + media_folder = "/Users/joshbudzynski/Downloads/example_folder/ppt/media" + output_folder = "/Users/joshbudzynski/Downloads/example_folder/ppt/AutomatedScript" + json_output = "/Users/joshbudzynski/Downloads/example_folder/ppt/json_output" + json_directory = "/Users/joshbudzynski/Downloads/example_folder/ppt/data/json" # Run the process for all slides - process_pptx_folders(slides_folder, rels_folder, media_folder, output_folder, json_output) + process_pptx_folders(slides_folder, rels_folder, media_folder, output_folder, json_output, json_directory) diff --git a/data_extraction/slide3_Descriptions.json b/data_extraction/slide3_Descriptions.json index 69c9e2f8..5a64c27b 100644 --- a/data_extraction/slide3_Descriptions.json +++ b/data_extraction/slide3_Descriptions.json @@ -1,9 +1,62 @@ { - "name": "Ilium", - "id": "ilium", - "description": [ - "The Ilium forms the superior part of the bony pelvis.", - "It articulates with the sacrum to form the sacroiliac joint.", - "No labels" + "slide": "slide3.xml", + "images": [ + { + "rId": "rId11", + "image_name": "image1.png" + }, + { + "rId": "rId12", + "image_name": "image2.png" + } + ], + "annotations": [ + {}, + { + "text": "Bony Pelvis" + }, + { + "text": "Home" + }, + {}, + {}, + {}, + { + "text": "Right Pelvis(medial aspect)" + }, + { + "text": "Right Pelvis (lateral aspect)" + }, + { + "text": "Iliac CrestAnterior Iliac spinesPosterior Iliac spinesAuricular surface" + }, + { + "text": "IliumThe Ilium forms the superior part of the bony pelvis.It articulates with the sacrum to form the sacroiliac joint." + }, + { + "text": "Bony Pelvis Ilium Ischium Pubis ", + "color": "000000" + }, + { + "text": "Iliac Crest" + }, + { + "text": "Anterior Superior Iliac Spine" + }, + { + "text": "Anterior Inferior Iliac Spine" + }, + { + "text": "Posterior Inferior Iliac Spine" + }, + { + "text": "Posterior Superior Iliac Spine" + }, + { + "text": "Auricular surface" + }, + { + "text": "No labels" + } ] } \ No newline at end of file From a08753cbd579d5ed540b46bd64b5fae3b6d2d322 Mon Sep 17 00:00:00 2001 From: Burhan Khan Date: Mon, 24 Mar 2025 15:06:10 -0500 Subject: [PATCH 089/143] Changed api file from Python file to node.js file where functionality remains the same. Working on combining all components into one single JSON file output. --- boneset-api/package-lock.json | 898 ++++++++++++++++++++++++++++++++++ boneset-api/package.json | 18 + boneset-api/server.js | 57 +++ 3 files changed, 973 insertions(+) create mode 100644 boneset-api/package-lock.json create mode 100644 boneset-api/package.json create mode 100644 boneset-api/server.js diff --git a/boneset-api/package-lock.json b/boneset-api/package-lock.json new file mode 100644 index 00000000..90170203 --- /dev/null +++ b/boneset-api/package-lock.json @@ -0,0 +1,898 @@ +{ + "name": "boneset-api", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "boneset-api", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "axios": "^1.8.4", + "cors": "^2.8.5", + "dotenv": "^16.4.7", + "express": "^4.21.2" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/axios": { + "version": "1.8.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz", + "integrity": "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/dotenv": { + "version": "16.4.7", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", + "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.12", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", + "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==" + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, + "node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "engines": { + "node": ">= 0.8" + } + } + } +} diff --git a/boneset-api/package.json b/boneset-api/package.json new file mode 100644 index 00000000..050c74ba --- /dev/null +++ b/boneset-api/package.json @@ -0,0 +1,18 @@ +{ + "name": "boneset-api", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "axios": "^1.8.4", + "cors": "^2.8.5", + "dotenv": "^16.4.7", + "express": "^4.21.2" + } +} diff --git a/boneset-api/server.js b/boneset-api/server.js new file mode 100644 index 00000000..24d76eb6 --- /dev/null +++ b/boneset-api/server.js @@ -0,0 +1,57 @@ +const express = require("express"); +const axios = require("axios"); +const cors = require("cors"); + +const app = express(); +const PORT = process.env.PORT || 8000; + +// Enable CORS to allow requests from frontend applications +app.use(cors()); + +// GitHub raw URLs for JSON files +const GITHUB_REPO = "https://raw.githubusercontent.com/oss-slu/DigitalBonesBox/data/DataPelvis/"; +const BONESET_JSON_URL = `${GITHUB_REPO}boneset/bony_pelvis.json`; +const BONES_DIR_URL = `${GITHUB_REPO}bones/`; // Directory for individual bone JSON files + +// Helper function to fetch JSON from GitHub +async function fetchJSON(url) { + try { + const response = await axios.get(url); + return response.data; + } catch (error) { + throw new Error(`Failed to fetch data from ${url}`); + } +} + +// Home route +app.get("/", (req, res) => { + res.json({ message: "Welcome to the Boneset API (GitHub-Integrated)" }); +}); + +// Fetch bony pelvis (boneset) details +app.get("/boneset", async (req, res) => { + try { + const bonesetData = await fetchJSON(BONESET_JSON_URL); + res.json(bonesetData); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// Fetch a specific bone's details +app.get("/bones/:bone_id", async (req, res) => { + const boneId = req.params.bone_id; + const boneJsonUrl = `${BONES_DIR_URL}${boneId}.json`; + + try { + const boneData = await fetchJSON(boneJsonUrl); + res.json(boneData); + } catch (error) { + res.status(404).json({ error: `Bone '${boneId}' not found` }); + } +}); + +// Start server +app.listen(PORT, () => { + console.log(`🚀 Server running on http://127.0.0.1:${PORT}`); +}); From 86b62e71a258845fc403241ed39fbdfbbd5769ee Mon Sep 17 00:00:00 2001 From: tluke900 Date: Mon, 24 Mar 2025 15:30:18 -0500 Subject: [PATCH 090/143] Created new Drop downs and oriented them correctly --- templates/boneset.html | 114 +++++++++++++++++++++++++++-------------- 1 file changed, 75 insertions(+), 39 deletions(-) diff --git a/templates/boneset.html b/templates/boneset.html index 60d8375e..9da9a2ce 100644 --- a/templates/boneset.html +++ b/templates/boneset.html @@ -8,7 +8,6 @@ -
                    @@ -21,12 +20,6 @@ Login Sign Up
                    - - - - - -
                    @@ -35,34 +28,42 @@
                    - -
                    -

                    Bone Set Viewer

                    - - - -
                      - - -
                      - - -
                      - - -
                      -

                      Bonesets

                      -
                        -
                        - - -
                        - + + \ No newline at end of file From 01bc14ecfb15b35b2514883dd82e190ce61f6bcb Mon Sep 17 00:00:00 2001 From: tluke900 Date: Mon, 7 Apr 2025 15:53:07 -0500 Subject: [PATCH 094/143] Update boneset.html --- templates/boneset.html | 232 +++++++++++++++-------------------------- 1 file changed, 86 insertions(+), 146 deletions(-) diff --git a/templates/boneset.html b/templates/boneset.html index 9da9a2ce..68f63927 100644 --- a/templates/boneset.html +++ b/templates/boneset.html @@ -53,9 +53,9 @@

                        Bone Set Viewer

                        -
                        -

                        Bonesets

                        -
                          +
                          +

                          Description

                          +
                            + From 906e9edf0520d515d965be54a91f0d41f48c341b Mon Sep 17 00:00:00 2001 From: JoshBudzynski <157167337+JoshBudzynski@users.noreply.github.com> Date: Sun, 13 Apr 2025 22:04:26 -0500 Subject: [PATCH 095/143] Issue 106 --- templates/boneset.html | 35 +++++++++++++++++++++++++++++++++-- templates/style.css | 7 +++++++ 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/templates/boneset.html b/templates/boneset.html index 9da9a2ce..7f001c56 100644 --- a/templates/boneset.html +++ b/templates/boneset.html @@ -61,6 +61,10 @@

                            Bonesets

                            @@ -77,8 +81,8 @@

                            Bonesets

                            return response.json(); } - // Reusable function to render a boneset - function renderBoneSet(boneSet) { + // Reusable function to render a boneset + function renderBoneSet(boneSet) { const boneSetList = document.getElementById('bone-set-list'); boneSetList.innerHTML = ''; @@ -93,6 +97,10 @@

                            Bonesets

                            subList.appendChild(subLi); }); boneSetList.appendChild(subList); + + // Display boneset image + const imageContainer = document.getElementById('boneset-image-container'); + const imageElement = document.getElementById('boneset-image'); } // Populate dropdown menu with boneset names @@ -108,6 +116,29 @@

                            Bonesets

                            }); } + // Load the image and annotations for the selected bone + async function loadImageAndAnnotations(boneId) { + const imageElement = document.getElementById('bone-image'); + const annotationsOverlay = document.getElementById('annotations-overlay'); + + // Load the image + imageElement.src = `https://raw.githubusercontent.com/oss-slu/DigitalBonesBox/data/databones/images/${boneId}.png`; + + // Fetch annotations and render them + const annotationsData = await fetchJSON(`annotations/${boneId}.json`); + annotationsOverlay.innerHTML = ''; // Clear previous annotations + + annotationsData.forEach(annotation => { + const annotationElement = document.createElement('div'); + annotationElement.classList.add('annotation'); + annotationElement.textContent = annotation.text; + annotationElement.style.top = `${annotation.y}%`; + annotationElement.style.left = `${annotation.x}%`; + + annotationsOverlay.appendChild(annotationElement); + }); + } + // Enable Bone dropdown based on Boneset selection document.getElementById('boneset-select').addEventListener('change', () => { const bonesetSelect = document.getElementById('boneset-select'); diff --git a/templates/style.css b/templates/style.css index 8e8b2059..437b5e63 100644 --- a/templates/style.css +++ b/templates/style.css @@ -169,4 +169,11 @@ ul li { } +.annotation-box { + position: absolute; + border: 2px solid red; + background-color: rgba(255, 0, 0, 0.2); + cursor: pointer; + pointer-events: auto; +} From f91a5b38da6fa88a00fd967521fb4ab565a8ec6e Mon Sep 17 00:00:00 2001 From: Burhan Khan Date: Mon, 14 Apr 2025 10:51:00 -0500 Subject: [PATCH 096/143] Fixed the format for the webpage but having troulbe with the API fetching the data and displaying it. --- package-lock.json | 80 ++++++++++++++--- package.json | 4 +- server.js | 4 +- templates/boneset.html | 189 ++++++++++++++++++++++------------------- 4 files changed, 174 insertions(+), 103 deletions(-) diff --git a/package-lock.json b/package-lock.json index fe19ddac..f11b0636 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,8 +9,10 @@ "version": "1.0.0", "license": "ISC", "dependencies": { + "axios": "^1.8.4", "body-parser": "^1.20.3", - "express": "^4.21.1", + "cors": "^2.8.5", + "express": "^4.21.2", "simple-git": "^3.27.0" }, "devDependencies": { @@ -1316,9 +1318,18 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true, "license": "MIT" }, + "node_modules/axios": { + "version": "1.8.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz", + "integrity": "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/babel-jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", @@ -1713,7 +1724,6 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, "license": "MIT", "dependencies": { "delayed-stream": "~1.0.0" @@ -1768,6 +1778,18 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/create-jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", @@ -1907,7 +1929,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.4.0" @@ -2186,9 +2207,9 @@ } }, "node_modules/express": { - "version": "4.21.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", - "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", @@ -2209,7 +2230,7 @@ "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.10", + "path-to-regexp": "0.1.12", "proxy-addr": "~2.0.7", "qs": "6.13.0", "range-parser": "~1.2.1", @@ -2224,6 +2245,10 @@ }, "engines": { "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/fast-json-stable-stringify": { @@ -2287,11 +2312,29 @@ "node": ">=8" } }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/form-data": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", - "dev": true, "license": "MIT", "dependencies": { "asynckit": "^0.4.0", @@ -3867,6 +3910,14 @@ "dev": true, "license": "MIT" }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/object-inspect": { "version": "1.13.2", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", @@ -4048,9 +4099,9 @@ "license": "MIT" }, "node_modules/path-to-regexp": { - "version": "0.1.10", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", - "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==" + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==" }, "node_modules/picocolors": { "version": "1.1.1", @@ -4149,6 +4200,11 @@ "node": ">= 0.10" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "node_modules/psl": { "version": "1.13.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.13.0.tgz", diff --git a/package.json b/package.json index 09523754..f9063ff2 100644 --- a/package.json +++ b/package.json @@ -14,8 +14,10 @@ "author": "", "license": "ISC", "dependencies": { + "axios": "^1.8.4", "body-parser": "^1.20.3", - "express": "^4.21.1", + "cors": "^2.8.5", + "express": "^4.21.2", "simple-git": "^3.27.0" }, "devDependencies": { diff --git a/server.js b/server.js index fb879ba4..379fa4a6 100644 --- a/server.js +++ b/server.js @@ -1,11 +1,11 @@ const express = require('express'); const bodyParser = require('body-parser'); -const simpleGit = require('simple-git'); +//const simpleGit = require('simple-git'); const fs = require('fs'); const path = require('path'); const app = express(); -const git = simpleGit('C:/DigitalBonesBox'); // Local repo path +//const git = simpleGit('C:/DigitalBonesBox'); // Local repo path app.use(bodyParser.urlencoded({ extended: false })); app.use(bodyParser.json()); diff --git a/templates/boneset.html b/templates/boneset.html index 01b89a46..f610d136 100644 --- a/templates/boneset.html +++ b/templates/boneset.html @@ -2,10 +2,10 @@ - - + + Bone Set Viewer - + @@ -17,7 +17,6 @@ Tutor Study Help -
                            Login Sign Up @@ -26,129 +25,143 @@ -

                            Bone Set Viewer

                            - + + +
                              + +
                              - - - - - - - - + +
                              + + + +
                              - -
                              -

                              Selected Information

                              -

                              No selection made yet.

                              + +
                              +

                              Bonesets

                              +
                                +
                                + +
                                From 99d86b2447f898372e7f33c832ad0e0f955f383a Mon Sep 17 00:00:00 2001 From: Burhan Khan Date: Mon, 14 Apr 2025 12:08:07 -0500 Subject: [PATCH 097/143] Fixed the format for the webpage but having troulbe with the API fetching the data and displaying it. --- server.js | 4 +- templates/boneset.html | 187 ++++++++++++++++++++++------------------- 2 files changed, 102 insertions(+), 89 deletions(-) diff --git a/server.js b/server.js index fb879ba4..379fa4a6 100644 --- a/server.js +++ b/server.js @@ -1,11 +1,11 @@ const express = require('express'); const bodyParser = require('body-parser'); -const simpleGit = require('simple-git'); +//const simpleGit = require('simple-git'); const fs = require('fs'); const path = require('path'); const app = express(); -const git = simpleGit('C:/DigitalBonesBox'); // Local repo path +//const git = simpleGit('C:/DigitalBonesBox'); // Local repo path app.use(bodyParser.urlencoded({ extended: false })); app.use(bodyParser.json()); diff --git a/templates/boneset.html b/templates/boneset.html index 01b89a46..55c2511d 100644 --- a/templates/boneset.html +++ b/templates/boneset.html @@ -2,10 +2,10 @@ - - + + Bone Set Viewer - + @@ -17,7 +17,6 @@ Tutor Study Help -
                                Login Sign Up @@ -26,129 +25,143 @@ -

                                Bone Set Viewer

                                - + + +
                                  + +
                                  - - - - - - - - + +
                                  + + + +
                                  - -
                                  -

                                  Selected Information

                                  -

                                  No selection made yet.

                                  + +
                                  +

                                  Bonesets

                                  +
                                    +
                                    + +
                                    From 358e4df11ef65af6f21684cfdee8273ca836643a Mon Sep 17 00:00:00 2001 From: Burhan Khan Date: Mon, 14 Apr 2025 12:12:42 -0500 Subject: [PATCH 098/143] Fixed the format for the webpage but having troulbe with the API fetching the data and displaying it. --- templates/boneset.html | 4 ---- 1 file changed, 4 deletions(-) diff --git a/templates/boneset.html b/templates/boneset.html index ec13deb7..55c2511d 100644 --- a/templates/boneset.html +++ b/templates/boneset.html @@ -65,11 +65,7 @@

                                    Bonesets

                                    - -
                                    -
                                    -

                                    Bone Set Viewer

                                    - - - -
                                      - - -
                                      - - -
                                      - - - -
                                      -
                                      - - -
                                      -

                                      Bonesets

                                      -
                                        -
                                        - - -
                                        - - + + - document.getElementById('next-button').addEventListener('click', () => { - if (currentSetIndex < boneSets.length - 1) { - currentSetIndex++; - renderBoneSet(boneSets[currentSetIndex]); - document.getElementById('prev-button').disabled = false; - if (currentSetIndex === boneSets.length - 1) { - document.getElementById('next-button').disabled = true; - } - } - }); - diff --git a/templates/boneset.js b/templates/boneset.js new file mode 100644 index 00000000..9e4c2b01 --- /dev/null +++ b/templates/boneset.js @@ -0,0 +1,49 @@ +import { fetchJSON } from './fetch.js'; +import { loadImageAndAnnotations } from './imageHandling.js'; // Import the new image handling functions + +const BONESET_JSON_PATH = 'boneset.json'; // Path to the boneset JSON file +let boneSets = []; // This will hold the fetched bone set data + +// Function to render the bone set data +function renderBoneSet(boneSet) { + const boneSetList = document.getElementById('bone-set-list'); + boneSetList.innerHTML = ''; + + const li = document.createElement('li'); + li.textContent = boneSet.name; + boneSetList.appendChild(li); + + const subList = document.createElement('ul'); + boneSet.bones.forEach(bone => { + const subLi = document.createElement('li'); + subLi.textContent = `ID: ${bone}`; + subList.appendChild(subLi); + }); + boneSetList.appendChild(subList); + + // Display boneset image and annotations + loadImageAndAnnotations(boneSet.bones[0]); // Load the first bone image and annotations by default +} + +// Event listeners for the "Previous" and "Next" buttons +document.getElementById('prev-button').addEventListener('click', () => { + if (currentSetIndex > 0) { + currentSetIndex--; + renderBoneSet(boneSets[currentSetIndex]); + document.getElementById('next-button').disabled = false; + if (currentSetIndex === 0) { + document.getElementById('prev-button').disabled = true; + } + } +}); + +document.getElementById('next-button').addEventListener('click', () => { + if (currentSetIndex < boneSets.length - 1) { + currentSetIndex++; + renderBoneSet(boneSets[currentSetIndex]); + document.getElementById('prev-button').disabled = false; + if (currentSetIndex === boneSets.length - 1) { + document.getElementById('next-button').disabled = true; + } + } +}); diff --git a/templates/fetch.js b/templates/fetch.js new file mode 100644 index 00000000..be1f4ee1 --- /dev/null +++ b/templates/fetch.js @@ -0,0 +1,17 @@ +/** + * Fetch JSON data from a given path. + * @param {string} path - The URL or path to the JSON file. + * @returns {Promise} - A promise that resolves to the JSON data. + */ + export async function fetchJSON(path) { + try { + const response = await fetch(path); + if (!response.ok) { + throw new Error(`Failed to fetch data from ${path}. Status: ${response.status}`); + } + return await response.json(); + } catch (error) { + console.error("Error fetching JSON:", error); + alert(`Error fetching data: ${error.message}`); + } +} diff --git a/templates/image_handeling.js b/templates/image_handeling.js new file mode 100644 index 00000000..24df8fd0 --- /dev/null +++ b/templates/image_handeling.js @@ -0,0 +1,44 @@ +/** + * Load the image for a selected bone. + * @param {string} boneId - The ID or name of the bone (used for the image file). + */ + export function loadBoneImage(boneId) { + const imageElement = document.getElementById('bone-image'); + imageElement.src = `https://raw.githubusercontent.com/oss-slu/DigitalBonesBox/data/DataPelvis/images/${boneId}.png`; +} + +/** + * Load the annotations for the selected bone and display them. + * @param {string} boneId - The ID or name of the bone (used for fetching annotation file). + */ +export async function loadAnnotations(boneId) { + const annotationsOverlay = document.getElementById('annotations-overlay'); + annotationsOverlay.innerHTML = ''; // Clear previous annotations + + try { + const annotationsData = await fetch(`https://raw.githubusercontent.com/oss-slu/DigitalBonesBox/data/databones/annotations/${boneId}.json`); + const annotations = await annotationsData.json(); + + annotations.forEach(annotation => { + const annotationElement = document.createElement('div'); + annotationElement.classList.add('annotation'); + annotationElement.textContent = annotation.text; + annotationElement.style.top = `${annotation.y}%`; + annotationElement.style.left = `${annotation.x}%`; + + annotationsOverlay.appendChild(annotationElement); + }); + } catch (error) { + console.error("Error loading annotations:", error); + alert(`Error loading annotations for ${boneId}: ${error.message}`); + } +} + +/** + * Load both the image and annotations for a selected bone. + * @param {string} boneId - The ID or name of the bone. + */ +export async function loadImageAndAnnotations(boneId) { + loadBoneImage(boneId); + await loadAnnotations(boneId); +} From 832f571e83ae3bd2af8e3a9748a01cad812d2e39 Mon Sep 17 00:00:00 2001 From: tluke900 Date: Mon, 14 Apr 2025 13:49:28 -0500 Subject: [PATCH 101/143] Updated to pull descriptions without API calls. --- templates/boneset.html | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/templates/boneset.html b/templates/boneset.html index 68f63927..17758c51 100644 --- a/templates/boneset.html +++ b/templates/boneset.html @@ -143,6 +143,8 @@

                                        Description

                                        populateBoneDropdown(selectedBonesetId); // Populate bone dropdown based on boneset document.getElementById('bone-select').disabled = false; // Enable bone select document.getElementById('subbone-select').disabled = true; // Disable sub-bone select + loadDescription(selectedBonesetId); // Load boneset description + } else { document.getElementById('bone-select').disabled = true; // Disable bone select document.getElementById('subbone-select').disabled = true; // Disable sub-bone select @@ -155,6 +157,8 @@

                                        Description

                                        if (selectedBoneId) { populateSubBoneDropdown(selectedBoneId); // Populate sub-bone dropdown based on bone document.getElementById('subbone-select').disabled = false; // Enable sub-bone select + loadDescription(selectedBoneId); // Load bone description + } else { document.getElementById('subbone-select').disabled = true; // Disable sub-bone select } @@ -163,7 +167,33 @@

                                        Description

                                        // Initialize the page and fetch the data document.addEventListener('DOMContentLoaded', () => { fetchBonesData(); // Fetch boneset, bone, and sub-bone data on page load + loadDescription(selectedSubBoneId); // Load sub-bone description + }); + + async function loadDescription(id) { + const GITHUB_RAW_URL = 'https://raw.githubusercontent.com/oss-slu/DigitalBonesBox/data/databones/json/'; + + const container = document.getElementById('description-Container'); + container.innerHTML = ""; // Clear previous description + + try { + const response = await fetch(GITHUB_RAW_URL); + const data = await response.json(); + + if (data.description) { + const li = document.createElement('li'); + li.textContent = data.description; + container.appendChild(li); + } else { + container.textContent = "No description found."; + } + } catch (error) { + container.textContent = "Error loading description."; + console.error("Error fetching description:", error); + } +} + From 3b52c2ce585492d3d476e8d51ff4f558603e352a Mon Sep 17 00:00:00 2001 From: Burhan Khan Date: Mon, 14 Apr 2025 14:12:42 -0500 Subject: [PATCH 102/143] Fixed the format for the webpage and deleted server.js --- server.js | 55 ------------------------------------------ templates/server.js | 58 --------------------------------------------- 2 files changed, 113 deletions(-) delete mode 100644 server.js delete mode 100644 templates/server.js diff --git a/server.js b/server.js deleted file mode 100644 index 379fa4a6..00000000 --- a/server.js +++ /dev/null @@ -1,55 +0,0 @@ -const express = require('express'); -const bodyParser = require('body-parser'); -//const simpleGit = require('simple-git'); -const fs = require('fs'); -const path = require('path'); - -const app = express(); -//const git = simpleGit('C:/DigitalBonesBox'); // Local repo path - -app.use(bodyParser.urlencoded({ extended: false })); -app.use(bodyParser.json()); - -// Serve the HTMX page -app.get('/', (req, res) => { - res.sendFile(path.join(__dirname, 'editor', 'push.html')); -}); - -// Endpoint for the publish action -app.post('/publish', async (req, res) => { - const { fileName, jsonData } = req.body; - const filePath = path.join('C:/DigitalBonesBox', 'databones', fileName); - - try { - // Checkout to the data branch - await git.checkout('data'); - - // Save the JSON data to a file - fs.writeFileSync(filePath, jsonData); - - // Git operations: add, commit, and push to the data branch - await git.add(filePath); - const commitMessage = `Automated publish at ${new Date().toISOString()}`; - await git.commit(commitMessage); - await git.push('origin', 'data'); - - // Switch back to the main branch - await git.checkout('main'); - - res.send('Publish successful! Changes pushed to the data branch.'); - } catch (error) { - console.error('Publish failed:', error); - // Attempt to switch back to main in case of error - try { - await git.checkout('main'); - } catch (checkoutError) { - console.error('Failed to switch back to the main branch:', checkoutError); - } - res.status(500).send('Publish failed. Check server logs for details.'); - } -}); - -const PORT = 3001; -app.listen(PORT, () => { - console.log(`Server running on http://localhost:${PORT}`); -}); diff --git a/templates/server.js b/templates/server.js deleted file mode 100644 index 24d72caa..00000000 --- a/templates/server.js +++ /dev/null @@ -1,58 +0,0 @@ -const express = require('express'); -const bodyParser = require('body-parser'); -const path = require('path'); -const axios = require('axios'); -const cors = require('cors'); // Add this line - -const app = express(); - -// Enable CORS for all routes -app.use(cors()); - -app.use(bodyParser.urlencoded({ extended: false })); -app.use(bodyParser.json()); - -// Serve static files -app.use(express.static(__dirname)); - -// Route for boneset.html -app.get('/', (req, res) => { - res.sendFile(path.join(__dirname, 'boneset.html')); -}); - -// Test endpoint to verify server is working -app.get('/test', (req, res) => { - res.json({ message: "Server is working!" }); -}); - -// Combined data endpoint -app.get('/combined-data', async (req, res) => { - console.log("Received request for /combined-data"); // Log to verify the endpoint is hit - - try { - // Mock data that matches your frontend's expected structure - const mockData = { - bonesets: [ - { id: '1', name: 'Bony Pelvis' } - ], - bones: [ - { id: '101', name: 'Sacrum', boneset: '1' }, - { id: '102', name: 'Coccyx', boneset: '1' } - ], - subbones: [ - { id: '1001', name: 'Base', bone: '101' } - ] - }; - - console.log("Sending mock data:", mockData); // Log the data being sent - res.json(mockData); - } catch (error) { - console.error("Error in /combined-data:", error); - res.status(500).json({ error: "Failed to load data" }); - } -}); - -const PORT = 3001; -app.listen(PORT, () => { - console.log(`Server running on http://localhost:${PORT}`); -}); \ No newline at end of file From 6781f0d3fd273c2a60864eff7f32832bf7706ba8 Mon Sep 17 00:00:00 2001 From: tluke900 Date: Mon, 14 Apr 2025 14:18:40 -0500 Subject: [PATCH 103/143] Added error checking --- templates/boneset.html | 77 +++++++++++++++++++++++++++++------------- 1 file changed, 54 insertions(+), 23 deletions(-) diff --git a/templates/boneset.html b/templates/boneset.html index 17758c51..d86eba4e 100644 --- a/templates/boneset.html +++ b/templates/boneset.html @@ -67,17 +67,21 @@

                                        Description

                                        From b12a433b82e6c93f3263916bc1d00c50c1b8a94a Mon Sep 17 00:00:00 2001 From: jacksayshi Date: Tue, 15 Apr 2025 22:52:55 -0500 Subject: [PATCH 104/143] Added GitHub Action: ESLint check --- .github/workflows/lint.yml | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 .github/workflows/lint.yml diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 00000000..84802681 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,27 @@ +name: Lint JavaScript + +on: + push: + branches: [main] + pull_request: + branches: [main] + + +jobs: + lint: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: '18' + + - name: Install dependencies + run: npm install + + - name: Run ESLint + run: npm run lint From 9605356ec034861dc5be9b0cc1fd9d8552a8f02c Mon Sep 17 00:00:00 2001 From: jacksayshi Date: Wed, 16 Apr 2025 01:00:09 -0500 Subject: [PATCH 105/143] linter- forgot to save changes? --- eslint.config.js | 17 +++++++++++++++++ package.json | 8 ++++++-- 2 files changed, 23 insertions(+), 2 deletions(-) create mode 100644 eslint.config.js diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 00000000..74afdb38 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,17 @@ +export default [ + { + files: ["**/*.js"], + languageOptions: { + ecmaVersion: "latest", + sourceType: "module", + globals: { + window: "readonly", + document: "readonly", + }, + }, + rules: { + semi: ["error", "always"], + quotes: ["error", "double"], + }, + }, +]; diff --git a/package.json b/package.json index f9063ff2..d4e09996 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,14 @@ { "name": "digitalbonesbox", "version": "1.0.0", + "type": "module", "description": "This project is a Web App project aims to convert an existing PowerPoint-based educational tool into an interactive, mobile-friendly web application.", "main": "server.js", "scripts": { "test": "jest", - "start": "node server.js" + "start": "node server.js", + "lint": "eslint . --ext .js", + "lint:fix": "eslint . --ext .js --fix" }, "jest": { "testEnvironment": "jsdom" @@ -22,7 +25,8 @@ }, "devDependencies": { "jest": "^29.7.0", - "jest-environment-jsdom": "^29.7.0" + "jest-environment-jsdom": "^29.7.0", + "eslint": "^9.24.0" }, "directories": { "test": "tests" From c00989b2e594318fca6b6d668363e77a3956c354 Mon Sep 17 00:00:00 2001 From: tluke900 Date: Sun, 27 Apr 2025 13:08:57 -0500 Subject: [PATCH 106/143] Added new files for JavaScript functionality, and rearranged the HTML structure to use the new JS. --- js/main.js | 42 ++++++++++++++++++++++++++++++++++++ js/navigation.js | 49 ++++++++++++++++++++++++++++++++++++++++++ templates/boneset.html | 6 ++++++ 3 files changed, 97 insertions(+) create mode 100644 js/main.js create mode 100644 js/navigation.js diff --git a/js/main.js b/js/main.js new file mode 100644 index 00000000..450ac7cf --- /dev/null +++ b/js/main.js @@ -0,0 +1,42 @@ +import { setupNavigation, setBoneAndSubbones, disableButtons } from './navigation.js'; +import { updateDescription } from './description.js'; // Assuming you already have this + +document.addEventListener('DOMContentLoaded', () => { + const prevButton = document.getElementById('prev-button'); + const nextButton = document.getElementById('next-button'); + const subboneDropdown = document.getElementById('subbone-dropdown'); + + setupNavigation(prevButton, nextButton, subboneDropdown, updateDescription); + + // Example: when a new bone is selected + document.getElementById('bone-dropdown').addEventListener('change', (event) => { + const selectedBone = event.target.value; + const selectedSubbones = getSubbonesForBone(selectedBone); // You need to implement this + + setBoneAndSubbones(selectedBone, selectedSubbones); + + // Update the dropdown options + populateSubboneDropdown(subboneDropdown, selectedSubbones); + + disableButtons(prevButton, nextButton); + }); +}); + +function populateSubboneDropdown(dropdown, subbones) { + dropdown.innerHTML = ''; + subbones.forEach((subbone, index) => { + const option = document.createElement('option'); + option.value = index; + option.textContent = subbone; + dropdown.appendChild(option); + }); +} + +// Dummy placeholder for subbones fetching +function getSubbonesForBone(bone) { + const exampleData = { + Humerus: ['Lateral Epicondyle', 'Medial Epicondyle'], + Ulna: ['Olecranon', 'Coronoid Process'] + }; + return exampleData[bone] || []; +} diff --git a/js/navigation.js b/js/navigation.js new file mode 100644 index 00000000..fe97fe10 --- /dev/null +++ b/js/navigation.js @@ -0,0 +1,49 @@ +let currentBone = null; +let currentSubboneIndex = -1; +let subbones = []; + +export function setupNavigation(prevButton, nextButton, subboneDropdown, updateDescription) { + prevButton.addEventListener('click', () => { + prevSubbone(); + updateUI(subboneDropdown, updateDescription); + }); + + nextButton.addEventListener('click', () => { + nextSubbone(); + updateUI(subboneDropdown, updateDescription); + }); + + disableButtons(prevButton, nextButton); +} + +export function setBoneAndSubbones(bone, boneSubbones) { + currentBone = bone; + subbones = boneSubbones || []; + currentSubboneIndex = subbones.length > 0 ? 0 : -1; +} + +function prevSubbone() { + if (currentSubboneIndex > 0) { + currentSubboneIndex--; + } +} + +function nextSubbone() { + if (currentSubboneIndex < subbones.length - 1) { + currentSubboneIndex++; + } +} + +function updateUI(subboneDropdown, updateDescription) { + if (subbones.length === 0 || currentSubboneIndex === -1) return; + + subboneDropdown.selectedIndex = currentSubboneIndex; + const selectedSubbone = subbones[currentSubboneIndex]; + updateDescription(selectedSubbone); +} + +export function disableButtons(prevButton, nextButton) { + const disabled = subbones.length === 0; + prevButton.disabled = disabled; + nextButton.disabled = disabled; +} diff --git a/templates/boneset.html b/templates/boneset.html index a9b8edc3..75bc92ec 100644 --- a/templates/boneset.html +++ b/templates/boneset.html @@ -164,6 +164,12 @@

                                        Description

                                        } }); + + + + + + \ No newline at end of file From 1f619b3d45e71eaa48dd816b2ca4cd55041e2345 Mon Sep 17 00:00:00 2001 From: Burhan Khan Date: Mon, 28 Apr 2025 10:36:17 -0500 Subject: [PATCH 107/143] Fixes Issue #113. Modualarized script in boneset.html to ensureclarity. Same functionality should still apply and also moved sidebar into new directory that holds all relevant js files. --- templates/boneset.html | 109 +--------------------------------- templates/js/api.js | 14 +++++ templates/js/descripition.js | 26 ++++++++ templates/js/dropdown.js | 60 +++++++++++++++++++ templates/js/main.js | 19 ++++++ templates/{ => js}/sidebar.js | 0 6 files changed, 120 insertions(+), 108 deletions(-) create mode 100644 templates/js/api.js create mode 100644 templates/js/descripition.js create mode 100644 templates/js/dropdown.js create mode 100644 templates/js/main.js rename templates/{ => js}/sidebar.js (100%) diff --git a/templates/boneset.html b/templates/boneset.html index a9b8edc3..c7ec22c9 100644 --- a/templates/boneset.html +++ b/templates/boneset.html @@ -56,114 +56,7 @@

                                        Description

                                        - + \ No newline at end of file diff --git a/templates/js/api.js b/templates/js/api.js new file mode 100644 index 00000000..9c98e0f8 --- /dev/null +++ b/templates/js/api.js @@ -0,0 +1,14 @@ +// js/api.js +export async function fetchCombinedData() { + const API_URL = 'http://127.0.0.1:8000/combined-data'; + + try { + const response = await fetch(API_URL); + if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`); + return await response.json(); + } catch (error) { + console.error("Error fetching combined data:", error); + alert("Failed to load data."); + return { bonesets: [], bones: [], subbones: [] }; + } +} diff --git a/templates/js/descripition.js b/templates/js/descripition.js new file mode 100644 index 00000000..459056e7 --- /dev/null +++ b/templates/js/descripition.js @@ -0,0 +1,26 @@ +// js/description.js +const GITHUB_RAW_URL = 'https://raw.githubusercontent.com/oss-slu/DigitalBonesBox/data/DataPelvis/descriptions/'; + +export async function loadDescription(id) { + const container = document.getElementById('description-Container'); + container.innerHTML = ""; + const descUrl = `${GITHUB_RAW_URL}${id}_description.json`; + + try { + const response = await fetch(descUrl); + const data = await response.json(); + + const nameItem = document.createElement('li'); + nameItem.innerHTML = `${data.name}`; + container.appendChild(nameItem); + + data.description.forEach(point => { + const li = document.createElement('li'); + li.textContent = point; + container.appendChild(li); + }); + } catch (error) { + container.innerHTML = "
                                      • Error loading description.
                                      • "; + console.error("Failed to fetch description:", error); + } +} diff --git a/templates/js/dropdown.js b/templates/js/dropdown.js new file mode 100644 index 00000000..f2743010 --- /dev/null +++ b/templates/js/dropdown.js @@ -0,0 +1,60 @@ +// js/dropdowns.js +import { loadDescription } from './description.js'; + +export function populateBonesetDropdown(bonesets) { + const bonesetSelect = document.getElementById('boneset-select'); + bonesetSelect.innerHTML = ''; + + bonesets.forEach(set => { + const option = document.createElement('option'); + option.value = set.id; + option.textContent = set.name; + bonesetSelect.appendChild(option); + }); +} + +export function setupDropdownListeners(combinedData) { + const bonesetSelect = document.getElementById('boneset-select'); + const boneSelect = document.getElementById('bone-select'); + const subboneSelect = document.getElementById('subbone-select'); + + bonesetSelect.addEventListener('change', (e) => { + const selectedBonesetId = e.target.value; + + boneSelect.innerHTML = ''; + subboneSelect.innerHTML = ''; + subboneSelect.disabled = true; + + const relatedBones = combinedData.bones.filter(b => b.boneset === selectedBonesetId); + relatedBones.forEach(bone => { + const option = document.createElement('option'); + option.value = bone.id; + option.textContent = bone.name; + boneSelect.appendChild(option); + }); + + boneSelect.disabled = relatedBones.length === 0; + if (selectedBonesetId) loadDescription(selectedBonesetId); + }); + + boneSelect.addEventListener('change', (e) => { + const selectedBoneId = e.target.value; + + subboneSelect.innerHTML = ''; + const relatedSubbones = combinedData.subbones.filter(sb => sb.bone === selectedBoneId); + relatedSubbones.forEach(sb => { + const option = document.createElement('option'); + option.value = sb.id; + option.textContent = sb.name; + subboneSelect.appendChild(option); + }); + + subboneSelect.disabled = relatedSubbones.length === 0; + if (selectedBoneId) loadDescription(selectedBoneId); + }); + + subboneSelect.addEventListener('change', (e) => { + const selectedSubboneId = e.target.value; + if (selectedSubboneId) loadDescription(selectedSubboneId); + }); +} diff --git a/templates/js/main.js b/templates/js/main.js new file mode 100644 index 00000000..488f742d --- /dev/null +++ b/templates/js/main.js @@ -0,0 +1,19 @@ +// js/main.js +import { fetchCombinedData } from './api.js'; +import { populateBonesetDropdown, setupDropdownListeners } from './dropdowns.js'; + +let combinedData = { bonesets: [], bones: [], subbones: [] }; + +document.addEventListener('DOMContentLoaded', async () => { + combinedData = await fetchCombinedData(); + populateBonesetDropdown(combinedData.bonesets); + setupDropdownListeners(combinedData); + + // Automatically pre-select the first boneset if available + const boneset = combinedData.bonesets[0]; + if (boneset) { + document.getElementById('boneset-select').value = boneset.id; + const event = new Event('change'); + document.getElementById('boneset-select').dispatchEvent(event); + } +}); diff --git a/templates/sidebar.js b/templates/js/sidebar.js similarity index 100% rename from templates/sidebar.js rename to templates/js/sidebar.js From 3a3f760ab4eacaad6196604d82907df60c154bfe Mon Sep 17 00:00:00 2001 From: JoshBudzynski <157167337+JoshBudzynski@users.noreply.github.com> Date: Mon, 28 Apr 2025 12:50:24 -0500 Subject: [PATCH 108/143] fixed some of the paths --- templates/boneset.html | 94 ++++++++++++++++--------- templates/boneset.js | 129 +++++++++++++++++++++++------------ templates/fetch.js | 31 ++++----- templates/image_handeling.js | 53 ++++++-------- templates/style.css | 24 +++++++ 5 files changed, 206 insertions(+), 125 deletions(-) diff --git a/templates/boneset.html b/templates/boneset.html index dcdabdf5..e70818cd 100644 --- a/templates/boneset.html +++ b/templates/boneset.html @@ -1,48 +1,80 @@ - - - Boneset Viewer - + + + Bone Set Viewer + - -
                                        - Bone Image -
                                        + +
                                        + + Home + Tutor + Study + Help +
                                        + Login + Sign Up
                                        +
                                        + + + + - - + +
                                        +
                                        - - +

                                        Bone Set Viewer

                                        - - + +
                                        + Bone Image +
                                        +
                                        - - + + +
                                          - -
                                            + +
                                            + +
                                            + + + +
                                            +
                                            - - - + +
                                            +

                                            Description

                                            +
                                              +
                                              - - - - + + + +
                                              +
                                              + + + + diff --git a/templates/boneset.js b/templates/boneset.js index 9e4c2b01..77be5eda 100644 --- a/templates/boneset.js +++ b/templates/boneset.js @@ -1,49 +1,88 @@ -import { fetchJSON } from './fetch.js'; -import { loadImageAndAnnotations } from './imageHandling.js'; // Import the new image handling functions - -const BONESET_JSON_PATH = 'boneset.json'; // Path to the boneset JSON file -let boneSets = []; // This will hold the fetched bone set data - -// Function to render the bone set data -function renderBoneSet(boneSet) { - const boneSetList = document.getElementById('bone-set-list'); - boneSetList.innerHTML = ''; - - const li = document.createElement('li'); - li.textContent = boneSet.name; - boneSetList.appendChild(li); - - const subList = document.createElement('ul'); - boneSet.bones.forEach(bone => { - const subLi = document.createElement('li'); - subLi.textContent = `ID: ${bone}`; - subList.appendChild(subLi); +// boneset.js +import { fetchJSON } from './fetch.js'; // your fetch helper +import { loadImageAndAnnotations } from './image_handeling.js'; + +const BONESET_JSON_PATH = + 'https://raw.githubusercontent.com/oss-slu/DigitalBonesBox/data/DataPelvis/boneset.json'; + +const bonesetSelect = document.getElementById('boneset-select'); +const boneSelect = document.getElementById('bone-select'); +const subboneSelect = document.getElementById('subbone-select'); + +let boneSets = []; + +// 1. On page load, fetch boneset.json and populate the first dropdown +window.addEventListener('DOMContentLoaded', async () => { + try { + boneSets = await fetchJSON(BONESET_JSON_PATH); + console.log('Loaded bonesets:', boneSets); + boneSets.forEach(set => { + const opt = document.createElement('option'); + opt.value = set.id; + opt.textContent = set.name; + bonesetSelect.appendChild(opt); }); - boneSetList.appendChild(subList); - - // Display boneset image and annotations - loadImageAndAnnotations(boneSet.bones[0]); // Load the first bone image and annotations by default -} - -// Event listeners for the "Previous" and "Next" buttons -document.getElementById('prev-button').addEventListener('click', () => { - if (currentSetIndex > 0) { - currentSetIndex--; - renderBoneSet(boneSets[currentSetIndex]); - document.getElementById('next-button').disabled = false; - if (currentSetIndex === 0) { - document.getElementById('prev-button').disabled = true; - } - } + } catch (e) { + console.error('Failed to load bonesets:', e); + } +}); + +// 2. When a boneset is selected, populate Bones dropdown and load its first image +bonesetSelect.addEventListener('change', () => { + const selectedId = bonesetSelect.value; + boneSelect.innerHTML = ''; + subboneSelect.innerHTML = ''; + subboneSelect.disabled = true; + + const set = boneSets.find(bs => bs.id === selectedId); + if (!set) { + boneSelect.disabled = true; + return; + } + + boneSelect.disabled = false; + set.bones.forEach(bone => { + const opt = document.createElement('option'); + opt.value = bone.id; + opt.textContent = bone.name; + boneSelect.appendChild(opt); + }); + + // Immediately display the first bone’s image and annotations + if (set.bones.length > 0) { + loadImageAndAnnotations(set.bones[0].id); + } +}); + +// 3. When a Bone is selected, populate Sub-bones (if any) and load its image +boneSelect.addEventListener('change', () => { + const boneId = boneSelect.value; + const set = boneSets.find(bs => + bs.bones.some(b => b.id === boneId) + ); + + subboneSelect.innerHTML = ''; + const bone = set.bones.find(b => b.id === boneId); + + if (bone.subbones && bone.subbones.length > 0) { + subboneSelect.disabled = false; + bone.subbones.forEach(sb => { + const opt = document.createElement('option'); + opt.value = sb.id; + opt.textContent = sb.name; + subboneSelect.appendChild(opt); + }); + } else { + subboneSelect.disabled = true; + } + + loadImageAndAnnotations(boneId); }); -document.getElementById('next-button').addEventListener('click', () => { - if (currentSetIndex < boneSets.length - 1) { - currentSetIndex++; - renderBoneSet(boneSets[currentSetIndex]); - document.getElementById('prev-button').disabled = false; - if (currentSetIndex === boneSets.length - 1) { - document.getElementById('next-button').disabled = true; - } - } +// 4. When a Sub-bone is selected, load its image and annotations +subboneSelect.addEventListener('change', () => { + const subboneId = subboneSelect.value; + if (subboneId) { + loadImageAndAnnotations(subboneId); + } }); diff --git a/templates/fetch.js b/templates/fetch.js index be1f4ee1..a8810a1e 100644 --- a/templates/fetch.js +++ b/templates/fetch.js @@ -1,17 +1,14 @@ -/** - * Fetch JSON data from a given path. - * @param {string} path - The URL or path to the JSON file. - * @returns {Promise} - A promise that resolves to the JSON data. - */ - export async function fetchJSON(path) { - try { - const response = await fetch(path); - if (!response.ok) { - throw new Error(`Failed to fetch data from ${path}. Status: ${response.status}`); - } - return await response.json(); - } catch (error) { - console.error("Error fetching JSON:", error); - alert(`Error fetching data: ${error.message}`); - } -} +// fetch.js +export async function fetchJSON(path) { + const res = await fetch(path); + if (!res.ok) throw new Error(`Fetch ${path} failed: ${res.status}`); + return await res.json(); + } + + /** + * Fetch the boneset definitions + */ + export function fetchBoneSets() { + return fetchJSON('boneset.json'); + } + \ No newline at end of file diff --git a/templates/image_handeling.js b/templates/image_handeling.js index 24df8fd0..b74d142e 100644 --- a/templates/image_handeling.js +++ b/templates/image_handeling.js @@ -1,44 +1,33 @@ -/** - * Load the image for a selected bone. - * @param {string} boneId - The ID or name of the bone (used for the image file). - */ - export function loadBoneImage(boneId) { - const imageElement = document.getElementById('bone-image'); - imageElement.src = `https://raw.githubusercontent.com/oss-slu/DigitalBonesBox/data/DataPelvis/images/${boneId}.png`; +// image_handling.js + +function loadBoneImage(boneId) { + document.getElementById('bone-image').src = + `https://raw.githubusercontent.com/oss-slu/DigitalBonesBox/data/DataPelvis/images/bony_pelvis.png`; } -/** - * Load the annotations for the selected bone and display them. - * @param {string} boneId - The ID or name of the bone (used for fetching annotation file). - */ -export async function loadAnnotations(boneId) { - const annotationsOverlay = document.getElementById('annotations-overlay'); - annotationsOverlay.innerHTML = ''; // Clear previous annotations +async function loadAnnotations(boneId) { + const overlay = document.getElementById('annotations-overlay'); + overlay.innerHTML = ''; try { - const annotationsData = await fetch(`https://raw.githubusercontent.com/oss-slu/DigitalBonesBox/data/databones/annotations/${boneId}.json`); - const annotations = await annotationsData.json(); - - annotations.forEach(annotation => { - const annotationElement = document.createElement('div'); - annotationElement.classList.add('annotation'); - annotationElement.textContent = annotation.text; - annotationElement.style.top = `${annotation.y}%`; - annotationElement.style.left = `${annotation.x}%`; - - annotationsOverlay.appendChild(annotationElement); + const data = await fetch( + `https://raw.githubusercontent.com/oss-slu/DigitalBonesBox/data/DataPelvis/annotations/${boneId}.json` + ); + const annotations = await data.json(); + annotations.forEach(a => { + const div = document.createElement('div'); + div.className = 'annotation'; + div.textContent = a.text; + div.style.top = `${a.y}%`; + div.style.left = `${a.x}%`; + overlay.appendChild(div); }); } catch (error) { - console.error("Error loading annotations:", error); - alert(`Error loading annotations for ${boneId}: ${error.message}`); + console.error('Failed to load annotations:', error); } } -/** - * Load both the image and annotations for a selected bone. - * @param {string} boneId - The ID or name of the bone. - */ -export async function loadImageAndAnnotations(boneId) { +async function loadImageAndAnnotations(boneId) { loadBoneImage(boneId); await loadAnnotations(boneId); } diff --git a/templates/style.css b/templates/style.css index 437b5e63..2743c3c9 100644 --- a/templates/style.css +++ b/templates/style.css @@ -176,4 +176,28 @@ ul li { cursor: pointer; pointer-events: auto; } +.imagescontainer { + position: relative; + width: 80%; + margin: 40px auto; + background-color: #fff; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); + border-radius: 8px; + overflow: hidden; +} + +#bone-image { + width: 100%; + height: auto; + display: block; +} + +#annotations-overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + pointer-events: none; /* So clicks pass through to the image */ +} From 8a8748f451780f3907f5999bba95a50fb263dda2 Mon Sep 17 00:00:00 2001 From: Burhan Khan Date: Mon, 5 May 2025 10:39:39 -0500 Subject: [PATCH 109/143] Addressed the peer review comments. Corrected file names of description.js and dropdowns.js, changed sidebar.js and main.js so that sidebar.js can be exported and main.js can import. Full modularization should be acheived. --- templates/boneset.html | 1 - .../js/{descripition.js => description.js} | 0 templates/js/{dropdown.js => dropdowns.js} | 0 templates/js/main.js | 6 +++- templates/js/sidebar.js | 29 ++++++++++--------- 5 files changed, 21 insertions(+), 15 deletions(-) rename templates/js/{descripition.js => description.js} (100%) rename templates/js/{dropdown.js => dropdowns.js} (100%) diff --git a/templates/boneset.html b/templates/boneset.html index c7ec22c9..0b0b5b7f 100644 --- a/templates/boneset.html +++ b/templates/boneset.html @@ -22,7 +22,6 @@ -
                                              diff --git a/templates/js/descripition.js b/templates/js/description.js similarity index 100% rename from templates/js/descripition.js rename to templates/js/description.js diff --git a/templates/js/dropdown.js b/templates/js/dropdowns.js similarity index 100% rename from templates/js/dropdown.js rename to templates/js/dropdowns.js diff --git a/templates/js/main.js b/templates/js/main.js index 488f742d..62fd9de2 100644 --- a/templates/js/main.js +++ b/templates/js/main.js @@ -1,15 +1,19 @@ // js/main.js import { fetchCombinedData } from './api.js'; import { populateBonesetDropdown, setupDropdownListeners } from './dropdowns.js'; +import { initializeSidebar } from './sidebar.js'; let combinedData = { bonesets: [], bones: [], subbones: [] }; document.addEventListener('DOMContentLoaded', async () => { + // Initialize sidebar toggle behavior + initializeSidebar(); + + // Fetch and render bone data combinedData = await fetchCombinedData(); populateBonesetDropdown(combinedData.bonesets); setupDropdownListeners(combinedData); - // Automatically pre-select the first boneset if available const boneset = combinedData.bonesets[0]; if (boneset) { document.getElementById('boneset-select').value = boneset.id; diff --git a/templates/js/sidebar.js b/templates/js/sidebar.js index 28d6ffd7..c977d953 100644 --- a/templates/js/sidebar.js +++ b/templates/js/sidebar.js @@ -1,8 +1,8 @@ -document.addEventListener('DOMContentLoaded', () => { +// js/sidebar.js +export function initializeSidebar() { const toggleButton = document.getElementById('toggle-sidebar'); const sidebarContainer = document.getElementById('sidebar-container'); - // Load the sidebar dynamically if not already loaded async function loadSidebar() { if (!sidebarContainer.innerHTML) { try { @@ -15,15 +15,18 @@ document.addEventListener('DOMContentLoaded', () => { } } - // Toggle sidebar visibility - toggleButton.addEventListener('click', async () => { - await loadSidebar(); // Ensure the sidebar is loaded - const sidebarElement = document.getElementById('sidebar'); + if (toggleButton) { + toggleButton.addEventListener('click', async () => { + await loadSidebar(); // Ensure the sidebar is loaded + const sidebarElement = document.getElementById('sidebar'); - if (sidebarElement.style.left === '0px') { - sidebarElement.style.left = '-250px'; // Close sidebar - } else { - sidebarElement.style.left = '0px'; // Open sidebar - } - }); -}); + if (sidebarElement) { + if (sidebarElement.style.left === '0px') { + sidebarElement.style.left = '-250px'; // Close sidebar + } else { + sidebarElement.style.left = '0px'; // Open sidebar + } + } + }); + } +} From 2f40d7ab586675eba3147279e37f60ff9966bccb Mon Sep 17 00:00:00 2001 From: JoshBudzynski <157167337+JoshBudzynski@users.noreply.github.com> Date: Mon, 5 May 2025 12:47:18 -0500 Subject: [PATCH 110/143] Commiting for a pull --- templates/boneset.html | 225 ++++++++++++++++++++++++++++------------- 1 file changed, 157 insertions(+), 68 deletions(-) diff --git a/templates/boneset.html b/templates/boneset.html index e70818cd..a9b8edc3 100644 --- a/templates/boneset.html +++ b/templates/boneset.html @@ -1,80 +1,169 @@ + - - - Bone Set Viewer - + + + Bone Set Viewer + - - -
                                              - - Home - Tutor - Study - Help -
                                              - Login - Sign Up -
                                              -
                                              - - - - - - -
                                              -
                                              - -

                                              Bone Set Viewer

                                              - - -
                                              - Bone Image -
                                              -
                                              - - - -
                                                - - -
                                                - -
                                                - - - + +
                                                + + Home + Tutor + Study + Help +
                                                + Login + Sign Up
                                                -
                                                +
                                                + + + - -
                                                -

                                                Description

                                                -
                                                  -
                                                  +
                                                  +
                                                  +

                                                  Bone Set Viewer

                                                  + +
                                                    +
                                                    + +
                                                    + + + +
                                                    +
                                                    - - +
                                                    +

                                                    Description

                                                    +
                                                      +
                                                      + +
                                                      -
                                                      - - - - + - + + \ No newline at end of file From 5fee3bd98eca8756e5cbd983f2145a07c3366d49 Mon Sep 17 00:00:00 2001 From: jacksayshi Date: Mon, 5 May 2025 14:41:44 -0500 Subject: [PATCH 111/143] moved navigation main to js directory --- js/main.js | 42 ------------------------------ templates/js/main.js | 39 ++++++++++++++++++++++++--- {js => templates/js}/navigation.js | 0 3 files changed, 36 insertions(+), 45 deletions(-) delete mode 100644 js/main.js rename {js => templates/js}/navigation.js (100%) diff --git a/js/main.js b/js/main.js deleted file mode 100644 index 450ac7cf..00000000 --- a/js/main.js +++ /dev/null @@ -1,42 +0,0 @@ -import { setupNavigation, setBoneAndSubbones, disableButtons } from './navigation.js'; -import { updateDescription } from './description.js'; // Assuming you already have this - -document.addEventListener('DOMContentLoaded', () => { - const prevButton = document.getElementById('prev-button'); - const nextButton = document.getElementById('next-button'); - const subboneDropdown = document.getElementById('subbone-dropdown'); - - setupNavigation(prevButton, nextButton, subboneDropdown, updateDescription); - - // Example: when a new bone is selected - document.getElementById('bone-dropdown').addEventListener('change', (event) => { - const selectedBone = event.target.value; - const selectedSubbones = getSubbonesForBone(selectedBone); // You need to implement this - - setBoneAndSubbones(selectedBone, selectedSubbones); - - // Update the dropdown options - populateSubboneDropdown(subboneDropdown, selectedSubbones); - - disableButtons(prevButton, nextButton); - }); -}); - -function populateSubboneDropdown(dropdown, subbones) { - dropdown.innerHTML = ''; - subbones.forEach((subbone, index) => { - const option = document.createElement('option'); - option.value = index; - option.textContent = subbone; - dropdown.appendChild(option); - }); -} - -// Dummy placeholder for subbones fetching -function getSubbonesForBone(bone) { - const exampleData = { - Humerus: ['Lateral Epicondyle', 'Medial Epicondyle'], - Ulna: ['Olecranon', 'Coronoid Process'] - }; - return exampleData[bone] || []; -} diff --git a/templates/js/main.js b/templates/js/main.js index 62fd9de2..240e416a 100644 --- a/templates/js/main.js +++ b/templates/js/main.js @@ -1,19 +1,42 @@ -// js/main.js import { fetchCombinedData } from './api.js'; import { populateBonesetDropdown, setupDropdownListeners } from './dropdowns.js'; import { initializeSidebar } from './sidebar.js'; +import { setupNavigation, setBoneAndSubbones, disableButtons } from './navigation.js'; +import { loadDescription } from './description.js'; // ✅ CORRECT function name let combinedData = { bonesets: [], bones: [], subbones: [] }; document.addEventListener('DOMContentLoaded', async () => { - // Initialize sidebar toggle behavior + // 1. Sidebar behavior initializeSidebar(); - // Fetch and render bone data + // 2. Fetch data and populate dropdowns combinedData = await fetchCombinedData(); populateBonesetDropdown(combinedData.bonesets); setupDropdownListeners(combinedData); + // 3. Hook up navigation buttons + const prevButton = document.getElementById('prev-button'); + const nextButton = document.getElementById('next-button'); + const subboneDropdown = document.getElementById('subbone-select'); + const boneDropdown = document.getElementById('bone-select'); + + setupNavigation(prevButton, nextButton, subboneDropdown, loadDescription); + + // 4. Update navigation when bone changes + boneDropdown.addEventListener('change', (event) => { + const selectedBone = event.target.value; + + const relatedSubbones = combinedData.subbones + .filter(sb => sb.bone === selectedBone) + .map(sb => sb.id); + + setBoneAndSubbones(selectedBone, relatedSubbones); + populateSubboneDropdown(subboneDropdown, relatedSubbones); + disableButtons(prevButton, nextButton); + }); + + // 5. Auto-select the first boneset const boneset = combinedData.bonesets[0]; if (boneset) { document.getElementById('boneset-select').value = boneset.id; @@ -21,3 +44,13 @@ document.addEventListener('DOMContentLoaded', async () => { document.getElementById('boneset-select').dispatchEvent(event); } }); + +function populateSubboneDropdown(dropdown, subbones) { + dropdown.innerHTML = ''; + subbones.forEach((subboneId) => { + const option = document.createElement('option'); + option.value = subboneId; + option.textContent = subboneId.replace(/_/g, ' '); + dropdown.appendChild(option); + }); +} diff --git a/js/navigation.js b/templates/js/navigation.js similarity index 100% rename from js/navigation.js rename to templates/js/navigation.js From 5b6999842693976c2406c95db47007656c38fdef Mon Sep 17 00:00:00 2001 From: JoshBudzynski <157167337+JoshBudzynski@users.noreply.github.com> Date: Mon, 5 May 2025 14:46:06 -0500 Subject: [PATCH 112/143] Image displayed 106 --- templates/boneset.html | 46 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/templates/boneset.html b/templates/boneset.html index a9b8edc3..8bc603d1 100644 --- a/templates/boneset.html +++ b/templates/boneset.html @@ -6,6 +6,8 @@ Bone Set Viewer + + @@ -53,6 +55,49 @@

                                                      Description

                                                      + +
                                                      + Bone Image +
                                                      +
                                                      + + +
                                                      @@ -153,6 +198,7 @@

                                                      Description

                                                      } document.addEventListener('DOMContentLoaded', async () => { + console.log("Fetching description for ID:", id); combinedData = await fetchCombinedData(); populateDropdowns(); From 73231c79efce1afe3faa7161beaabb6781f0eac1 Mon Sep 17 00:00:00 2001 From: jacksayshi Date: Mon, 5 May 2025 15:34:27 -0500 Subject: [PATCH 113/143] image fetching still needs work. making this a feature flag. commented logic --- templates/boneset.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/boneset.html b/templates/boneset.html index ce73b54b..04b14b27 100644 --- a/templates/boneset.html +++ b/templates/boneset.html @@ -61,8 +61,8 @@

                                                      Description

                                                      - - - - - -
                                                      - -
                                                      - - - - - - -
                                                      -

                                                      Bones Viewer

                                                      - -
                                                      -
                                                      - - - - diff --git a/templates/boneset.js b/templates/boneset.js deleted file mode 100644 index 77be5eda..00000000 --- a/templates/boneset.js +++ /dev/null @@ -1,88 +0,0 @@ -// boneset.js -import { fetchJSON } from './fetch.js'; // your fetch helper -import { loadImageAndAnnotations } from './image_handeling.js'; - -const BONESET_JSON_PATH = - 'https://raw.githubusercontent.com/oss-slu/DigitalBonesBox/data/DataPelvis/boneset.json'; - -const bonesetSelect = document.getElementById('boneset-select'); -const boneSelect = document.getElementById('bone-select'); -const subboneSelect = document.getElementById('subbone-select'); - -let boneSets = []; - -// 1. On page load, fetch boneset.json and populate the first dropdown -window.addEventListener('DOMContentLoaded', async () => { - try { - boneSets = await fetchJSON(BONESET_JSON_PATH); - console.log('Loaded bonesets:', boneSets); - boneSets.forEach(set => { - const opt = document.createElement('option'); - opt.value = set.id; - opt.textContent = set.name; - bonesetSelect.appendChild(opt); - }); - } catch (e) { - console.error('Failed to load bonesets:', e); - } -}); - -// 2. When a boneset is selected, populate Bones dropdown and load its first image -bonesetSelect.addEventListener('change', () => { - const selectedId = bonesetSelect.value; - boneSelect.innerHTML = ''; - subboneSelect.innerHTML = ''; - subboneSelect.disabled = true; - - const set = boneSets.find(bs => bs.id === selectedId); - if (!set) { - boneSelect.disabled = true; - return; - } - - boneSelect.disabled = false; - set.bones.forEach(bone => { - const opt = document.createElement('option'); - opt.value = bone.id; - opt.textContent = bone.name; - boneSelect.appendChild(opt); - }); - - // Immediately display the first bone’s image and annotations - if (set.bones.length > 0) { - loadImageAndAnnotations(set.bones[0].id); - } -}); - -// 3. When a Bone is selected, populate Sub-bones (if any) and load its image -boneSelect.addEventListener('change', () => { - const boneId = boneSelect.value; - const set = boneSets.find(bs => - bs.bones.some(b => b.id === boneId) - ); - - subboneSelect.innerHTML = ''; - const bone = set.bones.find(b => b.id === boneId); - - if (bone.subbones && bone.subbones.length > 0) { - subboneSelect.disabled = false; - bone.subbones.forEach(sb => { - const opt = document.createElement('option'); - opt.value = sb.id; - opt.textContent = sb.name; - subboneSelect.appendChild(opt); - }); - } else { - subboneSelect.disabled = true; - } - - loadImageAndAnnotations(boneId); -}); - -// 4. When a Sub-bone is selected, load its image and annotations -subboneSelect.addEventListener('change', () => { - const subboneId = subboneSelect.value; - if (subboneId) { - loadImageAndAnnotations(subboneId); - } -}); diff --git a/templates/fetch.js b/templates/fetch.js deleted file mode 100644 index a8810a1e..00000000 --- a/templates/fetch.js +++ /dev/null @@ -1,14 +0,0 @@ -// fetch.js -export async function fetchJSON(path) { - const res = await fetch(path); - if (!res.ok) throw new Error(`Fetch ${path} failed: ${res.status}`); - return await res.json(); - } - - /** - * Fetch the boneset definitions - */ - export function fetchBoneSets() { - return fetchJSON('boneset.json'); - } - \ No newline at end of file diff --git a/templates/image_handeling.js b/templates/image_handeling.js deleted file mode 100644 index b74d142e..00000000 --- a/templates/image_handeling.js +++ /dev/null @@ -1,33 +0,0 @@ -// image_handling.js - -function loadBoneImage(boneId) { - document.getElementById('bone-image').src = - `https://raw.githubusercontent.com/oss-slu/DigitalBonesBox/data/DataPelvis/images/bony_pelvis.png`; -} - -async function loadAnnotations(boneId) { - const overlay = document.getElementById('annotations-overlay'); - overlay.innerHTML = ''; - - try { - const data = await fetch( - `https://raw.githubusercontent.com/oss-slu/DigitalBonesBox/data/DataPelvis/annotations/${boneId}.json` - ); - const annotations = await data.json(); - annotations.forEach(a => { - const div = document.createElement('div'); - div.className = 'annotation'; - div.textContent = a.text; - div.style.top = `${a.y}%`; - div.style.left = `${a.x}%`; - overlay.appendChild(div); - }); - } catch (error) { - console.error('Failed to load annotations:', error); - } -} - -async function loadImageAndAnnotations(boneId) { - loadBoneImage(boneId); - await loadAnnotations(boneId); -} From 382075fbba829795384e187b7e869b3713a94ec0 Mon Sep 17 00:00:00 2001 From: Wendy Onwuagana Date: Wed, 10 Sep 2025 00:25:29 -0500 Subject: [PATCH 119/143] Integrate HTMX for dynamic description loading --- boneset-api/server.js | 134 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 124 insertions(+), 10 deletions(-) diff --git a/boneset-api/server.js b/boneset-api/server.js index eacb41fc..a675d0ae 100644 --- a/boneset-api/server.js +++ b/boneset-api/server.js @@ -1,23 +1,112 @@ +//const express = require("express"); +//const axios = require("axios"); +//const cors = require("cors"); +//const path = require('path'); // Added for consistency, though not strictly needed for this version +// +//const app = express(); +//const PORT = process.env.PORT || 8000; +// +//app.use(cors()); +// +//// --- Original GitHub URLs --- +//const GITHUB_REPO = "https://raw.githubusercontent.com/oss-slu/DigitalBonesBox/data/DataPelvis/"; +//const BONESET_JSON_URL = `${GITHUB_REPO}boneset/bony_pelvis.json`; +//const BONES_DIR_URL = `${GITHUB_REPO}bones/`; +// +//// Helper function to fetch JSON from GitHub +//async function fetchJSON(url) { +// try { +// const response = await axios.get(url); +// return response.data; +// } catch (error) { +// console.error(`Failed to fetch ${url}:`, error.message); +// return null; +// } +//} +// +//// Home route (fixes "Cannot GET /" issue) +//app.get("/", (req, res) => { +// res.json({ message: "Welcome to the Boneset API (GitHub-Integrated)" }); +//}); +// +//// --- Original Combined Data Endpoint --- +//// This endpoint still provides the main data for the dropdowns +//app.get("/combined-data", async (req, res) => { +// try { +// const bonesetData = await fetchJSON(BONESET_JSON_URL); +// if (!bonesetData) return res.status(500).json({ error: "Failed to load boneset data" }); +// +// const bonesets = [{ id: bonesetData.id, name: bonesetData.name }]; +// const bones = []; +// const subbones = []; +// +// for (const boneId of bonesetData.bones) { +// const boneJsonUrl = `${BONES_DIR_URL}${boneId}.json`; +// const boneData = await fetchJSON(boneJsonUrl); +// +// if (boneData) { +// bones.push({ id: boneData.id, name: boneData.name, boneset: bonesetData.id }); +// boneData.subBones.forEach(subBoneId => { +// subbones.push({ id: subBoneId, name: subBoneId.replace(/_/g, " "), bone: boneData.id }); +// }); +// } +// } +// +// res.json({ bonesets, bones, subbones }); +// +// } catch (error) { +// console.error("Error fetching combined data:", error.message); +// res.status(500).json({ error: "Internal Server Error" }); +// } +//}); +// +//// --- NEW HTMX ENDPOINT --- +//// This endpoint fetches a description and returns it as an HTML fragment +//app.get("/api/description/", async (req, res) => { // Path changed here +// const { boneId } = req.query; // Changed from req.params to req.query +// if (!boneId) { +// return res.send(''); // Send empty response if no boneId is provided +// } +// const GITHUB_DESC_URL = `https://raw.githubusercontent.com/oss-slu/DigitalBonesBox/data/DataPelvis/descriptions/${boneId}_description.json`; +// +// try { +// const response = await axios.get(GITHUB_DESC_URL); +// const descriptionData = response.data; +// +// let html = `
                                                    • ${descriptionData.name}
                                                    • `; +// descriptionData.description.forEach(point => { +// html += `
                                                    • ${point}
                                                    • `; +// }); +// res.send(html); +// +// } catch (error) { +// res.send('
                                                    • Description not available.
                                                    • '); +// } +//}); + + +// Start server +//app.listen(PORT, () => { +// console.log(`🚀 Server running on http://127.0.0.1:${PORT}`); +//}); + + + + const express = require("express"); const axios = require("axios"); const cors = require("cors"); +const path = require('path'); const app = express(); const PORT = process.env.PORT || 8000; app.use(cors()); -// Home route (fixes "Cannot GET /" issue) -app.get("/", (req, res) => { - res.json({ message: "Welcome to the Boneset API (GitHub-Integrated)" }); -}); - -// GitHub raw URLs const GITHUB_REPO = "https://raw.githubusercontent.com/oss-slu/DigitalBonesBox/data/DataPelvis/"; const BONESET_JSON_URL = `${GITHUB_REPO}boneset/bony_pelvis.json`; const BONES_DIR_URL = `${GITHUB_REPO}bones/`; -// Fetch JSON helper function async function fetchJSON(url) { try { const response = await axios.get(url); @@ -28,7 +117,10 @@ async function fetchJSON(url) { } } -// Combined data endpoint +app.get("/", (req, res) => { + res.json({ message: "Welcome to the Boneset API (GitHub-Integrated)" }); +}); + app.get("/combined-data", async (req, res) => { try { const bonesetData = await fetchJSON(BONESET_JSON_URL); @@ -58,7 +150,29 @@ app.get("/combined-data", async (req, res) => { } }); -// Start server +// --- CORRECTED HTMX ENDPOINT --- +app.get("/api/description/", async (req, res) => { // Path changed here (no :boneId) + const { boneId } = req.query; // Changed from req.params to req.query + if (!boneId) { + return res.send(''); // Send empty response if no boneId is provided + } + const GITHUB_DESC_URL = `https://raw.githubusercontent.com/oss-slu/DigitalBonesBox/data/DataPelvis/descriptions/${boneId}_description.json`; + + try { + const response = await axios.get(GITHUB_DESC_URL); + const descriptionData = response.data; + + let html = `
                                                    • ${descriptionData.name}
                                                    • `; + descriptionData.description.forEach(point => { + html += `
                                                    • ${point}
                                                    • `; + }); + res.send(html); + + } catch (error) { + res.send('
                                                    • Description not available.
                                                    • '); + } +}); + app.listen(PORT, () => { console.log(`🚀 Server running on http://127.0.0.1:${PORT}`); -}); +}); \ No newline at end of file From 7a1b351fb60751f0743739d167fc296575cab2dc Mon Sep 17 00:00:00 2001 From: Wendy Onwuagana Date: Wed, 10 Sep 2025 00:26:47 -0500 Subject: [PATCH 120/143] Integrate HTMX for dynamic description loading --- templates/boneset.html | 66 +++++++++++++-------------------------- templates/js/api.js | 5 +-- templates/js/dropdowns.js | 2 +- 3 files changed, 26 insertions(+), 47 deletions(-) diff --git a/templates/boneset.html b/templates/boneset.html index 04b14b27..e28f5e60 100644 --- a/templates/boneset.html +++ b/templates/boneset.html @@ -7,7 +7,7 @@ Bone Set Viewer - + @@ -33,13 +33,27 @@

                                                      Bone Set Viewer

                                                      - - -
                                                      @@ -47,7 +61,8 @@

                                                      Bone Set Viewer

                                                      Description

                                                      -
                                                        +
                                                          +
                                                        -
                                                        + diff --git a/templates/js/api.js b/templates/js/api.js index 9c98e0f8..ac61551f 100644 --- a/templates/js/api.js +++ b/templates/js/api.js @@ -1,5 +1,6 @@ -// js/api.js + export async function fetchCombinedData() { + // --- CORRECTED: Use the full URL of the backend server --- const API_URL = 'http://127.0.0.1:8000/combined-data'; try { @@ -11,4 +12,4 @@ export async function fetchCombinedData() { alert("Failed to load data."); return { bonesets: [], bones: [], subbones: [] }; } -} +} \ No newline at end of file diff --git a/templates/js/dropdowns.js b/templates/js/dropdowns.js index f2743010..0fbe9d2d 100644 --- a/templates/js/dropdowns.js +++ b/templates/js/dropdowns.js @@ -34,7 +34,7 @@ export function setupDropdownListeners(combinedData) { }); boneSelect.disabled = relatedBones.length === 0; - if (selectedBonesetId) loadDescription(selectedBonesetId); + // if (selectedBonesetId) loadDescription(selectedBonesetId); }); boneSelect.addEventListener('change', (e) => { From 4706457eadbafe1136fb2f589b7e015943923dac Mon Sep 17 00:00:00 2001 From: strejoarciles Date: Sat, 13 Sep 2025 12:05:52 -0500 Subject: [PATCH 121/143] Implement frontend mock data display for bone images and annotations --- templates/js/main.js | 113 +++++++++++++++++++++++++++++-- templates/js/mock-bone-data.json | 23 +++++++ templates/style.css | 75 +++++++++++++++----- 3 files changed, 187 insertions(+), 24 deletions(-) create mode 100644 templates/js/mock-bone-data.json diff --git a/templates/js/main.js b/templates/js/main.js index 240e416a..e6a1db31 100644 --- a/templates/js/main.js +++ b/templates/js/main.js @@ -2,20 +2,111 @@ import { fetchCombinedData } from './api.js'; import { populateBonesetDropdown, setupDropdownListeners } from './dropdowns.js'; import { initializeSidebar } from './sidebar.js'; import { setupNavigation, setBoneAndSubbones, disableButtons } from './navigation.js'; -import { loadDescription } from './description.js'; // ✅ CORRECT function name +import { loadDescription } from './description.js'; let combinedData = { bonesets: [], bones: [], subbones: [] }; +let mockBoneData = null; + +// Mock data fetching function +async function fetchMockBoneData() { + try { + const response = await fetch('./js/mock-bone-data.json'); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + const data = await response.json(); + return data; + } catch (error) { + console.error('Error fetching mock bone data:', error); + return null; + } +} + +// Display bone image and annotations +function displayBoneData(boneId) { + if (!mockBoneData) { + console.log('Mock data not available'); + return; + } + + const bone = mockBoneData.bones.find(b => b.id === boneId); + if (!bone) { + console.log(`No mock data found for bone: ${boneId}`); + clearBoneDisplay(); + return; + } + + // Update the image + const boneImage = document.getElementById('bone-image'); + if (boneImage) { + boneImage.src = bone.image_url; + boneImage.alt = `${bone.name} bone image`; + boneImage.style.display = 'block'; + } + + // Display annotations + displayAnnotations(bone.annotations); +} + +// Display annotations as a list +function displayAnnotations(annotations) { + const annotationsOverlay = document.getElementById('annotations-overlay'); + if (!annotationsOverlay) { + console.error('Annotations overlay element not found'); + return; + } + + // Clear previous annotations + annotationsOverlay.innerHTML = ''; + + if (!annotations || annotations.length === 0) { + annotationsOverlay.innerHTML = '

                                                        No annotations available for this bone.

                                                        '; + return; + } + + // Create annotation list + const annotationsList = document.createElement('ul'); + annotationsList.className = 'annotations-list'; + + annotations.forEach((annotation, index) => { + const listItem = document.createElement('li'); + listItem.className = 'annotation-item'; + listItem.textContent = annotation.text; + annotationsList.appendChild(listItem); + }); + + annotationsOverlay.appendChild(annotationsList); +} + +// Clear bone display +function clearBoneDisplay() { + const boneImage = document.getElementById('bone-image'); + const annotationsOverlay = document.getElementById('annotations-overlay'); + + if (boneImage) { + boneImage.src = ''; + boneImage.alt = ''; + boneImage.style.display = 'none'; + } + + if (annotationsOverlay) { + annotationsOverlay.innerHTML = '

                                                        Select a bone to view image and annotations.

                                                        '; + } +} document.addEventListener('DOMContentLoaded', async () => { // 1. Sidebar behavior initializeSidebar(); - // 2. Fetch data and populate dropdowns + // 2. Load mock bone data + mockBoneData = await fetchMockBoneData(); + + // 3. Fetch data and populate dropdowns combinedData = await fetchCombinedData(); populateBonesetDropdown(combinedData.bonesets); setupDropdownListeners(combinedData); - // 3. Hook up navigation buttons + // 4. Hook up navigation buttons const prevButton = document.getElementById('prev-button'); const nextButton = document.getElementById('next-button'); const subboneDropdown = document.getElementById('subbone-select'); @@ -23,7 +114,7 @@ document.addEventListener('DOMContentLoaded', async () => { setupNavigation(prevButton, nextButton, subboneDropdown, loadDescription); - // 4. Update navigation when bone changes + // 5. Update navigation when bone changes boneDropdown.addEventListener('change', (event) => { const selectedBone = event.target.value; @@ -34,15 +125,25 @@ document.addEventListener('DOMContentLoaded', async () => { setBoneAndSubbones(selectedBone, relatedSubbones); populateSubboneDropdown(subboneDropdown, relatedSubbones); disableButtons(prevButton, nextButton); + + // NEW: Display mock bone data when bone is selected + if (selectedBone) { + displayBoneData(selectedBone); + } else { + clearBoneDisplay(); + } }); - // 5. Auto-select the first boneset + // 6. Auto-select the first boneset const boneset = combinedData.bonesets[0]; if (boneset) { document.getElementById('boneset-select').value = boneset.id; const event = new Event('change'); document.getElementById('boneset-select').dispatchEvent(event); } + + // 7. Initialize display + clearBoneDisplay(); }); function populateSubboneDropdown(dropdown, subbones) { @@ -53,4 +154,4 @@ function populateSubboneDropdown(dropdown, subbones) { option.textContent = subboneId.replace(/_/g, ' '); dropdown.appendChild(option); }); -} +} \ No newline at end of file diff --git a/templates/js/mock-bone-data.json b/templates/js/mock-bone-data.json new file mode 100644 index 00000000..881818e1 --- /dev/null +++ b/templates/js/mock-bone-data.json @@ -0,0 +1,23 @@ +{ + "bones": [ + { + "id": "ischium", + "name": "Ischium", + "image_url": "https://via.placeholder.com/600x400/4A90E2/FFFFFF?text=Ischium+Bone", + "annotations": [ + { + "text": "Ischial Tuberosity - Attachment point for hamstring muscles", + "position": { "x": 300, "y": 150 } + }, + { + "text": "Ischial Spine - Forms part of the lesser sciatic notch", + "position": { "x": 250, "y": 100 } + }, + { + "text": "Ischial Ramus - Forms part of the obturator foramen", + "position": { "x": 350, "y": 200 } + } + ] + } + ] +} \ No newline at end of file diff --git a/templates/style.css b/templates/style.css index 2743c3c9..a6516574 100644 --- a/templates/style.css +++ b/templates/style.css @@ -119,45 +119,38 @@ ul li { padding: 20px; /* Keep consistent padding for readability */ } - #text-button-Home { padding: 20px; cursor: pointer; } - #text-button-Tutor { padding: 20px; cursor: pointer; } - #text-button-Study { padding: 20px; cursor: pointer; } + #text-button-Help { padding: 20px; cursor: pointer; } - #text-button-Login { padding: 20px; cursor: pointer; align-items: right; - } #text-button-SignUp { padding: 20px; cursor: pointer; align-items: right; - - } - #innerbadge { display: flex; /* Enable Flexbox for the container */ justify-content: flex-end; /* Align items to the far right */ @@ -166,9 +159,8 @@ ul li { padding-right: 20px; /* Adds space between the buttons and the right edge */ box-sizing: border-box; width: 75% - - } + .annotation-box { position: absolute; border: 2px solid red; @@ -176,6 +168,7 @@ ul li { cursor: pointer; pointer-events: auto; } + .imagescontainer { position: relative; width: 80%; @@ -186,18 +179,64 @@ ul li { overflow: hidden; } +/* Image and annotations display styles */ +.viewer-wrapper { + display: flex; + gap: 20px; + margin-top: 20px; + align-items: flex-start; +} + #bone-image { - width: 100%; + max-width: 600px; height: auto; - display: block; + border: 2px solid #ddd; + border-radius: 8px; + display: none; } #annotations-overlay { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - pointer-events: none; /* So clicks pass through to the image */ + flex: 1; + max-width: 400px; + padding: 15px; + background-color: #f8f9fa; + border: 1px solid #dee2e6; + border-radius: 8px; + min-height: 200px; } +.annotations-list { + list-style: none; + padding: 0; + margin: 0; +} + +.annotation-item { + background-color: white; + margin-bottom: 10px; + padding: 12px; + border-left: 4px solid #007bff; + border-radius: 4px; + box-shadow: 0 2px 4px rgba(0,0,0,0.1); + font-size: 14px; + line-height: 1.4; +} + +.annotation-item:last-child { + margin-bottom: 0; +} + +/* Responsive design for smaller screens */ +@media (max-width: 768px) { + .viewer-wrapper { + flex-direction: column; + } + + #annotations-overlay { + max-width: 100%; + } + + #bone-image { + max-width: 100%; + } +} From 3e152694ccb42c8d58493dd4fb3a1bdf110a99b7 Mon Sep 17 00:00:00 2001 From: Jenni Oishee Date: Sun, 14 Sep 2025 22:35:44 -0500 Subject: [PATCH 122/143] Implemented pop-up functionality for help modal. --- templates/boneset.html | 9 +++++++++ templates/js/sidebar.js | 14 ++++++++++++++ templates/style.css | 37 +++++++++++++++++++++++++++++++++++++ 3 files changed, 60 insertions(+) diff --git a/templates/boneset.html b/templates/boneset.html index e28f5e60..83013d33 100644 --- a/templates/boneset.html +++ b/templates/boneset.html @@ -79,6 +79,15 @@

                                                        Description

                                                        +
                                                        +
                                                        +

                                                        How to Use

                                                        +

                                                        + Use the dropfown menus to select a bonset, bone, or sub-bone.
                                                        + Once selected, you can view the image and description of the selected item.
                                                        +

                                                        + +
                                                        \ No newline at end of file diff --git a/templates/js/sidebar.js b/templates/js/sidebar.js index c977d953..d908d35b 100644 --- a/templates/js/sidebar.js +++ b/templates/js/sidebar.js @@ -30,3 +30,17 @@ export function initializeSidebar() { }); } } +document.addEventListener('DOMContentLoaded', () => { + const helpButton = document.getElementById('text-button-Help'); + const helpModal = document.getElementById('help-modal'); + const closeHelpModal = document.getElementById('close-help-modal'); + if (helpButton && helpModal && closeHelpModal) { + helpButton.addEventListener('click', () => { + helpModal.style.display = 'flex'; + }); + closeHelpModal.addEventListener('click', () => { + helpModal.style.display = 'none'; + }); + } +}) + diff --git a/templates/style.css b/templates/style.css index 2743c3c9..cc4a9ccb 100644 --- a/templates/style.css +++ b/templates/style.css @@ -201,3 +201,40 @@ ul li { pointer-events: none; /* So clicks pass through to the image */ } +#help-modal { + display: none; + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + background: rgba(0, 0, 0, 0.4); + z-index: 2000; + justify-content: center; + align-items: center; +} + +#help-modal-context { + background: #fff; + padding: 2em 1.5em; + border-radius: 10px; + max-width: 400px; + margin: auto; + box-shadow: 0 4px 24px rgba(0, 0, 0, 0.2); + text-align: center; + position: relative; +} + +#close-help-modal { + margin-top: 1em; + padding: 8px 20px; + background-color: #007bff; + color: #fff; + border: none; + border-radius: 4px; + cursor: pointer; +} + +#close-help-modal:hover { + background-color: #0056b3; +} From d81b8be0bff5a92bc5a4e4aa491be30c24d5afe5 Mon Sep 17 00:00:00 2001 From: Taktar Date: Sun, 14 Sep 2025 22:57:45 -0500 Subject: [PATCH 123/143] feat(api): add GET /api/boneset/:bonesetId and serve final_bony_pelvis.json (closes #119) --- boneset-api/data/final_bony_pelvis.json | 20 ++++++++++++++++++++ boneset-api/server.js | 20 +++++++++++++++++++- 2 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 boneset-api/data/final_bony_pelvis.json diff --git a/boneset-api/data/final_bony_pelvis.json b/boneset-api/data/final_bony_pelvis.json new file mode 100644 index 00000000..2439a1e2 --- /dev/null +++ b/boneset-api/data/final_bony_pelvis.json @@ -0,0 +1,20 @@ +{ + "id": "bony_pelvis", + "name": "Bony Pelvis", + "bones": [ + { + "id": "ilium", + "name": "Ilium", + "description": [ + "The Ilium forms the superior part of the bony pelvis.", + "It articulates with the sacrum to form the sacroiliac joint." + ], + "subbones": [ + { "id": "iliac_crest", "name": "Iliac Crest" }, + { "id": "anterior_superior_iliac_spine", "name": "Anterior Superior Iliac Spine" } + ] + }, + { "id": "ischium", "name": "Ischium", "subbones": [] }, + { "id": "pubis", "name": "Pubis", "subbones": [] } + ] +} diff --git a/boneset-api/server.js b/boneset-api/server.js index a675d0ae..bb78aa90 100644 --- a/boneset-api/server.js +++ b/boneset-api/server.js @@ -92,11 +92,11 @@ - const express = require("express"); const axios = require("axios"); const cors = require("cors"); const path = require('path'); +const fs = require('fs/promises'); // <-- ADDED const app = express(); const PORT = process.env.PORT || 8000; @@ -107,6 +107,8 @@ const GITHUB_REPO = "https://raw.githubusercontent.com/oss-slu/DigitalBonesBox/d const BONESET_JSON_URL = `${GITHUB_REPO}boneset/bony_pelvis.json`; const BONES_DIR_URL = `${GITHUB_REPO}bones/`; +const DATA_DIR = path.join(__dirname, "data"); // <-- ADDED + async function fetchJSON(url) { try { const response = await axios.get(url); @@ -173,6 +175,22 @@ app.get("/api/description/", async (req, res) => { // Path changed here (no :bon } }); +// --- ADDED: Serve merged boneset JSON from /data --- +app.get("/api/boneset/:bonesetId", async (req, res) => { + const { bonesetId } = req.params; + const filePath = path.join(DATA_DIR, `final_${bonesetId}.json`); + try { + const raw = await fs.readFile(filePath, "utf8"); + res.type("application/json").send(raw); + } catch (err) { + if (err.code === "ENOENT") { + return res.status(404).json({ error: `Boneset '${bonesetId}' not found` }); + } + console.error("Error reading boneset file:", err); + res.status(500).json({ error: "Internal Server Error" }); + } +}); + app.listen(PORT, () => { console.log(`🚀 Server running on http://127.0.0.1:${PORT}`); }); \ No newline at end of file From c59f34e4c908ed6ba37f15ae607ed37f150c17ac Mon Sep 17 00:00:00 2001 From: Taktar Date: Sun, 14 Sep 2025 23:09:37 -0500 Subject: [PATCH 124/143] chore(lint): switch to double quotes per ESLint rules --- DigitalBonesBox | 0 boneset-api/server.js | 8 ++--- templates/js/api.js | 2 +- templates/js/description.js | 8 ++--- templates/js/dropdowns.js | 30 ++++++++--------- templates/js/main.js | 34 +++++++++---------- templates/js/navigation.js | 4 +-- templates/js/sidebar.js | 18 +++++----- templates/test.js | 66 ++++++++++++++++++------------------- 9 files changed, 85 insertions(+), 85 deletions(-) create mode 100644 DigitalBonesBox diff --git a/DigitalBonesBox b/DigitalBonesBox new file mode 100644 index 00000000..e69de29b diff --git a/boneset-api/server.js b/boneset-api/server.js index bb78aa90..6a6b9093 100644 --- a/boneset-api/server.js +++ b/boneset-api/server.js @@ -95,8 +95,8 @@ const express = require("express"); const axios = require("axios"); const cors = require("cors"); -const path = require('path'); -const fs = require('fs/promises'); // <-- ADDED +const path = require("path"); +const fs = require("fs/promises"); // <-- ADDED const app = express(); const PORT = process.env.PORT || 8000; @@ -156,7 +156,7 @@ app.get("/combined-data", async (req, res) => { app.get("/api/description/", async (req, res) => { // Path changed here (no :boneId) const { boneId } = req.query; // Changed from req.params to req.query if (!boneId) { - return res.send(''); // Send empty response if no boneId is provided + return res.send(""); // Send empty response if no boneId is provided } const GITHUB_DESC_URL = `https://raw.githubusercontent.com/oss-slu/DigitalBonesBox/data/DataPelvis/descriptions/${boneId}_description.json`; @@ -171,7 +171,7 @@ app.get("/api/description/", async (req, res) => { // Path changed here (no :bon res.send(html); } catch (error) { - res.send('
                                                      • Description not available.
                                                      • '); + res.send("
                                                      • Description not available.
                                                      • "); } }); diff --git a/templates/js/api.js b/templates/js/api.js index ac61551f..2493d257 100644 --- a/templates/js/api.js +++ b/templates/js/api.js @@ -1,7 +1,7 @@ export async function fetchCombinedData() { // --- CORRECTED: Use the full URL of the backend server --- - const API_URL = 'http://127.0.0.1:8000/combined-data'; + const API_URL = "http://127.0.0.1:8000/combined-data"; try { const response = await fetch(API_URL); diff --git a/templates/js/description.js b/templates/js/description.js index 459056e7..6e418326 100644 --- a/templates/js/description.js +++ b/templates/js/description.js @@ -1,8 +1,8 @@ // js/description.js -const GITHUB_RAW_URL = 'https://raw.githubusercontent.com/oss-slu/DigitalBonesBox/data/DataPelvis/descriptions/'; +const GITHUB_RAW_URL = "https://raw.githubusercontent.com/oss-slu/DigitalBonesBox/data/DataPelvis/descriptions/"; export async function loadDescription(id) { - const container = document.getElementById('description-Container'); + const container = document.getElementById("description-Container"); container.innerHTML = ""; const descUrl = `${GITHUB_RAW_URL}${id}_description.json`; @@ -10,12 +10,12 @@ export async function loadDescription(id) { const response = await fetch(descUrl); const data = await response.json(); - const nameItem = document.createElement('li'); + const nameItem = document.createElement("li"); nameItem.innerHTML = `${data.name}`; container.appendChild(nameItem); data.description.forEach(point => { - const li = document.createElement('li'); + const li = document.createElement("li"); li.textContent = point; container.appendChild(li); }); diff --git a/templates/js/dropdowns.js b/templates/js/dropdowns.js index 0fbe9d2d..6f0a4b31 100644 --- a/templates/js/dropdowns.js +++ b/templates/js/dropdowns.js @@ -1,12 +1,12 @@ // js/dropdowns.js -import { loadDescription } from './description.js'; +import { loadDescription } from "./description.js"; export function populateBonesetDropdown(bonesets) { - const bonesetSelect = document.getElementById('boneset-select'); - bonesetSelect.innerHTML = ''; + const bonesetSelect = document.getElementById("boneset-select"); + bonesetSelect.innerHTML = ""; bonesets.forEach(set => { - const option = document.createElement('option'); + const option = document.createElement("option"); option.value = set.id; option.textContent = set.name; bonesetSelect.appendChild(option); @@ -14,20 +14,20 @@ export function populateBonesetDropdown(bonesets) { } export function setupDropdownListeners(combinedData) { - const bonesetSelect = document.getElementById('boneset-select'); - const boneSelect = document.getElementById('bone-select'); - const subboneSelect = document.getElementById('subbone-select'); + const bonesetSelect = document.getElementById("boneset-select"); + const boneSelect = document.getElementById("bone-select"); + const subboneSelect = document.getElementById("subbone-select"); - bonesetSelect.addEventListener('change', (e) => { + bonesetSelect.addEventListener("change", (e) => { const selectedBonesetId = e.target.value; - boneSelect.innerHTML = ''; - subboneSelect.innerHTML = ''; + boneSelect.innerHTML = ""; + subboneSelect.innerHTML = ""; subboneSelect.disabled = true; const relatedBones = combinedData.bones.filter(b => b.boneset === selectedBonesetId); relatedBones.forEach(bone => { - const option = document.createElement('option'); + const option = document.createElement("option"); option.value = bone.id; option.textContent = bone.name; boneSelect.appendChild(option); @@ -37,13 +37,13 @@ export function setupDropdownListeners(combinedData) { // if (selectedBonesetId) loadDescription(selectedBonesetId); }); - boneSelect.addEventListener('change', (e) => { + boneSelect.addEventListener("change", (e) => { const selectedBoneId = e.target.value; - subboneSelect.innerHTML = ''; + subboneSelect.innerHTML = ""; const relatedSubbones = combinedData.subbones.filter(sb => sb.bone === selectedBoneId); relatedSubbones.forEach(sb => { - const option = document.createElement('option'); + const option = document.createElement("option"); option.value = sb.id; option.textContent = sb.name; subboneSelect.appendChild(option); @@ -53,7 +53,7 @@ export function setupDropdownListeners(combinedData) { if (selectedBoneId) loadDescription(selectedBoneId); }); - subboneSelect.addEventListener('change', (e) => { + subboneSelect.addEventListener("change", (e) => { const selectedSubboneId = e.target.value; if (selectedSubboneId) loadDescription(selectedSubboneId); }); diff --git a/templates/js/main.js b/templates/js/main.js index 240e416a..ecc4139c 100644 --- a/templates/js/main.js +++ b/templates/js/main.js @@ -1,12 +1,12 @@ -import { fetchCombinedData } from './api.js'; -import { populateBonesetDropdown, setupDropdownListeners } from './dropdowns.js'; -import { initializeSidebar } from './sidebar.js'; -import { setupNavigation, setBoneAndSubbones, disableButtons } from './navigation.js'; -import { loadDescription } from './description.js'; // ✅ CORRECT function name +import { fetchCombinedData } from "./api.js"; +import { populateBonesetDropdown, setupDropdownListeners } from "./dropdowns.js"; +import { initializeSidebar } from "./sidebar.js"; +import { setupNavigation, setBoneAndSubbones, disableButtons } from "./navigation.js"; +import { loadDescription } from "./description.js"; // ✅ CORRECT function name let combinedData = { bonesets: [], bones: [], subbones: [] }; -document.addEventListener('DOMContentLoaded', async () => { +document.addEventListener("DOMContentLoaded", async () => { // 1. Sidebar behavior initializeSidebar(); @@ -16,15 +16,15 @@ document.addEventListener('DOMContentLoaded', async () => { setupDropdownListeners(combinedData); // 3. Hook up navigation buttons - const prevButton = document.getElementById('prev-button'); - const nextButton = document.getElementById('next-button'); - const subboneDropdown = document.getElementById('subbone-select'); - const boneDropdown = document.getElementById('bone-select'); + const prevButton = document.getElementById("prev-button"); + const nextButton = document.getElementById("next-button"); + const subboneDropdown = document.getElementById("subbone-select"); + const boneDropdown = document.getElementById("bone-select"); setupNavigation(prevButton, nextButton, subboneDropdown, loadDescription); // 4. Update navigation when bone changes - boneDropdown.addEventListener('change', (event) => { + boneDropdown.addEventListener("change", (event) => { const selectedBone = event.target.value; const relatedSubbones = combinedData.subbones @@ -39,18 +39,18 @@ document.addEventListener('DOMContentLoaded', async () => { // 5. Auto-select the first boneset const boneset = combinedData.bonesets[0]; if (boneset) { - document.getElementById('boneset-select').value = boneset.id; - const event = new Event('change'); - document.getElementById('boneset-select').dispatchEvent(event); + document.getElementById("boneset-select").value = boneset.id; + const event = new Event("change"); + document.getElementById("boneset-select").dispatchEvent(event); } }); function populateSubboneDropdown(dropdown, subbones) { - dropdown.innerHTML = ''; + dropdown.innerHTML = ""; subbones.forEach((subboneId) => { - const option = document.createElement('option'); + const option = document.createElement("option"); option.value = subboneId; - option.textContent = subboneId.replace(/_/g, ' '); + option.textContent = subboneId.replace(/_/g, " "); dropdown.appendChild(option); }); } diff --git a/templates/js/navigation.js b/templates/js/navigation.js index fe97fe10..7e087160 100644 --- a/templates/js/navigation.js +++ b/templates/js/navigation.js @@ -3,12 +3,12 @@ let currentSubboneIndex = -1; let subbones = []; export function setupNavigation(prevButton, nextButton, subboneDropdown, updateDescription) { - prevButton.addEventListener('click', () => { + prevButton.addEventListener("click", () => { prevSubbone(); updateUI(subboneDropdown, updateDescription); }); - nextButton.addEventListener('click', () => { + nextButton.addEventListener("click", () => { nextSubbone(); updateUI(subboneDropdown, updateDescription); }); diff --git a/templates/js/sidebar.js b/templates/js/sidebar.js index c977d953..f714c888 100644 --- a/templates/js/sidebar.js +++ b/templates/js/sidebar.js @@ -1,30 +1,30 @@ // js/sidebar.js export function initializeSidebar() { - const toggleButton = document.getElementById('toggle-sidebar'); - const sidebarContainer = document.getElementById('sidebar-container'); + const toggleButton = document.getElementById("toggle-sidebar"); + const sidebarContainer = document.getElementById("sidebar-container"); async function loadSidebar() { if (!sidebarContainer.innerHTML) { try { - const response = await fetch('sidebar.html'); + const response = await fetch("sidebar.html"); const sidebarHTML = await response.text(); sidebarContainer.innerHTML = sidebarHTML; } catch (error) { - console.error('Error loading sidebar:', error); + console.error("Error loading sidebar:", error); } } } if (toggleButton) { - toggleButton.addEventListener('click', async () => { + toggleButton.addEventListener("click", async () => { await loadSidebar(); // Ensure the sidebar is loaded - const sidebarElement = document.getElementById('sidebar'); + const sidebarElement = document.getElementById("sidebar"); if (sidebarElement) { - if (sidebarElement.style.left === '0px') { - sidebarElement.style.left = '-250px'; // Close sidebar + if (sidebarElement.style.left === "0px") { + sidebarElement.style.left = "-250px"; // Close sidebar } else { - sidebarElement.style.left = '0px'; // Open sidebar + sidebarElement.style.left = "0px"; // Open sidebar } } }); diff --git a/templates/test.js b/templates/test.js index 33810e9f..9b169349 100644 --- a/templates/test.js +++ b/templates/test.js @@ -9,7 +9,7 @@ const html = ` `; document.body.innerHTML = html; -require('./sidebar.js'); // Import the JavaScript code +require("./sidebar.js"); // Import the JavaScript code beforeEach(() => { global.fetch = jest.fn(() => @@ -23,85 +23,85 @@ beforeEach(() => { }) ); - const sidebarContainer = document.getElementById('sidebar-container'); - sidebarContainer.innerHTML = ''; // Reset sidebar container + const sidebarContainer = document.getElementById("sidebar-container"); + sidebarContainer.innerHTML = ""; // Reset sidebar container - const sidebarElement = document.getElementById('sidebar'); + const sidebarElement = document.getElementById("sidebar"); if (sidebarElement) { - sidebarElement.style.left = '-250px'; // Explicitly set the initial state + sidebarElement.style.left = "-250px"; // Explicitly set the initial state } }); beforeAll(() => { - document.dispatchEvent(new Event('DOMContentLoaded')); // Simulate DOMContentLoaded + document.dispatchEvent(new Event("DOMContentLoaded")); // Simulate DOMContentLoaded }); afterEach(() => { // Reset sidebar state after each test - const sidebarElement = document.getElementById('sidebar'); + const sidebarElement = document.getElementById("sidebar"); if (sidebarElement) { - sidebarElement.style.left = '-250px'; // Ensure sidebar is closed + sidebarElement.style.left = "-250px"; // Ensure sidebar is closed } jest.restoreAllMocks(); // Clean up mocks }); -describe('Sidebar Toggle Functionality', () => { - test('loads sidebar content when the button is clicked', async () => { - const toggleButton = document.getElementById('toggle-sidebar'); +describe("Sidebar Toggle Functionality", () => { + test("loads sidebar content when the button is clicked", async () => { + const toggleButton = document.getElementById("toggle-sidebar"); toggleButton.click(); await new Promise(process.nextTick); - const sidebarElement = document.getElementById('sidebar'); + const sidebarElement = document.getElementById("sidebar"); expect(sidebarElement).toBeTruthy(); - expect(sidebarElement.innerHTML).toContain('Sidebar Content'); + expect(sidebarElement.innerHTML).toContain("Sidebar Content"); }); - test('toggles sidebar open and closed', async () => { - const toggleButton = document.getElementById('toggle-sidebar'); - const sidebarElement = document.getElementById('sidebar'); + test("toggles sidebar open and closed", async () => { + const toggleButton = document.getElementById("toggle-sidebar"); + const sidebarElement = document.getElementById("sidebar"); // Ensure the sidebar starts hidden let computedStyle = window.getComputedStyle(sidebarElement); - expect(computedStyle.left).toBe('-250px'); // Sidebar is initially hidden + expect(computedStyle.left).toBe("-250px"); // Sidebar is initially hidden // Simulate opening the sidebar toggleButton.click(); await new Promise((resolve) => setTimeout(resolve, 50)); // Wait for event propagation computedStyle = window.getComputedStyle(sidebarElement); - expect(computedStyle.left).toBe('0px'); // Sidebar is open + expect(computedStyle.left).toBe("0px"); // Sidebar is open // Simulate closing the sidebar toggleButton.click(); await new Promise((resolve) => setTimeout(resolve, 50)); // Wait for event propagation computedStyle = window.getComputedStyle(sidebarElement); - expect(computedStyle.left).toBe('-250px'); // Sidebar is closed + expect(computedStyle.left).toBe("-250px"); // Sidebar is closed }); }); -describe('Badge Button', () => { - test('badge button is present and clickable', () => { - const badgeButton = document.getElementById('toggle-sidebar'); +describe("Badge Button", () => { + test("badge button is present and clickable", () => { + const badgeButton = document.getElementById("toggle-sidebar"); expect(badgeButton).toBeTruthy(); - expect(badgeButton.tagName).toBe('BUTTON'); + expect(badgeButton.tagName).toBe("BUTTON"); }); - test('badge button has correct initial text', () => { - const badgeButton = document.getElementById('toggle-sidebar'); - expect(badgeButton.textContent).toBe('☰'); + test("badge button has correct initial text", () => { + const badgeButton = document.getElementById("toggle-sidebar"); + expect(badgeButton.textContent).toBe("☰"); }); }); -describe('Sidebar Styling', () => { - test('sidebar starts hidden by default', () => { - const sidebar = document.getElementById('sidebar'); +describe("Sidebar Styling", () => { + test("sidebar starts hidden by default", () => { + const sidebar = document.getElementById("sidebar"); expect(sidebar).toBeTruthy(); const computedStyle = window.getComputedStyle(sidebar); - expect(computedStyle.left).toBe('-250px'); // Sidebar is hidden initially + expect(computedStyle.left).toBe("-250px"); // Sidebar is hidden initially }); - test('sidebar transitions smoothly', () => { - const sidebar = document.getElementById('sidebar'); - expect(sidebar.style.transition).toContain('left 0.3s ease'); + test("sidebar transitions smoothly", () => { + const sidebar = document.getElementById("sidebar"); + expect(sidebar.style.transition).toContain("left 0.3s ease"); }); }); From 4b8a2bd4dc164cd8ed49a1caedb4b8fdb702d699 Mon Sep 17 00:00:00 2001 From: Taktar Date: Mon, 15 Sep 2025 16:08:35 -0500 Subject: [PATCH 125/143] data: fill bony pelvis with ilium/ischium/pubis substructures --- boneset-api/data/final_bony_pelvis.json | 163 +++++++++++++++++++++++- 1 file changed, 157 insertions(+), 6 deletions(-) diff --git a/boneset-api/data/final_bony_pelvis.json b/boneset-api/data/final_bony_pelvis.json index 2439a1e2..9052b63b 100644 --- a/boneset-api/data/final_bony_pelvis.json +++ b/boneset-api/data/final_bony_pelvis.json @@ -6,15 +6,166 @@ "id": "ilium", "name": "Ilium", "description": [ - "The Ilium forms the superior part of the bony pelvis.", - "It articulates with the sacrum to form the sacroiliac joint." + "Forms the superior part of the bony pelvis.", + "Articulates with the sacrum to form the sacroiliac joint." ], "subbones": [ - { "id": "iliac_crest", "name": "Iliac Crest" }, - { "id": "anterior_superior_iliac_spine", "name": "Anterior Superior Iliac Spine" } + { + "id": "iliac_crest", + "name": "Iliac Crest", + "description": [ + "Superior border of the ilium from ASIS to PSIS.", + "External lip: attachments for tensor fascia lata, external oblique, latissimus dorsi.", + "Inner lip: attachments for iliac fascia, internal oblique, transversus abdominis, quadratus lumborum, sacrospinalis, iliacus.", + "Surface landmark for L4 level; used for lumbar puncture." + ] + }, + { + "id": "anterior_superior_iliac_spine", + "name": "Anterior Superior Iliac Spine", + "description": [ + "Bony projection at the anterior end of the iliac crest.", + "Attachment for inguinal ligament and sartorius origin." + ] + }, + { + "id": "anterior_inferior_iliac_spine", + "name": "Anterior Inferior Iliac Spine", + "description": [ + "Inferior to ASIS on anterior border of ilium.", + "Attachment for straight head of rectus femoris and iliofemoral ligament." + ] + }, + { + "id": "posterior_superior_iliac_spine", + "name": "Posterior Superior Iliac Spine", + "description": [ + "Posterior end of the iliac crest; surface landmark.", + "Attachment for posterior sacroiliac ligaments." + ] + }, + { + "id": "posterior_inferior_iliac_spine", + "name": "Posterior Inferior Iliac Spine", + "description": [ + "Small prominence along the border of the greater sciatic notch." + ] + }, + { + "id": "auricular_surface", + "name": "Auricular Surface", + "description": [ + "Roughened posterior ilium surface, normally covered with cartilage.", + "Articulates with the lateral surface of the sacrum at the sacroiliac joint." + ] + } ] }, - { "id": "ischium", "name": "Ischium", "subbones": [] }, - { "id": "pubis", "name": "Pubis", "subbones": [] } + { + "id": "ischium", + "name": "Ischium", + "description": [ + "Forms the inferoposterior part of the pelvic bone.", + "Situated posterior to the pubis and inferior to the ilium." + ], + "subbones": [ + { + "id": "ischial_ramus", + "name": "Ramus", + "description": [ + "Inferior portion of the ischium; continuous with the inferior pubic ramus.", + "Forms part of the border of the obturator foramen.", + "Attachment for ischiocavernosus and the crus of the penis or clitoris." + ] + }, + { + "id": "ischial_tuberosity", + "name": "Ischial Tuberosity", + "description": [ + "Large posterior swelling (the 'sits bone').", + "Attachments: adductor magnus, semimembranosus, semitendinosus, long head of biceps femoris.", + "Attachment for the sacrotuberous ligament." + ] + }, + { + "id": "ischial_spine", + "name": "Ischial Spine", + "description": [ + "Bony projection on posterior surface of the body of the ischium.", + "Separates the greater and lesser sciatic notches.", + "Attachment for the sacrospinous ligament and superior gemellus." + ] + }, + { + "id": "greater_sciatic_notch", + "name": "Greater Sciatic Notch", + "description": [ + "Large notch superior to the ischial spine.", + "Converted to the greater sciatic foramen by the sacrospinous and sacrotuberous ligaments.", + "Transmits: superior gluteal vessels/nerve, piriformis, sciatic nerve, inferior gluteal vessels/nerve, internal pudendal vessels and pudendal nerve, posterior femoral cutaneous nerve, nerves to quadratus femoris and obturator internus." + ] + }, + { + "id": "lesser_sciatic_notch", + "name": "Lesser Sciatic Notch", + "description": [ + "Smaller notch inferior to the ischial spine.", + "Converted to the lesser sciatic foramen by the sacrotuberous ligament.", + "Transmits: tendon of obturator internus, internal pudendal vessels and pudendal nerve, nerve to obturator internus." + ] + } + ] + }, + { + "id": "pubis", + "name": "Pubis", + "description": [ + "Forms the anteroinferior part of the pelvis.", + "Connects the two pelvic bones from each side." + ], + "subbones": [ + { + "id": "superior_pubic_ramus", + "name": "Superior Pubic Ramus", + "description": [ + "Projects posterolaterally from the body; contains the pectineal line.", + "Obturator groove on inferior surface contributes to the obturator canal." + ] + }, + { + "id": "inferior_pubic_ramus", + "name": "Inferior Pubic Ramus", + "description": [ + "Projects inferiorly and laterally from the body.", + "Joins with the ischial ramus to form part of the obturator foramen." + ] + }, + { + "id": "pectineal_line", + "name": "Pectineal Line", + "description": [ + "Prominent ridge on the superior ramus.", + "Attachment for pectineus muscle, conjoint tendon, and lacunar and pectineal ligaments." + ] + }, + { + "id": "symphyseal_surface", + "name": "Symphyseal Surface", + "description": [ + "Joint surface on each pubis forming a secondary cartilaginous joint (symphysis).", + "Covered with hyaline cartilage and linked by the fibrocartilaginous pubic symphysis.", + "Remodels during pregnancy, increasing flexibility for delivery." + ] + }, + { + "id": "pubic_tubercle", + "name": "Pubic Tubercle", + "description": [ + "Bony projection at the lateral end of the pubic crest.", + "Attachment for the inguinal ligament." + ] + } + ] + } ] } From 8eb55bcf145862dd09c1c522261d787cb5d5ce65 Mon Sep 17 00:00:00 2001 From: Taktar Date: Thu, 18 Sep 2025 00:24:49 -0500 Subject: [PATCH 126/143] tested --- boneset-api/data/final_bony_pelvis.json | 70 ++++++++--- boneset-api/server.js | 149 +++++++++++++----------- 2 files changed, 132 insertions(+), 87 deletions(-) diff --git a/boneset-api/data/final_bony_pelvis.json b/boneset-api/data/final_bony_pelvis.json index 9052b63b..e1364b82 100644 --- a/boneset-api/data/final_bony_pelvis.json +++ b/boneset-api/data/final_bony_pelvis.json @@ -9,6 +9,8 @@ "Forms the superior part of the bony pelvis.", "Articulates with the sacrum to form the sacroiliac joint." ], + "image_url": "/images/bony_pelvis/ilium.jpg", + "annotations": [], "subbones": [ { "id": "iliac_crest", @@ -18,7 +20,9 @@ "External lip: attachments for tensor fascia lata, external oblique, latissimus dorsi.", "Inner lip: attachments for iliac fascia, internal oblique, transversus abdominis, quadratus lumborum, sacrospinalis, iliacus.", "Surface landmark for L4 level; used for lumbar puncture." - ] + ], + "image_url": "/images/bony_pelvis/iliac_crest.jpg", + "annotations": [] }, { "id": "anterior_superior_iliac_spine", @@ -26,7 +30,9 @@ "description": [ "Bony projection at the anterior end of the iliac crest.", "Attachment for inguinal ligament and sartorius origin." - ] + ], + "image_url": "/images/bony_pelvis/anterior_superior_iliac_spine.jpg", + "annotations": [] }, { "id": "anterior_inferior_iliac_spine", @@ -34,7 +40,9 @@ "description": [ "Inferior to ASIS on anterior border of ilium.", "Attachment for straight head of rectus femoris and iliofemoral ligament." - ] + ], + "image_url": "/images/bony_pelvis/anterior_inferior_iliac_spine.jpg", + "annotations": [] }, { "id": "posterior_superior_iliac_spine", @@ -42,14 +50,18 @@ "description": [ "Posterior end of the iliac crest; surface landmark.", "Attachment for posterior sacroiliac ligaments." - ] + ], + "image_url": "/images/bony_pelvis/posterior_superior_iliac_spine.jpg", + "annotations": [] }, { "id": "posterior_inferior_iliac_spine", "name": "Posterior Inferior Iliac Spine", "description": [ "Small prominence along the border of the greater sciatic notch." - ] + ], + "image_url": "/images/bony_pelvis/posterior_inferior_iliac_spine.jpg", + "annotations": [] }, { "id": "auricular_surface", @@ -57,7 +69,9 @@ "description": [ "Roughened posterior ilium surface, normally covered with cartilage.", "Articulates with the lateral surface of the sacrum at the sacroiliac joint." - ] + ], + "image_url": "/images/bony_pelvis/auricular_surface.jpg", + "annotations": [] } ] }, @@ -68,6 +82,8 @@ "Forms the inferoposterior part of the pelvic bone.", "Situated posterior to the pubis and inferior to the ilium." ], + "image_url": "/images/bony_pelvis/ischium.jpg", + "annotations": [], "subbones": [ { "id": "ischial_ramus", @@ -76,7 +92,9 @@ "Inferior portion of the ischium; continuous with the inferior pubic ramus.", "Forms part of the border of the obturator foramen.", "Attachment for ischiocavernosus and the crus of the penis or clitoris." - ] + ], + "image_url": "/images/bony_pelvis/ischial_ramus.jpg", + "annotations": [] }, { "id": "ischial_tuberosity", @@ -85,7 +103,9 @@ "Large posterior swelling (the 'sits bone').", "Attachments: adductor magnus, semimembranosus, semitendinosus, long head of biceps femoris.", "Attachment for the sacrotuberous ligament." - ] + ], + "image_url": "/images/bony_pelvis/ischial_tuberosity.jpg", + "annotations": [] }, { "id": "ischial_spine", @@ -94,7 +114,9 @@ "Bony projection on posterior surface of the body of the ischium.", "Separates the greater and lesser sciatic notches.", "Attachment for the sacrospinous ligament and superior gemellus." - ] + ], + "image_url": "/images/bony_pelvis/ischial_spine.jpg", + "annotations": [] }, { "id": "greater_sciatic_notch", @@ -103,7 +125,9 @@ "Large notch superior to the ischial spine.", "Converted to the greater sciatic foramen by the sacrospinous and sacrotuberous ligaments.", "Transmits: superior gluteal vessels/nerve, piriformis, sciatic nerve, inferior gluteal vessels/nerve, internal pudendal vessels and pudendal nerve, posterior femoral cutaneous nerve, nerves to quadratus femoris and obturator internus." - ] + ], + "image_url": "/images/bony_pelvis/greater_sciatic_notch.jpg", + "annotations": [] }, { "id": "lesser_sciatic_notch", @@ -112,7 +136,9 @@ "Smaller notch inferior to the ischial spine.", "Converted to the lesser sciatic foramen by the sacrotuberous ligament.", "Transmits: tendon of obturator internus, internal pudendal vessels and pudendal nerve, nerve to obturator internus." - ] + ], + "image_url": "/images/bony_pelvis/lesser_sciatic_notch.jpg", + "annotations": [] } ] }, @@ -123,6 +149,8 @@ "Forms the anteroinferior part of the pelvis.", "Connects the two pelvic bones from each side." ], + "image_url": "/images/bony_pelvis/pubis.jpg", + "annotations": [], "subbones": [ { "id": "superior_pubic_ramus", @@ -130,7 +158,9 @@ "description": [ "Projects posterolaterally from the body; contains the pectineal line.", "Obturator groove on inferior surface contributes to the obturator canal." - ] + ], + "image_url": "/images/bony_pelvis/superior_pubic_ramus.jpg", + "annotations": [] }, { "id": "inferior_pubic_ramus", @@ -138,7 +168,9 @@ "description": [ "Projects inferiorly and laterally from the body.", "Joins with the ischial ramus to form part of the obturator foramen." - ] + ], + "image_url": "/images/bony_pelvis/inferior_pubic_ramus.jpg", + "annotations": [] }, { "id": "pectineal_line", @@ -146,7 +178,9 @@ "description": [ "Prominent ridge on the superior ramus.", "Attachment for pectineus muscle, conjoint tendon, and lacunar and pectineal ligaments." - ] + ], + "image_url": "/images/bony_pelvis/pectineal_line.jpg", + "annotations": [] }, { "id": "symphyseal_surface", @@ -155,7 +189,9 @@ "Joint surface on each pubis forming a secondary cartilaginous joint (symphysis).", "Covered with hyaline cartilage and linked by the fibrocartilaginous pubic symphysis.", "Remodels during pregnancy, increasing flexibility for delivery." - ] + ], + "image_url": "/images/bony_pelvis/symphyseal_surface.jpg", + "annotations": [] }, { "id": "pubic_tubercle", @@ -163,7 +199,9 @@ "description": [ "Bony projection at the lateral end of the pubic crest.", "Attachment for the inguinal ligament." - ] + ], + "image_url": "/images/bony_pelvis/pubic_tubercle.jpg", + "annotations": [] } ] } diff --git a/boneset-api/server.js b/boneset-api/server.js index 6a6b9093..54383142 100644 --- a/boneset-api/server.js +++ b/boneset-api/server.js @@ -90,107 +90,114 @@ // console.log(`🚀 Server running on http://127.0.0.1:${PORT}`); //}); - - const express = require("express"); const axios = require("axios"); const cors = require("cors"); const path = require("path"); -const fs = require("fs/promises"); // <-- ADDED +const fs = require("fs/promises"); const app = express(); const PORT = process.env.PORT || 8000; app.use(cors()); +// Remote data (kept for existing dropdowns) const GITHUB_REPO = "https://raw.githubusercontent.com/oss-slu/DigitalBonesBox/data/DataPelvis/"; const BONESET_JSON_URL = `${GITHUB_REPO}boneset/bony_pelvis.json`; const BONES_DIR_URL = `${GITHUB_REPO}bones/`; -const DATA_DIR = path.join(__dirname, "data"); // <-- ADDED +// Local merged JSON directory +const DATA_DIR = path.join(__dirname, "data"); +// Helper to fetch JSON async function fetchJSON(url) { - try { - const response = await axios.get(url); - return response.data; - } catch (error) { - console.error(`Failed to fetch ${url}:`, error.message); - return null; - } + try { + const response = await axios.get(url); + return response.data; + } catch (error) { + console.error(`Failed to fetch ${url}:`, error.message); + return null; + } } +// Simple home app.get("/", (req, res) => { - res.json({ message: "Welcome to the Boneset API (GitHub-Integrated)" }); + res.json({ message: "Welcome to the Boneset API (GitHub-Integrated)" }); }); +// Existing combined-data endpoint (unchanged) app.get("/combined-data", async (req, res) => { - try { - const bonesetData = await fetchJSON(BONESET_JSON_URL); - if (!bonesetData) return res.status(500).json({ error: "Failed to load boneset data" }); - - const bonesets = [{ id: bonesetData.id, name: bonesetData.name }]; - const bones = []; - const subbones = []; - - for (const boneId of bonesetData.bones) { - const boneJsonUrl = `${BONES_DIR_URL}${boneId}.json`; - const boneData = await fetchJSON(boneJsonUrl); - - if (boneData) { - bones.push({ id: boneData.id, name: boneData.name, boneset: bonesetData.id }); - boneData.subBones.forEach(subBoneId => { - subbones.push({ id: subBoneId, name: subBoneId.replace(/_/g, " "), bone: boneData.id }); - }); - } - } - - res.json({ bonesets, bones, subbones }); - - } catch (error) { - console.error("Error fetching combined data:", error.message); - res.status(500).json({ error: "Internal Server Error" }); - } -}); - -// --- CORRECTED HTMX ENDPOINT --- -app.get("/api/description/", async (req, res) => { // Path changed here (no :boneId) - const { boneId } = req.query; // Changed from req.params to req.query - if (!boneId) { - return res.send(""); // Send empty response if no boneId is provided + try { + const bonesetData = await fetchJSON(BONESET_JSON_URL); + if (!bonesetData) return res.status(500).json({ error: "Failed to load boneset data" }); + + const bonesets = [{ id: bonesetData.id, name: bonesetData.name }]; + const bones = []; + const subbones = []; + + for (const boneId of bonesetData.bones) { + const boneJsonUrl = `${BONES_DIR_URL}${boneId}.json`; + const boneData = await fetchJSON(boneJsonUrl); + + if (boneData) { + bones.push({ id: boneData.id, name: boneData.name, boneset: bonesetData.id }); + boneData.subBones.forEach((subBoneId) => { + subbones.push({ id: subBoneId, name: subBoneId.replace(/_/g, " "), bone: boneData.id }); + }); + } } - const GITHUB_DESC_URL = `https://raw.githubusercontent.com/oss-slu/DigitalBonesBox/data/DataPelvis/descriptions/${boneId}_description.json`; - - try { - const response = await axios.get(GITHUB_DESC_URL); - const descriptionData = response.data; - let html = `
                                                      • ${descriptionData.name}
                                                      • `; - descriptionData.description.forEach(point => { - html += `
                                                      • ${point}
                                                      • `; - }); - res.send(html); + res.json({ bonesets, bones, subbones }); + } catch (error) { + console.error("Error fetching combined data:", error.message); + res.status(500).json({ error: "Internal Server Error" }); + } +}); - } catch (error) { - res.send("
                                                      • Description not available.
                                                      • "); - } +// HTMX description fragment (unchanged behavior) +app.get("/api/description/", async (req, res) => { + const { boneId } = req.query; + if (!boneId) { + return res.send(""); + } + const GITHUB_DESC_URL = `https://raw.githubusercontent.com/oss-slu/DigitalBonesBox/data/DataPelvis/descriptions/${boneId}_description.json`; + + try { + const response = await axios.get(GITHUB_DESC_URL); + const descriptionData = response.data; + + let html = `
                                                      • ${descriptionData.name}
                                                      • `; + descriptionData.description.forEach((point) => { + html += `
                                                      • ${point}
                                                      • `; + }); + res.send(html); + } catch (error) { + res.send("
                                                      • Description not available.
                                                      • "); + } }); -// --- ADDED: Serve merged boneset JSON from /data --- +// New: serve the merged boneset file with safe JSON parse + clear errors app.get("/api/boneset/:bonesetId", async (req, res) => { - const { bonesetId } = req.params; - const filePath = path.join(DATA_DIR, `final_${bonesetId}.json`); + const { bonesetId } = req.params; + const filePath = path.join(DATA_DIR, `final_${bonesetId}.json`); + try { + const raw = await fs.readFile(filePath, "utf8"); try { - const raw = await fs.readFile(filePath, "utf8"); - res.type("application/json").send(raw); - } catch (err) { - if (err.code === "ENOENT") { - return res.status(404).json({ error: `Boneset '${bonesetId}' not found` }); - } - console.error("Error reading boneset file:", err); - res.status(500).json({ error: "Internal Server Error" }); + const data = JSON.parse(raw); // guard against corrupt JSON + return res.json(data); + } catch (parseErr) { + console.error("Invalid JSON in file:", filePath, parseErr); + return res.status(500).json({ error: "Invalid boneset JSON" }); } + } catch (err) { + if (err.code === "ENOENT") { + return res.status(404).json({ error: `Boneset '${bonesetId}' not found` }); + } + console.error("Error reading boneset file:", err); + return res.status(500).json({ error: "Internal Server Error" }); + } }); app.listen(PORT, () => { - console.log(`🚀 Server running on http://127.0.0.1:${PORT}`); -}); \ No newline at end of file + console.log(`🚀 Server running on http://127.0.0.1:${PORT}`); +}); From a99b16bb5276a2732d66d21a6d5849652f395d75 Mon Sep 17 00:00:00 2001 From: Taktar Date: Thu, 18 Sep 2025 00:36:51 -0500 Subject: [PATCH 127/143] express-rate-limit --- boneset-api/package-lock.json | 30 +++++++- boneset-api/package.json | 3 +- boneset-api/server.js | 138 +++++++++++++++++++++++++--------- 3 files changed, 132 insertions(+), 39 deletions(-) diff --git a/boneset-api/package-lock.json b/boneset-api/package-lock.json index 90170203..e2c4edd3 100644 --- a/boneset-api/package-lock.json +++ b/boneset-api/package-lock.json @@ -12,7 +12,8 @@ "axios": "^1.8.4", "cors": "^2.8.5", "dotenv": "^16.4.7", - "express": "^4.21.2" + "express": "^4.21.2", + "express-rate-limit": "^8.1.0" } }, "node_modules/accepts": { @@ -329,6 +330,24 @@ "url": "https://opencollective.com/express" } }, + "node_modules/express-rate-limit": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.1.0.tgz", + "integrity": "sha512-4nLnATuKupnmwqiJc27b4dCFmB/T60ExgmtDD7waf4LdrbJ8CPZzZRHYErDYNhoz+ql8fUdYwM/opf90PoPAQA==", + "license": "MIT", + "dependencies": { + "ip-address": "10.0.1" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": ">= 4.11" + } + }, "node_modules/finalhandler": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", @@ -516,6 +535,15 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, + "node_modules/ip-address": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz", + "integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", diff --git a/boneset-api/package.json b/boneset-api/package.json index 050c74ba..06265763 100644 --- a/boneset-api/package.json +++ b/boneset-api/package.json @@ -13,6 +13,7 @@ "axios": "^1.8.4", "cors": "^2.8.5", "dotenv": "^16.4.7", - "express": "^4.21.2" + "express": "^4.21.2", + "express-rate-limit": "^8.1.0" } } diff --git a/boneset-api/server.js b/boneset-api/server.js index 54383142..25d57449 100644 --- a/boneset-api/server.js +++ b/boneset-api/server.js @@ -90,29 +90,42 @@ // console.log(`🚀 Server running on http://127.0.0.1:${PORT}`); //}); +// boneset-api/server.js const express = require("express"); const axios = require("axios"); const cors = require("cors"); const path = require("path"); const fs = require("fs/promises"); +const rateLimit = require("express-rate-limit"); const app = express(); const PORT = process.env.PORT || 8000; app.use(cors()); -// Remote data (kept for existing dropdowns) +// ---- Existing GitHub sources used only by /combined-data (unchanged) ---- const GITHUB_REPO = "https://raw.githubusercontent.com/oss-slu/DigitalBonesBox/data/DataPelvis/"; const BONESET_JSON_URL = `${GITHUB_REPO}boneset/bony_pelvis.json`; const BONES_DIR_URL = `${GITHUB_REPO}bones/`; -// Local merged JSON directory +// ---- Local data directory for merged files ---- const DATA_DIR = path.join(__dirname, "data"); -// Helper to fetch JSON +// ---- Simple rate limiter for FS-backed endpoints ---- +const bonesetLimiter = rateLimit({ + windowMs: 60 * 1000, // 1 minute + max: 60, // 60 requests / min / IP + standardHeaders: true, + legacyHeaders: false, +}); + +// ---- Only allow bonesets we ship locally right now ---- +const ALLOWED_BONESETS = new Set(["bony_pelvis"]); + +// ---- Helpers ---- async function fetchJSON(url) { try { - const response = await axios.get(url); + const response = await axios.get(url, { timeout: 10_000 }); return response.data; } catch (error) { console.error(`Failed to fetch ${url}:`, error.message); @@ -120,13 +133,58 @@ async function fetchJSON(url) { } } -// Simple home -app.get("/", (req, res) => { +// Ensure any resolved path stays inside DATA_DIR +function safeDataPath(fileName) { + const base = path.resolve(DATA_DIR); + const candidate = path.resolve(DATA_DIR, fileName); + if (!candidate.startsWith(base + path.sep)) { + const err = new Error("Invalid path"); + err.code = "EINVAL"; + throw err; + } + return candidate; +} + +// Tiny HTML escape +function escapeHtml(str = "") { + return String(str).replace(/[&<>"']/g, (c) => ({ + "&": "&", + "<": "<", + ">": ">", + '"': """, + "'": "'", + })[c]); +} + +// Cache the merged boneset for fast description lookups +let cachedBoneset = null; +async function loadBoneset() { + if (cachedBoneset) return cachedBoneset; + const file = safeDataPath("final_bony_pelvis.json"); + const raw = await fs.readFile(file, "utf8"); + cachedBoneset = JSON.parse(raw); + return cachedBoneset; +} + +function findNodeById(boneset, id) { + if (!boneset) return null; + for (const bone of boneset.bones || []) { + if (bone.id === id) return bone; + for (const sub of bone.subbones || []) { + if (sub.id === id) return sub; + } + } + return null; +} + +// ---- Routes ---- + +app.get("/", (_req, res) => { res.json({ message: "Welcome to the Boneset API (GitHub-Integrated)" }); }); -// Existing combined-data endpoint (unchanged) -app.get("/combined-data", async (req, res) => { +// Unchanged: used by the dropdowns in the current UI +app.get("/combined-data", async (_req, res) => { try { const bonesetData = await fetchJSON(BONESET_JSON_URL); if (!bonesetData) return res.status(500).json({ error: "Failed to load boneset data" }); @@ -138,10 +196,9 @@ app.get("/combined-data", async (req, res) => { for (const boneId of bonesetData.bones) { const boneJsonUrl = `${BONES_DIR_URL}${boneId}.json`; const boneData = await fetchJSON(boneJsonUrl); - if (boneData) { bones.push({ id: boneData.id, name: boneData.name, boneset: bonesetData.id }); - boneData.subBones.forEach((subBoneId) => { + (boneData.subBones || []).forEach((subBoneId) => { subbones.push({ id: subBoneId, name: subBoneId.replace(/_/g, " "), bone: boneData.id }); }); } @@ -154,47 +211,54 @@ app.get("/combined-data", async (req, res) => { } }); -// HTMX description fragment (unchanged behavior) -app.get("/api/description/", async (req, res) => { - const { boneId } = req.query; - if (!boneId) { - return res.send(""); +// ✅ UPDATED: serve description from the local merged JSON (no SSRF) +app.get("/api/description", bonesetLimiter, async (req, res) => { + const boneId = String(req.query.boneId || ""); + + // Basic allowlist-style validation + if (!/^[a-z0-9_]+$/.test(boneId)) { + return res.type("text/html").send(""); // same behavior as before when no/invalid boneId } - const GITHUB_DESC_URL = `https://raw.githubusercontent.com/oss-slu/DigitalBonesBox/data/DataPelvis/descriptions/${boneId}_description.json`; try { - const response = await axios.get(GITHUB_DESC_URL); - const descriptionData = response.data; - - let html = `
                                                      • ${descriptionData.name}
                                                      • `; - descriptionData.description.forEach((point) => { - html += `
                                                      • ${point}
                                                      • `; - }); - res.send(html); - } catch (error) { - res.send("
                                                      • Description not available.
                                                      • "); + const set = await loadBoneset(); + const node = findNodeById(set, boneId); + if (!node) return res.type("text/html").send(""); + + const name = node.name || boneId.replace(/_/g, " "); + const lines = Array.isArray(node.description) ? node.description : []; + + // HTMX expects an
                                                      • list fragment + let html = `
                                                      • ${escapeHtml(name)}
                                                      • `; + for (const line of lines) { + html += `
                                                      • ${escapeHtml(line)}
                                                      • `; + } + res.type("text/html").send(html); + } catch (err) { + console.error("description error:", err); + res.type("text/html").send("
                                                      • Description not available.
                                                      • "); } }); -// New: serve the merged boneset file with safe JSON parse + clear errors -app.get("/api/boneset/:bonesetId", async (req, res) => { +// ✅ Hardened: safe path + allowlist + rate limit +app.get("/api/boneset/:bonesetId", bonesetLimiter, async (req, res) => { const { bonesetId } = req.params; - const filePath = path.join(DATA_DIR, `final_${bonesetId}.json`); + + // Allow only known IDs we host locally + if (!ALLOWED_BONESETS.has(bonesetId)) { + return res.status(404).json({ error: `Boneset '${bonesetId}' not found` }); + } + try { + const filePath = safeDataPath(`final_${bonesetId}.json`); const raw = await fs.readFile(filePath, "utf8"); - try { - const data = JSON.parse(raw); // guard against corrupt JSON - return res.json(data); - } catch (parseErr) { - console.error("Invalid JSON in file:", filePath, parseErr); - return res.status(500).json({ error: "Invalid boneset JSON" }); - } + res.type("application/json").send(raw); } catch (err) { if (err.code === "ENOENT") { return res.status(404).json({ error: `Boneset '${bonesetId}' not found` }); } console.error("Error reading boneset file:", err); - return res.status(500).json({ error: "Internal Server Error" }); + res.status(500).json({ error: "Internal Server Error" }); } }); From e8fbb13f9eddc91ade3670445a7dc2987dd7d6cc Mon Sep 17 00:00:00 2001 From: Taktar Date: Thu, 18 Sep 2025 00:39:42 -0500 Subject: [PATCH 128/143] express-rate-limit --- boneset-api/server.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/boneset-api/server.js b/boneset-api/server.js index 25d57449..89b65032 100644 --- a/boneset-api/server.js +++ b/boneset-api/server.js @@ -151,7 +151,7 @@ function escapeHtml(str = "") { "&": "&", "<": "<", ">": ">", - '"': """, + "\"": """, "'": "'", })[c]); } From c3845d1ed19fe23d4649326554a949643a4794f4 Mon Sep 17 00:00:00 2001 From: strejoarciles Date: Thu, 18 Sep 2025 14:37:59 -0500 Subject: [PATCH 129/143] changes --- boneset-api/server.js | 6 +- templates/js/api.js | 41 +++++- templates/js/description.js | 8 +- templates/js/dropdowns.js | 30 ++-- templates/js/main.js | 97 ++----------- templates/js/mock-bone-data.json | 20 +++ templates/js/navigation.js | 4 +- templates/js/sidebar.js | 18 +-- templates/js/viewer.js | 104 +++++++++++++ templates/test.js | 242 ++++++++++++++++++++++++++----- 10 files changed, 416 insertions(+), 154 deletions(-) create mode 100644 templates/js/viewer.js diff --git a/boneset-api/server.js b/boneset-api/server.js index a675d0ae..f1228884 100644 --- a/boneset-api/server.js +++ b/boneset-api/server.js @@ -96,7 +96,7 @@ const express = require("express"); const axios = require("axios"); const cors = require("cors"); -const path = require('path'); +const path = require("path"); const app = express(); const PORT = process.env.PORT || 8000; @@ -154,7 +154,7 @@ app.get("/combined-data", async (req, res) => { app.get("/api/description/", async (req, res) => { // Path changed here (no :boneId) const { boneId } = req.query; // Changed from req.params to req.query if (!boneId) { - return res.send(''); // Send empty response if no boneId is provided + return res.send(""); // Send empty response if no boneId is provided } const GITHUB_DESC_URL = `https://raw.githubusercontent.com/oss-slu/DigitalBonesBox/data/DataPelvis/descriptions/${boneId}_description.json`; @@ -169,7 +169,7 @@ app.get("/api/description/", async (req, res) => { // Path changed here (no :bon res.send(html); } catch (error) { - res.send('
                                                      • Description not available.
                                                      • '); + res.send("
                                                      • Description not available.
                                                      • "); } }); diff --git a/templates/js/api.js b/templates/js/api.js index ac61551f..318a9eb7 100644 --- a/templates/js/api.js +++ b/templates/js/api.js @@ -1,15 +1,42 @@ +// api.js - Centralized API configuration and data fetching + +// Centralized API configuration +const API_CONFIG = { + BASE_URL: 'http://127.0.0.1:8000', + ENDPOINTS: { + COMBINED_DATA: '/combined-data', + MOCK_BONE_DATA: './js/mock-bone-data.json' + } +}; export async function fetchCombinedData() { - // --- CORRECTED: Use the full URL of the backend server --- - const API_URL = 'http://127.0.0.1:8000/combined-data'; + const API_URL = `${API_CONFIG.BASE_URL}${API_CONFIG.ENDPOINTS.COMBINED_DATA}`; try { const response = await fetch(API_URL); - if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } return await response.json(); } catch (error) { - console.error("Error fetching combined data:", error); - alert("Failed to load data."); - return { bonesets: [], bones: [], subbones: [] }; + console.error('Error fetching combined data:', error); + throw error; } -} \ No newline at end of file +} + +export async function fetchMockBoneData() { + try { + const response = await fetch(API_CONFIG.ENDPOINTS.MOCK_BONE_DATA); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + const data = await response.json(); + return data; + } catch (error) { + console.error('Error fetching mock bone data:', error); + return null; + } +} + +// Export configuration for other modules to use +export { API_CONFIG }; diff --git a/templates/js/description.js b/templates/js/description.js index 459056e7..6e418326 100644 --- a/templates/js/description.js +++ b/templates/js/description.js @@ -1,8 +1,8 @@ // js/description.js -const GITHUB_RAW_URL = 'https://raw.githubusercontent.com/oss-slu/DigitalBonesBox/data/DataPelvis/descriptions/'; +const GITHUB_RAW_URL = "https://raw.githubusercontent.com/oss-slu/DigitalBonesBox/data/DataPelvis/descriptions/"; export async function loadDescription(id) { - const container = document.getElementById('description-Container'); + const container = document.getElementById("description-Container"); container.innerHTML = ""; const descUrl = `${GITHUB_RAW_URL}${id}_description.json`; @@ -10,12 +10,12 @@ export async function loadDescription(id) { const response = await fetch(descUrl); const data = await response.json(); - const nameItem = document.createElement('li'); + const nameItem = document.createElement("li"); nameItem.innerHTML = `${data.name}`; container.appendChild(nameItem); data.description.forEach(point => { - const li = document.createElement('li'); + const li = document.createElement("li"); li.textContent = point; container.appendChild(li); }); diff --git a/templates/js/dropdowns.js b/templates/js/dropdowns.js index 0fbe9d2d..6f0a4b31 100644 --- a/templates/js/dropdowns.js +++ b/templates/js/dropdowns.js @@ -1,12 +1,12 @@ // js/dropdowns.js -import { loadDescription } from './description.js'; +import { loadDescription } from "./description.js"; export function populateBonesetDropdown(bonesets) { - const bonesetSelect = document.getElementById('boneset-select'); - bonesetSelect.innerHTML = ''; + const bonesetSelect = document.getElementById("boneset-select"); + bonesetSelect.innerHTML = ""; bonesets.forEach(set => { - const option = document.createElement('option'); + const option = document.createElement("option"); option.value = set.id; option.textContent = set.name; bonesetSelect.appendChild(option); @@ -14,20 +14,20 @@ export function populateBonesetDropdown(bonesets) { } export function setupDropdownListeners(combinedData) { - const bonesetSelect = document.getElementById('boneset-select'); - const boneSelect = document.getElementById('bone-select'); - const subboneSelect = document.getElementById('subbone-select'); + const bonesetSelect = document.getElementById("boneset-select"); + const boneSelect = document.getElementById("bone-select"); + const subboneSelect = document.getElementById("subbone-select"); - bonesetSelect.addEventListener('change', (e) => { + bonesetSelect.addEventListener("change", (e) => { const selectedBonesetId = e.target.value; - boneSelect.innerHTML = ''; - subboneSelect.innerHTML = ''; + boneSelect.innerHTML = ""; + subboneSelect.innerHTML = ""; subboneSelect.disabled = true; const relatedBones = combinedData.bones.filter(b => b.boneset === selectedBonesetId); relatedBones.forEach(bone => { - const option = document.createElement('option'); + const option = document.createElement("option"); option.value = bone.id; option.textContent = bone.name; boneSelect.appendChild(option); @@ -37,13 +37,13 @@ export function setupDropdownListeners(combinedData) { // if (selectedBonesetId) loadDescription(selectedBonesetId); }); - boneSelect.addEventListener('change', (e) => { + boneSelect.addEventListener("change", (e) => { const selectedBoneId = e.target.value; - subboneSelect.innerHTML = ''; + subboneSelect.innerHTML = ""; const relatedSubbones = combinedData.subbones.filter(sb => sb.bone === selectedBoneId); relatedSubbones.forEach(sb => { - const option = document.createElement('option'); + const option = document.createElement("option"); option.value = sb.id; option.textContent = sb.name; subboneSelect.appendChild(option); @@ -53,7 +53,7 @@ export function setupDropdownListeners(combinedData) { if (selectedBoneId) loadDescription(selectedBoneId); }); - subboneSelect.addEventListener('change', (e) => { + subboneSelect.addEventListener("change", (e) => { const selectedSubboneId = e.target.value; if (selectedSubboneId) loadDescription(selectedSubboneId); }); diff --git a/templates/js/main.js b/templates/js/main.js index e6a1db31..3d43e502 100644 --- a/templates/js/main.js +++ b/templates/js/main.js @@ -1,29 +1,18 @@ -import { fetchCombinedData } from './api.js'; +import { fetchCombinedData, fetchMockBoneData } from './api.js'; import { populateBonesetDropdown, setupDropdownListeners } from './dropdowns.js'; import { initializeSidebar } from './sidebar.js'; import { setupNavigation, setBoneAndSubbones, disableButtons } from './navigation.js'; import { loadDescription } from './description.js'; +import { displayBoneData, clearViewer } from './viewer.js'; let combinedData = { bonesets: [], bones: [], subbones: [] }; let mockBoneData = null; -// Mock data fetching function -async function fetchMockBoneData() { - try { - const response = await fetch('./js/mock-bone-data.json'); - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - const data = await response.json(); - return data; - } catch (error) { - console.error('Error fetching mock bone data:', error); - return null; - } -} - -// Display bone image and annotations -function displayBoneData(boneId) { +/** + * Handles bone selection from dropdown + * @param {string} boneId - The ID of the selected bone + */ +function handleBoneSelection(boneId) { if (!mockBoneData) { console.log('Mock data not available'); return; @@ -32,73 +21,19 @@ function displayBoneData(boneId) { const bone = mockBoneData.bones.find(b => b.id === boneId); if (!bone) { console.log(`No mock data found for bone: ${boneId}`); - clearBoneDisplay(); + clearViewer(); return; } - // Update the image - const boneImage = document.getElementById('bone-image'); - if (boneImage) { - boneImage.src = bone.image_url; - boneImage.alt = `${bone.name} bone image`; - boneImage.style.display = 'block'; - } - - // Display annotations - displayAnnotations(bone.annotations); -} - -// Display annotations as a list -function displayAnnotations(annotations) { - const annotationsOverlay = document.getElementById('annotations-overlay'); - if (!annotationsOverlay) { - console.error('Annotations overlay element not found'); - return; - } - - // Clear previous annotations - annotationsOverlay.innerHTML = ''; - - if (!annotations || annotations.length === 0) { - annotationsOverlay.innerHTML = '

                                                        No annotations available for this bone.

                                                        '; - return; - } - - // Create annotation list - const annotationsList = document.createElement('ul'); - annotationsList.className = 'annotations-list'; - - annotations.forEach((annotation, index) => { - const listItem = document.createElement('li'); - listItem.className = 'annotation-item'; - listItem.textContent = annotation.text; - annotationsList.appendChild(listItem); - }); - - annotationsOverlay.appendChild(annotationsList); -} - -// Clear bone display -function clearBoneDisplay() { - const boneImage = document.getElementById('bone-image'); - const annotationsOverlay = document.getElementById('annotations-overlay'); - - if (boneImage) { - boneImage.src = ''; - boneImage.alt = ''; - boneImage.style.display = 'none'; - } - - if (annotationsOverlay) { - annotationsOverlay.innerHTML = '

                                                        Select a bone to view image and annotations.

                                                        '; - } + // Use the dedicated viewer module to display the bone + displayBoneData(bone); } document.addEventListener('DOMContentLoaded', async () => { // 1. Sidebar behavior initializeSidebar(); - // 2. Load mock bone data + // 2. Load mock bone data using centralized API mockBoneData = await fetchMockBoneData(); // 3. Fetch data and populate dropdowns @@ -126,11 +61,11 @@ document.addEventListener('DOMContentLoaded', async () => { populateSubboneDropdown(subboneDropdown, relatedSubbones); disableButtons(prevButton, nextButton); - // NEW: Display mock bone data when bone is selected + // Handle bone selection using dedicated function if (selectedBone) { - displayBoneData(selectedBone); + handleBoneSelection(selectedBone); } else { - clearBoneDisplay(); + clearViewer(); } }); @@ -143,7 +78,7 @@ document.addEventListener('DOMContentLoaded', async () => { } // 7. Initialize display - clearBoneDisplay(); + clearViewer(); }); function populateSubboneDropdown(dropdown, subbones) { @@ -154,4 +89,4 @@ function populateSubboneDropdown(dropdown, subbones) { option.textContent = subboneId.replace(/_/g, ' '); dropdown.appendChild(option); }); -} \ No newline at end of file +} diff --git a/templates/js/mock-bone-data.json b/templates/js/mock-bone-data.json index 881818e1..7d50f098 100644 --- a/templates/js/mock-bone-data.json +++ b/templates/js/mock-bone-data.json @@ -18,6 +18,26 @@ "position": { "x": 350, "y": 200 } } ] + }, + { + "id": "ilium", + "name": "Ilium", + "image_url": "https://via.placeholder.com/600x400/50C878/FFFFFF?text=Ilium+Bone", + "annotations": [] + }, + { + "id": "pubis", + "name": "Pubis", + "annotations": [ + { + "text": "Pubic Symphysis - Joint where left and right pubic bones meet", + "position": { "x": 300, "y": 60 } + }, + { + "text": "Superior Pubic Ramus - Upper branch of the pubis", + "position": { "x": 250, "y": 180 } + } + ] } ] } \ No newline at end of file diff --git a/templates/js/navigation.js b/templates/js/navigation.js index fe97fe10..7e087160 100644 --- a/templates/js/navigation.js +++ b/templates/js/navigation.js @@ -3,12 +3,12 @@ let currentSubboneIndex = -1; let subbones = []; export function setupNavigation(prevButton, nextButton, subboneDropdown, updateDescription) { - prevButton.addEventListener('click', () => { + prevButton.addEventListener("click", () => { prevSubbone(); updateUI(subboneDropdown, updateDescription); }); - nextButton.addEventListener('click', () => { + nextButton.addEventListener("click", () => { nextSubbone(); updateUI(subboneDropdown, updateDescription); }); diff --git a/templates/js/sidebar.js b/templates/js/sidebar.js index c977d953..f714c888 100644 --- a/templates/js/sidebar.js +++ b/templates/js/sidebar.js @@ -1,30 +1,30 @@ // js/sidebar.js export function initializeSidebar() { - const toggleButton = document.getElementById('toggle-sidebar'); - const sidebarContainer = document.getElementById('sidebar-container'); + const toggleButton = document.getElementById("toggle-sidebar"); + const sidebarContainer = document.getElementById("sidebar-container"); async function loadSidebar() { if (!sidebarContainer.innerHTML) { try { - const response = await fetch('sidebar.html'); + const response = await fetch("sidebar.html"); const sidebarHTML = await response.text(); sidebarContainer.innerHTML = sidebarHTML; } catch (error) { - console.error('Error loading sidebar:', error); + console.error("Error loading sidebar:", error); } } } if (toggleButton) { - toggleButton.addEventListener('click', async () => { + toggleButton.addEventListener("click", async () => { await loadSidebar(); // Ensure the sidebar is loaded - const sidebarElement = document.getElementById('sidebar'); + const sidebarElement = document.getElementById("sidebar"); if (sidebarElement) { - if (sidebarElement.style.left === '0px') { - sidebarElement.style.left = '-250px'; // Close sidebar + if (sidebarElement.style.left === "0px") { + sidebarElement.style.left = "-250px"; // Close sidebar } else { - sidebarElement.style.left = '0px'; // Open sidebar + sidebarElement.style.left = "0px"; // Open sidebar } } }); diff --git a/templates/js/viewer.js b/templates/js/viewer.js new file mode 100644 index 00000000..b3af4518 --- /dev/null +++ b/templates/js/viewer.js @@ -0,0 +1,104 @@ +// viewer.js - Dedicated module for managing viewer state and display + +/** + * Displays bone image with error handling for broken URLs + * @param {Object} boneData - The bone object from mock data + */ +export function displayBoneImage(boneData) { + const boneImage = document.getElementById('bone-image'); + if (!boneImage) { + console.error('Bone image element not found'); + return; + } + + if (boneData.image_url) { + boneImage.src = boneData.image_url; + boneImage.alt = `${boneData.name} bone image`; + boneImage.style.display = 'block'; + + // Handle image load errors gracefully + boneImage.onerror = () => { + console.warn(`Failed to load image for ${boneData.name}: ${boneData.image_url}`); + boneImage.src = 'https://via.placeholder.com/600x400/CCCCCC/666666?text=Image+Load+Failed'; + boneImage.alt = `${boneData.name} - Image failed to load`; + }; + + // Clear any previous error handlers when image loads successfully + boneImage.onload = () => { + boneImage.onerror = null; + }; + } else { + // Handle missing image_url + boneImage.src = 'https://via.placeholder.com/600x400/CCCCCC/666666?text=No+Image+Available'; + boneImage.alt = `${boneData.name} - No image available`; + boneImage.style.display = 'block'; + console.warn(`No image URL provided for bone: ${boneData.name}`); + } +} + +/** + * Displays annotations list for the selected bone + * @param {Array} annotations - Array of annotation objects + */ +export function displayAnnotations(annotations) { + const annotationsOverlay = document.getElementById('annotations-overlay'); + if (!annotationsOverlay) { + console.error('Annotations overlay element not found'); + return; + } + + // Clear previous annotations + annotationsOverlay.innerHTML = ''; + + if (!annotations || annotations.length === 0) { + annotationsOverlay.innerHTML = '

                                                        No annotations available for this bone.

                                                        '; + return; + } + + // Create annotation list + const annotationsList = document.createElement('ul'); + annotationsList.className = 'annotations-list'; + + annotations.forEach((annotation) => { + const listItem = document.createElement('li'); + listItem.className = 'annotation-item'; + listItem.textContent = annotation.text; + annotationsList.appendChild(listItem); + }); + + annotationsOverlay.appendChild(annotationsList); +} + +/** + * Main function to display complete bone data (image + annotations) + * @param {Object} boneData - The complete bone object + */ +export function displayBoneData(boneData) { + if (!boneData) { + console.error('No bone data provided to display'); + return; + } + + displayBoneImage(boneData); + displayAnnotations(boneData.annotations); +} + +/** + * Clears the viewer display + */ +export function clearViewer() { + const boneImage = document.getElementById('bone-image'); + const annotationsOverlay = document.getElementById('annotations-overlay'); + + if (boneImage) { + boneImage.src = ''; + boneImage.alt = ''; + boneImage.style.display = 'none'; + boneImage.onerror = null; // Clear error handlers + boneImage.onload = null; + } + + if (annotationsOverlay) { + annotationsOverlay.innerHTML = '

                                                        Select a bone to view image and annotations.

                                                        '; + } +} \ No newline at end of file diff --git a/templates/test.js b/templates/test.js index 33810e9f..9dccb2a4 100644 --- a/templates/test.js +++ b/templates/test.js @@ -9,7 +9,7 @@ const html = ` `; document.body.innerHTML = html; -require('./sidebar.js'); // Import the JavaScript code +require("./sidebar.js"); // Import the JavaScript code beforeEach(() => { global.fetch = jest.fn(() => @@ -23,85 +23,261 @@ beforeEach(() => { }) ); - const sidebarContainer = document.getElementById('sidebar-container'); - sidebarContainer.innerHTML = ''; // Reset sidebar container + const sidebarContainer = document.getElementById("sidebar-container"); + sidebarContainer.innerHTML = ""; // Reset sidebar container - const sidebarElement = document.getElementById('sidebar'); + const sidebarElement = document.getElementById("sidebar"); if (sidebarElement) { - sidebarElement.style.left = '-250px'; // Explicitly set the initial state + sidebarElement.style.left = "-250px"; // Explicitly set the initial state } }); beforeAll(() => { - document.dispatchEvent(new Event('DOMContentLoaded')); // Simulate DOMContentLoaded + document.dispatchEvent(new Event("DOMContentLoaded")); // Simulate DOMContentLoaded }); afterEach(() => { // Reset sidebar state after each test - const sidebarElement = document.getElementById('sidebar'); + const sidebarElement = document.getElementById("sidebar"); if (sidebarElement) { - sidebarElement.style.left = '-250px'; // Ensure sidebar is closed + sidebarElement.style.left = "-250px"; // Ensure sidebar is closed } jest.restoreAllMocks(); // Clean up mocks }); -describe('Sidebar Toggle Functionality', () => { - test('loads sidebar content when the button is clicked', async () => { - const toggleButton = document.getElementById('toggle-sidebar'); +describe("Sidebar Toggle Functionality", () => { + test("loads sidebar content when the button is clicked", async () => { + const toggleButton = document.getElementById("toggle-sidebar"); toggleButton.click(); await new Promise(process.nextTick); - const sidebarElement = document.getElementById('sidebar'); + const sidebarElement = document.getElementById("sidebar"); expect(sidebarElement).toBeTruthy(); - expect(sidebarElement.innerHTML).toContain('Sidebar Content'); + expect(sidebarElement.innerHTML).toContain("Sidebar Content"); }); - test('toggles sidebar open and closed', async () => { - const toggleButton = document.getElementById('toggle-sidebar'); - const sidebarElement = document.getElementById('sidebar'); + test("toggles sidebar open and closed", async () => { + const toggleButton = document.getElementById("toggle-sidebar"); + const sidebarElement = document.getElementById("sidebar"); // Ensure the sidebar starts hidden let computedStyle = window.getComputedStyle(sidebarElement); - expect(computedStyle.left).toBe('-250px'); // Sidebar is initially hidden + expect(computedStyle.left).toBe("-250px"); // Sidebar is initially hidden // Simulate opening the sidebar toggleButton.click(); await new Promise((resolve) => setTimeout(resolve, 50)); // Wait for event propagation computedStyle = window.getComputedStyle(sidebarElement); - expect(computedStyle.left).toBe('0px'); // Sidebar is open + expect(computedStyle.left).toBe("0px"); // Sidebar is open // Simulate closing the sidebar toggleButton.click(); await new Promise((resolve) => setTimeout(resolve, 50)); // Wait for event propagation computedStyle = window.getComputedStyle(sidebarElement); - expect(computedStyle.left).toBe('-250px'); // Sidebar is closed + expect(computedStyle.left).toBe("-250px"); // Sidebar is closed }); }); -describe('Badge Button', () => { - test('badge button is present and clickable', () => { - const badgeButton = document.getElementById('toggle-sidebar'); +describe("Badge Button", () => { + test("badge button is present and clickable", () => { + const badgeButton = document.getElementById("toggle-sidebar"); expect(badgeButton).toBeTruthy(); - expect(badgeButton.tagName).toBe('BUTTON'); + expect(badgeButton.tagName).toBe("BUTTON"); }); - test('badge button has correct initial text', () => { - const badgeButton = document.getElementById('toggle-sidebar'); - expect(badgeButton.textContent).toBe('☰'); + test("badge button has correct initial text", () => { + const badgeButton = document.getElementById("toggle-sidebar"); + expect(badgeButton.textContent).toBe("☰"); }); }); -describe('Sidebar Styling', () => { - test('sidebar starts hidden by default', () => { - const sidebar = document.getElementById('sidebar'); +describe("Sidebar Styling", () => { + test("sidebar starts hidden by default", () => { + const sidebar = document.getElementById("sidebar"); expect(sidebar).toBeTruthy(); const computedStyle = window.getComputedStyle(sidebar); - expect(computedStyle.left).toBe('-250px'); // Sidebar is hidden initially + expect(computedStyle.left).toBe("-250px"); // Sidebar is hidden initially }); - test('sidebar transitions smoothly', () => { - const sidebar = document.getElementById('sidebar'); - expect(sidebar.style.transition).toContain('left 0.3s ease'); + test("sidebar transitions smoothly", () => { + const sidebar = document.getElementById("sidebar"); + expect(sidebar.style.transition).toContain("left 0.3s ease"); + }); + + + +}); + +// Add this test suite to the end of your templates/test.js file + +describe("Viewer Display Logic", () => { + let mockBoneData; + + beforeEach(() => { + // Add viewer HTML elements to the DOM + const viewerHTML = ` +
                                                        + +
                                                        +

                                                        Select a bone to view image and annotations.

                                                        +
                                                        +
                                                        + `; + document.body.innerHTML += viewerHTML; + + // Mock the bone data structure + mockBoneData = { + bones: [ + { + id: "ischium", + name: "Ischium", + image_url: "https://via.placeholder.com/600x400/4A90E2/FFFFFF?text=Ischium+Bone", + annotations: [ + { + text: "Ischial Tuberosity - Attachment point for hamstring muscles", + position: { x: 300, y: 150 } + }, + { + text: "Ischial Spine - Forms part of the lesser sciatic notch", + position: { x: 250, y: 100 } + }, + { + text: "Ischial Ramus - Forms part of the obturator foramen", + position: { x: 350, y: 200 } + } + ] + } + ] + }; + + // Mock fetch for the mock data file + global.fetch = jest.fn((url) => { + if (url.includes('mock-bone-data.json')) { + return Promise.resolve({ + ok: true, + json: () => Promise.resolve(mockBoneData) + }); + } + return Promise.reject(new Error('Not found')); + }); + }); + + afterEach(() => { + // Clean up DOM + const viewerWrapper = document.querySelector('.viewer-wrapper'); + if (viewerWrapper) { + viewerWrapper.remove(); + } + jest.restoreAllMocks(); + }); + + test("bone image src attribute is correctly updated after selection", () => { + const boneImage = document.getElementById("bone-image"); + const bone = mockBoneData.bones[0]; + + // Simulate the displayBoneData function logic + boneImage.src = bone.image_url; + boneImage.alt = `${bone.name} bone image`; + boneImage.style.display = "block"; + + expect(boneImage.src).toBe("https://via.placeholder.com/600x400/4A90E2/FFFFFF?text=Ischium+Bone"); + expect(boneImage.alt).toBe("Ischium bone image"); + expect(boneImage.style.display).toBe("block"); + }); + + test("correct number of annotation elements are created in annotations overlay", () => { + const annotationsOverlay = document.getElementById("annotations-overlay"); + const bone = mockBoneData.bones[0]; + + // Clear previous content + annotationsOverlay.innerHTML = ""; + + // Simulate the displayAnnotations function logic + const annotationsList = document.createElement("ul"); + annotationsList.className = "annotations-list"; + + bone.annotations.forEach((annotation) => { + const listItem = document.createElement("li"); + listItem.className = "annotation-item"; + listItem.textContent = annotation.text; + annotationsList.appendChild(listItem); + }); + + annotationsOverlay.appendChild(annotationsList); + + // Verify correct number of annotations + const annotationItems = annotationsOverlay.querySelectorAll(".annotation-item"); + expect(annotationItems).toHaveLength(3); + + // Verify annotation content + expect(annotationItems[0].textContent).toBe("Ischial Tuberosity - Attachment point for hamstring muscles"); + expect(annotationItems[1].textContent).toBe("Ischial Spine - Forms part of the lesser sciatic notch"); + expect(annotationItems[2].textContent).toBe("Ischial Ramus - Forms part of the obturator foramen"); + }); + + test("placeholder message is shown when no bone is selected", () => { + const boneImage = document.getElementById("bone-image"); + const annotationsOverlay = document.getElementById("annotations-overlay"); + + // Simulate clearBoneDisplay function logic + boneImage.src = ""; + boneImage.alt = ""; + boneImage.style.display = "none"; + annotationsOverlay.innerHTML = "

                                                        Select a bone to view image and annotations.

                                                        "; + + expect(boneImage.src).toBe(""); + expect(boneImage.style.display).toBe("none"); + expect(annotationsOverlay.innerHTML).toBe("

                                                        Select a bone to view image and annotations.

                                                        "); + }); + + test("handles bone with no annotations gracefully", () => { + const annotationsOverlay = document.getElementById("annotations-overlay"); + + // Simulate bone with empty annotations array + const boneWithNoAnnotations = { + id: "test_bone", + name: "Test Bone", + image_url: "test-url.jpg", + annotations: [] + }; + + // Clear previous content + annotationsOverlay.innerHTML = ""; + + // Simulate displayAnnotations with empty array + if (!boneWithNoAnnotations.annotations || boneWithNoAnnotations.annotations.length === 0) { + annotationsOverlay.innerHTML = "

                                                        No annotations available for this bone.

                                                        "; + } + + expect(annotationsOverlay.innerHTML).toBe("

                                                        No annotations available for this bone.

                                                        "); + }); + + test("annotation items have correct CSS classes", () => { + const annotationsOverlay = document.getElementById("annotations-overlay"); + const bone = mockBoneData.bones[0]; + + // Clear and populate annotations + annotationsOverlay.innerHTML = ""; + const annotationsList = document.createElement("ul"); + annotationsList.className = "annotations-list"; + + bone.annotations.forEach((annotation) => { + const listItem = document.createElement("li"); + listItem.className = "annotation-item"; + listItem.textContent = annotation.text; + annotationsList.appendChild(listItem); + }); + + annotationsOverlay.appendChild(annotationsList); + + // Verify CSS classes + const list = annotationsOverlay.querySelector("ul"); + expect(list.className).toBe("annotations-list"); + + const items = annotationsOverlay.querySelectorAll("li"); + items.forEach(item => { + expect(item.className).toBe("annotation-item"); + }); }); }); From 7b6ca59222e825615bb6e3825c93f853d001b6d2 Mon Sep 17 00:00:00 2001 From: strejoarciles Date: Thu, 18 Sep 2025 14:42:46 -0500 Subject: [PATCH 130/143] linting errors --- templates/js/api.js | 10 +++++----- templates/js/main.js | 38 +++++++++++++++++++------------------- templates/js/viewer.js | 42 +++++++++++++++++++++--------------------- templates/test.js | 6 +++--- 4 files changed, 48 insertions(+), 48 deletions(-) diff --git a/templates/js/api.js b/templates/js/api.js index 318a9eb7..2741e8d6 100644 --- a/templates/js/api.js +++ b/templates/js/api.js @@ -2,10 +2,10 @@ // Centralized API configuration const API_CONFIG = { - BASE_URL: 'http://127.0.0.1:8000', + BASE_URL: "http://127.0.0.1:8000", ENDPOINTS: { - COMBINED_DATA: '/combined-data', - MOCK_BONE_DATA: './js/mock-bone-data.json' + COMBINED_DATA: "/combined-data", + MOCK_BONE_DATA: "./js/mock-bone-data.json" } }; @@ -19,7 +19,7 @@ export async function fetchCombinedData() { } return await response.json(); } catch (error) { - console.error('Error fetching combined data:', error); + console.error("Error fetching combined data:", error); throw error; } } @@ -33,7 +33,7 @@ export async function fetchMockBoneData() { const data = await response.json(); return data; } catch (error) { - console.error('Error fetching mock bone data:', error); + console.error("Error fetching mock bone data:", error); return null; } } diff --git a/templates/js/main.js b/templates/js/main.js index 3d43e502..43df81a6 100644 --- a/templates/js/main.js +++ b/templates/js/main.js @@ -1,9 +1,9 @@ -import { fetchCombinedData, fetchMockBoneData } from './api.js'; -import { populateBonesetDropdown, setupDropdownListeners } from './dropdowns.js'; -import { initializeSidebar } from './sidebar.js'; -import { setupNavigation, setBoneAndSubbones, disableButtons } from './navigation.js'; -import { loadDescription } from './description.js'; -import { displayBoneData, clearViewer } from './viewer.js'; +import { fetchCombinedData, fetchMockBoneData } from "./api.js"; +import { populateBonesetDropdown, setupDropdownListeners } from "./dropdowns.js"; +import { initializeSidebar } from "./sidebar.js"; +import { setupNavigation, setBoneAndSubbones, disableButtons } from "./navigation.js"; +import { loadDescription } from "./description.js"; +import { displayBoneData, clearViewer } from "./viewer.js"; let combinedData = { bonesets: [], bones: [], subbones: [] }; let mockBoneData = null; @@ -14,7 +14,7 @@ let mockBoneData = null; */ function handleBoneSelection(boneId) { if (!mockBoneData) { - console.log('Mock data not available'); + console.log("Mock data not available"); return; } @@ -29,7 +29,7 @@ function handleBoneSelection(boneId) { displayBoneData(bone); } -document.addEventListener('DOMContentLoaded', async () => { +document.addEventListener("DOMContentLoaded", async () => { // 1. Sidebar behavior initializeSidebar(); @@ -42,15 +42,15 @@ document.addEventListener('DOMContentLoaded', async () => { setupDropdownListeners(combinedData); // 4. Hook up navigation buttons - const prevButton = document.getElementById('prev-button'); - const nextButton = document.getElementById('next-button'); - const subboneDropdown = document.getElementById('subbone-select'); - const boneDropdown = document.getElementById('bone-select'); + const prevButton = document.getElementById("prev-button"); + const nextButton = document.getElementById("next-button"); + const subboneDropdown = document.getElementById("subbone-select"); + const boneDropdown = document.getElementById("bone-select"); setupNavigation(prevButton, nextButton, subboneDropdown, loadDescription); // 5. Update navigation when bone changes - boneDropdown.addEventListener('change', (event) => { + boneDropdown.addEventListener("change", (event) => { const selectedBone = event.target.value; const relatedSubbones = combinedData.subbones @@ -72,9 +72,9 @@ document.addEventListener('DOMContentLoaded', async () => { // 6. Auto-select the first boneset const boneset = combinedData.bonesets[0]; if (boneset) { - document.getElementById('boneset-select').value = boneset.id; - const event = new Event('change'); - document.getElementById('boneset-select').dispatchEvent(event); + document.getElementById("boneset-select").value = boneset.id; + const event = new Event("change"); + document.getElementById("boneset-select").dispatchEvent(event); } // 7. Initialize display @@ -82,11 +82,11 @@ document.addEventListener('DOMContentLoaded', async () => { }); function populateSubboneDropdown(dropdown, subbones) { - dropdown.innerHTML = ''; + dropdown.innerHTML = ""; subbones.forEach((subboneId) => { - const option = document.createElement('option'); + const option = document.createElement("option"); option.value = subboneId; - option.textContent = subboneId.replace(/_/g, ' '); + option.textContent = subboneId.replace(/_/g, " "); dropdown.appendChild(option); }); } diff --git a/templates/js/viewer.js b/templates/js/viewer.js index b3af4518..5280be97 100644 --- a/templates/js/viewer.js +++ b/templates/js/viewer.js @@ -5,21 +5,21 @@ * @param {Object} boneData - The bone object from mock data */ export function displayBoneImage(boneData) { - const boneImage = document.getElementById('bone-image'); + const boneImage = document.getElementById("bone-image"); if (!boneImage) { - console.error('Bone image element not found'); + console.error("Bone image element not found"); return; } if (boneData.image_url) { boneImage.src = boneData.image_url; boneImage.alt = `${boneData.name} bone image`; - boneImage.style.display = 'block'; + boneImage.style.display = "block"; // Handle image load errors gracefully boneImage.onerror = () => { console.warn(`Failed to load image for ${boneData.name}: ${boneData.image_url}`); - boneImage.src = 'https://via.placeholder.com/600x400/CCCCCC/666666?text=Image+Load+Failed'; + boneImage.src = "https://via.placeholder.com/600x400/CCCCCC/666666?text=Image+Load+Failed"; boneImage.alt = `${boneData.name} - Image failed to load`; }; @@ -29,9 +29,9 @@ export function displayBoneImage(boneData) { }; } else { // Handle missing image_url - boneImage.src = 'https://via.placeholder.com/600x400/CCCCCC/666666?text=No+Image+Available'; + boneImage.src = "https://via.placeholder.com/600x400/CCCCCC/666666?text=No+Image+Available"; boneImage.alt = `${boneData.name} - No image available`; - boneImage.style.display = 'block'; + boneImage.style.display = "block"; console.warn(`No image URL provided for bone: ${boneData.name}`); } } @@ -41,27 +41,27 @@ export function displayBoneImage(boneData) { * @param {Array} annotations - Array of annotation objects */ export function displayAnnotations(annotations) { - const annotationsOverlay = document.getElementById('annotations-overlay'); + const annotationsOverlay = document.getElementById("annotations-overlay"); if (!annotationsOverlay) { - console.error('Annotations overlay element not found'); + console.error("Annotations overlay element not found"); return; } // Clear previous annotations - annotationsOverlay.innerHTML = ''; + annotationsOverlay.innerHTML = ""; if (!annotations || annotations.length === 0) { - annotationsOverlay.innerHTML = '

                                                        No annotations available for this bone.

                                                        '; + annotationsOverlay.innerHTML = "

                                                        No annotations available for this bone.

                                                        "; return; } // Create annotation list - const annotationsList = document.createElement('ul'); - annotationsList.className = 'annotations-list'; + const annotationsList = document.createElement("ul"); + annotationsList.className = "annotations-list"; annotations.forEach((annotation) => { - const listItem = document.createElement('li'); - listItem.className = 'annotation-item'; + const listItem = document.createElement("li"); + listItem.className = "annotation-item"; listItem.textContent = annotation.text; annotationsList.appendChild(listItem); }); @@ -75,7 +75,7 @@ export function displayAnnotations(annotations) { */ export function displayBoneData(boneData) { if (!boneData) { - console.error('No bone data provided to display'); + console.error("No bone data provided to display"); return; } @@ -87,18 +87,18 @@ export function displayBoneData(boneData) { * Clears the viewer display */ export function clearViewer() { - const boneImage = document.getElementById('bone-image'); - const annotationsOverlay = document.getElementById('annotations-overlay'); + const boneImage = document.getElementById("bone-image"); + const annotationsOverlay = document.getElementById("annotations-overlay"); if (boneImage) { - boneImage.src = ''; - boneImage.alt = ''; - boneImage.style.display = 'none'; + boneImage.src = ""; + boneImage.alt = ""; + boneImage.style.display = "none"; boneImage.onerror = null; // Clear error handlers boneImage.onload = null; } if (annotationsOverlay) { - annotationsOverlay.innerHTML = '

                                                        Select a bone to view image and annotations.

                                                        '; + annotationsOverlay.innerHTML = "

                                                        Select a bone to view image and annotations.

                                                        "; } } \ No newline at end of file diff --git a/templates/test.js b/templates/test.js index 9dccb2a4..643b28ab 100644 --- a/templates/test.js +++ b/templates/test.js @@ -153,19 +153,19 @@ describe("Viewer Display Logic", () => { // Mock fetch for the mock data file global.fetch = jest.fn((url) => { - if (url.includes('mock-bone-data.json')) { + if (url.includes("mock-bone-data.json")) { return Promise.resolve({ ok: true, json: () => Promise.resolve(mockBoneData) }); } - return Promise.reject(new Error('Not found')); + return Promise.reject(new Error("Not found")); }); }); afterEach(() => { // Clean up DOM - const viewerWrapper = document.querySelector('.viewer-wrapper'); + const viewerWrapper = document.querySelector(".viewer-wrapper"); if (viewerWrapper) { viewerWrapper.remove(); } From ed0220e3fb2f8125504fd89261fd340292a51675 Mon Sep 17 00:00:00 2001 From: Jenni Oishee Date: Thu, 18 Sep 2025 23:53:31 -0500 Subject: [PATCH 131/143] Submitting Changes --- boneset-api/package-lock.json | 24 ++++++++++++++++-------- boneset-api/server.js | 6 +++--- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/boneset-api/package-lock.json b/boneset-api/package-lock.json index 90170203..431b06c4 100644 --- a/boneset-api/package-lock.json +++ b/boneset-api/package-lock.json @@ -35,15 +35,17 @@ "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" }, "node_modules/axios": { - "version": "1.8.4", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz", - "integrity": "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.12.2.tgz", + "integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==", + "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", - "form-data": "^4.0.0", + "form-data": "^4.0.4", "proxy-from-env": "^1.1.0" } }, @@ -109,6 +111,7 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", "dependencies": { "delayed-stream": "~1.0.0" }, @@ -172,6 +175,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", "engines": { "node": ">=0.4.0" } @@ -261,6 +265,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", @@ -366,13 +371,15 @@ } }, "node_modules/form-data": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", - "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", "mime-types": "^2.1.12" }, "engines": { @@ -464,6 +471,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", "dependencies": { "has-symbols": "^1.0.3" }, diff --git a/boneset-api/server.js b/boneset-api/server.js index a675d0ae..0a63526c 100644 --- a/boneset-api/server.js +++ b/boneset-api/server.js @@ -96,7 +96,7 @@ const express = require("express"); const axios = require("axios"); const cors = require("cors"); -const path = require('path'); +const path = require("path"); const app = express(); const PORT = process.env.PORT || 8000; @@ -154,7 +154,7 @@ app.get("/combined-data", async (req, res) => { app.get("/api/description/", async (req, res) => { // Path changed here (no :boneId) const { boneId } = req.query; // Changed from req.params to req.query if (!boneId) { - return res.send(''); // Send empty response if no boneId is provided + return res.send(" "); // Send empty response if no boneId is provided } const GITHUB_DESC_URL = `https://raw.githubusercontent.com/oss-slu/DigitalBonesBox/data/DataPelvis/descriptions/${boneId}_description.json`; @@ -169,7 +169,7 @@ app.get("/api/description/", async (req, res) => { // Path changed here (no :bon res.send(html); } catch (error) { - res.send('
                                                      • Description not available.
                                                      • '); + res.send("
                                                      • Description not available.
                                                      • "); } }); From cbd19476f12eeb8a528488f654aa9c634b4941a0 Mon Sep 17 00:00:00 2001 From: Jenni Oishee Date: Fri, 19 Sep 2025 00:00:43 -0500 Subject: [PATCH 132/143] Submitting new changes --- templates/boneset.html | 13 +---- templates/helpButton.html | 43 ++++++++++++++ templates/js/api.js | 2 +- templates/js/description.js | 8 +-- templates/js/dropdowns.js | 4 +- templates/js/sidebar.js | 59 +++++++++++++++---- templates/style.css | 111 +++++++++++++++++++++++++++++------- templates/test.js | 56 ++++++++++++++++++ 8 files changed, 245 insertions(+), 51 deletions(-) create mode 100644 templates/helpButton.html diff --git a/templates/boneset.html b/templates/boneset.html index 83013d33..f02169e6 100644 --- a/templates/boneset.html +++ b/templates/boneset.html @@ -16,7 +16,7 @@ Home Tutor Study - Help +
                                                        Login Sign Up @@ -78,16 +78,9 @@

                                                        Description

                                                        + -
                                                        -
                                                        -

                                                        How to Use

                                                        -

                                                        - Use the dropfown menus to select a bonset, bone, or sub-bone.
                                                        - Once selected, you can view the image and description of the selected item.
                                                        -

                                                        - -
                                                        + \ No newline at end of file diff --git a/templates/helpButton.html b/templates/helpButton.html new file mode 100644 index 00000000..8694784d --- /dev/null +++ b/templates/helpButton.html @@ -0,0 +1,43 @@ + + Help + + + \ No newline at end of file diff --git a/templates/js/api.js b/templates/js/api.js index ac61551f..2493d257 100644 --- a/templates/js/api.js +++ b/templates/js/api.js @@ -1,7 +1,7 @@ export async function fetchCombinedData() { // --- CORRECTED: Use the full URL of the backend server --- - const API_URL = 'http://127.0.0.1:8000/combined-data'; + const API_URL = "http://127.0.0.1:8000/combined-data"; try { const response = await fetch(API_URL); diff --git a/templates/js/description.js b/templates/js/description.js index 459056e7..6e418326 100644 --- a/templates/js/description.js +++ b/templates/js/description.js @@ -1,8 +1,8 @@ // js/description.js -const GITHUB_RAW_URL = 'https://raw.githubusercontent.com/oss-slu/DigitalBonesBox/data/DataPelvis/descriptions/'; +const GITHUB_RAW_URL = "https://raw.githubusercontent.com/oss-slu/DigitalBonesBox/data/DataPelvis/descriptions/"; export async function loadDescription(id) { - const container = document.getElementById('description-Container'); + const container = document.getElementById("description-Container"); container.innerHTML = ""; const descUrl = `${GITHUB_RAW_URL}${id}_description.json`; @@ -10,12 +10,12 @@ export async function loadDescription(id) { const response = await fetch(descUrl); const data = await response.json(); - const nameItem = document.createElement('li'); + const nameItem = document.createElement("li"); nameItem.innerHTML = `${data.name}`; container.appendChild(nameItem); data.description.forEach(point => { - const li = document.createElement('li'); + const li = document.createElement("li"); li.textContent = point; container.appendChild(li); }); diff --git a/templates/js/dropdowns.js b/templates/js/dropdowns.js index 0fbe9d2d..0678172b 100644 --- a/templates/js/dropdowns.js +++ b/templates/js/dropdowns.js @@ -1,8 +1,8 @@ // js/dropdowns.js -import { loadDescription } from './description.js'; +import { loadDescription } from "./description.js"; export function populateBonesetDropdown(bonesets) { - const bonesetSelect = document.getElementById('boneset-select'); + const bonesetSelect = document.getElementById("boneset-select"); bonesetSelect.innerHTML = ''; bonesets.forEach(set => { diff --git a/templates/js/sidebar.js b/templates/js/sidebar.js index d908d35b..bd254c03 100644 --- a/templates/js/sidebar.js +++ b/templates/js/sidebar.js @@ -30,17 +30,52 @@ export function initializeSidebar() { }); } } -document.addEventListener('DOMContentLoaded', () => { - const helpButton = document.getElementById('text-button-Help'); - const helpModal = document.getElementById('help-modal'); - const closeHelpModal = document.getElementById('close-help-modal'); - if (helpButton && helpModal && closeHelpModal) { - helpButton.addEventListener('click', () => { - helpModal.style.display = 'flex'; - }); - closeHelpModal.addEventListener('click', () => { - helpModal.style.display = 'none'; - }); + +export async function loadHelpButton() { + const helpButtonContainer = document.getElementById('help-button-container'); + if (helpButtonContainer) { + try { + const response = await fetch('helpButton.html'); + const helpButtonHTML = await response.text(); + helpButtonContainer.innerHTML = helpButtonHTML; + + const helpButton = document.getElementById('text-button-Help'); + const helpModal = document.getElementById('help-modal'); + const closeHelpModal = document.getElementById('close-help-modal'); + + if (helpButton && helpModal && closeHelpModal) { + // Handle click events + helpButton.addEventListener('click', () => { + helpModal.classList.add('is-visible'); + }); + + closeHelpModal.addEventListener('click', () => { + helpModal.classList.remove('is-visible'); + }); + + // Handle keyboard events + helpButton.addEventListener('keydown', (event) => { + if (event.key === 'Enter' || event.key === ' ') { + event.preventDefault(); + helpModal.classList.add('is-visible'); + } + }); + + // Close on escape key + document.addEventListener('keydown', (event) => { + if (event.key === 'Escape' && helpModal.classList.contains('is-visible')) { + helpModal.classList.remove('is-visible'); + } + }); + } + } catch (error) { + console.error('Error loading help button:', error); + } } -}) +} + +document.addEventListener('DOMContentLoaded', () => { + initializeSidebar(); + loadHelpButton(); +}); diff --git a/templates/style.css b/templates/style.css index cc4a9ccb..120a278b 100644 --- a/templates/style.css +++ b/templates/style.css @@ -201,40 +201,107 @@ ul li { pointer-events: none; /* So clicks pass through to the image */ } -#help-modal { - display: none; - position: fixed; +.help-modal { + position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; - background: rgba(0, 0, 0, 0.4); - z-index: 2000; + background: rgba(0, 0, 0, 0.85); + display: none; + z-index: 1000; justify-content: center; align-items: center; + padding: 1.5rem; + backdrop-filter: blur(4px); + transition: opacity 0.3s ease; } -#help-modal-context { - background: #fff; - padding: 2em 1.5em; - border-radius: 10px; - max-width: 400px; - margin: auto; - box-shadow: 0 4px 24px rgba(0, 0, 0, 0.2); - text-align: center; - position: relative; +.help-modal.is-visible { + display: flex; + animation: modalFadeIn 0.3s ease; +} + +.help-modal-content { + background: linear-gradient(to bottom, #ffffff, #f8f9fa); + padding: 1rem; + border-radius: 12px; + width: 90%; + max-width: 500px; + max-height: 85vh; + overflow-y: auto; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2); +} + +#help-modal-title { + color: #1a4b8c; + font-size: 1.4rem; + margin-bottom: 0.5rem; + border-bottom: 2px solid #e9ecef; + padding-bottom: 0.5rem; +} + +.help-content { + padding: 0; +} + +.help-content p { + margin-bottom: 0.75rem; + line-height: 1.4; + color: #1a4b8c; +} + +.help-content ul { + margin: 0; + padding-left: 1rem; +} + +.help-content li { + color: #1a4b8c; + margin-bottom: 0.4rem; + line-height: 1.3; } #close-help-modal { - margin-top: 1em; - padding: 8px 20px; - background-color: #007bff; - color: #fff; - border: none; - border-radius: 4px; - cursor: pointer; + margin-top: 1rem; + padding: 0.5rem 1.25rem; + width: auto; + min-width: 120px; } #close-help-modal:hover { - background-color: #0056b3; + background-color: #0d2d5e; + transform: translateY(-1px); + box-shadow: 0 4px 12px rgba(26, 75, 140, 0.2); } + +#close-help-modal:focus { + outline: none; + box-shadow: 0 0 0 3px rgba(26, 75, 140, 0.4); +} + +@keyframes modalFadeIn { + from { opacity: 0; } + to { opacity: 1; } +} + +@keyframes modalSlideUp { + from { + transform: translateY(20px); + opacity: 0; + } + to { + transform: translateY(0); + opacity: 1; + } +} + +/* High contrast mode support */ +@media (forced-colors: active) { + .help-modal-content { + border: 2px solid CanvasText; + } + #close-help-modal { + border: 2px solid CanvasText; + } +} \ No newline at end of file diff --git a/templates/test.js b/templates/test.js index 33810e9f..8339039a 100644 --- a/templates/test.js +++ b/templates/test.js @@ -105,3 +105,59 @@ describe('Sidebar Styling', () => { expect(sidebar.style.transition).toContain('left 0.3s ease'); }); }); + +describe('Help Modal Functionality', () => { + beforeEach(() => { + // Add help button and modal HTML to the test environment + document.body.innerHTML += ` + Help +
                                                        +
                                                        + +
                                                        +
                                                        + `; + }); + + test('modal is hidden by default', () => { + const helpModal = document.getElementById('help-modal'); + expect(helpModal.classList.contains('is-visible')).toBeFalsy(); + const computedStyle = window.getComputedStyle(helpModal); + expect(computedStyle.display).not.toBe('flex'); + }); + + test('modal becomes visible when Help button is clicked', () => { + const helpButton = document.getElementById('text-button-Help'); + const helpModal = document.getElementById('help-modal'); + + helpButton.click(); + + expect(helpModal.classList.contains('is-visible')).toBeTruthy(); + }); + + test('modal becomes hidden when Close button is clicked', () => { + const helpModal = document.getElementById('help-modal'); + const closeButton = document.getElementById('close-help-modal'); + + // First make modal visible + helpModal.classList.add('is-visible'); + + // Then click close button + closeButton.click(); + + expect(helpModal.classList.contains('is-visible')).toBeFalsy(); + }); + + test('modal closes when Escape key is pressed', () => { + const helpModal = document.getElementById('help-modal'); + + // First make modal visible + helpModal.classList.add('is-visible'); + + // Simulate pressing Escape key + const escapeKeyEvent = new KeyboardEvent('keydown', { key: 'Escape' }); + document.dispatchEvent(escapeKeyEvent); + + expect(helpModal.classList.contains('is-visible')).toBeFalsy(); + }); +}); From 4994dabe162bcb670a1b827cd5768d6721cd0ca4 Mon Sep 17 00:00:00 2001 From: Jenni Oishee Date: Fri, 19 Sep 2025 00:10:12 -0500 Subject: [PATCH 133/143] Submitting new changes --- templates/js/dropdowns.js | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/templates/js/dropdowns.js b/templates/js/dropdowns.js index 0678172b..9842b883 100644 --- a/templates/js/dropdowns.js +++ b/templates/js/dropdowns.js @@ -3,10 +3,9 @@ import { loadDescription } from "./description.js"; export function populateBonesetDropdown(bonesets) { const bonesetSelect = document.getElementById("boneset-select"); - bonesetSelect.innerHTML = ''; - - bonesets.forEach(set => { - const option = document.createElement('option'); + bonesetSelect.innerHTML = ""; + bonesets.forEach(set => { + const option = document.createElement("option"); option.value = set.id; option.textContent = set.name; bonesetSelect.appendChild(option); @@ -14,20 +13,20 @@ export function populateBonesetDropdown(bonesets) { } export function setupDropdownListeners(combinedData) { - const bonesetSelect = document.getElementById('boneset-select'); - const boneSelect = document.getElementById('bone-select'); - const subboneSelect = document.getElementById('subbone-select'); + const bonesetSelect = document.getElementById("boneset-select"); + const boneSelect = document.getElementById("bone-select"); + const subboneSelect = document.getElementById("subbone-select"); - bonesetSelect.addEventListener('change', (e) => { + bonesetSelect.addEventListener("change", (e) => { const selectedBonesetId = e.target.value; - boneSelect.innerHTML = ''; - subboneSelect.innerHTML = ''; + boneSelect.innerHTML = ""; + subboneSelect.innerHTML = ""; subboneSelect.disabled = true; const relatedBones = combinedData.bones.filter(b => b.boneset === selectedBonesetId); relatedBones.forEach(bone => { - const option = document.createElement('option'); + const option = document.createElement("option"); option.value = bone.id; option.textContent = bone.name; boneSelect.appendChild(option); @@ -37,13 +36,13 @@ export function setupDropdownListeners(combinedData) { // if (selectedBonesetId) loadDescription(selectedBonesetId); }); - boneSelect.addEventListener('change', (e) => { + boneSelect.addEventListener("change", (e) => { const selectedBoneId = e.target.value; - subboneSelect.innerHTML = ''; + subboneSelect.innerHTML = ""; const relatedSubbones = combinedData.subbones.filter(sb => sb.bone === selectedBoneId); relatedSubbones.forEach(sb => { - const option = document.createElement('option'); + const option = document.createElement("option"); option.value = sb.id; option.textContent = sb.name; subboneSelect.appendChild(option); @@ -53,7 +52,7 @@ export function setupDropdownListeners(combinedData) { if (selectedBoneId) loadDescription(selectedBoneId); }); - subboneSelect.addEventListener('change', (e) => { + subboneSelect.addEventListener("change", (e) => { const selectedSubboneId = e.target.value; if (selectedSubboneId) loadDescription(selectedSubboneId); }); From 56999052cfd0deb2372f9a19480103569cfedb84 Mon Sep 17 00:00:00 2001 From: Jenni Oishee Date: Fri, 19 Sep 2025 00:13:47 -0500 Subject: [PATCH 134/143] Submit --- templates/js/main.js | 34 ++++++------ templates/js/navigation.js | 4 +- templates/js/sidebar.js | 52 +++++++++---------- templates/test.js | 104 ++++++++++++++++++------------------- 4 files changed, 97 insertions(+), 97 deletions(-) diff --git a/templates/js/main.js b/templates/js/main.js index 240e416a..ecc4139c 100644 --- a/templates/js/main.js +++ b/templates/js/main.js @@ -1,12 +1,12 @@ -import { fetchCombinedData } from './api.js'; -import { populateBonesetDropdown, setupDropdownListeners } from './dropdowns.js'; -import { initializeSidebar } from './sidebar.js'; -import { setupNavigation, setBoneAndSubbones, disableButtons } from './navigation.js'; -import { loadDescription } from './description.js'; // ✅ CORRECT function name +import { fetchCombinedData } from "./api.js"; +import { populateBonesetDropdown, setupDropdownListeners } from "./dropdowns.js"; +import { initializeSidebar } from "./sidebar.js"; +import { setupNavigation, setBoneAndSubbones, disableButtons } from "./navigation.js"; +import { loadDescription } from "./description.js"; // ✅ CORRECT function name let combinedData = { bonesets: [], bones: [], subbones: [] }; -document.addEventListener('DOMContentLoaded', async () => { +document.addEventListener("DOMContentLoaded", async () => { // 1. Sidebar behavior initializeSidebar(); @@ -16,15 +16,15 @@ document.addEventListener('DOMContentLoaded', async () => { setupDropdownListeners(combinedData); // 3. Hook up navigation buttons - const prevButton = document.getElementById('prev-button'); - const nextButton = document.getElementById('next-button'); - const subboneDropdown = document.getElementById('subbone-select'); - const boneDropdown = document.getElementById('bone-select'); + const prevButton = document.getElementById("prev-button"); + const nextButton = document.getElementById("next-button"); + const subboneDropdown = document.getElementById("subbone-select"); + const boneDropdown = document.getElementById("bone-select"); setupNavigation(prevButton, nextButton, subboneDropdown, loadDescription); // 4. Update navigation when bone changes - boneDropdown.addEventListener('change', (event) => { + boneDropdown.addEventListener("change", (event) => { const selectedBone = event.target.value; const relatedSubbones = combinedData.subbones @@ -39,18 +39,18 @@ document.addEventListener('DOMContentLoaded', async () => { // 5. Auto-select the first boneset const boneset = combinedData.bonesets[0]; if (boneset) { - document.getElementById('boneset-select').value = boneset.id; - const event = new Event('change'); - document.getElementById('boneset-select').dispatchEvent(event); + document.getElementById("boneset-select").value = boneset.id; + const event = new Event("change"); + document.getElementById("boneset-select").dispatchEvent(event); } }); function populateSubboneDropdown(dropdown, subbones) { - dropdown.innerHTML = ''; + dropdown.innerHTML = ""; subbones.forEach((subboneId) => { - const option = document.createElement('option'); + const option = document.createElement("option"); option.value = subboneId; - option.textContent = subboneId.replace(/_/g, ' '); + option.textContent = subboneId.replace(/_/g, " "); dropdown.appendChild(option); }); } diff --git a/templates/js/navigation.js b/templates/js/navigation.js index fe97fe10..7e087160 100644 --- a/templates/js/navigation.js +++ b/templates/js/navigation.js @@ -3,12 +3,12 @@ let currentSubboneIndex = -1; let subbones = []; export function setupNavigation(prevButton, nextButton, subboneDropdown, updateDescription) { - prevButton.addEventListener('click', () => { + prevButton.addEventListener("click", () => { prevSubbone(); updateUI(subboneDropdown, updateDescription); }); - nextButton.addEventListener('click', () => { + nextButton.addEventListener("click", () => { nextSubbone(); updateUI(subboneDropdown, updateDescription); }); diff --git a/templates/js/sidebar.js b/templates/js/sidebar.js index bd254c03..1ac2f11d 100644 --- a/templates/js/sidebar.js +++ b/templates/js/sidebar.js @@ -1,30 +1,30 @@ // js/sidebar.js export function initializeSidebar() { - const toggleButton = document.getElementById('toggle-sidebar'); - const sidebarContainer = document.getElementById('sidebar-container'); + const toggleButton = document.getElementById("toggle-sidebar"); + const sidebarContainer = document.getElementById("sidebar-container"); async function loadSidebar() { if (!sidebarContainer.innerHTML) { try { - const response = await fetch('sidebar.html'); + const response = await fetch("sidebar.html"); const sidebarHTML = await response.text(); sidebarContainer.innerHTML = sidebarHTML; } catch (error) { - console.error('Error loading sidebar:', error); + console.error("Error loading sidebar:", error); } } } if (toggleButton) { - toggleButton.addEventListener('click', async () => { + toggleButton.addEventListener("click", async () => { await loadSidebar(); // Ensure the sidebar is loaded - const sidebarElement = document.getElementById('sidebar'); + const sidebarElement = document.getElementById("sidebar"); if (sidebarElement) { - if (sidebarElement.style.left === '0px') { - sidebarElement.style.left = '-250px'; // Close sidebar + if (sidebarElement.style.left === "0px") { + sidebarElement.style.left = "-250px"; // Close sidebar } else { - sidebarElement.style.left = '0px'; // Open sidebar + sidebarElement.style.left = "0px"; // Open sidebar } } }); @@ -32,49 +32,49 @@ export function initializeSidebar() { } export async function loadHelpButton() { - const helpButtonContainer = document.getElementById('help-button-container'); + const helpButtonContainer = document.getElementById("help-button-container"); if (helpButtonContainer) { try { - const response = await fetch('helpButton.html'); + const response = await fetch("helpButton.html"); const helpButtonHTML = await response.text(); helpButtonContainer.innerHTML = helpButtonHTML; - const helpButton = document.getElementById('text-button-Help'); - const helpModal = document.getElementById('help-modal'); - const closeHelpModal = document.getElementById('close-help-modal'); + const helpButton = document.getElementById("text-button-Help"); + const helpModal = document.getElementById("help-modal"); + const closeHelpModal = document.getElementById("close-help-modal"); if (helpButton && helpModal && closeHelpModal) { // Handle click events - helpButton.addEventListener('click', () => { - helpModal.classList.add('is-visible'); + helpButton.addEventListener("click", () => { + helpModal.classList.add("is-visible"); }); - closeHelpModal.addEventListener('click', () => { - helpModal.classList.remove('is-visible'); + closeHelpModal.addEventListener("click", () => { + helpModal.classList.remove("is-visible"); }); // Handle keyboard events - helpButton.addEventListener('keydown', (event) => { - if (event.key === 'Enter' || event.key === ' ') { + helpButton.addEventListener("keydown", (event) => { + if (event.key === "Enter" || event.key === " ") { event.preventDefault(); - helpModal.classList.add('is-visible'); + helpModal.classList.add("is-visible"); } }); // Close on escape key - document.addEventListener('keydown', (event) => { - if (event.key === 'Escape' && helpModal.classList.contains('is-visible')) { - helpModal.classList.remove('is-visible'); + document.addEventListener("keydown", (event) => { + if (event.key === "Escape" && helpModal.classList.contains("is-visible")) { + helpModal.classList.remove("is-visible"); } }); } } catch (error) { - console.error('Error loading help button:', error); + console.error("Error loading help button:", error); } } } -document.addEventListener('DOMContentLoaded', () => { +document.addEventListener("DOMContentLoaded", () => { initializeSidebar(); loadHelpButton(); }); diff --git a/templates/test.js b/templates/test.js index 8339039a..864e10d5 100644 --- a/templates/test.js +++ b/templates/test.js @@ -9,7 +9,7 @@ const html = ` `; document.body.innerHTML = html; -require('./sidebar.js'); // Import the JavaScript code +require("./sidebar.js"); // Import the JavaScript code beforeEach(() => { global.fetch = jest.fn(() => @@ -23,90 +23,90 @@ beforeEach(() => { }) ); - const sidebarContainer = document.getElementById('sidebar-container'); - sidebarContainer.innerHTML = ''; // Reset sidebar container + const sidebarContainer = document.getElementById("sidebar-container"); + sidebarContainer.innerHTML = ""; // Reset sidebar container - const sidebarElement = document.getElementById('sidebar'); + const sidebarElement = document.getElementById("sidebar"); if (sidebarElement) { - sidebarElement.style.left = '-250px'; // Explicitly set the initial state + sidebarElement.style.left = "-250px"; // Explicitly set the initial state } }); beforeAll(() => { - document.dispatchEvent(new Event('DOMContentLoaded')); // Simulate DOMContentLoaded + document.dispatchEvent(new Event("DOMContentLoaded")); // Simulate DOMContentLoaded }); afterEach(() => { // Reset sidebar state after each test - const sidebarElement = document.getElementById('sidebar'); + const sidebarElement = document.getElementById("sidebar"); if (sidebarElement) { - sidebarElement.style.left = '-250px'; // Ensure sidebar is closed + sidebarElement.style.left = "-250px"; // Ensure sidebar is closed } jest.restoreAllMocks(); // Clean up mocks }); -describe('Sidebar Toggle Functionality', () => { - test('loads sidebar content when the button is clicked', async () => { - const toggleButton = document.getElementById('toggle-sidebar'); +describe("Sidebar Toggle Functionality", () => { + test("loads sidebar content when the button is clicked", async () => { + const toggleButton = document.getElementById("toggle-sidebar"); toggleButton.click(); await new Promise(process.nextTick); - const sidebarElement = document.getElementById('sidebar'); + const sidebarElement = document.getElementById("sidebar"); expect(sidebarElement).toBeTruthy(); - expect(sidebarElement.innerHTML).toContain('Sidebar Content'); + expect(sidebarElement.innerHTML).toContain("Sidebar Content"); }); - test('toggles sidebar open and closed', async () => { - const toggleButton = document.getElementById('toggle-sidebar'); - const sidebarElement = document.getElementById('sidebar'); + test("toggles sidebar open and closed", async () => { + const toggleButton = document.getElementById("toggle-sidebar"); + const sidebarElement = document.getElementById("sidebar"); // Ensure the sidebar starts hidden let computedStyle = window.getComputedStyle(sidebarElement); - expect(computedStyle.left).toBe('-250px'); // Sidebar is initially hidden + expect(computedStyle.left).toBe("-250px"); // Sidebar is initially hidden // Simulate opening the sidebar toggleButton.click(); await new Promise((resolve) => setTimeout(resolve, 50)); // Wait for event propagation computedStyle = window.getComputedStyle(sidebarElement); - expect(computedStyle.left).toBe('0px'); // Sidebar is open + expect(computedStyle.left).toBe("0px"); // Sidebar is open // Simulate closing the sidebar toggleButton.click(); await new Promise((resolve) => setTimeout(resolve, 50)); // Wait for event propagation computedStyle = window.getComputedStyle(sidebarElement); - expect(computedStyle.left).toBe('-250px'); // Sidebar is closed + expect(computedStyle.left).toBe("-250px"); // Sidebar is closed }); }); -describe('Badge Button', () => { - test('badge button is present and clickable', () => { - const badgeButton = document.getElementById('toggle-sidebar'); +describe("Badge Button", () => { + test("badge button is present and clickable", () => { + const badgeButton = document.getElementById("toggle-sidebar"); expect(badgeButton).toBeTruthy(); - expect(badgeButton.tagName).toBe('BUTTON'); + expect(badgeButton.tagName).toBe("BUTTON"); }); - test('badge button has correct initial text', () => { - const badgeButton = document.getElementById('toggle-sidebar'); - expect(badgeButton.textContent).toBe('☰'); + test("badge button has correct initial text", () => { + const badgeButton = document.getElementById("toggle-sidebar"); + expect(badgeButton.textContent).toBe("☰"); }); }); -describe('Sidebar Styling', () => { - test('sidebar starts hidden by default', () => { - const sidebar = document.getElementById('sidebar'); +describe("Sidebar Styling", () => { + test("sidebar starts hidden by default", () => { + const sidebar = document.getElementById("sidebar"); expect(sidebar).toBeTruthy(); const computedStyle = window.getComputedStyle(sidebar); - expect(computedStyle.left).toBe('-250px'); // Sidebar is hidden initially + expect(computedStyle.left).toBe("-250px"); // Sidebar is hidden initially }); - test('sidebar transitions smoothly', () => { - const sidebar = document.getElementById('sidebar'); - expect(sidebar.style.transition).toContain('left 0.3s ease'); + test("sidebar transitions smoothly", () => { + const sidebar = document.getElementById("sidebar"); + expect(sidebar.style.transition).toContain("left 0.3s ease"); }); }); -describe('Help Modal Functionality', () => { +describe("Help Modal Functionality", () => { beforeEach(() => { // Add help button and modal HTML to the test environment document.body.innerHTML += ` @@ -119,45 +119,45 @@ describe('Help Modal Functionality', () => { `; }); - test('modal is hidden by default', () => { - const helpModal = document.getElementById('help-modal'); - expect(helpModal.classList.contains('is-visible')).toBeFalsy(); + test("modal is hidden by default", () => { + const helpModal = document.getElementById("help-modal"); + expect(helpModal.classList.contains("is-visible")).toBeFalsy(); const computedStyle = window.getComputedStyle(helpModal); - expect(computedStyle.display).not.toBe('flex'); + expect(computedStyle.display).not.toBe("flex"); }); - test('modal becomes visible when Help button is clicked', () => { - const helpButton = document.getElementById('text-button-Help'); - const helpModal = document.getElementById('help-modal'); + test("modal becomes visible when Help button is clicked", () => { + const helpButton = document.getElementById("text-button-Help"); + const helpModal = document.getElementById("help-modal"); helpButton.click(); - expect(helpModal.classList.contains('is-visible')).toBeTruthy(); + expect(helpModal.classList.contains("is-visible")).toBeTruthy(); }); - test('modal becomes hidden when Close button is clicked', () => { - const helpModal = document.getElementById('help-modal'); - const closeButton = document.getElementById('close-help-modal'); + test("modal becomes hidden when Close button is clicked", () => { + const helpModal = document.getElementById("help-modal"); + const closeButton = document.getElementById("close-help-modal"); // First make modal visible - helpModal.classList.add('is-visible'); + helpModal.classList.add("is-visible"); // Then click close button closeButton.click(); - expect(helpModal.classList.contains('is-visible')).toBeFalsy(); + expect(helpModal.classList.contains("is-visible")).toBeFalsy(); }); - test('modal closes when Escape key is pressed', () => { - const helpModal = document.getElementById('help-modal'); + test("modal closes when Escape key is pressed", () => { + const helpModal = document.getElementById("help-modal"); // First make modal visible - helpModal.classList.add('is-visible'); + helpModal.classList.add("is-visible"); // Simulate pressing Escape key - const escapeKeyEvent = new KeyboardEvent('keydown', { key: 'Escape' }); + const escapeKeyEvent = new KeyboardEvent("keydown", { key: "Escape" }); document.dispatchEvent(escapeKeyEvent); - expect(helpModal.classList.contains('is-visible')).toBeFalsy(); + expect(helpModal.classList.contains("is-visible")).toBeFalsy(); }); }); From 716add488fbf25864b1378ae693b51ad840b2d86 Mon Sep 17 00:00:00 2001 From: strejoarciles Date: Sat, 20 Sep 2025 11:56:24 -0500 Subject: [PATCH 135/143] resolve merge conflicts --- boneset-api/server.js | 24 ++---------------------- 1 file changed, 2 insertions(+), 22 deletions(-) diff --git a/boneset-api/server.js b/boneset-api/server.js index f1228884..97b2170e 100644 --- a/boneset-api/server.js +++ b/boneset-api/server.js @@ -89,24 +89,16 @@ //app.listen(PORT, () => { // console.log(`🚀 Server running on http://127.0.0.1:${PORT}`); //}); - - - - const express = require("express"); const axios = require("axios"); const cors = require("cors"); const path = require("path"); - const app = express(); const PORT = process.env.PORT || 8000; - app.use(cors()); - const GITHUB_REPO = "https://raw.githubusercontent.com/oss-slu/DigitalBonesBox/data/DataPelvis/"; const BONESET_JSON_URL = `${GITHUB_REPO}boneset/bony_pelvis.json`; const BONES_DIR_URL = `${GITHUB_REPO}bones/`; - async function fetchJSON(url) { try { const response = await axios.get(url); @@ -116,24 +108,19 @@ async function fetchJSON(url) { return null; } } - app.get("/", (req, res) => { res.json({ message: "Welcome to the Boneset API (GitHub-Integrated)" }); }); - app.get("/combined-data", async (req, res) => { try { const bonesetData = await fetchJSON(BONESET_JSON_URL); if (!bonesetData) return res.status(500).json({ error: "Failed to load boneset data" }); - const bonesets = [{ id: bonesetData.id, name: bonesetData.name }]; const bones = []; const subbones = []; - for (const boneId of bonesetData.bones) { const boneJsonUrl = `${BONES_DIR_URL}${boneId}.json`; const boneData = await fetchJSON(boneJsonUrl); - if (boneData) { bones.push({ id: boneData.id, name: boneData.name, boneset: bonesetData.id }); boneData.subBones.forEach(subBoneId => { @@ -141,38 +128,31 @@ app.get("/combined-data", async (req, res) => { }); } } - res.json({ bonesets, bones, subbones }); - } catch (error) { console.error("Error fetching combined data:", error.message); res.status(500).json({ error: "Internal Server Error" }); } }); - // --- CORRECTED HTMX ENDPOINT --- app.get("/api/description/", async (req, res) => { // Path changed here (no :boneId) const { boneId } = req.query; // Changed from req.params to req.query if (!boneId) { - return res.send(""); // Send empty response if no boneId is provided + return res.send(" "); // Send empty response if no boneId is provided } const GITHUB_DESC_URL = `https://raw.githubusercontent.com/oss-slu/DigitalBonesBox/data/DataPelvis/descriptions/${boneId}_description.json`; - try { const response = await axios.get(GITHUB_DESC_URL); const descriptionData = response.data; - let html = `
                                                      • ${descriptionData.name}
                                                      • `; descriptionData.description.forEach(point => { html += `
                                                      • ${point}
                                                      • `; }); res.send(html); - } catch (error) { res.send("
                                                      • Description not available.
                                                      • "); } }); - app.listen(PORT, () => { console.log(`🚀 Server running on http://127.0.0.1:${PORT}`); -}); \ No newline at end of file +}); From 6cc84039a698d0eb6fec9482d974c7df77a13e2d Mon Sep 17 00:00:00 2001 From: Taktar <157086064+Taktar@users.noreply.github.com> Date: Sat, 20 Sep 2025 15:35:42 -0500 Subject: [PATCH 136/143] Update server.js --- boneset-api/server.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/boneset-api/server.js b/boneset-api/server.js index c92813eb..a91c63d3 100644 --- a/boneset-api/server.js +++ b/boneset-api/server.js @@ -147,14 +147,16 @@ function safeDataPath(fileName) { // Tiny HTML escape function escapeHtml(str = "") { - return String(str).replace(/[&<>"]/g, (c) => ({ + return String(str).replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", - '"': """, + "\"": """, // double quote + "'": "'", // apostrophe })[c]); } + // Cache the merged boneset for fast description lookups let cachedBoneset = null; async function loadBoneset() { From 89068e22050a58ae251f4492ba66da0163c9b5f3 Mon Sep 17 00:00:00 2001 From: Wendy Onwuagana Date: Sun, 21 Sep 2025 19:27:23 -0500 Subject: [PATCH 137/143] resolve error --- templates/test.js | 66 ++++++++++++++++++++++++----------------------- 1 file changed, 34 insertions(+), 32 deletions(-) diff --git a/templates/test.js b/templates/test.js index 5b216860..d6a8d6d0 100644 --- a/templates/test.js +++ b/templates/test.js @@ -105,14 +105,14 @@ describe("Sidebar Styling", () => { expect(sidebar.style.transition).toContain("left 0.3s ease"); }); - + }); // testing for viewer display describe("Viewer Display Logic", () => { let mockBoneData; - + beforeEach(() => { // Add viewer HTML elements to the DOM const viewerHTML = ` @@ -124,7 +124,7 @@ describe("Viewer Display Logic", () => {
                                                        `; document.body.innerHTML += viewerHTML; - + // Mock the bone data structure mockBoneData = { bones: [ @@ -149,7 +149,7 @@ describe("Viewer Display Logic", () => { } ] }; - + // Mock fetch for the mock data file global.fetch = jest.fn((url) => { if (url.includes("mock-bone-data.json")) { @@ -174,12 +174,12 @@ describe("Viewer Display Logic", () => { test("bone image src attribute is correctly updated after selection", () => { const boneImage = document.getElementById("bone-image"); const bone = mockBoneData.bones[0]; - + // Simulate the displayBoneData function logic boneImage.src = bone.image_url; boneImage.alt = `${bone.name} bone image`; boneImage.style.display = "block"; - + expect(boneImage.src).toBe("https://via.placeholder.com/600x400/4A90E2/FFFFFF?text=Ischium+Bone"); expect(boneImage.alt).toBe("Ischium bone image"); expect(boneImage.style.display).toBe("block"); @@ -188,27 +188,27 @@ describe("Viewer Display Logic", () => { test("correct number of annotation elements are created in annotations overlay", () => { const annotationsOverlay = document.getElementById("annotations-overlay"); const bone = mockBoneData.bones[0]; - + // Clear previous content annotationsOverlay.innerHTML = ""; - + // Simulate the displayAnnotations function logic const annotationsList = document.createElement("ul"); annotationsList.className = "annotations-list"; - + bone.annotations.forEach((annotation) => { const listItem = document.createElement("li"); listItem.className = "annotation-item"; listItem.textContent = annotation.text; annotationsList.appendChild(listItem); }); - + annotationsOverlay.appendChild(annotationsList); - + // Verify correct number of annotations const annotationItems = annotationsOverlay.querySelectorAll(".annotation-item"); expect(annotationItems).toHaveLength(3); - + // Verify annotation content expect(annotationItems[0].textContent).toBe("Ischial Tuberosity - Attachment point for hamstring muscles"); expect(annotationItems[1].textContent).toBe("Ischial Spine - Forms part of the lesser sciatic notch"); @@ -218,13 +218,13 @@ describe("Viewer Display Logic", () => { test("placeholder message is shown when no bone is selected", () => { const boneImage = document.getElementById("bone-image"); const annotationsOverlay = document.getElementById("annotations-overlay"); - + // Simulate clearBoneDisplay function logic boneImage.src = ""; boneImage.alt = ""; boneImage.style.display = "none"; annotationsOverlay.innerHTML = "

                                                        Select a bone to view image and annotations.

                                                        "; - + expect(boneImage.src).toBe(""); expect(boneImage.style.display).toBe("none"); expect(annotationsOverlay.innerHTML).toBe("

                                                        Select a bone to view image and annotations.

                                                        "); @@ -232,7 +232,7 @@ describe("Viewer Display Logic", () => { test("handles bone with no annotations gracefully", () => { const annotationsOverlay = document.getElementById("annotations-overlay"); - + // Simulate bone with empty annotations array const boneWithNoAnnotations = { id: "test_bone", @@ -240,45 +240,47 @@ describe("Viewer Display Logic", () => { image_url: "test-url.jpg", annotations: [] }; - + // Clear previous content annotationsOverlay.innerHTML = ""; - + // Simulate displayAnnotations with empty array if (!boneWithNoAnnotations.annotations || boneWithNoAnnotations.annotations.length === 0) { annotationsOverlay.innerHTML = "

                                                        No annotations available for this bone.

                                                        "; } - + expect(annotationsOverlay.innerHTML).toBe("

                                                        No annotations available for this bone.

                                                        "); }); test("annotation items have correct CSS classes", () => { const annotationsOverlay = document.getElementById("annotations-overlay"); const bone = mockBoneData.bones[0]; - + // Clear and populate annotations annotationsOverlay.innerHTML = ""; const annotationsList = document.createElement("ul"); annotationsList.className = "annotations-list"; - + bone.annotations.forEach((annotation) => { const listItem = document.createElement("li"); listItem.className = "annotation-item"; listItem.textContent = annotation.text; annotationsList.appendChild(listItem); }); - + annotationsOverlay.appendChild(annotationsList); - + // Verify CSS classes const list = annotationsOverlay.querySelector("ul"); expect(list.className).toBe("annotations-list"); - + const items = annotationsOverlay.querySelectorAll("li"); items.forEach(item => { expect(item.className).toBe("annotation-item"); }); -======= + + }); + }); describe("Help Modal Functionality", () => { @@ -304,35 +306,35 @@ describe("Help Modal Functionality", () => { test("modal becomes visible when Help button is clicked", () => { const helpButton = document.getElementById("text-button-Help"); const helpModal = document.getElementById("help-modal"); - + helpButton.click(); - + expect(helpModal.classList.contains("is-visible")).toBeTruthy(); }); test("modal becomes hidden when Close button is clicked", () => { const helpModal = document.getElementById("help-modal"); const closeButton = document.getElementById("close-help-modal"); - + // First make modal visible helpModal.classList.add("is-visible"); - + // Then click close button closeButton.click(); - + expect(helpModal.classList.contains("is-visible")).toBeFalsy(); }); test("modal closes when Escape key is pressed", () => { const helpModal = document.getElementById("help-modal"); - + // First make modal visible helpModal.classList.add("is-visible"); - + // Simulate pressing Escape key const escapeKeyEvent = new KeyboardEvent("keydown", { key: "Escape" }); document.dispatchEvent(escapeKeyEvent); - + expect(helpModal.classList.contains("is-visible")).toBeFalsy(); }); From 38220d773f4201789c2708be51ab99d017c284da Mon Sep 17 00:00:00 2001 From: Taktar Date: Sun, 21 Sep 2025 22:24:54 -0500 Subject: [PATCH 138/143] feat: add merge script + update final_bony_pelvis.json with annotations (incl. Acetabulum) --- boneset-api/data/final_bony_pelvis.json | 127 +++++++++++++++++++-- data_extraction/slide3_annotations.json | 5 +- merge_annotations_into_final.py | 145 ++++++++++++++++++++++++ 3 files changed, 263 insertions(+), 14 deletions(-) create mode 100644 merge_annotations_into_final.py diff --git a/boneset-api/data/final_bony_pelvis.json b/boneset-api/data/final_bony_pelvis.json index e1364b82..cb79eb95 100644 --- a/boneset-api/data/final_bony_pelvis.json +++ b/boneset-api/data/final_bony_pelvis.json @@ -10,7 +10,17 @@ "Articulates with the sacrum to form the sacroiliac joint." ], "image_url": "/images/bony_pelvis/ilium.jpg", - "annotations": [], + "annotations": [ + { + "text": "Ilium", + "position": { + "x": 5586937, + "y": 2083816, + "width": 393457, + "height": 215444 + } + } + ], "subbones": [ { "id": "iliac_crest", @@ -22,8 +32,35 @@ "Surface landmark for L4 level; used for lumbar puncture." ], "image_url": "/images/bony_pelvis/iliac_crest.jpg", - "annotations": [] + "annotations": [ + { + "text": "Iliac Crest", + "position": { + "x": 5431116, + "y": 1912315, + "width": 595035, + "height": 215444 + } + } + ] + }, + + { + "id": "acetabulum", + "name": "Acetabulum", + "description": [ + "Deep hemispherical socket of the hip bone.", + "Formed by the ilium (superior), ischium (posteroinferior), and pubis (anteroinferior); receives the head of the femur." + ], + "image_url": "/images/bony_pelvis/acetabulum.jpg", + "annotations": [ + { + "text": "Acetabulum", + "position": { "x": 5450756, "y": 3572102, "width": 692939, "height": 215444 } + } + ] }, + { "id": "anterior_superior_iliac_spine", "name": "Anterior Superior Iliac Spine", @@ -32,7 +69,17 @@ "Attachment for inguinal ligament and sartorius origin." ], "image_url": "/images/bony_pelvis/anterior_superior_iliac_spine.jpg", - "annotations": [] + "annotations": [ + { + "text": "Anterior Superior Iliac Spine", + "position": { + "x": 5407752, + "y": 2590801, + "width": 766897, + "height": 461665 + } + } + ] }, { "id": "anterior_inferior_iliac_spine", @@ -42,7 +89,17 @@ "Attachment for straight head of rectus femoris and iliofemoral ligament." ], "image_url": "/images/bony_pelvis/anterior_inferior_iliac_spine.jpg", - "annotations": [] + "annotations": [ + { + "text": "Anterior Inferior Iliac Spine", + "position": { + "x": 5271485, + "y": 3229154, + "width": 766897, + "height": 461665 + } + } + ] }, { "id": "posterior_superior_iliac_spine", @@ -52,7 +109,17 @@ "Attachment for posterior sacroiliac ligaments." ], "image_url": "/images/bony_pelvis/posterior_superior_iliac_spine.jpg", - "annotations": [] + "annotations": [ + { + "text": "Posterior Superior Iliac Spine", + "position": { + "x": 7881241, + "y": 1445569, + "width": 766897, + "height": 461665 + } + } + ] }, { "id": "posterior_inferior_iliac_spine", @@ -61,7 +128,17 @@ "Small prominence along the border of the greater sciatic notch." ], "image_url": "/images/bony_pelvis/posterior_inferior_iliac_spine.jpg", - "annotations": [] + "annotations": [ + { + "text": "Posterior Inferior Iliac Spine", + "position": { + "x": 7887969, + "y": 3816698, + "width": 766897, + "height": 461665 + } + } + ] }, { "id": "auricular_surface", @@ -71,7 +148,17 @@ "Articulates with the lateral surface of the sacrum at the sacroiliac joint." ], "image_url": "/images/bony_pelvis/auricular_surface.jpg", - "annotations": [] + "annotations": [ + { + "text": "Auricular surface", + "position": { + "x": 7169365, + "y": 1417638, + "width": 624749, + "height": 338554 + } + } + ] } ] }, @@ -83,7 +170,17 @@ "Situated posterior to the pubis and inferior to the ilium." ], "image_url": "/images/bony_pelvis/ischium.jpg", - "annotations": [], + "annotations": [ + { + "text": "Ischium", + "position": { + "x": 5446767, + "y": 5263629, + "width": 507320, + "height": 215444 + } + } + ], "subbones": [ { "id": "ischial_ramus", @@ -150,7 +247,17 @@ "Connects the two pelvic bones from each side." ], "image_url": "/images/bony_pelvis/pubis.jpg", - "annotations": [], + "annotations": [ + { + "text": "Pubis", + "position": { + "x": 5682077, + "y": 4259252, + "width": 419318, + "height": 215444 + } + } + ], "subbones": [ { "id": "superior_pubic_ramus", @@ -206,4 +313,4 @@ ] } ] -} +} \ No newline at end of file diff --git a/data_extraction/slide3_annotations.json b/data_extraction/slide3_annotations.json index 81652878..8ad01e76 100644 --- a/data_extraction/slide3_annotations.json +++ b/data_extraction/slide3_annotations.json @@ -223,10 +223,7 @@ "height": "311497" } }, - { - "shape": "line", - - }, + { "text": "Radius", "position": { diff --git a/merge_annotations_into_final.py b/merge_annotations_into_final.py new file mode 100644 index 00000000..5745cd97 --- /dev/null +++ b/merge_annotations_into_final.py @@ -0,0 +1,145 @@ +#!/usr/bin/env python3 +""" +merge_annotations_into_final.py + +Merges label annotations extracted from slides into the merged boneset JSON: +- Reads boneset-api/data/final_bony_pelvis.json +- Scans data_extraction/*annotations*.json for label items +- For each annotation with a non-empty "text" and a "position" object, + finds the matching bone or sub-bone by id or name and appends the annotation. + +Run from the repository root: + python3 merge_annotations_into_final.py +""" + +import os +import re +import json +import glob + +# ---- Paths --------------------------------------------------------------- + +FINAL_JSON = os.path.join("boneset-api", "data", "final_bony_pelvis.json") +ANNO_DIR = "data_extraction" + +# ---- Normalization / matching helpers ------------------------------------ + +# Optional aliases if the slide labels use a shorter/common term +ALIASES = { + "ramus": "ischial_ramus", # slide might say just "Ramus" + "asis": "anterior_superior_iliac_spine", + "aiis": "anterior_inferior_iliac_spine", + "psis": "posterior_superior_iliac_spine", + "piis": "posterior_inferior_iliac_spine", +} + +def norm(s: str) -> str: + """Normalize ids/names/labels to snake_case-ish for matching.""" + s = str(s).strip().lower() + s = s.replace("’", "'") + s = re.sub(r"[\s\-]+", "_", s) # spaces/hyphens -> underscores + s = re.sub(r"[^a-z0-9_]+", "", s) # drop other punctuation + return s + +def as_int(v): + """Convert numeric-like strings to int; otherwise return the input.""" + try: + # handles "123", "123.0", 123, etc. + return int(float(v)) + except (TypeError, ValueError): + return v + +# ---- Load current final JSON -------------------------------------------- + +if not os.path.exists(FINAL_JSON): + raise SystemExit(f"[error] Missing file: {FINAL_JSON}") + +with open(FINAL_JSON, "r") as f: + data = json.load(f) + +# ---- Index bones and sub-bones by id and name --------------------------- + +bones_by_key = {} +subs_by_key = {} + +for b in data.get("bones", []) or []: + if b.get("id"): bones_by_key[norm(b["id"])] = b + if b.get("name"): bones_by_key[norm(b["name"])] = b + for sb in b.get("subbones", []) or []: + if sb.get("id"): subs_by_key[norm(sb["id"])] = sb + if sb.get("name"): subs_by_key[norm(sb["name"])] = sb + +def find_target_by_text(text: str): + """Find the bone/sub-bone node matching this label text.""" + k = norm(text) + k = ALIASES.get(k, k) + return bones_by_key.get(k) or subs_by_key.get(k) + +def add_annotation(node: dict, ann: dict) -> bool: + """Append annotation if not already present (simple de-dup).""" + node.setdefault("annotations", []) + if ann not in node["annotations"]: + node["annotations"].append(ann) + return True + return False + +# ---- Walk annotation files and merge ------------------------------------ + +added = dup = miss = files = 0 + +for path in sorted(glob.glob(os.path.join(ANNO_DIR, "*annotations*.json"))): + files += 1 + try: + with open(path, "r") as f: + payload = json.load(f) + except Exception as e: + print(f"[skip] {path}: {e}") + continue + + # payload can be a list or a dict with {"annotations": [...]} + items = payload.get("annotations") if isinstance(payload, dict) else payload + if not isinstance(items, list): + print(f"[skip] {path}: unexpected payload shape") + continue + + for item in items: + if not isinstance(item, dict): + continue + + text = (item.get("text") or "").strip() + pos = item.get("position") + + # We only merge actual labeled annotations with a position + if not text or not isinstance(pos, dict): + continue + + tgt = find_target_by_text(text) + if not tgt: + miss += 1 + print(f"[miss] {text!r} in {os.path.basename(path)}") + continue + + ann = { + "text": text, + "position": { + "x": as_int(pos.get("x")), + "y": as_int(pos.get("y")), + }, + } + if "width" in pos and "height" in pos: + ann["position"]["width"] = as_int(pos["width"]) + ann["position"]["height"] = as_int(pos["height"]) + + if add_annotation(tgt, ann): + added += 1 + print(f"[add] {text!r} -> {tgt.get('id')}") + else: + dup += 1 + print(f"[dup] {text!r} -> {tgt.get('id')}") + +# ---- Write back final JSON ---------------------------------------------- + +with open(FINAL_JSON, "w") as f: + json.dump(data, f, indent=2) + +print(f"[done] files={files} added={added} dup={dup} miss={miss} -> {FINAL_JSON}") From a7d8668d4bc0c997272f05b80fe7f1ba6731ccc8 Mon Sep 17 00:00:00 2001 From: Jenni Oishee Date: Sun, 28 Sep 2025 23:13:56 -0500 Subject: [PATCH 139/143] Submitting --- boneset-api/server.js | 265 +++++++++++++++++++++++++----------------- 1 file changed, 156 insertions(+), 109 deletions(-) diff --git a/boneset-api/server.js b/boneset-api/server.js index e060ce80..adf31667 100644 --- a/boneset-api/server.js +++ b/boneset-api/server.js @@ -103,7 +103,6 @@ const PORT = process.env.PORT || 8000; app.use(cors()); -// ---- Existing GitHub sources used only by /combined-data (unchanged) ---- const GITHUB_REPO = "https://raw.githubusercontent.com/oss-slu/DigitalBonesBox/data/DataPelvis/"; const BONESET_JSON_URL = `${GITHUB_REPO}boneset/bony_pelvis.json`; const BONES_DIR_URL = `${GITHUB_REPO}bones/`; @@ -113,10 +112,10 @@ const DATA_DIR = path.join(__dirname, "data"); // ---- Simple rate limiter for FS-backed endpoints ---- const bonesetLimiter = rateLimit({ - windowMs: 60 * 1000, // 1 minute - max: 60, // 60 requests / min / IP - standardHeaders: true, - legacyHeaders: false, + windowMs: 60 * 1000, // 1 minute + max: 60, // 60 requests / min / IP + standardHeaders: true, + legacyHeaders: false, }); // ---- Only allow bonesets we ship locally right now ---- @@ -124,143 +123,191 @@ const ALLOWED_BONESETS = new Set(["bony_pelvis"]); // ---- Helpers ---- async function fetchJSON(url) { - try { - const response = await axios.get(url, { timeout: 10_000 }); - return response.data; - } catch (error) { - console.error(`Failed to fetch ${url}:`, error.message); - return null; - } + try { + const response = await axios.get(url, { timeout: 10_000 }); + return response.data; + } catch (error) { + console.error(`Failed to fetch ${url}:`, error.message); + return null; + } } // Ensure any resolved path stays inside DATA_DIR function safeDataPath(fileName) { - const base = path.resolve(DATA_DIR); - const candidate = path.resolve(DATA_DIR, fileName); - if (!candidate.startsWith(base + path.sep)) { - const err = new Error("Invalid path"); - err.code = "EINVAL"; - throw err; - } - return candidate; + const base = path.resolve(DATA_DIR); + const candidate = path.resolve(DATA_DIR, fileName); + if (!candidate.startsWith(base + path.sep)) { + const err = new Error("Invalid path"); + err.code = "EINVAL"; + throw err; + } + return candidate; } -// Tiny HTML escape (double-quotes everywhere for ESLint) function escapeHtml(str = "") { - return String(str).replace(/[&<>"']/g, (c) => ({ - "&": "&", - "<": "<", - ">": ">", - "\"": """, - "'": "'", - })[c]); + return String(str).replace(/[&<>"']/g, (c) => ({ + "&": "&", + "<": "<", + ">": ">", + "\"": """, + "'": "'", + })[c]); } // Cache the merged boneset for fast description lookups let cachedBoneset = null; async function loadBoneset() { - if (cachedBoneset) return cachedBoneset; - const file = safeDataPath("final_bony_pelvis.json"); - const raw = await fs.readFile(file, "utf8"); - cachedBoneset = JSON.parse(raw); - return cachedBoneset; + if (cachedBoneset) return cachedBoneset; + const file = safeDataPath("final_bony_pelvis.json"); + const raw = await fs.readFile(file, "utf8"); + cachedBoneset = JSON.parse(raw); + return cachedBoneset; } function findNodeById(boneset, id) { - if (!boneset) return null; - for (const bone of boneset.bones || []) { - if (bone.id === id) return bone; - for (const sub of bone.subbones || []) { - if (sub.id === id) return sub; + if (!boneset) return null; + for (const bone of boneset.bones || []) { + if (bone.id === id) return bone; + for (const sub of bone.subbones || []) { + if (sub.id === id) return sub; + } } - } - return null; + return null; } -// ---- Routes ---- app.get("/", (_req, res) => { - res.json({ message: "Welcome to the Boneset API (GitHub-Integrated)" }); + res.json({ message: "Welcome to the Boneset API (GitHub-Integrated)" }); }); -// Unchanged: used by the dropdowns in the current UI app.get("/combined-data", async (_req, res) => { - try { - const bonesetData = await fetchJSON(BONESET_JSON_URL); - if (!bonesetData) return res.status(500).json({ error: "Failed to load boneset data" }); - - const bonesets = [{ id: bonesetData.id, name: bonesetData.name }]; - const bones = []; - const subbones = []; - - for (const boneId of bonesetData.bones) { - const boneJsonUrl = `${BONES_DIR_URL}${boneId}.json`; - const boneData = await fetchJSON(boneJsonUrl); - if (boneData) { - bones.push({ id: boneData.id, name: boneData.name, boneset: bonesetData.id }); - (boneData.subBones || []).forEach((subBoneId) => { - subbones.push({ id: subBoneId, name: subBoneId.replace(/_/g, " "), bone: boneData.id }); - }); - } - } + try { + const bonesetData = await fetchJSON(BONESET_JSON_URL); + if (!bonesetData) return res.status(500).json({ error: "Failed to load boneset data" }); - res.json({ bonesets, bones, subbones }); - } catch (error) { - console.error("Error fetching combined data:", error.message); - res.status(500).json({ error: "Internal Server Error" }); - } -}); + const bonesets = [{ id: bonesetData.id, name: bonesetData.name }]; + const bones = []; + const subbones = []; -// Serve description from the local merged JSON (no SSRF) -app.get("/api/description", bonesetLimiter, async (req, res) => { - const boneId = String(req.query.boneId || ""); + for (const boneId of bonesetData.bones) { + const boneJsonUrl = `${BONES_DIR_URL}${boneId}.json`; + const boneData = await fetchJSON(boneJsonUrl); + if (boneData) { + bones.push({ id: boneData.id, name: boneData.name, boneset: bonesetData.id }); + (boneData.subBones || []).forEach((subBoneId) => { + subbones.push({ id: subBoneId, name: subBoneId.replace(/_/g, " "), bone: boneData.id }); + }); + } + } - // Basic allowlist-style validation - if (!/^[a-z0-9_]+$/.test(boneId)) { - return res.type("text/html").send(""); - } + res.json({ bonesets, bones, subbones }); + } catch (error) { + console.error("Error fetching combined data:", error.message); + res.status(500).json({ error: "Internal Server Error" }); + } +}); - try { - const set = await loadBoneset(); - const node = findNodeById(set, boneId); - if (!node) return res.type("text/html").send(""); +app.get("/api/description/", async (req, res) => { + const { boneId } = req.query; + if (!boneId) { + return res.send(" "); + } + const GITHUB_DESC_URL = `https://raw.githubusercontent.com/oss-slu/DigitalBonesBox/data/DataPelvis/descriptions/${boneId}_description.json`; - const name = node.name || boneId.replace(/_/g, " "); - const lines = Array.isArray(node.description) ? node.description : []; + try { + const response = await axios.get(GITHUB_DESC_URL); + const descriptionData = response.data; - // HTMX expects an
                                                      • list fragment - let html = `
                                                      • ${escapeHtml(name)}
                                                      • `; - for (const line of lines) { - html += `
                                                      • ${escapeHtml(line)}
                                                      • `; + let html = `
                                                      • ${descriptionData.name}
                                                      • `; + descriptionData.description.forEach(point => { + html += `
                                                      • ${point}
                                                      • `; + }); + res.send(html); + + } catch (error) { + res.send("
                                                      • Description not available.
                                                      • "); } - res.type("text/html").send(html); - } catch (err) { - console.error("description error:", err); - res.type("text/html").send("
                                                      • Description not available.
                                                      • "); - } }); -// Safe path + allowlist + rate limit -app.get("/api/boneset/:bonesetId", bonesetLimiter, async (req, res) => { - const { bonesetId } = req.params; - - if (!ALLOWED_BONESETS.has(bonesetId)) { - return res.status(404).json({ error: `Boneset '${bonesetId}' not found` }); - } - - try { - const filePath = safeDataPath(`final_${bonesetId}.json`); - const raw = await fs.readFile(filePath, "utf8"); - res.type("application/json").send(raw); - } catch (err) { - if (err.code === "ENOENT") { - return res.status(404).json({ error: `Boneset '${bonesetId}' not found` }); +app.get("/api/search", async (req, res) => { + const query = req.query.q; + + if (!query || query.trim() === "") { + return res.send("
                                                      • Enter a search term
                                                      • "); + } + + const searchTerm = query.toLowerCase().trim(); + + try { + const bonesetData = await fetchJSON(BONESET_JSON_URL); + if (!bonesetData) { + return res.send("
                                                      • No data available
                                                      • "); + } + + const results = []; + + // Search boneset name + if (bonesetData.name && bonesetData.name.toLowerCase().includes(searchTerm)) { + results.push({ + type: "boneset", + id: bonesetData.id, + name: bonesetData.name + }); + } + + // Search through bones + if (bonesetData.bones) { + for (const boneId of bonesetData.bones) { + const boneData = await fetchJSON(`${BONES_DIR_URL}${boneId}.json`); + + if (boneData) { + // Search bone name + if (boneData.name && boneData.name.toLowerCase().includes(searchTerm)) { + results.push({ + type: "bone", + id: boneData.id, + name: boneData.name + }); + } + + // Search sub-bones + if (boneData.subBones) { + for (const subBoneId of boneData.subBones) { + const subBoneName = subBoneId.replace(/_/g, " "); + if (subBoneName.toLowerCase().includes(searchTerm)) { + results.push({ + type: "subbone", + id: subBoneId, + name: subBoneName, + parentBone: boneData.id + }); + } + } + } + } + } + } + + // Format results as HTML + if (results.length === 0) { + return res.send("
                                                      • No results found
                                                      • "); + } + + let html = ""; + results.forEach(result => { + const escapedName = result.name.replace(/"/g, """).replace(/'/g, "'"); + html += `
                                                      • ${escapedName} (${result.type})
                                                      • `; + }); + + res.send(html); + + } catch (error) { + console.error("Search error:", error); + res.status(500).send("
                                                      • Search error occurred
                                                      • "); } - console.error("Error reading boneset file:", err); - res.status(500).json({ error: "Internal Server Error" }); - } }); +// Only one app.listen() at the very end app.listen(PORT, () => { - console.log(`🚀 Server running on http://127.0.0.1:${PORT}`); -}); + console.log(`🚀 Server running on http://127.0.0.1:${PORT}`); +}); \ No newline at end of file From afcd3a1fbc03853ff79b019a7649ca046ddd169f Mon Sep 17 00:00:00 2001 From: Jenni Oishee Date: Sun, 5 Oct 2025 22:25:26 -0500 Subject: [PATCH 140/143] Submitting --- boneset-api/server.js | 350 +++++++++++++++++------------------------ templates/boneset.html | 28 ++-- templates/js/main.js | 36 +++-- templates/js/search.js | 186 ++++++++++++++++++++++ templates/style.css | 101 +++++++++++- 5 files changed, 465 insertions(+), 236 deletions(-) create mode 100644 templates/js/search.js diff --git a/boneset-api/server.js b/boneset-api/server.js index adf31667..d0505adc 100644 --- a/boneset-api/server.js +++ b/boneset-api/server.js @@ -1,127 +1,42 @@ -//const express = require("express"); -//const axios = require("axios"); -//const cors = require("cors"); -//const path = require('path'); // Added for consistency, though not strictly needed for this version -// -//const app = express(); -//const PORT = process.env.PORT || 8000; -// -//app.use(cors()); -// -//// --- Original GitHub URLs --- -//const GITHUB_REPO = "https://raw.githubusercontent.com/oss-slu/DigitalBonesBox/data/DataPelvis/"; -//const BONESET_JSON_URL = `${GITHUB_REPO}boneset/bony_pelvis.json`; -//const BONES_DIR_URL = `${GITHUB_REPO}bones/`; -// -//// Helper function to fetch JSON from GitHub -//async function fetchJSON(url) { -// try { -// const response = await axios.get(url); -// return response.data; -// } catch (error) { -// console.error(`Failed to fetch ${url}:`, error.message); -// return null; -// } -//} -// -//// Home route (fixes "Cannot GET /" issue) -//app.get("/", (req, res) => { -// res.json({ message: "Welcome to the Boneset API (GitHub-Integrated)" }); -//}); -// -//// --- Original Combined Data Endpoint --- -//// This endpoint still provides the main data for the dropdowns -//app.get("/combined-data", async (req, res) => { -// try { -// const bonesetData = await fetchJSON(BONESET_JSON_URL); -// if (!bonesetData) return res.status(500).json({ error: "Failed to load boneset data" }); -// -// const bonesets = [{ id: bonesetData.id, name: bonesetData.name }]; -// const bones = []; -// const subbones = []; -// -// for (const boneId of bonesetData.bones) { -// const boneJsonUrl = `${BONES_DIR_URL}${boneId}.json`; -// const boneData = await fetchJSON(boneJsonUrl); -// -// if (boneData) { -// bones.push({ id: boneData.id, name: boneData.name, boneset: bonesetData.id }); -// boneData.subBones.forEach(subBoneId => { -// subbones.push({ id: subBoneId, name: subBoneId.replace(/_/g, " "), bone: boneData.id }); -// }); -// } -// } -// -// res.json({ bonesets, bones, subbones }); -// -// } catch (error) { -// console.error("Error fetching combined data:", error.message); -// res.status(500).json({ error: "Internal Server Error" }); -// } -//}); -// -//// --- NEW HTMX ENDPOINT --- -//// This endpoint fetches a description and returns it as an HTML fragment -//app.get("/api/description/", async (req, res) => { // Path changed here -// const { boneId } = req.query; // Changed from req.params to req.query -// if (!boneId) { -// return res.send(''); // Send empty response if no boneId is provided -// } -// const GITHUB_DESC_URL = `https://raw.githubusercontent.com/oss-slu/DigitalBonesBox/data/DataPelvis/descriptions/${boneId}_description.json`; -// -// try { -// const response = await axios.get(GITHUB_DESC_URL); -// const descriptionData = response.data; -// -// let html = `
                                                      • ${descriptionData.name}
                                                      • `; -// descriptionData.description.forEach(point => { -// html += `
                                                      • ${point}
                                                      • `; -// }); -// res.send(html); -// -// } catch (error) { -// res.send('
                                                      • Description not available.
                                                      • '); -// } -//}); - - -// Start server -//app.listen(PORT, () => { -// console.log(`🚀 Server running on http://127.0.0.1:${PORT}`); -//}); - // boneset-api/server.js const express = require("express"); const axios = require("axios"); const cors = require("cors"); -const path = require("path"); -const fs = require("fs/promises"); const rateLimit = require("express-rate-limit"); const app = express(); const PORT = process.env.PORT || 8000; app.use(cors()); +app.use(express.json()); const GITHUB_REPO = "https://raw.githubusercontent.com/oss-slu/DigitalBonesBox/data/DataPelvis/"; const BONESET_JSON_URL = `${GITHUB_REPO}boneset/bony_pelvis.json`; const BONES_DIR_URL = `${GITHUB_REPO}bones/`; -// ---- Local data directory for merged files ---- -const DATA_DIR = path.join(__dirname, "data"); - -// ---- Simple rate limiter for FS-backed endpoints ---- -const bonesetLimiter = rateLimit({ - windowMs: 60 * 1000, // 1 minute - max: 60, // 60 requests / min / IP +// Rate limiter for search endpoint +const searchLimiter = rateLimit({ + windowMs: 60 * 1000, + max: 100, standardHeaders: true, legacyHeaders: false, }); -// ---- Only allow bonesets we ship locally right now ---- -const ALLOWED_BONESETS = new Set(["bony_pelvis"]); +// Cache for search data +let searchCache = null; + +// HTML escaping helper +function escapeHtml(str = "") { + return String(str).replace(/[&<>"']/g, (c) => ({ + "&": "&", + "<": "<", + ">": ">", + "\"": """, + "'": "'", + })[c]); +} -// ---- Helpers ---- +// GitHub JSON fetcher async function fetchJSON(url) { try { const response = await axios.get(url, { timeout: 10_000 }); @@ -132,66 +47,112 @@ async function fetchJSON(url) { } } -// Ensure any resolved path stays inside DATA_DIR -function safeDataPath(fileName) { - const base = path.resolve(DATA_DIR); - const candidate = path.resolve(DATA_DIR, fileName); - if (!candidate.startsWith(base + path.sep)) { - const err = new Error("Invalid path"); - err.code = "EINVAL"; - throw err; - } - return candidate; -} +// Initialize search cache at startup +async function initializeSearchCache() { + try { + console.log("Initializing search cache..."); + const bonesetData = await fetchJSON(BONESET_JSON_URL); + if (!bonesetData) { + console.error("Failed to load boneset data for search cache"); + return; + } -function escapeHtml(str = "") { - return String(str).replace(/[&<>"']/g, (c) => ({ - "&": "&", - "<": "<", - ">": ">", - "\"": """, - "'": "'", - })[c]); -} + const searchData = []; -// Cache the merged boneset for fast description lookups -let cachedBoneset = null; -async function loadBoneset() { - if (cachedBoneset) return cachedBoneset; - const file = safeDataPath("final_bony_pelvis.json"); - const raw = await fs.readFile(file, "utf8"); - cachedBoneset = JSON.parse(raw); - return cachedBoneset; -} + // Add boneset to search data + searchData.push({ + id: bonesetData.id, + name: bonesetData.name, + type: "boneset", + boneset: bonesetData.id, + bone: null, + subbone: null + }); -function findNodeById(boneset, id) { - if (!boneset) return null; - for (const bone of boneset.bones || []) { - if (bone.id === id) return bone; - for (const sub of bone.subbones || []) { - if (sub.id === id) return sub; + // Load all bones and sub-bones + for (const boneId of bonesetData.bones || []) { + const boneData = await fetchJSON(`${BONES_DIR_URL}${boneId}.json`); + if (boneData) { + // Add bone to search data + searchData.push({ + id: boneData.id, + name: boneData.name, + type: "bone", + boneset: bonesetData.id, + bone: boneData.id, + subbone: null + }); + + // Add sub-bones to search data + for (const subBoneId of boneData.subBones || []) { + const subBoneName = subBoneId.replace(/_/g, " "); + searchData.push({ + id: subBoneId, + name: subBoneName, + type: "subbone", + boneset: bonesetData.id, + bone: boneData.id, + subbone: subBoneId + }); + } + } } + + searchCache = searchData; + console.log(`Search cache initialized with ${searchData.length} items`); + } catch (error) { + console.error("Error initializing search cache:", error); } - return null; } +// Search function with ranking +function searchItems(query, limit = 20) { + if (!searchCache) return []; + + const q = query.toLowerCase().trim(); + const results = []; + + // First pass: prefix matches (higher priority) + for (const item of searchCache) { + if (item.name.toLowerCase().startsWith(q)) { + results.push({ ...item, priority: 1 }); + } + } + + // Second pass: substring matches (lower priority) + for (const item of searchCache) { + if (!item.name.toLowerCase().startsWith(q) && item.name.toLowerCase().includes(q)) { + results.push({ ...item, priority: 2 }); + } + } + + // Sort by priority, then by name + results.sort((a, b) => { + if (a.priority !== b.priority) return a.priority - b.priority; + return a.name.localeCompare(b.name); + }); + + return results.slice(0, limit); +} +// Routes app.get("/", (_req, res) => { - res.json({ message: "Welcome to the Boneset API (GitHub-Integrated)" }); + res.json({ message: "Welcome to the Boneset API" }); }); app.get("/combined-data", async (_req, res) => { try { const bonesetData = await fetchJSON(BONESET_JSON_URL); - if (!bonesetData) return res.status(500).json({ error: "Failed to load boneset data" }); + if (!bonesetData) { + return res.status(500).json({ error: "Failed to load boneset data" }); + } const bonesets = [{ id: bonesetData.id, name: bonesetData.name }]; const bones = []; const subbones = []; - for (const boneId of bonesetData.bones) { - const boneJsonUrl = `${BONES_DIR_URL}${boneId}.json`; - const boneData = await fetchJSON(boneJsonUrl); + for (const boneId of bonesetData.bones || []) { + const boneData = await fetchJSON(`${BONES_DIR_URL}${boneId}.json`); if (boneData) { bones.push({ id: boneData.id, name: boneData.name, boneset: bonesetData.id }); (boneData.subBones || []).forEach((subBoneId) => { @@ -212,102 +173,75 @@ app.get("/api/description/", async (req, res) => { if (!boneId) { return res.send(" "); } + const GITHUB_DESC_URL = `https://raw.githubusercontent.com/oss-slu/DigitalBonesBox/data/DataPelvis/descriptions/${boneId}_description.json`; try { const response = await axios.get(GITHUB_DESC_URL); const descriptionData = response.data; - let html = `
                                                      • ${descriptionData.name}
                                                      • `; + let html = `
                                                      • ${escapeHtml(descriptionData.name)}
                                                      • `; descriptionData.description.forEach(point => { - html += `
                                                      • ${point}
                                                      • `; + html += `
                                                      • ${escapeHtml(point)}
                                                      • `; }); res.send(html); - } catch (error) { res.send("
                                                      • Description not available.
                                                      • "); } }); -app.get("/api/search", async (req, res) => { +// Search endpoint +app.get("/api/search", searchLimiter, (req, res) => { const query = req.query.q; + + console.log("Search request received for:", query); - if (!query || query.trim() === "") { - return res.send("
                                                      • Enter a search term
                                                      • "); + // Handle empty or too short queries + if (!query || query.trim().length < 2) { + return res.send("
                                                      • Enter at least 2 characters to search
                                                      • "); } - const searchTerm = query.toLowerCase().trim(); - + const searchTerm = query.trim(); + try { - const bonesetData = await fetchJSON(BONESET_JSON_URL); - if (!bonesetData) { - return res.send("
                                                      • No data available
                                                      • "); - } - - const results = []; - - // Search boneset name - if (bonesetData.name && bonesetData.name.toLowerCase().includes(searchTerm)) { - results.push({ - type: "boneset", - id: bonesetData.id, - name: bonesetData.name - }); + if (!searchCache) { + return res.send("
                                                      • Search not available - cache not initialized
                                                      • "); } - // Search through bones - if (bonesetData.bones) { - for (const boneId of bonesetData.bones) { - const boneData = await fetchJSON(`${BONES_DIR_URL}${boneId}.json`); + const results = searchItems(searchTerm, 20); + console.log(`Found ${results.length} results for "${searchTerm}"`); - if (boneData) { - // Search bone name - if (boneData.name && boneData.name.toLowerCase().includes(searchTerm)) { - results.push({ - type: "bone", - id: boneData.id, - name: boneData.name - }); - } - - // Search sub-bones - if (boneData.subBones) { - for (const subBoneId of boneData.subBones) { - const subBoneName = subBoneId.replace(/_/g, " "); - if (subBoneName.toLowerCase().includes(searchTerm)) { - results.push({ - type: "subbone", - id: subBoneId, - name: subBoneName, - parentBone: boneData.id - }); - } - } - } - } - } - } - - // Format results as HTML if (results.length === 0) { - return res.send("
                                                      • No results found
                                                      • "); + return res.send("
                                                      • No results found
                                                      • "); } let html = ""; - results.forEach(result => { - const escapedName = result.name.replace(/"/g, """).replace(/'/g, "'"); - html += `
                                                      • ${escapedName} (${result.type})
                                                      • `; - }); + for (const result of results) { + const escapedName = escapeHtml(result.name); + const escapedType = escapeHtml(result.type); + + html += `
                                                      • + ${escapedName} (${escapedType}) +
                                                      • `; + } res.send(html); - } catch (error) { console.error("Search error:", error); - res.status(500).send("
                                                      • Search error occurred
                                                      • "); + res.status(500).send("
                                                      • Search error occurred
                                                      • "); } }); -// Only one app.listen() at the very end +// Initialize search cache on startup +initializeSearchCache(); + app.listen(PORT, () => { console.log(`🚀 Server running on http://127.0.0.1:${PORT}`); }); \ No newline at end of file diff --git a/templates/boneset.html b/templates/boneset.html index f02169e6..ec0c4085 100644 --- a/templates/boneset.html +++ b/templates/boneset.html @@ -7,7 +7,7 @@ Bone Set Viewer - + @@ -28,8 +28,18 @@

                                                        Bone Set Viewer

                                                        - -
                                                          + + +
                                                          + + +
                                                          + +
                                                            +
                                                            @@ -61,8 +71,7 @@

                                                            Bone Set Viewer

                                                            Description

                                                            -
                                                              -
                                                            +
                                                              - -
                                                              +
                                                              - - - + + + \ No newline at end of file diff --git a/templates/js/main.js b/templates/js/main.js index 43df81a6..e53df251 100644 --- a/templates/js/main.js +++ b/templates/js/main.js @@ -1,9 +1,10 @@ import { fetchCombinedData, fetchMockBoneData } from "./api.js"; import { populateBonesetDropdown, setupDropdownListeners } from "./dropdowns.js"; -import { initializeSidebar } from "./sidebar.js"; +import { initializeSidebar, loadHelpButton } from "./sidebar.js"; import { setupNavigation, setBoneAndSubbones, disableButtons } from "./navigation.js"; import { loadDescription } from "./description.js"; import { displayBoneData, clearViewer } from "./viewer.js"; +import { initializeSearch } from "./search.js"; let combinedData = { bonesets: [], bones: [], subbones: [] }; let mockBoneData = null; @@ -30,26 +31,27 @@ function handleBoneSelection(boneId) { } document.addEventListener("DOMContentLoaded", async () => { - // 1. Sidebar behavior + // 1. Initialize search functionality + initializeSearch(); + + // 2. Sidebar behavior and help button initializeSidebar(); + loadHelpButton(); - // 2. Load mock bone data using centralized API + // 3. Load mock bone data using centralized API mockBoneData = await fetchMockBoneData(); - // 3. Fetch data and populate dropdowns + // 4. Fetch data and populate dropdowns combinedData = await fetchCombinedData(); populateBonesetDropdown(combinedData.bonesets); setupDropdownListeners(combinedData); - // 4. Hook up navigation buttons - const prevButton = document.getElementById("prev-button"); - const nextButton = document.getElementById("next-button"); - const subboneDropdown = document.getElementById("subbone-select"); - const boneDropdown = document.getElementById("bone-select"); - - setupNavigation(prevButton, nextButton, subboneDropdown, loadDescription); + // 5. Setup navigation after everything else + setupNavigation(combinedData); + disableButtons(); - // 5. Update navigation when bone changes + // 6. Update navigation when bone changes + const boneDropdown = document.getElementById("bone-select"); boneDropdown.addEventListener("change", (event) => { const selectedBone = event.target.value; @@ -58,9 +60,9 @@ document.addEventListener("DOMContentLoaded", async () => { .map(sb => sb.id); setBoneAndSubbones(selectedBone, relatedSubbones); - populateSubboneDropdown(subboneDropdown, relatedSubbones); - disableButtons(prevButton, nextButton); - + populateSubboneDropdown(document.getElementById("subbone-select"), relatedSubbones); + disableButtons(); + // Handle bone selection using dedicated function if (selectedBone) { handleBoneSelection(selectedBone); @@ -69,7 +71,7 @@ document.addEventListener("DOMContentLoaded", async () => { } }); - // 6. Auto-select the first boneset + // 7. Auto-select the first boneset const boneset = combinedData.bonesets[0]; if (boneset) { document.getElementById("boneset-select").value = boneset.id; @@ -77,7 +79,7 @@ document.addEventListener("DOMContentLoaded", async () => { document.getElementById("boneset-select").dispatchEvent(event); } - // 7. Initialize display + // 8. Initialize display clearViewer(); }); diff --git a/templates/js/search.js b/templates/js/search.js new file mode 100644 index 00000000..01cc1e57 --- /dev/null +++ b/templates/js/search.js @@ -0,0 +1,186 @@ +let selectedIndex = -1; +let searchTimeout; + +// Handle search result clicks and keyboard navigation +export function initializeSearch() { + const searchBar = document.getElementById("search-bar"); + const searchResultsContainer = document.getElementById("search-results"); + const searchLoading = document.getElementById("search-loading"); + + if (!searchBar || !searchResultsContainer) { + console.error("Search elements not found"); + return; + } + + console.log("Search initialized"); + + // Handle typing in search bar + searchBar.addEventListener("input", (e) => { + clearTimeout(searchTimeout); + const query = e.target.value.trim(); + + if (query.length < 2) { + searchResultsContainer.innerHTML = ""; + searchLoading.style.display = "none"; + return; + } + + searchLoading.style.display = "block"; + + searchTimeout = setTimeout(() => { + performSearch(query); + }, 300); + }); + + // Handle keyboard navigation + searchBar.addEventListener("keydown", (e) => { + const results = searchResultsContainer.querySelectorAll(".search-result"); + + if (e.key === "ArrowDown") { + e.preventDefault(); + selectedIndex = Math.min(selectedIndex + 1, results.length - 1); + updateSelection(results); + } else if (e.key === "ArrowUp") { + e.preventDefault(); + selectedIndex = Math.max(selectedIndex - 1, -1); + updateSelection(results); + } else if (e.key === "Enter") { + e.preventDefault(); + if (selectedIndex >= 0 && results[selectedIndex]) { + selectSearchResult(results[selectedIndex]); + } + } else if (e.key === "Escape") { + clearSearch(); + } + }); + + // Handle clicks outside search to close results + document.addEventListener("click", (e) => { + if (!searchBar.contains(e.target) && !searchResultsContainer.contains(e.target)) { + if (!e.target.closest(".search-result")) { + clearSearchResults(); + } + } + }); +} + +async function performSearch(query) { + const searchResultsContainer = document.getElementById("search-results"); + const searchLoading = document.getElementById("search-loading"); + + try { + console.log("Performing search for:", query); + const response = await fetch(`http://127.0.0.1:8000/api/search?q=${encodeURIComponent(query)}`); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const html = await response.text(); + console.log("Search response received"); + + searchResultsContainer.innerHTML = html; + searchLoading.style.display = "none"; + selectedIndex = -1; + + // Attach click handlers to new results + attachClickHandlers(); + + } catch (error) { + console.error("Search error:", error); + searchResultsContainer.innerHTML = "
                                                            • Search failed. Make sure the server is running.
                                                            • "; + searchLoading.style.display = "none"; + } +} + +function attachClickHandlers() { + const results = document.querySelectorAll(".search-result"); + results.forEach(result => { + result.addEventListener("click", (e) => { + e.preventDefault(); + selectSearchResult(result); + }); + }); +} + +function updateSelection(results) { + results.forEach((result, index) => { + if (index === selectedIndex) { + result.classList.add("selected"); + result.scrollIntoView({ block: "nearest" }); + } else { + result.classList.remove("selected"); + } + }); +} + +function selectSearchResult(resultElement) { + const type = resultElement.dataset.type; + const bonesetId = resultElement.dataset.boneset; + const boneId = resultElement.dataset.bone; + const subboneId = resultElement.dataset.subbone; + + console.log("Selected search result:", { type, bonesetId, boneId, subboneId }); + + // Update dropdowns based on search result + updateDropdowns(type, bonesetId, boneId, subboneId); + + // Clear search after selection + clearSearch(); +} + +function updateDropdowns(type, bonesetId, boneId, subboneId) { + const bonesetSelect = document.getElementById("boneset-select"); + const boneSelect = document.getElementById("bone-select"); + const subboneSelect = document.getElementById("subbone-select"); + + // Always set boneset first + if (bonesetId && bonesetSelect) { + bonesetSelect.value = bonesetId; + bonesetSelect.dispatchEvent(new Event("change")); + + // Wait for bone dropdown to populate, then set bone + if (boneId && (type === "bone" || type === "subbone")) { + setTimeout(() => { + if (boneSelect) { + boneSelect.disabled = false; + boneSelect.value = boneId; + boneSelect.dispatchEvent(new Event("change")); + + // Wait for subbone dropdown to populate, then set subbone + if (subboneId && type === "subbone") { + setTimeout(() => { + if (subboneSelect) { + subboneSelect.disabled = false; + subboneSelect.value = subboneId; + subboneSelect.dispatchEvent(new Event("change")); + } + }, 200); + } + } + }, 200); + } + } +} + +function clearSearch() { + const searchBar = document.getElementById("search-bar"); + searchBar.value = ""; + clearSearchResults(); +} + +function clearSearchResults() { + const searchResults = document.getElementById("search-results"); + const searchLoading = document.getElementById("search-loading"); + + if (searchResults) { + searchResults.innerHTML = ""; + } + if (searchLoading) { + searchLoading.style.display = "none"; + } + selectedIndex = -1; +} + +// Initialize when DOM is loaded +document.addEventListener("DOMContentLoaded", initializeSearch); \ No newline at end of file diff --git a/templates/style.css b/templates/style.css index 7862007f..730828a1 100644 --- a/templates/style.css +++ b/templates/style.css @@ -226,6 +226,105 @@ ul li { margin-bottom: 0; } +/* Search functionality styles */ +.search-container { + position: relative; + margin-bottom: 10px; +} + +#search-bar { + width: 100%; + padding: 12px 16px; + border: 2px solid #ddd; + border-radius: 8px; + font-size: 16px; + transition: border-color 0.2s ease; + box-sizing: border-box; +} + +#search-bar:focus { + outline: none; + border-color: #007bff; + box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.1); +} + +.search-loading { + position: absolute; + right: 16px; + top: 50%; + transform: translateY(-50%); + font-size: 12px; + color: #666; + background: white; + padding: 2px 6px; + border-radius: 4px; +} + +.search-results { + max-height: 300px; + overflow-y: auto; + border: 1px solid #ddd; + border-radius: 8px; + background: white; + margin-bottom: 20px; + box-shadow: 0 2px 8px rgba(0,0,0,0.1); + list-style: none; + padding: 0; + margin: 0 0 20px 0; +} + +.search-results:empty { + display: none; +} + +.search-result { + padding: 12px 16px; + border-bottom: 1px solid #eee; + cursor: pointer; + transition: background-color 0.2s ease; + display: flex; + justify-content: space-between; + align-items: center; +} + +.search-result:last-child { + border-bottom: none; +} + +.search-result:hover, +.search-result:focus { + background-color: #f0f8ff; + outline: none; +} + +.search-result.selected { + background-color: #e6f3ff; +} + +.search-result small { + color: #666; + font-size: 0.85em; + font-weight: normal; +} + +.search-placeholder, +.search-error, +.search-no-results { + padding: 12px 16px; + color: #666; + font-style: italic; + cursor: default; + text-align: center; +} + +.search-error { + color: #d32f2f; +} + +.search-no-results { + color: #666; +} + /* Responsive design for smaller screens */ @media (max-width: 768px) { .viewer-wrapper { @@ -240,7 +339,7 @@ ul li { max-width: 100%; } } -======= + .help-modal { position: fixed; top: 0; From dee434c83e026fd4f82af9bd56d159e942f5f3fd Mon Sep 17 00:00:00 2001 From: Wendy Onwuagana Date: Mon, 6 Oct 2025 00:46:45 -0500 Subject: [PATCH 141/143] Fix npm start configuration and remove redundant files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Update package.json to properly open app on port 5500 - Fix sidebar toggle functionality using getComputedStyle - Remove unused files: data_extraction/api.py, output.json, mock-bone-data.json 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- data_extraction/api.py | 31 ----------------------- data_extraction/output.json | 21 ---------------- package.json | 4 ++- templates/js/mock-bone-data.json | 43 -------------------------------- templates/js/sidebar.js | 3 ++- 5 files changed, 5 insertions(+), 97 deletions(-) delete mode 100644 data_extraction/api.py delete mode 100644 data_extraction/output.json delete mode 100644 templates/js/mock-bone-data.json diff --git a/data_extraction/api.py b/data_extraction/api.py deleted file mode 100644 index 3a4070ab..00000000 --- a/data_extraction/api.py +++ /dev/null @@ -1,31 +0,0 @@ -import requests -from fastapi import FastAPI, HTTPException - -app = FastAPI() - -# GitHub raw URLs for JSON files -GITHUB_REPO = "https://raw.githubusercontent.com/oss-slu/DigitalBonesBox/data/DataPelvis/" -BONESET_JSON_URL = GITHUB_REPO + "boneset/bony_pelvis.json" -BONES_DIR_URL = GITHUB_REPO + "bones/" # Directory for individual bone JSON files - -# Helper function to fetch JSON from GitHub -def fetch_json(url): - response = requests.get(url) - if response.status_code != 200: - raise HTTPException(status_code=500, detail=f"Failed to fetch data from {url}") - return response.json() - -@app.get("/") -def home(): - return {"message": "Welcome to the Boneset API (GitHub-Integrated)"} - -@app.get("/boneset") -def get_boneset(): - """Fetch bony pelvis details from GitHub""" - return fetch_json(BONESET_JSON_URL) - -@app.get("/bones/{bone_id}") -def get_bone(bone_id: str): - """Fetch a specific bone's details from GitHub""" - bone_json_url = BONES_DIR_URL + f"{bone_id}.json" - return fetch_json(bone_json_url) diff --git a/data_extraction/output.json b/data_extraction/output.json deleted file mode 100644 index 004bf27c..00000000 --- a/data_extraction/output.json +++ /dev/null @@ -1,21 +0,0 @@ -[ - { - "name": "Bony Pelvis", - "id": "bony_pelvis", - "bones": [ - "Ilium", - "Ischium", - "Pubis" - ] - }, - { - "name": "Ischium", - "id": "ischium", - "bones": [ - "Ramus", - "Ischial tuberosity", - "Ischial spine", - "Sciatic notches" - ] - } -] \ No newline at end of file diff --git a/package.json b/package.json index ecda2026..d9153f85 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,9 @@ "main": "server.js", "scripts": { "test": "jest", - "start": "concurrently \"npm start --prefix boneset-api\" \"live-server templates --open=boneset.html\"", + "start": "concurrently \"npm run start:api\" \"npm run start:web\"", + "start:api": "npm start --prefix boneset-api", + "start:web": "live-server --port=5500 --open=templates/boneset.html", "lint": "eslint . --ext .js", "lint:fix": "eslint . --ext .js --fix" }, diff --git a/templates/js/mock-bone-data.json b/templates/js/mock-bone-data.json deleted file mode 100644 index 7d50f098..00000000 --- a/templates/js/mock-bone-data.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "bones": [ - { - "id": "ischium", - "name": "Ischium", - "image_url": "https://via.placeholder.com/600x400/4A90E2/FFFFFF?text=Ischium+Bone", - "annotations": [ - { - "text": "Ischial Tuberosity - Attachment point for hamstring muscles", - "position": { "x": 300, "y": 150 } - }, - { - "text": "Ischial Spine - Forms part of the lesser sciatic notch", - "position": { "x": 250, "y": 100 } - }, - { - "text": "Ischial Ramus - Forms part of the obturator foramen", - "position": { "x": 350, "y": 200 } - } - ] - }, - { - "id": "ilium", - "name": "Ilium", - "image_url": "https://via.placeholder.com/600x400/50C878/FFFFFF?text=Ilium+Bone", - "annotations": [] - }, - { - "id": "pubis", - "name": "Pubis", - "annotations": [ - { - "text": "Pubic Symphysis - Joint where left and right pubic bones meet", - "position": { "x": 300, "y": 60 } - }, - { - "text": "Superior Pubic Ramus - Upper branch of the pubis", - "position": { "x": 250, "y": 180 } - } - ] - } - ] -} \ No newline at end of file diff --git a/templates/js/sidebar.js b/templates/js/sidebar.js index 1ac2f11d..7e382119 100644 --- a/templates/js/sidebar.js +++ b/templates/js/sidebar.js @@ -21,7 +21,8 @@ export function initializeSidebar() { const sidebarElement = document.getElementById("sidebar"); if (sidebarElement) { - if (sidebarElement.style.left === "0px") { + const currentLeft = window.getComputedStyle(sidebarElement).left; + if (currentLeft === "0px") { sidebarElement.style.left = "-250px"; // Close sidebar } else { sidebarElement.style.left = "0px"; // Open sidebar From 747e2d236c4585cc952f403b72eedbb475230151 Mon Sep 17 00:00:00 2001 From: Wendy Onwuagana Date: Thu, 16 Oct 2025 19:52:39 -0500 Subject: [PATCH 142/143] added good first issue template,tastcard template and updated the contributing.md template with new cantact information --- .../ISSUE_TEMPLATE/CONTRIBUTING.md | 162 +++++++++--------- .github/ISSUE_TEMPLATE/GOOD_FIRST_ISSUE.md | 44 +++++ .github/ISSUE_TEMPLATE/taskcard.md | 35 ++++ DigitalBonesBox | 0 merge_annotations_into_final.py | 145 ---------------- 5 files changed, 160 insertions(+), 226 deletions(-) rename CONTRIBUTING.md => .github/ISSUE_TEMPLATE/CONTRIBUTING.md (95%) create mode 100644 .github/ISSUE_TEMPLATE/GOOD_FIRST_ISSUE.md create mode 100644 .github/ISSUE_TEMPLATE/taskcard.md delete mode 100644 DigitalBonesBox delete mode 100644 merge_annotations_into_final.py diff --git a/CONTRIBUTING.md b/.github/ISSUE_TEMPLATE/CONTRIBUTING.md similarity index 95% rename from CONTRIBUTING.md rename to .github/ISSUE_TEMPLATE/CONTRIBUTING.md index bee3eb43..1bb02829 100644 --- a/CONTRIBUTING.md +++ b/.github/ISSUE_TEMPLATE/CONTRIBUTING.md @@ -1,81 +1,81 @@ -# Contributing to Digital Bones Box - -Thank you for checking out **Digital Bones Box**! We are so happy you are here. If you are intrested in contributing to the project below are some guidelines that help us make most out of your contribution. - -## Getting Started - -### How to Contribute - -### 1. **Fork the Repository** -- Navigate to the Digital Bones Box repository on GitHub -- Click the "Fork" button on the top right of the repository page to create your own copy. - - -### 2. **Clone Your Fork** -- Clone the repository to your local machine: - ```bash - git clone https://github.com/oss-slu/DigitalBonesBox - cd your-repo - - -### 3. Create a New Branch -- Create a new branch for your contribution: - ```bash - git checkout -b feature-branch - ``` - ---- - -## Contribution Workflow - -### 1.Choose an Issue associated with the label **hacktoberfest** -- Choose the Issue associated with the label hacktoberfest and clearly read all the details and acceptance criteria for the issue before starting to work on the issue. - -### 2. Make Your Changes -- Work on your branch locally. Implement your changes and test them thoroughly to ensure they work correctly. -- For any UI changes, please include screenshots in the pull request. - -### 3. Commit Your Changes -- Commit messages should be clear and concise. Follow the format: - ``` - git commit -m "Add feature X to improve performance" - ``` -- Make sure your commit is properly documented and explains the **why** and **what** of the changes. - -### 4. Push Your Changes -- Push your branch to GitHub: - ```bash - git push origin feature-branch - ``` - -### 5. Submit a Pull Request (PR) -- Navigate to your fork on GitHub and click the **Pull Request** button. -- Ensure your PR: - - References the related issue number (e.g., `Fixes #123`). - - Provides a clear description of what was changed and why. - - Includes relevant tests or screenshots where applicable. - - follow the Pull Request template. - ---- - -## Code Guidelines - -- Keep your code readable, maintainable, and well-documented. - -### 1. Documentation -- Update documentation as necessary. If your change impacts functionality, be sure to update the corresponding documentation in the **Help** or **README** files. - ---- - -## Issue Reporting - -### 1. Suggesting Enhancements -- If you have ideas for new features, improvements, or optimizations, submit them through a GitHub issue and tag it with **enhancement**. - ---- - -## Community - -- If you have any questions or need guidance, feel free to or reach out via email sritammiraja.iragavarapu@slu.edu - -Thank you for your contributions! +# Contributing to Digital Bones Box + +Thank you for checking out **Digital Bones Box**! We are so happy you are here. If you are intrested in contributing to the project below are some guidelines that help us make most out of your contribution. + +## Getting Started + +### How to Contribute + +### 1. **Fork the Repository** +- Navigate to the Digital Bones Box repository on GitHub +- Click the "Fork" button on the top right of the repository page to create your own copy. + + +### 2. **Clone Your Fork** +- Clone the repository to your local machine: + ```bash + git clone https://github.com/oss-slu/DigitalBonesBox + cd your-repo + + +### 3. Create a New Branch +- Create a new branch for your contribution: + ```bash + git checkout -b feature-branch + ``` + +--- + +## Contribution Workflow + +### 1.Choose an Issue associated with the label **hacktoberfest** +- Choose the Issue associated with the label hacktoberfest and clearly read all the details and acceptance criteria for the issue before starting to work on the issue. + +### 2. Make Your Changes +- Work on your branch locally. Implement your changes and test them thoroughly to ensure they work correctly. +- For any UI changes, please include screenshots in the pull request. + +### 3. Commit Your Changes +- Commit messages should be clear and concise. Follow the format: + ``` + git commit -m "Add feature X to improve performance" + ``` +- Make sure your commit is properly documented and explains the **why** and **what** of the changes. + +### 4. Push Your Changes +- Push your branch to GitHub: + ```bash + git push origin feature-branch + ``` + +### 5. Submit a Pull Request (PR) +- Navigate to your fork on GitHub and click the **Pull Request** button. +- Ensure your PR: + - References the related issue number (e.g., `Fixes #123`). + - Provides a clear description of what was changed and why. + - Includes relevant tests or screenshots where applicable. + - follow the Pull Request template. + +--- + +## Code Guidelines + +- Keep your code readable, maintainable, and well-documented. + +### 1. Documentation +- Update documentation as necessary. If your change impacts functionality, be sure to update the corresponding documentation in the **Help** or **README** files. + +--- + +## Issue Reporting + +### 1. Suggesting Enhancements +- If you have ideas for new features, improvements, or optimizations, submit them through a GitHub issue and tag it with **enhancement**. + +--- + +## Community + +- If you have any questions or need guidance, feel free to or reach out via email wendy.onwuagana@slu.edu or daniel.shown@slu.edu + +Thank you for your contributions! diff --git a/.github/ISSUE_TEMPLATE/GOOD_FIRST_ISSUE.md b/.github/ISSUE_TEMPLATE/GOOD_FIRST_ISSUE.md new file mode 100644 index 00000000..2c676834 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/GOOD_FIRST_ISSUE.md @@ -0,0 +1,44 @@ +### Finding Your First Contribution +Welcome to the DigitalBoneBox project! We're thrilled you're interested in contributing. This guide is designed to help you find and complete your first issue. + +### What is a "Good First Issue"? +A "Good First Issue" is a task that has been specifically identified by our team as being a great entry point for new contributors. These issues are typically: + +1. Well-defined: The goal is clear and the scope is small. + +2. Low-impact: They don't involve changing critical parts of the application. + +3. A great learning opportunity: They are a perfect way to get familiar with our codebase, our contribution workflow, and our team. + +Examples of good first issues in this project might include: + +Fixing a typo in the UI. + +Improving the styling of a button or a menu. + +Adding a small, self-contained feature, like a "Back to Top" button. + +Improving the comments or documentation in a specific part of the code. + +### How to Find a Good First Issue +We use a specific label on GitHub to mark these tasks. You can find a good first issue by looking at our wide range of issues created in our repo and identifying an issue tagged "Good First Issue". + +### Your First Contribution Workflow +Once you've found an issue that interests you, here’s how to get started: + +1. Claim the Issue: Let everyone know you're working on it! You can assign the issue to yourself by clicking the "assign" button in the sidebar on the right of the issue page. It's also a good idea to leave a quick comment like, "I'd like to work on this!" This helps us avoid having multiple people working on the same thing. + +2. Follow the Contribution Guide: Our main CONTRIBUTING.md file has all the technical steps you need to get the project set up on your local machine. Please follow it carefully to fork the repository, create your branch, and run the application. + +3. Work on the Code: Make the necessary changes to the code to address the issue. + +4. Submit a Pull Request: Once you're finished, submit a pull request. we have a pull request template we use for our project which you can use for your reference under ISSUE_TEMPLATE. Once that is done, Our team will then review your work. + +### Questions or Need Help? +We're here to help you succeed! If you get stuck, have a question, or just want to discuss an idea, please don't hesitate to: + +Leave a comment on the issue you're working on. + +Reach out to the project maintainers via the contact information in our CONTRIBUTING.md file. + +Thank you for your interest in contributing to DigitalBoneBox! diff --git a/.github/ISSUE_TEMPLATE/taskcard.md b/.github/ISSUE_TEMPLATE/taskcard.md new file mode 100644 index 00000000..0bac1aa9 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/taskcard.md @@ -0,0 +1,35 @@ +--- +name: General Task Card +about: For standard development tasks, chores, or refactoring. +title: '[Task]: ' +labels: task, enhancement +assignees: '' + +--- + +### 🎯 Goal / Objective +*A clear and concise description of what this task aims to achieve. Why is this task important for the sprint?* + +--- + +### ✅ Tasks to be Completed +*A checklist of the specific, actionable steps required to complete this issue. This helps track progress.* + +- [ ] Task 1: +- [ ] Task 2: +- [ ] Task 3: + +--- + +### Acceptance Criteria +*A checklist of conditions that must be met for this task to be considered complete. How will we verify that it's done correctly?* + +- [ ] The new feature is implemented as described. +- [ ] All related code passes the repository's workflow checks (linting, tests). +- [ ] The application runs without errors after the changes are made. +- [ ] (If applicable) New automated tests have been added to cover the changes. + +--- + +### Additional Context +*Add any other context, notes, screenshots, or links that might be helpful for completing this task.* \ No newline at end of file diff --git a/DigitalBonesBox b/DigitalBonesBox deleted file mode 100644 index e69de29b..00000000 diff --git a/merge_annotations_into_final.py b/merge_annotations_into_final.py deleted file mode 100644 index 5745cd97..00000000 --- a/merge_annotations_into_final.py +++ /dev/null @@ -1,145 +0,0 @@ -#!/usr/bin/env python3 -""" -merge_annotations_into_final.py - -Merges label annotations extracted from slides into the merged boneset JSON: -- Reads boneset-api/data/final_bony_pelvis.json -- Scans data_extraction/*annotations*.json for label items -- For each annotation with a non-empty "text" and a "position" object, - finds the matching bone or sub-bone by id or name and appends the annotation. - -Run from the repository root: - python3 merge_annotations_into_final.py -""" - -import os -import re -import json -import glob - -# ---- Paths --------------------------------------------------------------- - -FINAL_JSON = os.path.join("boneset-api", "data", "final_bony_pelvis.json") -ANNO_DIR = "data_extraction" - -# ---- Normalization / matching helpers ------------------------------------ - -# Optional aliases if the slide labels use a shorter/common term -ALIASES = { - "ramus": "ischial_ramus", # slide might say just "Ramus" - "asis": "anterior_superior_iliac_spine", - "aiis": "anterior_inferior_iliac_spine", - "psis": "posterior_superior_iliac_spine", - "piis": "posterior_inferior_iliac_spine", -} - -def norm(s: str) -> str: - """Normalize ids/names/labels to snake_case-ish for matching.""" - s = str(s).strip().lower() - s = s.replace("’", "'") - s = re.sub(r"[\s\-]+", "_", s) # spaces/hyphens -> underscores - s = re.sub(r"[^a-z0-9_]+", "", s) # drop other punctuation - return s - -def as_int(v): - """Convert numeric-like strings to int; otherwise return the input.""" - try: - # handles "123", "123.0", 123, etc. - return int(float(v)) - except (TypeError, ValueError): - return v - -# ---- Load current final JSON -------------------------------------------- - -if not os.path.exists(FINAL_JSON): - raise SystemExit(f"[error] Missing file: {FINAL_JSON}") - -with open(FINAL_JSON, "r") as f: - data = json.load(f) - -# ---- Index bones and sub-bones by id and name --------------------------- - -bones_by_key = {} -subs_by_key = {} - -for b in data.get("bones", []) or []: - if b.get("id"): bones_by_key[norm(b["id"])] = b - if b.get("name"): bones_by_key[norm(b["name"])] = b - for sb in b.get("subbones", []) or []: - if sb.get("id"): subs_by_key[norm(sb["id"])] = sb - if sb.get("name"): subs_by_key[norm(sb["name"])] = sb - -def find_target_by_text(text: str): - """Find the bone/sub-bone node matching this label text.""" - k = norm(text) - k = ALIASES.get(k, k) - return bones_by_key.get(k) or subs_by_key.get(k) - -def add_annotation(node: dict, ann: dict) -> bool: - """Append annotation if not already present (simple de-dup).""" - node.setdefault("annotations", []) - if ann not in node["annotations"]: - node["annotations"].append(ann) - return True - return False - -# ---- Walk annotation files and merge ------------------------------------ - -added = dup = miss = files = 0 - -for path in sorted(glob.glob(os.path.join(ANNO_DIR, "*annotations*.json"))): - files += 1 - try: - with open(path, "r") as f: - payload = json.load(f) - except Exception as e: - print(f"[skip] {path}: {e}") - continue - - # payload can be a list or a dict with {"annotations": [...]} - items = payload.get("annotations") if isinstance(payload, dict) else payload - if not isinstance(items, list): - print(f"[skip] {path}: unexpected payload shape") - continue - - for item in items: - if not isinstance(item, dict): - continue - - text = (item.get("text") or "").strip() - pos = item.get("position") - - # We only merge actual labeled annotations with a position - if not text or not isinstance(pos, dict): - continue - - tgt = find_target_by_text(text) - if not tgt: - miss += 1 - print(f"[miss] {text!r} in {os.path.basename(path)}") - continue - - ann = { - "text": text, - "position": { - "x": as_int(pos.get("x")), - "y": as_int(pos.get("y")), - }, - } - if "width" in pos and "height" in pos: - ann["position"]["width"] = as_int(pos["width"]) - ann["position"]["height"] = as_int(pos["height"]) - - if add_annotation(tgt, ann): - added += 1 - print(f"[add] {text!r} -> {tgt.get('id')}") - else: - dup += 1 - print(f"[dup] {text!r} -> {tgt.get('id')}") - -# ---- Write back final JSON ---------------------------------------------- - -with open(FINAL_JSON, "w") as f: - json.dump(data, f, indent=2) - -print(f"[done] files={files} added={added} dup={dup} miss={miss} -> {FINAL_JSON}") From 0a63069104d0ffe127a6a84bbac04f4f11899653 Mon Sep 17 00:00:00 2001 From: Taktar Date: Sun, 19 Oct 2025 19:49:14 -0500 Subject: [PATCH 143/143] feat(data_extraction): add pelvis extraction scripts + READMEs --- .../scripts/README_bony_pelvis_rotation.md | 85 ++++++ .../scripts/README_bony_pelvis_text_labels.md | 93 +++++++ .../scripts/bony_pelvis_rotation.py | 211 ++++++++++++++ .../scripts/bony_pelvis_text_labels.py | 262 ++++++++++++++++++ 4 files changed, 651 insertions(+) create mode 100644 data_extraction/scripts/README_bony_pelvis_rotation.md create mode 100644 data_extraction/scripts/README_bony_pelvis_text_labels.md create mode 100644 data_extraction/scripts/bony_pelvis_rotation.py create mode 100644 data_extraction/scripts/bony_pelvis_text_labels.py diff --git a/data_extraction/scripts/README_bony_pelvis_rotation.md b/data_extraction/scripts/README_bony_pelvis_rotation.md new file mode 100644 index 00000000..e0cf76de --- /dev/null +++ b/data_extraction/scripts/README_bony_pelvis_rotation.md @@ -0,0 +1,85 @@ +### Purpose: + +Parses PowerPoint slide XML to: + +derive a normalized two-image template (left/right bounds, rotation, flips) from a representative slide, + +extract per-slide media rIds and (optionally) resolve them to media file paths via .rels, + +optionally audit each slide to see whether its two main images match the template within a tolerance. + +⚠️ Designed for slides that have two main pictures (side-by-side). One-image slides will fail the audit with reason: "fewer-than-two-pics". + +### Expected inputs: + +--slides-dir: directory containing slideN.xml + +--rels-dir (optional): directory containing slideN.xml.rels + +Representative slide number with the desired geometry (defaults to 2) + +### Outputs: + +--out-template (JSON): normalized geometry for left/right images +(template_bony_pelvis.json) + +--out-metadata (JSON): list of slides with media rIds, and if --rels-dir is provided, resolved media targets/paths (bony_pelvis_metadata.json) + +When --audit is set: an "audit" block with tolerance, verified_slides, and failed_slides. + +### Key options: + +--slides: list of slide numbers to process (default: 2 3) + +--representative: slide to build the template from (default: 2) + +--audit: run audit and include results in metadata output + +--tolerance: numeric tolerance for normalized geometry comparisons (default: 0.02) + +--min-area: minimum area fraction to consider a picture “main” (default: 0.05) + +### Quickstart: + +# 1) Create template + metadata (no audit) +python3 data_extraction/scripts/bony_pelvis_rotation.py \ + --slides-dir data_extraction/fixtures/slides \ + --rels-dir data_extraction/fixtures/rels \ + --slides 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 \ + --representative 2 \ + --out-template data_extraction/annotations/template_bony_pelvis.json \ + --out-metadata data_extraction/annotations/bony_pelvis_metadata.json + +# 2) With audit (recommended before committing) +python3 data_extraction/scripts/bony_pelvis_rotation.py \ + --slides-dir data_extraction/fixtures/slides \ + --rels-dir data_extraction/fixtures/rels \ + --slides 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 \ + --out-metadata data_extraction/annotations/bony_pelvis_metadata.json \ + --audit --tolerance 0.02 + +### Interpreting the audit: + +verified_slides: slides whose two main images match the template within --tolerance + +failed_slides: slides that differ; failure keys like left.normY, right.normH, or left.rot_deg + +reason: "fewer-than-two-pics": the slide doesn’t have two “main” pictures (common for single-image slides) + +### Handling one-image slides: + +This script can’t produce a side-by-side match for single-image slides. You can either: + +Exclude them from --slides when auditing, or + +Keep them listed but expect fewer-than-two-pics in audit results. +(Downstream apps should treat these as single-image layouts.) + +### File structure example: +data_extraction/ + fixtures/ + slides/ slide2.xml ... slide20.xml + rels/ slide2.xml.rels ... slide20.xml.rels + annotations/ + template_bony_pelvis.json + bony_pelvis_metadata.json diff --git a/data_extraction/scripts/README_bony_pelvis_text_labels.md b/data_extraction/scripts/README_bony_pelvis_text_labels.md new file mode 100644 index 00000000..1d821a79 --- /dev/null +++ b/data_extraction/scripts/README_bony_pelvis_text_labels.md @@ -0,0 +1,93 @@ +### Purpose: + +Extracts white text labels and the white leader lines/connector lines that point from labels into the image on a given slide. Outputs one JSON per slide containing: + +- label text + +- text box position/size/rotation (in EMUs) + +- any hyperlinks on the label (and their target slide, if present) + +- line geometry for the connected pointer/connector shapes + +- deduplicated target regions (endpoints of the connected graph) the label points to + +Uses a small graph walk from any line endpoints that touch the label’s text box (with padding), then collects terminal endpoints outside the box. + +### Expected inputs: + +--slides-dir: directory containing slideN.xml + +--rels-dir: directory containing slideN.xml.rels + +--slide: the slide number to process + +### Outputs: + +--out: path to write JSON. If not provided, writes +data_extraction/annotations/slide{N}_text_annotations.json. + +Each JSON contains: + +{ + "slide_number": 9, + "text_annotations": [ + { + "annotation_id": "annot_1", + "bone_name": "Bony Pelvis", + "subbone_name": "Ischial spine", + "text_box": { "x": ..., "y": ..., "width": ..., "height": ..., "rotation_emu": ... }, + "has_hyperlink": true, + "hyperlink": { "rId": "rId8", "target": "/ppt/slides/slide10.xml", "target_slide": 10 }, + "pointer_lines": [ { "kind": "connector", "start_point": {...}, "end_point": {...}, ... } ], + "target_regions": [ { "x": ..., "y": ... }, ... ] + } + ], + "total_text_annotations": 3, + "config": { "padding_emu": 4000.0, "snap_emu": 8000.0 } +} + +### How it works (brief): + +Detect labels: White-filled text (either srgbClr #FFFFFF or scheme colors lt1/bg1). + +Detect lines: White-stroke p:cxnSp connectors and simple p:sp lines. + +Graph follow: Build a snapped endpoint graph, start from nodes that lie within the label’s padded box, traverse connected edges, and collect terminal nodes outside the label as candidate target regions. + +# Key options: + +--padding (default 4000 EMU): padding around the text box to consider endpoints “touching” the label. + +--snap (default 8000 EMU): snap size for coalescing nearly identical line junctions. + +### Quickstart: + +# Example: dump slide 9 labels/lines to default path +python3 data_extraction/scripts/bony_pelvis_text_labels.py \ + --slides-dir data_extraction/fixtures/slides \ + --rels-dir data_extraction/fixtures/rels \ + --slide 9 + +# Write to a custom file (e.g., renamed convention) +python3 data_extraction/scripts/bony_pelvis_text_labels.py \ + --slides-dir data_extraction/fixtures/slides \ + --rels-dir data_extraction/fixtures/rels \ + --slide 9 \ + --out DataPelvis/annotations/slide09_ischium_text_labels.json + +#### Notes & tips: + +Only white labels/lines are considered—keep that styling consistent in PPT. + +If a label has no connected white lines, pointer_lines can be empty and target_regions may be []. + +Hyperlinks on either the shape’s cNvPr or text runs are captured when present in .rels. + +Common troubleshooting + +“No labels found”: ensure label text is actually white and not an inherited theme color that resolves to non-white. + +Too many target points: reduce --padding and/or increase --snap so endpoints near the label consolidate better. + +Missing hyperlink target_slide: requires a valid rId in the label and a matching entry in slideN.xml.rels. \ No newline at end of file diff --git a/data_extraction/scripts/bony_pelvis_rotation.py b/data_extraction/scripts/bony_pelvis_rotation.py new file mode 100644 index 00000000..72382794 --- /dev/null +++ b/data_extraction/scripts/bony_pelvis_rotation.py @@ -0,0 +1,211 @@ +import argparse, json, os, re +import xml.etree.ElementTree as ET + +NS = { + "p": "http://schemas.openxmlformats.org/presentationml/2006/main", + "a": "http://schemas.openxmlformats.org/drawingml/2006/main", + "r": "http://schemas.openxmlformats.org/officeDocument/2006/relationships", +} +PKG_REL_NS = "http://schemas.openxmlformats.org/package/2006/relationships" + +EMU_PER_DEG = 60000.0 +_SLIDE_NUM_RE = re.compile(r"(\d+)") + +def _num(el, attr, default=0.0): + try: return float(el.get(attr)) + except Exception: return default + +def _slide_num(path): + m = _SLIDE_NUM_RE.search(os.path.splitext(os.path.basename(path))[0]) + return int(m.group(1)) if m else None + +def two_largest_pics(root, min_area_frac=0.05): + pics = [] + for pic in root.findall(".//p:pic", NS): + xfrm = pic.find(".//a:xfrm", NS) + if xfrm is None: + continue + off, ext = xfrm.find("a:off", NS), xfrm.find("a:ext", NS) + if off is None or ext is None: + continue + x, y = _num(off, "x"), _num(off, "y") + cx, cy = _num(ext, "cx"), _num(ext, "cy") + area = cx * cy + blip = pic.find(".//a:blip", NS) + embed = blip.get("{%s}embed" % NS["r"]) if blip is not None else None + rot_emu = _num(xfrm, "rot", 0.0) + pics.append({ + "x": x, "y": y, "cx": cx, "cy": cy, "area": area, + "rot_emu": rot_emu, + "rot_deg": rot_emu / EMU_PER_DEG, + "flipH": (xfrm.get("flipH") == "1"), + "flipV": (xfrm.get("flipV") == "1"), + "embed": embed + }) + pics.sort(key=lambda d: d["area"], reverse=True) + if not pics: + return [] + thr = pics[0]["area"] * float(min_area_frac or 0.0) + filtered = [p for p in pics if p["area"] >= thr] + return (filtered[:2] if len(filtered) >= 2 else pics[:2]) + +def _bbox(a, b): + x1, y1 = min(a["x"], b["x"]), min(a["y"], b["y"]) + x2, y2 = max(a["x"]+a["cx"], b["x"]+b["cx"]), max(a["y"]+a["cy"], b["y"]+b["cy"]) + return {"x": x1, "y": y1, "w": (x2-x1), "h": (y2-y1)} + +def _norm(item, box): + w, h = (box["w"] or 1.0), (box["h"] or 1.0) + return { + "normX": (item["x"]-box["x"])/w, "normY": (item["y"]-box["y"])/h, + "normW": item["cx"]/w, "normH": item["cy"]/h + } + +def compute_template(slide_path, min_area_frac): + root = ET.parse(slide_path).getroot() + top2 = two_largest_pics(root, min_area_frac) + if len(top2) < 2: + raise SystemExit("Template slide must contain two main pictures.") + left, right = sorted(top2, key=lambda d: d["x"]) + box = _bbox(left, right) + return { + "layout": "side-by-side", + "left": { + **_norm(left, box), + "rot_deg": left["rot_deg"], + "rot_emu": left["rot_emu"], + "flipH": left["flipH"], "flipV": left["flipV"] + }, + "right": { + **_norm(right, box), + "rot_deg": right["rot_deg"], + "rot_emu": right["rot_emu"], + "flipH": right["flipH"], "flipV": right["flipV"] + }, + "norm_basis": "two-image-union" + } + +def extract_slide_metadata(slide_path, bone_set, min_area_frac): + root = ET.parse(slide_path).getroot() + top2 = two_largest_pics(root, min_area_frac) + if len(top2) < 2: + return None + left, right = sorted(top2, key=lambda d: d["x"]) + return { + "slide": _slide_num(slide_path), + "bone_set": bone_set, + "left_media": left["embed"], + "right_media": right["embed"], + "subbone": None, "sub_subbone": None + } + +def _close(a, b, tol): + return abs(a-b) <= tol + +def audit_slide(slide_path, template, tol, min_area_frac): + root = ET.parse(slide_path).getroot() + top2 = two_largest_pics(root, min_area_frac) + if len(top2) < 2: + return {"slide": _slide_num(slide_path), "ok": False, "reason": "fewer-than-two-pics"} + left, right = sorted(top2, key=lambda d: d["x"]) + box = _bbox(left, right) + L = {**_norm(left, box), "rot_deg": left["rot_deg"]} + R = {**_norm(right, box), "rot_deg": right["rot_deg"]} + fails = [] + for side, got, want in (("left", L, template["left"]), ("right", R, template["right"])): + for k in ("normX","normY","normW","normH"): + if not _close(got[k], want[k], tol): + fails.append(f"{side}.{k}") + if not _close(got["rot_deg"], want["rot_deg"], tol*10): + fails.append(f"{side}.rot_deg") + return {"slide": _slide_num(slide_path), "ok": not fails, "fails": fails} + +# ---- rId -> filename/path resolver (optional via --rels-dir) ---- + +def _read_rels_map(rels_path): + if not os.path.exists(rels_path): + return {} + root = ET.parse(rels_path).getroot() + out = {} + for rel in root.findall(f".//{{{PKG_REL_NS}}}Relationship"): + rid = rel.attrib.get("Id") + if rid: + out[rid] = {"Type": rel.attrib.get("Type",""), "Target": rel.attrib.get("Target","")} + return out + +def resolve_media_path(slides_dir, rels_dir, slide_num, rid): + rels_path = os.path.join(rels_dir, f"slide{slide_num}.xml.rels") + rels = _read_rels_map(rels_path) + info = rels.get(rid) + if not info or not info.get("Target"): + return {"target": "", "path": ""} + target = info["Target"] + fs_path = os.path.normpath(os.path.join(slides_dir, target)) + return {"target": target, "path": fs_path} + +def main(): + ap = argparse.ArgumentParser() + ap.add_argument("--slides-dir", required=True) + ap.add_argument("--rels-dir", required=False, default=None, help="Directory of slideN.xml.rels (optional)") + ap.add_argument("--slides", type=int, nargs="+", default=[2,3]) + ap.add_argument("--representative", type=int, default=2) + ap.add_argument("--out-template", default="data_extraction/annotations/template_bony_pelvis.json") + ap.add_argument("--out-metadata", default="data_extraction/annotations/bony_pelvis_metadata.json") + ap.add_argument("--audit", action="store_true") + ap.add_argument("--tolerance", type=float, default=0.02) + ap.add_argument("--min-area", type=float, default=0.05) + args = ap.parse_args() + + rep_path = os.path.join(args.slides_dir, f"slide{args.representative}.xml") + if not os.path.exists(rep_path): + raise SystemExit(f"Missing representative slide: {rep_path}") + + template = compute_template(rep_path, args.min_area) + + tpl_dir = os.path.dirname(args.out_template) + if tpl_dir: + os.makedirs(tpl_dir, exist_ok=True) + with open(args.out_template, "w") as f: + json.dump({ + "bone_set": "Bony Pelvis", + "display_format": "side-by-side", + "extracted_from_slide": args.representative, + "normalized_geometry": template + }, f, indent=2) + + metadata, verified, failures = [], [], [] + for n in args.slides: + path = os.path.join(args.slides_dir, f"slide{n}.xml") + if not os.path.exists(path): + continue + md = extract_slide_metadata(path, "Bony Pelvis", args.min_area) + if md: + # enrich with media targets/paths if rels-dir provided + if args.rels_dir: + left = resolve_media_path(args.slides_dir, args.rels_dir, n, md["left_media"]) + right = resolve_media_path(args.slides_dir, args.rels_dir, n, md["right_media"]) + md["left_media_target"] = left["target"] + md["right_media_target"] = right["target"] + md["left_media_path"] = left["path"] + md["right_media_path"] = right["path"] + metadata.append(md) + if args.audit: + res = audit_slide(path, template, args.tolerance, args.min_area) + (verified if res["ok"] else failures).append(res) + + os.makedirs(os.path.dirname(args.out_metadata) or ".", exist_ok=True) + out = {"slides": metadata} + if args.audit: + out["audit"] = { + "tolerance": args.tolerance, + "verified_slides": [v["slide"] for v in verified], + "failed_slides": failures + } + with open(args.out_metadata, "w") as f: + json.dump(out, f, indent=2) + + print(f"template -> {args.out_template}") + print(f"metadata -> {args.out_metadata}") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/data_extraction/scripts/bony_pelvis_text_labels.py b/data_extraction/scripts/bony_pelvis_text_labels.py new file mode 100644 index 00000000..2718eb4b --- /dev/null +++ b/data_extraction/scripts/bony_pelvis_text_labels.py @@ -0,0 +1,262 @@ +import argparse, json, os, re, math +import xml.etree.ElementTree as ET + +NS = { + "p": "http://schemas.openxmlformats.org/presentationml/2006/main", + "a": "http://schemas.openxmlformats.org/drawingml/2006/main", + "r": "http://schemas.openxmlformats.org/officeDocument/2006/relationships", +} +PKG_REL_NS = "http://schemas.openxmlformats.org/package/2006/relationships" +R_ID_KEY = "{%s}id" % NS["r"] + +# --------------------------- helpers --------------------------- + +def emu_box(xfrm): + off = xfrm.find("a:off", NS); ext = xfrm.find("a:ext", NS) + x = float(off.get("x")); y = float(off.get("y")) + w = float(ext.get("cx")); h = float(ext.get("cy")) + rot_emu = float(xfrm.get("rot") or 0.0) + return {"x": x, "y": y, "width": w, "height": h, "rotation_emu": rot_emu} + +def is_white_color(solid_fill): + if solid_fill is None: return False + srgb = solid_fill.find("a:srgbClr", NS) + if srgb is not None: return srgb.get("val","").upper() == "FFFFFF" + scheme = solid_fill.find("a:schemeClr", NS) + return scheme is not None and scheme.get("val") in ("lt1","bg1") + +def text_is_white(sp): + for rpr in sp.findall(".//a:rPr", NS): + if is_white_color(rpr.find("a:solidFill", NS)): + return True + return False + +def line_is_white(shape): + ln = shape.find(".//a:ln", NS) + return is_white_color(ln.find("a:solidFill", NS)) if ln is not None else False + +def build_rels_map(rels_path): + m = {} + if not os.path.exists(rels_path): return m + root = ET.parse(rels_path).getroot() + for rel in root.findall(f".//{{{PKG_REL_NS}}}Relationship"): + rid = rel.attrib.get("Id") + if rid: + m[rid] = {"Type": rel.attrib.get("Type",""), "Target": rel.attrib.get("Target","")} + return m + +def _link_payload(rid, rels_map): + info = {"rId": rid, "target": (rels_map.get(rid) or {}).get("Target","")} + m = re.search(r"slide(\d+)\.xml", info["target"]) + if m: info["target_slide"] = int(m.group(1)) + return info + +def resolve_hyperlink_for_shape(shape_el, rels_map): + cNvPr = shape_el.find("./p:nvSpPr/p:cNvPr", NS) + if cNvPr is not None: + hl = cNvPr.find("a:hlinkClick", NS) + if hl is not None and R_ID_KEY in hl.attrib: + return _link_payload(hl.attrib[R_ID_KEY], rels_map) + for rPr in shape_el.findall(".//a:rPr", NS): + hl = rPr.find("a:hlinkClick", NS) + if hl is not None and R_ID_KEY in hl.attrib: + return _link_payload(hl.attrib[R_ID_KEY], rels_map) + return None + +def rect_contains_with_padding(rect, x, y, pad): + return (rect["x"]-pad <= x <= rect["x"]+rect["width"]+pad and + rect["y"]-pad <= y <= rect["y"]+rect["height"]+pad) + +def snap_point(pt, snap): + return (round(pt[0]/snap)*snap, round(pt[1]/snap)*snap) + +def rot_cos_sin(emu): + # 60000 EMUs = 1 degree + ang = (emu/60000.0) * math.pi/180.0 + return math.cos(ang), math.sin(ang) + +# Given a line's a:xfrm box, compute **true endpoints** accounting for rotation. +def endpoints_from_xfrm(xfrm): + box = emu_box(xfrm) + cx, cy = box["width"]/2.0, box["height"]/2.0 + cx0, cy0 = box["x"] + cx, box["y"] + cy # center + c, s = rot_cos_sin(box["rotation_emu"]) + # vector from center to corner of bbox (cx,cy) rotated; endpoints are +/- that vector + vx, vy = (c*cx - s*cy), (s*cx + c*cy) + p1 = (cx0 - vx, cy0 - vy) + p2 = (cx0 + vx, cy0 + vy) + return p1, p2, box + +# ---------------------- extraction: texts ---------------------- + +def extract_text_boxes(root, rels_map, bone_set="Bony Pelvis"): + out = [] + for sp in root.findall(".//p:sp", NS): + if sp.find(".//p:txBody", NS) is None: + continue + if not text_is_white(sp): + continue + xfrm = sp.find(".//a:xfrm", NS) + if xfrm is None: + continue + text = "".join(t.text or "" for t in sp.findall(".//a:t", NS)).strip() + if not text: + continue + hyperlink = resolve_hyperlink_for_shape(sp, rels_map) + cNv = sp.find("./p:nvSpPr/p:cNvPr", NS) + out.append({ + "annotation_id": f"annot_{len(out)+1}", + "bone_name": bone_set, + "subbone_name": text, + "text_content": text, + "text_box": emu_box(xfrm) | {"shape_id": cNv.get("id") if cNv is not None else None}, + "has_hyperlink": hyperlink is not None, + "hyperlink": hyperlink or {} + }) + return out + +# ---------------------- extraction: lines ---------------------- + +def _collect_line_shape(shp, kind, lines): + if not line_is_white(shp): + return + xfrm = shp.find(".//a:xfrm", NS) + if xfrm is None: + return + p1, p2, box = endpoints_from_xfrm(xfrm) + ln = shp.find(".//a:ln", NS) + width = int(ln.get("w") or 0) if ln is not None else 0 + head = ln.find("a:headEnd", NS) if ln is not None else None + cNv = shp.find(f"./p:nv{'CxnSp' if kind=='connector' else 'Sp'}Pr/p:cNvPr", NS) + lines.append({ + "line_id": f"line_{len(lines)+1}", + "kind": kind, + "start_point": {"x": p1[0], "y": p1[1]}, + "end_point": {"x": p2[0], "y": p2[1]}, + "style": {"width": width, "arrow_head": (head.get("type") if head is not None else "none")}, + "shape_id": cNv.get("id") if cNv is not None else None, + "bbox": box + }) + +def extract_lines(root): + lines = [] + for shp in root.findall(".//p:cxnSp", NS): # connectors + _collect_line_shape(shp, "connector", lines) + # simple line shapes (p:sp with no text, but with stroke) + for shp in root.findall(".//p:sp", NS): + if shp.find(".//p:txBody", NS) is None and shp.find(".//a:ln", NS) is not None: + _collect_line_shape(shp, "line", lines) + return lines + +# -------------------- graph + connection logic -------------------- + +def build_graph(lines, snap): + nodes = {} # snapped point -> index + deg = {} # degree per node + edges = [] # (u, v, line_obj) + def add_node(pt): + key = snap_point(pt, snap) + if key not in nodes: + nodes[key] = len(nodes) + deg[nodes[key]] = 0 + return nodes[key], key + for ln in lines: + u, uk = add_node((ln["start_point"]["x"], ln["start_point"]["y"])) + v, vk = add_node((ln["end_point"]["x"], ln["end_point"]["y"])) + edges.append((u, v, ln)) + deg[u] += 1; deg[v] += 1 + adj = {i: [] for i in range(len(nodes))} + for u, v, ln in edges: + adj[u].append((v, ln)); adj[v].append((u, ln)) + inv_nodes = {idx: pt for pt, idx in nodes.items()} + return adj, inv_nodes, deg + +def follow_from_label(text_box, adj, inv_nodes, deg, pad): + # start nodes: any graph node inside/on the padded text box + starts = [i for i, (x,y) in inv_nodes.items() if rect_contains_with_padding(text_box, x, y, pad)] + visited_nodes, visited_edges = set(), set() + terminals = set() + stack = starts[:] + while stack: + cur = stack.pop() + if cur in visited_nodes: + continue + visited_nodes.add(cur) + is_touching_label = rect_contains_with_padding(text_box, *inv_nodes[cur], pad) + for nxt, ln in adj.get(cur, []): + eid = id(ln) + if eid not in visited_edges: + visited_edges.add(eid) + if nxt not in visited_nodes: + stack.append(nxt) + # terminal if degree==1 and not touching label + if deg.get(cur, 0) <= 1 and not is_touching_label: + terminals.add(cur) + return list(terminals), list(visited_edges) + +# ----------------------------- main flow ----------------------------- + +def main(): + ap = argparse.ArgumentParser() + ap.add_argument("--slides-dir", required=True) + ap.add_argument("--rels-dir", required=True) + ap.add_argument("--slide", type=int, required=True) + ap.add_argument("--out") + ap.add_argument("--padding", type=float, default=4000.0, help="EMU padding around text box") + ap.add_argument("--snap", type=float, default=8000.0, help="EMU snap size for junctions") + args = ap.parse_args() + + slide_xml = os.path.join(args.slides_dir, f"slide{args.slide}.xml") + rels_xml = os.path.join(args.rels_dir, f"slide{args.slide}.xml.rels") + root = ET.parse(slide_xml).getroot() + rels_map = build_rels_map(rels_xml) + + texts = extract_text_boxes(root, rels_map, bone_set="Bony Pelvis") + lines = extract_lines(root) + adj, inv_nodes, deg = build_graph(lines, snap=args.snap) + + # connection-based association + for t in texts: + terminals, used_edges = follow_from_label(t["text_box"], adj, inv_nodes, deg, pad=args.padding) + # dedupe nearly-identical terminals by snapped point + term_pts = [] + seen = set() + for idx in terminals: + pt = inv_nodes[idx] + if pt not in seen: + seen.add(pt) + term_pts.append({"x": pt[0], "y": pt[1]}) + # collect the actual line objects that were traversed + pointer_lines = [] + used = set() + for u in adj: + for v, ln in adj[u]: + eid = id(ln) + if eid in used_edges and eid not in used: + used.add(eid) + pointer_lines.append(ln) + # keep outputs small & stable + pointer_lines.sort(key=lambda ln: (ln["shape_id"] or 0, ln["line_id"])) + # sort target points by distance from the label center + tcx = t["text_box"]["x"] + t["text_box"]["width"] / 2.0 + tcy = t["text_box"]["y"] + t["text_box"]["height"] / 2.0 + term_pts.sort(key=lambda p: (p["x"] - tcx)**2 + (p["y"] - tcy)**2) + + t["pointer_lines"] = pointer_lines + t["target_regions"] = term_pts + + payload = { + "slide_number": args.slide, + "text_annotations": texts, + "total_text_annotations": len(texts), + "config": {"padding_emu": args.padding, "snap_emu": args.snap} + } + + out_path = args.out or f"data_extraction/annotations/slide{args.slide}_text_annotations.json" + os.makedirs(os.path.dirname(out_path), exist_ok=True) + with open(out_path, "w") as f: + json.dump(payload, f, indent=2) + print(f"Wrote {out_path}") + +if __name__ == "__main__": + main()