Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@
import uk.ac.sanger.sccp.utils.BasicUtils;

import java.time.LocalDate;
import java.util.List;
import java.util.Objects;

import static uk.ac.sanger.sccp.utils.BasicUtils.nullToEmpty;

/**
* One section in one labware of a section registration request.
* @author dr6
Expand All @@ -15,7 +18,7 @@ public class SectionRegisterContent {
private String donorIdentifier;
private LifeStage lifeStage;
private String species;
private Address address;
private List<Address> addresses = List.of();
private String hmdmc;
private String externalIdentifier;
private String tissueType;
Expand All @@ -38,12 +41,12 @@ public SectionRegisterContent(String donorIdentifier, LifeStage lifeStage, Strin
this.species = species;
}

public Address getAddress() {
return this.address;
public List<Address> getAddresses() {
return this.addresses;
}

public void setAddress(Address address) {
this.address = address;
public void setAddresses(List<Address> addresses) {
this.addresses = nullToEmpty(addresses);
}

public String getSpecies() {
Expand Down Expand Up @@ -183,7 +186,7 @@ public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
SectionRegisterContent that = (SectionRegisterContent) o;
return (Objects.equals(this.address, that.address)
return (Objects.equals(this.addresses, that.addresses)
&& Objects.equals(this.species, that.species)
&& Objects.equals(this.hmdmc, that.hmdmc)
&& Objects.equals(this.donorIdentifier, that.donorIdentifier)
Expand All @@ -205,13 +208,13 @@ public boolean equals(Object o) {

@Override
public int hashCode() {
return Objects.hash(address, externalIdentifier);
return Objects.hash(addresses, externalIdentifier);
}

@Override
public String toString() {
return BasicUtils.describe("SectionRegisterContent")
.add("address", address)
.add("addresses", addresses)
.add("species", species)
.add("hmdmc", hmdmc)
.add("donorIdentifier", donorIdentifier)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import uk.ac.sanger.sccp.utils.UCMap;

import java.util.*;
import java.util.stream.Stream;

import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toMap;
Expand Down Expand Up @@ -194,10 +195,10 @@ public Labware createLabware(SectionRegisterLabware srl, UCMap<LabwareType> labw
}
Labware lw = lwService.create(lt, prebarcode, externalBarcode);
for (var content : srl.getContents()) {
Slot slot = lw.getSlot(content.getAddress());
Sample sample = sampleMap.get(content.getExternalIdentifier());
slot.getSamples().add(sample);
slotRepo.save(slot);
List<Slot> slots = lwSlots(lw, content.getAddresses()).toList();
slots.forEach(slot -> slot.addSample(sample));
slotRepo.saveAll(slots);
}
return lw;
}
Expand Down Expand Up @@ -271,14 +272,18 @@ public Iterable<Measurement> createMeasurements(SectionRegisterLabware srl, Labw
continue;
}
Sample sample = sampleMap.get(src.getExternalIdentifier());
Slot slot = lw.getSlot(src.getAddress());
List<Slot> slots = lwSlots(lw, src.getAddresses()).toList();
if (src.getSectionThickness() != null) {
measurements.add(new Measurement(null, "Thickness", src.getSectionThickness(),
sample.getId(), op.getId(), slot.getId()));
for (Slot slot : slots) {
measurements.add(new Measurement(null, "Thickness", src.getSectionThickness(),
sample.getId(), op.getId(), slot.getId()));
}
}
if (src.getDateSectioned() != null) {
measurements.add(new Measurement(null, "Date sectioned", src.getDateSectioned().toString(),
sample.getId(), op.getId(), slot.getId()));
for (Slot slot : slots) {
measurements.add(new Measurement(null, "Date sectioned", src.getDateSectioned().toString(),
sample.getId(), op.getId(), slot.getId()));
}
}
}
return (measurements.isEmpty() ? measurements : measurementRepo.saveAll(measurements));
Expand All @@ -297,13 +302,20 @@ public Iterable<SamplePosition> createSamplePositions(SectionRegisterLabware srl
UCMap<Sample> sampleMap, UCMap<SlotRegion> regionMap) {
List<SamplePosition> samps = srl.getContents().stream()
.filter(src -> !nullOrEmpty(src.getRegion()))
.map(src -> new SamplePosition(lw.getSlot(src.getAddress()).getId(),
sampleMap.get(src.getExternalIdentifier()).getId(),
regionMap.get(src.getRegion()), opId))
.collect(toList());
.flatMap(src -> newSamplePositions(src, lw, opId, sampleMap, regionMap))
.toList();
return (samps.isEmpty() ? samps : samplePositionRepo.saveAll(samps));
}

/** Makes new (unsaved) sample position objects for a given source */
Stream<SamplePosition> newSamplePositions(final SectionRegisterContent src, final Labware lw, final Integer opId,
final UCMap<Sample> sampleMap, final UCMap<SlotRegion> regionMap) {
final Integer sampleId = sampleMap.get(src.getExternalIdentifier()).getId();
final SlotRegion region = regionMap.get(src.getRegion());
return lwSlots(lw, src.getAddresses())
.map(slot -> new SamplePosition(slot.getId(), sampleId, region, opId));
}

