pylantir 0.2.3__tar.gz → 0.3.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (24) hide show
  1. {pylantir-0.2.3 → pylantir-0.3.1}/PKG-INFO +316 -33
  2. {pylantir-0.2.3 → pylantir-0.3.1}/README.md +312 -32
  3. {pylantir-0.2.3 → pylantir-0.3.1}/pyproject.toml +5 -2
  4. {pylantir-0.2.3 → pylantir-0.3.1}/src/pylantir/api_server.py +13 -9
  5. {pylantir-0.2.3 → pylantir-0.3.1}/src/pylantir/cli/run.py +307 -41
  6. pylantir-0.3.1/src/pylantir/config/calpendo_config_example.json +65 -0
  7. {pylantir-0.2.3 → pylantir-0.3.1}/src/pylantir/config/mwl_config.json +3 -1
  8. pylantir-0.3.1/src/pylantir/data_sources/__init__.py +84 -0
  9. pylantir-0.3.1/src/pylantir/data_sources/base.py +117 -0
  10. pylantir-0.3.1/src/pylantir/data_sources/calpendo_plugin.py +702 -0
  11. pylantir-0.3.1/src/pylantir/data_sources/redcap_plugin.py +367 -0
  12. {pylantir-0.2.3 → pylantir-0.3.1}/src/pylantir/db_setup.py +3 -0
  13. {pylantir-0.2.3 → pylantir-0.3.1}/src/pylantir/models.py +3 -0
  14. {pylantir-0.2.3 → pylantir-0.3.1}/src/pylantir/populate_db.py +6 -3
  15. {pylantir-0.2.3 → pylantir-0.3.1}/src/pylantir/redcap_to_db.py +128 -81
  16. {pylantir-0.2.3 → pylantir-0.3.1}/LICENSE +0 -0
  17. {pylantir-0.2.3 → pylantir-0.3.1}/src/pylantir/__init__.py +0 -0
  18. {pylantir-0.2.3 → pylantir-0.3.1}/src/pylantir/auth_db_setup.py +0 -0
  19. {pylantir-0.2.3 → pylantir-0.3.1}/src/pylantir/auth_models.py +0 -0
  20. {pylantir-0.2.3 → pylantir-0.3.1}/src/pylantir/auth_utils.py +0 -0
  21. {pylantir-0.2.3 → pylantir-0.3.1}/src/pylantir/cli/__init__.py +0 -0
  22. {pylantir-0.2.3 → pylantir-0.3.1}/src/pylantir/config/config_example_with_cors.json +0 -0
  23. {pylantir-0.2.3 → pylantir-0.3.1}/src/pylantir/db_concurrency.py +0 -0
  24. {pylantir-0.2.3 → pylantir-0.3.1}/src/pylantir/mwl_server.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pylantir
3
- Version: 0.2.3
3
+ Version: 0.3.1
4
4
  Summary: Python - DICOM Modality WorkList with Optional API
5
5
  Author-email: Milton Camacho <miltoncamachoicc@gmail.com>
6
6
  Requires-Python: >=3.11.1
@@ -21,6 +21,8 @@ Requires-Dist: uuid
21
21
  Requires-Dist: coloredlogs
22
22
  Requires-Dist: python-dotenv
23
23
  Requires-Dist: pandas
24
+ Requires-Dist: requests
25
+ Requires-Dist: pytz
24
26
  Requires-Dist: fastapi>=0.104.1 ; extra == "api"
25
27
  Requires-Dist: uvicorn[standard]>=0.24.0 ; extra == "api"
26
28
  Requires-Dist: passlib[bcrypt]==1.7.4 ; extra == "api"
@@ -46,6 +48,7 @@ Requires-Dist: pytest-runner ; extra == "test"
46
48
  Requires-Dist: pytest==7.3.1 ; extra == "test"
47
49
  Requires-Dist: pytest-github-actions-annotate-failures ; extra == "test"
48
50
  Requires-Dist: shellcheck-py==0.9.0.2 ; extra == "test"
51
+ Requires-Dist: responses>=0.23.0 ; extra == "test"
49
52
  Project-URL: Documentation, https://github.com/miltoncamacho/pylantir/tree/main#readme
