Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[API] Add Project endpoint "Dicoms" #6775

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions modules/api/docs/LorisRESTAPI_v0.0.4-dev.md
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,47 @@ will return a JSON object of the form:
}
```

#### 2.1.6 Single project dicoms tar files

GET /projects/$ProjectName/dicoms

will return a JSON object of the form:

```js
{
"Dicoms": [
{
"Candidate": "587630",
"PSCID": "DCC090",
"Entity_type": "Human",
"Visit": "V1",
"Visit_date": "2018-04-20",
"CenterID": "1",
"Site": "Data Coordinating Center",
"date_acquired": "2018-04-20",
"date_first_archived": "2019-05-23 13:46:39",
"date_last_archived": "2019-05-23 13:46:39",
"tarchiveid": "27",
"DicomArchiveID": "1.2.840.113745.101000.1022000.39911.6153.5242769",
"Archive": "2018/DCM_2018-04-20_ImagingUpload-13-42-zcoZR0.tar",
"Source": "/tmp/ImagingUpload-13-42-zcoZR0",
"FileName": "DCM_2018-04-20_ImagingUpload-13-42-zcoZR0.tar"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there any reason why the following fields are not returned?

AcquisitionCount
DicomFileCount
ScannerManufacturer
ScannerModel
ScannerSoftware
ScannerSerialNumber

Those are fields I could see as being informative to the user accessing the DICOM archives.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, I notice that in the JSON response, keys have all kinds of different convention. I would tend to make them consistent (either stick to CamelCase, or _ separated values, consistent casing etc...).

Sorry, my brain gets easily distracted with inconsistencies ;).

},
...
]
}

```
It is possible to provide a GET parameter named `since` where the value need to be a date or datetime.
```
ex: 2016-08-09 or 2016-08-09 10:00:00 or 2016-08-09T10:00:00-05:00
```
We recommend using a format that includes timezone. The `since` parameter should be
passed like the following example:
```
https://demo.loris.ca/api/v0.0.4-dev/projects/pumpernickel/dicoms?2016-08-09T10:00:00-05:00
```
xlecours marked this conversation as resolved.
Show resolved Hide resolved

### 2.2 Instrument Forms

```
Expand Down
169 changes: 169 additions & 0 deletions modules/api/php/endpoints/project/dicoms.class.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
<?php
/**
* This implements the Dicoms page class under Project
*
* PHP Version 7
*
* @category API
* @package Loris
* @author Simon Pelletier <simon.pelletier@mcin.ca>
* @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3
* @link https://github.com/aces/Loris
*/
namespace LORIS\api\Endpoints\Project;

use \Psr\Http\Message\ServerRequestInterface;
use \Psr\Http\Message\ResponseInterface;
use \LORIS\api\Endpoint;

/**
* A class for handling the /projects/$projectname/dicoms endpoint.
*
* @category API
* @package Loris
* @author Simon Pelletier <simon.pelletier@mcin.ca>
* @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3
* @link https://github.com/aces/Loris
*/
class Dicoms extends Endpoint implements \LORIS\Middleware\ETagCalculator
{
/**
* A cache of the results of the endpoint, so that
* it doesn't need to be recalculated for the ETag and handler
*/
private $_cache;

/**
* The requested project
*/
private $_project;

/**
* Permission checks
*
* @param \User $user The requesting user
*
* @return boolean true if access is permitted
*/
private function _hasAccess(\User $user)
{
return $user->hasPermission('dicom_archive_view_allsites');
}

/**
* Contructor
*
* @param \Project $project The requested project
*/
public function __construct(\Project $project)
{
$this->_project = $project;
}

/**
* Return which methods are supported by this endpoint.
*
* @return array supported HTTP methods
*/
protected function allowedMethods() : array
{
return ['GET'];
}

/**
* Versions of the LORIS API which are supported by this
* endpoint.
*
* @return array a list of supported API versions.
*/
protected function supportedVersions() : array
{
return ["v0.0.4-dev"];
}

/**
* Handles a request that starts with /projects/$projectname/candidates
*
* @param ServerRequestInterface $request The incoming PSR7 request
*
* @return ResponseInterface The outgoing PSR7 response
*/
public function handle(ServerRequestInterface $request) : ResponseInterface
{
$user = $request->getAttribute('user');
if ($user instanceof \LORIS\AnonymousUser) {
return new \LORIS\Http\Response\JSON\Unauthorized();
}

if (!$this->_hasAccess($user)) {
return new \LORIS\Http\Response\JSON\Forbidden();
}

$pathparts = $request->getAttribute('pathparts');
if (count($pathparts) !== 0) {
return new \LORIS\Http\Response\JSON\NotFound();
}

switch ($request->getMethod()) {
case 'GET':
return $this->_handleGET($request);

case 'OPTIONS':
return (new \LORIS\Http\Response())
->withHeader('Allow', $this->allowedMethods());
default:
return new \LORIS\Http\Response\JSON\MethodNotAllowed(
$this->allowedMethods()
);
}
}

/**
* Create an array representation of this endpoint's reponse body
*
* @param ServerRequestInterface $request The incoming PSR7 request
*
* @return ResponseInterface The outgoing PSR7 response
*/
private function _handleGET(ServerRequestInterface $request): ResponseInterface
{
if (isset($this->_cache)) {
spell00 marked this conversation as resolved.
Show resolved Hide resolved
return $this->_cache;
}

try {
$datestring = $request->getQueryParams()['since'] ?? '1970-01-01';
$since = new \DateTime($datestring);
} catch (\Exception $e) {
return new \LORIS\Http\Response\JSON\BadRequest(
$e->getMessage()
);
}

$provisioner = new \LORIS\api\Provisioners\ProjectDicomsObjectProvisioner(
$this->_project,
$since,
'\LORIS\api\Models\ProjectDicoms'
);

$dicoms = iterator_to_array($provisioner->getAllInstances());

$this->_cache = new \LORIS\Http\Response\JsonResponse(
['Dicoms' => $dicoms]
);

return $this->_cache;
}

/**
* Implements the ETagCalculator interface
*
* @param ServerRequestInterface $request The PSR7 incoming request.
*
* @return string etag summarizing value of this request.
*/
public function ETag(ServerRequestInterface $request) : string
{
return md5(json_encode($this->_handleGET($request)->getBody()));
}
}
3 changes: 3 additions & 0 deletions modules/api/php/endpoints/project/project.class.inc
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,9 @@ class Project extends Endpoint implements \LORIS\Middleware\ETagCalculator
case 'recordings':
$handler = new Recordings($this->_project);
break;
case 'dicoms':
$handler = new Dicoms($this->_project);
break;
default:
return new \LORIS\Http\Response\JSON\NotFound();
}
Expand Down
36 changes: 36 additions & 0 deletions modules/api/php/models/projectdicoms.class.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php declare(strict_types=1);
/**
* PHP Version 7
*
* @category API
* @package Loris
* @author Simon Pelletier <simon.pelletier@mcin.ca>
* @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3
* @link https://www.github.com/aces/Loris/
*/