/**
* Links the indicated bio risks to the new samples
* @param srl relevant part of the request
Expand All @@ -317,4 +329,14 @@ public void linkBioRisks(SectionRegisterLabware srl, Integer opId, UCMap<Sample>
.collect(toMap(src -> sampleMap.get(src.getExternalIdentifier()).getId(), src -> bioRiskMap.get(src.getBioRiskCode())));
bioRiskService.recordSampleBioRisks(sampleBioRisks, opId);
}

/**
* Streams the slots with the given addresses from the given labware
* @param lw the labware to get slots from
* @param addresses the addresses of the slots
* @return a stream of slots
*/
static Stream<Slot> lwSlots(Labware lw, Collection<Address> addresses) {
return addresses.stream().map(lw::getSlot);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -198,15 +198,23 @@ public UCMap<LabwareType> validateLabwareTypes() {
continue;
}
for (var content : lw.getContents()) {
Address address = content.getAddress();
if (address==null) {
if (nullOrEmpty(content.getAddresses())) {
addProblem("Missing slot address.");
} else if (lt.indexOf(address) < 0) {
addProblem("Invalid address %s in labware type %s.", address, lt.getName());
continue;
}
Set<Address> seen = new HashSet<>(content.getAddresses().size());
for (Address address : content.getAddresses()) {
if (address == null) {
addProblem("Missing slot address.");
} else if (!seen.add(address)) {
addProblem("Slot address %s given multiple times for the same section.", address);
} else if (lt.indexOf(address) < 0) {
addProblem("Invalid address %s in labware type %s.", address, lt.getName());
}
}
}
}
// Allow duplicate addresses
// Allow duplicate addresses across different sections but not in the same section
return lwTypeMap;
}