50
53
  Project-URL: Source, https://github.com/miltoncamacho/pylantir
51
54
  Project-URL: Tracker, https://github.com/miltoncamacho/pylantir/issues
@@ -61,9 +64,9 @@ Provides-Extra: test
61
64
  <img src="pylantir.png" alt="Pylantir" width="50%">
62
65
  </div>
63
66
 
64
- This project's goal is to significantly reduce the number of human-related errors when manualy registering participants for medical imaging procedures.
67
+ This project's goal is to significantly reduce the number of human-related errors when manually registering participants for medical imaging procedures.
65
68
 
66
- It effectively provides a python based DICOM Modality Worklist Server (SCP) and Modality Performed Procedure Step (SCP) able to receive requests from medical imaging equipemnt based on DICOM network comunication (e.g., a C-FIND, N-CREATE, N-SET requests).
69
+ It effectively provides a python based DICOM Modality Worklist Server (SCP) and Modality Performed Procedure Step (SCP) able to receive requests from medical imaging equipment based on DICOM network communication (e.g., C-FIND, N-CREATE, N-SET requests).
67
70
 
68
71
  It will build/update a database based on the information entered in the study-related REDCap database using a REDCap API (You will require to have API access to the study).
69
72
 
@@ -147,7 +150,7 @@ cd pylantir/tests
147
150
  Query the worklist database to check that you have some entries using:
148
151
 
149
152
  ```bash
150
- python query-db.py
153
+ python tests/query_db.py
151
154
  ```
152
155
 
153
156
  Then, you can get a StudyUID from one of the entries to test the MPPS workflow. For example: 1.2.840.10008.3.1.2.3.4.55635351412689303463019139483773956632
@@ -155,19 +158,19 @@ Then, you can get a StudyUID from one of the entries to test the MPPS workflow.
155
158
  Take this and run a create action to mark the worklist Procedure Step Status as IN_PROGRESS
156
159
 
157
160
  ```bash
158
- python test-mpps.py --AEtitle MWL_SERVER --mpps_action create --callingAEtitle MWL_TESTER --ip 127.0.0.1 --port 4242 --study_uid 1.2.840.10008.3.1.2.3.4.55635351412689303463019139483773956632
161
+ python tests/mpps_tester.py --AEtitle MWL_SERVER --mpps_action create --callingAEtitle MWL_TESTER --ip 127.0.0.1 --port 4242 --study_uid 1.2.840.10008.3.1.2.3.4.55635351412689303463019139483773956632
159
162
  ```
160
163
 
161
- You can verify that this in fact modified your database re-running:
164
+ You can verify that this in fact modified your database by re-running:
162
165
 
163
166
  ```bash
164
- python query-db.py
167
+ python tests/query_db.py
165
168
  ```
166
169
 
167
- Finally, you can also simulate the pocedure completion efectively updating the Procedure Step Status to COMPLETED or DISCONTINUED:
170
+ Finally, you can also simulate the procedure completion, effectively updating the Procedure Step Status to COMPLETED or DISCONTINUED:
168
171
 
169
172
  ```bash
170
- python test-mpps.py --AEtitle MWL_SERVER --mpps_action set --mpps_status COMPLETED --callingAEtitle MWL_TESTER --ip 127.0.0.1 --port 4242 --study_uid 1.2.840.10008.3.1.2.3.4.55635351412689303463019139483773956632 --sop_uid 1.2.840.10008.3.1.2.3.4.187176383255263644225774937658729238426
173
+ python tests/mpps_tester.py --AEtitle MWL_SERVER --mpps_action set --mpps_status COMPLETED --callingAEtitle MWL_TESTER --ip 127.0.0.1 --port 4242 --study_uid 1.2.840.10008.3.1.2.3.4.55635351412689303463019139483773956632 --sop_uid 1.2.840.10008.3.1.2.3.4.187176383255263644225774937658729238426
171
174
  ```
172
175
 
173
176
  ## Usage