namespace LORIS\api\Models;

/**
* A ProjectDicoms contains values from a Dicom file of a project.
*
* @category API
* @package Loris
* @author Simon Pelletier <simon.pelletier@mcin.ca>
* @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3
* @link https://www.github.com/aces/Loris/
*/
class ProjectDicoms implements \LORIS\Data\DataInstance
{
/**
* Implements \LORIS\Data\DataInstance interface for this object.
*
* @return array the object data.
*/
public function jsonSerialize() : array
{
$obj = get_object_vars($this);
$obj['FileName'] = basename($obj['Archive']);
return $obj;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
<?php declare(strict_types=1);
/**
* This file implements a data provisioner to get all dicoms of a project
* created since a given date.
*
* PHP Version 7
*
* @category API
* @package Loris
* @author Simon Pelletier <simon.pelletier@mcin.ca>
* @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3
* @link https://www.github.com/aces/Loris/
*/

namespace LORIS\api\Provisioners;

use \LORIS\Data\Provisioners\DBObjectProvisioner;
/**
* This file implements a data provisioner to get all dicoms of a project
* created since a given date.
*
* PHP Version 7
*
* @category API
* @package Loris
* @author Simon Pelletier <simon.pelletier@mcin.ca>
* @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3
* @link https://www.github.com/aces/Loris/
*/
class ProjectDicomsObjectProvisioner extends DBObjectProvisioner
{
/**
* Create a ObjectProvisioner
*
* @param \Project $project The project from which dicoms are requested
* @param \DateTime $since The date from which dicoms are requested
* @param string $classname The class name of the returned objects
*/
function __construct(
\Project $project,
\DateTime $since,
string $classname='LORIS\api\Models\ProjectDicoms'
) {
parent::__construct(
'
SELECT
s.CandID as Candidate,
c.PSCID as PSCID,
c.Entity_type as Entity_type,
s.Visit_label as Visit,
s.Date_visit as Visit_date,
s.CenterID as CenterID,
p.Name as Site,
t.DateAcquired as date_acquired,
t.DateFirstArchived as date_first_archived,
t.DateLastArchived as date_last_archived,
t.TarchiveID as tarchiveid,
t.DicomArchiveID as DicomArchiveID,
t.ArchiveLocation as Archive,
t.SourceLocation as Source
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
t.SourceLocation as Source
t.SourceLocation as Source

FROM
tarchive t
LEFT JOIN session s
ON (t.SessionID = s.ID)
LEFT JOIN candidate c
ON (s.CandID = c.CandID)
LEFT JOIN psc p
ON (s.CenterID = p.CenterID)
LEFT JOIN Project project
ON (c.RegistrationProjectID = project.ProjectID)
WHERE
c.Active = \'Y\' AND
s.Active = \'Y\' AND
project.Name = :v_projectname AND
t.DateLastArchived > :v_time
ORDER BY t.DateLastArchived ASC; ',
[
'v_projectname' => $project->getName(),
'v_time' => $since->getTimestamp(),
],
$classname
);
}
}

Loading