Expand Down Expand Up @@ -510,8 +518,9 @@ public UCMap<SlotRegion> validateRegions() {

for (var srl : request.getLabware()) {
Stream<Map.Entry<Address, String>> regionStream = srl.getContents().stream()
.filter(src -> !nullOrEmpty(src.getRegion()) && src.getAddress()!=null)
.map(src -> Map.entry(src.getAddress(), src.getRegion()));
.filter(src -> !nullOrEmpty(src.getRegion()) && !nullOrEmpty(src.getAddresses()))
.flatMap(src -> src.getAddresses().stream()
.map(ad -> Map.entry(ad, src.getRegion())));
problems.addAll(slotRegionService.validateSlotRegions(slotRegions, regionStream));
}

Expand All @@ -520,8 +529,10 @@ public UCMap<SlotRegion> validateRegions() {

public boolean anyMissingRegions(SectionRegisterLabware srl) {
return slotRegionService.anyMissingRegions(srl.getContents().stream()
.filter(src -> src.getAddress()!=null)
.map(src -> simpleEntry(src.getAddress(), src.getRegion())));
.filter(src -> !nullOrEmpty(src.getAddresses()))
.flatMap(src -> src.getAddresses().stream()
.filter(Objects::nonNull)
.map(ad -> simpleEntry(ad, src.getRegion()))));
}

private Stream<SectionRegisterContent> contentStream() {
Expand Down Expand Up @@ -551,9 +562,9 @@ public void throwError() throws ValidationException {
}

private <E> UCMap<E> loadAllFromSectionsToStringMap(SectionRegisterRequest request,
Function<SectionRegisterContent, String> requestFunction,
Function<? super E, String> entityFunction,
Function<? super Set<String>, ? extends Collection<E>> lookupFunction) {
Function<SectionRegisterContent, String> requestFunction,
Function<? super E, String> entityFunction,
Function<? super Set<String>, ? extends Collection<E>> lookupFunction) {
Set<String> strings = request.getLabware().stream()
.flatMap(lw -> lw.getContents().stream().map(requestFunction))
.filter(s -> s!=null && !s.isEmpty())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,15 +151,28 @@ public LifeStage valueToLifeStage(Collection<String> problems, String lifeStageS
return null;
}

public Address valueToAddress(Collection<String> problems, String addressString) {
if (!nullOrEmpty(addressString)) {
try {
return Address.valueOf(addressString);
} catch (IllegalArgumentException e) {
problems.add(e.getMessage());
}
/** Converts a string to a list of slot addresses */
public List<Address> valueToAddresses(Collection<String> problems, String addressString) {
if (nullOrEmpty(addressString)) {
return List.of();
}
return null;
try {
return Arrays.stream(addressString.replace(',', ' ').split("\\s+"))
.filter(s -> !s.isEmpty())
.map(Address::valueOf)
.toList();
} catch (IllegalArgumentException ignored) {
// try again without removing commas
}
try {
return Arrays.stream(addressString.split("\\s+"))
.filter(s -> !s.isEmpty())
.map(Address::valueOf)
.toList();
} catch (IllegalArgumentException e) {
problems.add("Invalid addresses: "+repr(addressString));
}
return List.of();
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ enum Column implements IColumn {
External_slide_ID(Pattern.compile("(external )?(slide (barcode|id|identifier)|barcode.*slide)", Pattern.CASE_INSENSITIVE|Pattern.DOTALL)),
Prebarcode(String.class, Pattern.compile("(xenium( slide)?\\s*|pre)barcode", Pattern.CASE_INSENSITIVE), false),
Lot(String.class, Pattern.compile("(xenium )?(slide )?lot\\b.*", Pattern.CASE_INSENSITIVE|Pattern.DOTALL), false),
Section_address,
Section_address(Pattern.compile("(section|slot)\\s*address(es)?\\b.*", Pattern.CASE_INSENSITIVE|Pattern.DOTALL)),
Fixative,
Embedding_medium,
Donor_ID,
Expand All @@ -41,6 +41,7 @@ enum Column implements IColumn {
Section_thickness(String.class),
Date_sectioned(LocalDate.class, Pattern.compile("date.*sectioned|section.*date", Pattern.CASE_INSENSITIVE|Pattern.DOTALL), false),
Section_position(Pattern.compile("(if.+)?(section\\s+)?position|position.*slot.*", Pattern.CASE_INSENSITIVE|Pattern.DOTALL)),
Comment(Void.class, Pattern.compile("(information|comment).*", Pattern.CASE_INSENSITIVE|Pattern.DOTALL), false),
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Are we ignoring this extra "Information or comments about section?" column?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes.

;

private final Pattern pattern;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ public SectionRegisterLabware createRequestLabware(Collection<String> problems,
*/
public SectionRegisterContent createRequestContent(Collection<String> problems, Map<Column, Object> row) {
SectionRegisterContent content = new SectionRegisterContent();
content.setAddress(valueToAddress(problems, (String) row.get(Column.Section_address)));
content.setAddresses(valueToAddresses(problems, (String) row.get(Column.Section_address)));
content.setExternalIdentifier((String) row.get(Column.Section_external_ID));
content.setFixative((String) row.get(Column.Fixative));
content.setHmdmc((String) row.get(Column.HuMFre));
Expand Down
4 changes: 2 additions & 2 deletions src/main/resources/schema.graphqls
Original file line number Diff line number Diff line change
Expand Up @@ -377,8 +377,8 @@ input BlockRegisterRequest {

"""Information about a section of tissue (already taken from some a block tracked elsewhere) to register."""
input SectionRegisterContent {
"""The address of the slot in the labware where this section should be created."""
address: Address!
"""The addresses of the slots in the labware where this section should be created."""
addresses: [Address!]!
"""The species from which this section originates."""
species: String!
"""The cellular classification of the section."""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -295,8 +295,8 @@ public void testCreateLabware(boolean prebarcode) {

verify(mockLwService).create(lt, prebarcode ? xb : null, xb);

verify(mockSlotRepo, times(2)).save(lw.getSlot(A1));
verify(mockSlotRepo).save(lw.getSlot(B2));
verify(mockSlotRepo, times(2)).saveAll(List.of(lw.getSlot(A1)));
verify(mockSlotRepo).saveAll(List.of(lw.getSlot(B2)));

assertEquals(lw, result);
assertThat(lw.getSlot(A1).getSamples()).containsExactlyInAnyOrder(sample1, sample2);
Expand All @@ -310,15 +310,15 @@ private SectionRegisterContent content(Address address, String extName) {

private SectionRegisterContent content(Address address, String extName, String regionName) {
SectionRegisterContent src = new SectionRegisterContent();
src.setAddress(address);
src.setAddresses(address==null ? List.of() : List.of(address));
src.setExternalIdentifier(extName);
src.setRegion(regionName);
return src;
}

private SectionRegisterContent content(Address address, String extName, Integer thickness) {
SectionRegisterContent content = new SectionRegisterContent();
content.setAddress(address);
content.setAddresses(address==null ? List.of() : List.of(address));
content.setExternalIdentifier(extName);
content.setSectionThickness(thickness==null ? null : thickness.toString());
return content;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -852,7 +852,7 @@ private static SectionRegisterContent content(String extName, Integer section, I

private static SectionRegisterContent content(Address address, String regionName) {
SectionRegisterContent src = new SectionRegisterContent();
src.setAddress(address);
src.setAddresses(address==null ? List.of() : List.of(address));
src.setRegion(regionName);
return src;
}
Expand Down Expand Up @@ -966,7 +966,7 @@ private static SectionRegisterRequest labwareTypeRequest(Collection<?> data) {
assert current != null;
Address ad = (Address) obj;
SectionRegisterContent content = new SectionRegisterContent();
content.setAddress(ad);
content.setAddresses(ad==null ? List.of() : List.of(ad));
current.getContents().add(content);
}
}
Expand Down
Loading
Loading