@@ -197,14 +200,16 @@ usage: pylantir [-h] [--AEtitle AETITLE] [--ip IP] [--port PORT] [--pylantir_con
197
200
  - **--ip IP**: IP/host address for the server
198
201
  - **--port PORT**: Port for the server
199
202
  - **--pylantir_config PYLANTIR_CONFIG**: Path to the configuration JSON file containing pylantir configs:
203
+ - **data_sources**: Array of data source configurations (recommended new format)
204
+ - Each source has: `name`, `type`, `enabled`, `sync_interval`, `operation_interval`, `config`, `field_mapping`
205
+ - **redcap2wl**: Legacy field mapping (deprecated, auto-converts to data_sources)
200
206
  - **allowed_aet**: List of allowed AE titles e.g. `["MRI_SCANNER", "MRI_SCANNER_2"]`
201
- - **site**: Site ID:string
207
+ - **site**: Site ID (legacy format, deprecated)
202
208
  - **protocol**: `{"site": "protocol_name", "mapping": "HIS/RIS mapping"}`
203
- - **redcap2wl**: Dictionary of REDCap fields to worklist fields mapping e.g., `{"redcap_field": "worklist_field"}`
204
209
  - **db_path**: Path to main worklist database e.g., `"/path/to/worklist.db"`
205
210
  - **users_db_path**: Optional path to users authentication database e.g., `"/path/to/users.db"`
206
- - **db_update_interval**: How often to reload the database e
207
- - **operation_interval**: What is the time range in a day in which the database will be updated e.g., `{"start_time":[hours,minutes],"end_time":[hours,minutes]}`
211
+ - **db_update_interval**: Legacy sync interval (deprecated, use sync_interval in data_sources)
212
+ - **operation_interval**: Legacy operation window (deprecated, use operation_interval in data_sources)
208
213
  - **--mpps_action {create,set}**: Action to perform for MPPS either create or set
209
214
  - **--mpps_status {COMPLETED,DISCONTINUED}**: Status to set for MPPS either COMPLETED or DISCONTINUED
210
215
  - **--callingAEtitle CALLINGAETITLE**: Calling AE Title for MPPS, it helps when the MWL is limited to only accept certain AE titles
@@ -213,40 +218,318 @@ usage: pylantir [-h] [--AEtitle AETITLE] [--ip IP] [--port PORT] [--pylantir_con
213
218
 
214
219
  ## Configuration JSON file
215
220
 
216
- As a default pylantir will try to read a JSON structured file with the following structure:
221
+ Pylantir supports a modular data sources configuration that allows you to connect to multiple data sources simultaneously.
222
+
223
+ ### New Data Sources Format (Recommended)
224
+
225
+ The new configuration format uses a `data_sources` array to define one or more data sources:
217
226
 
218
227
  ```json
219
228
  {
220
229
  "db_path": "/path/to/worklist.db",
221
230
  "users_db_path": "/path/to/users.db",
222
231
  "db_echo": "False",
223
- "db_update_interval": 60,
224
- "operation_interval": {"start_time": [0,0],"end_time": [23,59]},
225
232
  "allowed_aet": [],
233
+ "data_sources": [
234
+ {
235
+ "name": "main_redcap",
236
+ "type": "redcap",
237
+ "enabled": true,
238
+ "sync_interval": 60,
239
+ "operation_interval": {
240
+ "start_time": [0, 0],
241
+ "end_time": [23, 59]
242
+ },
243
+ "config": {
244
+ "site_id": "792",
245
+ "protocol": "BRAIN_MRI_3T"
246
+ },
247
+ "field_mapping": {
248
+ "study_id": "study_id",
249
+ "instrument": "redcap_repeat_instrument",
250
+ "session_id": "mri_instance",
251
+ "family_id": "family_id",
252
+ "youth_dob_y": "youth_dob_y",
253
+ "t1_date": "t1_date",
254
+ "demo_sex": "demo_sex",
255
+ "scheduled_date": "mri_date",
256
+ "scheduled_time": "mri_time",
257
+ "mri_wt_lbs": "patient_weight_lb",
258
+ "referring_physician": "referring_physician_name",
259
+ "performing_physician": "performing_physician",
260
+ "station_name": "station_name",
261
+ "status": "performed_procedure_step_status"
262
+ }
263
+ }
264
+ ],
265
+ "protocol": {
266
+ "792": "BRAIN_MRI_3T",
267
+ "mapping": "GEHC"
268
+ }
269
+ }
270
+ ```
271
+
272
+ **Data Source Configuration Fields:**
273
+
274
+ - **`name`**: Unique identifier for this data source (used in logs and database tracking)
275
+ - **`type`**: Data source type (currently supports `"redcap"`; extensible for future sources)
276
+ - **`enabled`**: Boolean to enable/disable this source without removing its configuration
277
+ - **`sync_interval`**: How often to sync data (in seconds)
278
+ - **`operation_interval`**: Time window when sync should occur (24-hour format)
279
+ - `start_time`: `[hours, minutes]` - Start of operation window
280
+ - `end_time`: `[hours, minutes]` - End of operation window
281
+ - **`config`**: Source-specific configuration
282
+ - For REDCap: `site_id`, `protocol`, and optional API credentials
283
+ - **`field_mapping`**: Maps source fields to DICOM worklist fields
284
+ - **`window_mode`** (Calpendo optional): `rolling` (default) or `today`
285
+ - **`daily_window`** (Calpendo optional): `{"start_time": [h, m], "end_time": [h, m]}`
286
+
287
+ ### Multiple Data Sources Example
288
+
289
+ You can configure multiple data sources to sync simultaneously:
290
+
291
+ ```json
292
+ {
293
+ "db_path": "/path/to/worklist.db",
294
+ "data_sources": [
295
+ {
296
+ "name": "site_792_redcap",
297
+ "type": "redcap",
298
+ "enabled": true,
299
+ "sync_interval": 60,
300
+ "config": {
301
+ "site_id": "792",
302
+ "protocol": "BRAIN_MRI_3T"
303
+ },
304
+ "field_mapping": { "study_id": "study_id" }
305
+ },
306
+ {
307
+ "name": "site_793_redcap",
308
+ "type": "redcap",
309
+ "enabled": true,
310
+ "sync_interval": 120,
311
+ "config": {
312
+ "site_id": "793",
313
+ "protocol": "CARDIAC_MRI"
314
+ },
315
+ "field_mapping": { "patient_id": "patient_id" }
316
+ }
317
+ ]
318
+ }
319
+ ```
320
+
321
+ ## Calpendo Data Source Integration
322
+
323
+ Pylantir supports integration with Calpendo booking systems to automatically sync MRI/EEG scanner bookings into the DICOM worklist. This allows scanner operators to see scheduled bookings directly on their imaging equipment.
324
+
325
+ ### Prerequisites
326
+
327
+ Set up environment variables for Calpendo authentication:
328
+
329
+ ```bash
330
+ export CALPENDO_USERNAME=<your_calpendo_username>
331
+ export CALPENDO_PASSWORD=<your_calpendo_password>
332
+ ```
333
+
334
+ ### Minimal Configuration
335
+
336
+ ```json
337
+ {
338
+ "db_path": "/path/to/worklist.db",
339
+ "data_sources": [
340
+ {
341
+ "name": "calpendo_mri",
342
+ "type": "calpendo",
343
+ "enabled": true,
344
+ "sync_interval": 60,
345
+ "config": {
346
+ "base_url": "https://your-institution.calpendo.com",
347
+ "resources": ["3T Diagnostic", "EEG Lab"]
348
+ },
349
+ "field_mapping": {
350
+ "patient_id": {
351
+ "source_field": "title",
352
+ "_extract": {
353
+ "pattern": "^([A-Z0-9]+)_.*",
354
+ "group": 1
355
+ }
356
+ },
357
+ "patient_name": {
358
+ "source_field": "title",
359
+ "_extract": {
360
+ "pattern": "^[A-Z0-9]+_(.+)$",
361
+ "group": 1
362
+ }
363
+ },
364
+ "study_description": {
365
+ "source_field": "properties.project.formattedName",
366
+ "_extract": {
367
+ "pattern": "^([^(]+)",
368
+ "group": 1
369
+ }
370
+ },
371
+ "accession_number": "id"
372
+ }
373
+ }
374
+ ]
375
+ }
376
+ ```
377
+
378
+ ### Full Configuration with All Options
379
+
380
+ ```json
381
+ {
382
+ "name": "calpendo_scanners",
383
+ "type": "calpendo",
384
+ "enabled": true,
385
+ "sync_interval": 60,
386
+ "config": {
387
+ "base_url": "https://your-institution.calpendo.com",
388
+ "resources": ["3T Diagnostic", "EEG Lab", "Mock Scanner"],
389
+ "status_filter": "Approved",
390
+ "lookback_multiplier": 2,
391
+ "timezone": "America/Edmonton",
392
+ "resource_modality_mapping": {
393
+ "3T": "MR",
394
+ "EEG": "EEG",
395
+ "Mock": "OT"
396
+ }
397
+ },
398
+ "field_mapping": {
399
+ "patient_id": {
400
+ "source_field": "title",
401
+ "_extract": {
402
+ "pattern": "^([A-Z0-9]+)_.*",
403
+ "group": 1
404
+ }
405
+ },
406
+ "patient_name": {
407
+ "source_field": "title",
408
+ "_extract": {
409
+ "pattern": "^[A-Z0-9]+_(.+)$",
410
+ "group": 1
411
+ }
412
+ },
413
+ "study_description": {
414
+ "source_field": "properties.project.formattedName",
415
+ "_extract": {
416
+ "pattern": "^([^(]+)",
417
+ "group": 1
418
+ }
419
+ },
420
+ "accession_number": "id",
421
+ "study_instance_uid": "id"
422
+ }
423
+ }
424
+ ```
425
+
426
+ **Calpendo Configuration Fields:**
427
+
428
+ - **`base_url`**: Calpendo server URL (e.g., `https://your-institution.calpendo.com`)
429
+ - **`resources`**: Array of resource names to sync (e.g., scanner names)
430
+ - **`status_filter`** (optional): Only sync bookings with this status (e.g., `"Approved"`)
431
+ - **`lookback_multiplier`** (optional, default: 2): Rolling window multiplier for incremental sync
432
+ - **`timezone`** (optional, default: `"America/Edmonton"`): Timezone for booking timestamps
433
+ - **`resource_modality_mapping`** (optional): Map resource names to DICOM modality codes
434
+ - **`field_mapping`** (at data source root): Maps Calpendo fields to worklist fields
435
+ - **`window_mode`** (optional): `rolling` (default) or `today`
436
+ - **`daily_window`** (optional): `{"start_time": [h, m], "end_time": [h, m]}`
437
+ - Use **`_extract`** for regex-based field extraction:
438
+ - **`pattern`**: Regular expression pattern (use `\\` for escaping in JSON)
439
+ - **`group`**: Capture group number (0 = full match, 1+ = capture groups)
440
+
441
+ ### Regex Pattern Examples
442
+
443
+ Common patterns for extracting information from Calpendo booking titles:
444
+
445
+ ```json
446
+ {
447
+ "patient_id": {
448
+ "source_field": "title",
449
+ "_extract": {
450
+ "pattern": "^([A-Z0-9]+)_.*",
451
+ "group": 1
452
+ }
453
+ },
454
+ "patient_name": {
455
+ "source_field": "title",
456
+ "_extract": {
457
+ "pattern": "^[A-Z0-9]+_(.+)$",
458
+ "group": 1
459
+ }
460
+ },
461
+ "study_description": {
462
+ "source_field": "properties.project.formattedName",
463
+ "_extract": {
464
+ "pattern": "^([^(]+)",
465
+ "group": 1
466
+ }
467
+ }
468
+ }
469
+ ```
470
+
471
+ ### Troubleshooting
472
+
473
+ **Authentication Errors:**
474
+ - Verify environment variables are set correctly
475
+ - Check Calpendo username/password are valid
476
+ - Ensure API access is enabled for your account
477
+
478
+ **No Bookings Synced:**
479
+ - Check `resources` list matches actual Calpendo resource names
480
+ - Verify `status_filter` isn't too restrictive
481
+ - Check booking dates are within the rolling window (current time ± lookback window)
482
+
483
+ **Regex Extraction Failures:**
484
+ - Test regex patterns with sample data before deployment
485
+ - Check JSON escaping (use `\\` for backslashes)
486
+ - Set logging to DEBUG to see extraction warnings
487
+
488
+ ### Workflow Notes
489
+
490
+ - MPPS status updates are owned by N-CREATE/N-SET; sync operations do not overwrite existing `performed_procedure_step_status` values.
491
+ - Calpendo booking times are interpreted in the configured local timezone and stored in the legacy formats `YYYY-MM-DD` (date) and `HH:MM` (time).
492
+
493
+ For more details, see the [Calpendo plugin quickstart](specs/002-calpendo-plugin/quickstart.md).
494
+
495
+ ### Legacy Configuration Format (Deprecated)
496
+
497
+ ⚠️ **The legacy configuration format is deprecated but still supported for backward compatibility.**
498
+
499
+ If you're using the old format, Pylantir will automatically convert it to the new format at runtime:
500
+
501
+ ```json
502
+ {
503
+ "db_path": "/path/to/worklist.db",
504
+ "db_echo": "False",
505
+ "db_update_interval": 60,
506
+ "operation_interval": {"start_time": [0,0], "end_time": [23,59]},
226
507
  "site": "792",
227
508
  "redcap2wl": {
228
509
  "study_id": "study_id",
229
- "instrument": "redcap_repeat_instrument",
230
- "session_id" : "mri_instance",
231
- "family_id": "family_id",
232
- "youth_dob_y": "youth_dob_y",
233
- "t1_date": "t1_date",
234
- "demo_sex": "demo_sex",
235
- "scheduled_date": "mri_date",
236
- "scheduled_time": "mri_time",
237
- "mri_wt_lbs": "patient_weight_lb",
238
- "referring_physician": "referring_physician_name",
239
- "performing_physician": "performing_physician",
240
- "station_name": "station_name",
241
- "status": "performed_procedure_step_status"
510
+ "demo_sex": "demo_sex"
242
511
  },
243
512
  "protocol": {
244
- "792": "BRAIN_MRI_3T",
245
- "mapping": "GEHC"
513
+ "792": "BRAIN_MRI_3T"
246
514
  }
247
515
  }
248
516
  ```
249
517
 
518
+ When using the legacy format, you'll see a deprecation warning:
519
+ ```
520
+ WARNING: Legacy configuration format detected.
521
+ Consider migrating to 'data_sources' format for better flexibility.
522
+ ```
523
+
524
+ **Migration Note**: To migrate from legacy to new format:
525
+ 1. Rename `redcap2wl` → `field_mapping`
526
+ 2. Move `site` → `config.site_id`
527
+ 3. Move `protocol[site]` → `config.protocol`
528
+ 4. Move `db_update_interval` → `sync_interval`
529
+ 5. Wrap everything in a `data_sources` array with `name`, `type`, and `enabled` fields
530
+
531
+ See `config/mwl_config_multi_source_example.json` for a complete example.
532
+
250
533
  ### Memory Management (Optional)
251
534
 
252
535
  When you install the `monitoring` optional dependency (`pip install pylantir[monitoring]`), Pylantir gains enhanced memory monitoring capabilities during REDCap synchronization:
@@ -262,7 +545,7 @@ This is particularly useful for production deployments with frequent synchroniza
262
545
 
263
546
  ## FastAPI REST API (Optional)
264
547
 
265
- **New in v0.2.0**: Pylantir now includes an optional REST API for programmatic access to worklist data and user management.
548
+ Pylantir now includes an optional REST API for programmatic access to worklist data and user management.
266
549
 
267
550
  ### Installation with API Support
268
551
 
@@ -580,7 +863,7 @@ Control Cross-Origin Resource Sharing (CORS) for web frontend integration:
580
863
 
581
864
  ## Clean Stop of the MWL and Database Sync
582
865
 
583
- To cleanly stop the MWL server and ensure the database syncronization properly, press `Ctrl + C` (you might need to press it twice).
866
+ To cleanly stop the MWL server and ensure the database synchronization properly, press `Ctrl + C` (you might need to press it twice).
584
867
 
585
868
  To stop the API server, use `Ctrl + C` in the terminal where it's running.
586
869